[{"content":"Erdős Problem Solver Agent An AI-powered CLI tool that automatically discovers and attempts to solve open mathematical problems from the Erdős Problems database.\nFeatures 🔍 Scrapes and parses open problems with associated prizes 🤖 Supports multiple LLM providers (OpenAI, Gemini, Anthropic) via anyllm 📝 Generates structured formal proof attempts ♻️ Continuous execution loop across multiple problems ⏱️ Robust error handling with retry logic and configurable timeouts Tech Stack Go · anyllm · colly · GitHub Actions\nLinks Source Code: github.com/demirbey05/erdos-agent Blog Post: Building an AI Agent to Solve Open Mathematical Problems ","permalink":"https://demirbey05.github.io/projects/erdos-agent/","summary":"A Go CLI agent that fetches unsolved Erdős problems and uses LLMs to generate formal proof attempts.","title":"Erdős Problem Solver"},{"content":"Introduction I am currently interviewing for a SecAI role. To be honest, I didn\u0026rsquo;t have prior InfoSec experience. While I\u0026rsquo;ve built various AI agents for different purposes, I hadn\u0026rsquo;t tackled cybersecurity before. To prepare, I built a specialized cybersecurity agent called SecAgent using pydantic-ai. Rather than attempting to cover every possible task, I focused on a concrete, high-value problem: alert triage analysis.\nIn this post, I want to share the practical lessons I learned throughout this process.\nFirst, what is alert triage analysis, and why is it crucial? In modern cybersecurity operations, Security Operations Center (SOC) analysts are overwhelmed by the sheer volume of alerts generated daily by firewalls, IDS/IPS, endpoint security tools, SIEM platforms, etc. Analysts must prioritize these alerts and decide which ones require further investigation.\nTo simulate this environment, I set up Elasticsearch on Docker as a SIEM and generated realistic test alerts with Claude across 6 distinct scenarios:\nBrute Force: The attacker used an automated Python script to perform a sustained brute-force attack against a single user\u0026rsquo;s password. Password Spray: The attacker attempted a low-and-slow password spraying attack against 40 different accounts (generating 55 failures overall) from a single external IP. Impossible Travel: The same account authenticated successfully from two geographically distant locations within a very short timeframe. Off-hours Admin: A user authenticated successfully from an anomalous location (Germany) and time (10:45 PM) that fell outside their typical working patterns. Dormant Account: A user account that had been inactive for 120 days was used to log into the network. Service Account Misuse: A service account was used interactively to log in and access sensitive data. I had Claude generate events for these scenarios and inject them into the Elasticsearch SIEM. You can inspect the raw alert dataset here: Alert Reports (CSV).\nThe important stuff here is how to design harness, which tools you should use, how to manage context and measure the best practices.\nIn particular, I want to focus on context compaction, as cybersecurity triage is inherently log-intensive.\nHarness Design As shown in the architecture above, the setup consists of 3 core tools, 5 message-level compaction methods, and tool output compaction.\nCore Tools search_events: Fetches authentication events, sorted newest or oldest first. aggregate_events: Counts events grouped by a specified field over time. entity_baseline: Profiles what an entity normally looks like prior to the alert window. Note: I asked Claude to suggest practical tools for this task and chose these three. If you come from a cybersecurity background and have better recommendations, please feel free to reach out!\nCompaction Methods clamp: Uses ClampOversizedMessages from the pydantic-ai library. It takes two arguments (keep_head_chars and keep_tail_chars) and preserves only the first and last NNN characters of oversized messages. dedupe: As the documentation describes: \u0026ldquo;When the same file is read more than once, only the latest read keeps its content; earlier reads are blanked with a placeholder.\u0026rdquo; Since we aren\u0026rsquo;t reading files in this scenario, its utility is limited here. clear_tools: Replaces older tool results with concise placeholders while keeping the most recent keep_pairs tool-call / tool-return pairs intact. sliding: Retains only the most recent keep_messages messages and discards older ones. summarize: Performs recursive LLM-based summarization of conversation history. This is the most promising method for retaining semantic context, though one must be mindful of added token costs and latency. Tool Output Compaction The most effective way to optimize an agent is to understand how it interacts with the environment—identifying what the outputs look like and where bottlenecks emerge.\nI used Logfire for tracing and observing agent execution. While inspecting traces, I discovered that the search_events tool frequently returned repetitive log entries:\nlog 1 - user1 logged in log 2 - user1 logged in log 3 - user1 logged in ... log N - user1 logged in Instead of returning verbose individual entries, compacting them into something like user1 logged in (N times) saves a substantial number of tokens.\nTo quantify these bottlenecks, I measured the raw token consumption across different tool calls:\nTool Call (Scenario) Tokens entity_baseline(user.name) ~214 entity_baseline(source.ip) ~116 aggregate_events(user.name) ~24 aggregate_events(source.ip, interval=1m) ~77 aggregate_events(..., interval=1h, 7d) ~5,065 search_events(size=20) ~4,264 search_events(size=50) ~10,661 I got something unexpected I just thought search_events was the only tool output bottleneck but it wasn\u0026rsquo;t. As you can see from above aggregate_events is also very costly for the context window. So it\u0026rsquo;s best to properly benchmark and measure after something discovered.\nAfter I implemented simple deterministic compaction I got those results in tool output token usage :\nTool Call Before After Reduction search_events(size=20) 4,264 580 −87% search_events(size=50) 10,661 985 −91% aggregate_events(1h, 7d) 5,065 1,905 −63% ","permalink":"https://demirbey05.github.io/posts/learnings-from-secagent-dev/","summary":"Insights and lessons learned while building and optimizing SecAgent—a cybersecurity alert triage agent built with PydanticAI.","title":"Learnings from SecAgent Development"},{"content":"PyTorch Reshaping with None Currently I am learning attention mechanism from Dive into Deep Learning book. In the book I see following implementation in masked softmax:\ndef sequence_mask(X, valid_len, value= -1e6): \u0026#34;\u0026#34;\u0026#34; X is 2D array (number_of_points, maxlen), valid_len is 1D array (number_of_points)\u0026#34;\u0026#34;\u0026#34; max_len = X.size(1) mask = torch.arange(max_len, dtype=torch.float32, device=X.device)[None, :] \u0026lt; valid_len[:, None] X[~mask] = value return X In sequential data processing, I mean processing natural language. The sequence length might be variable for each data point. For example :\n1 : \u0026ldquo;Welcome To My Blog\u0026rdquo;\n2 : \u0026ldquo;Hello World\u0026rdquo;\nTo solve that problem , we fill remaining values with a special token.\n1 : \u0026ldquo;Welcome To My Blog\u0026rdquo;\n2 : \u0026ldquo;Hello World blnk blnk\u0026rdquo;\nIn attention, we do not want to attend to blnk tokens. So we create mask for that. In the code portion max_len is the maximum length of the sequence and valid_len is the actual length of the sequence. I mean for 1st data point valid_len is 3 and for 2nd data point valid_len is 2.\nIn the code portion, we are trying to create mask for that. Let\u0026rsquo;s say we have following dictionary [\u0026lsquo;blnk\u0026rsquo;, \u0026lsquo;Welcome\u0026rsquo;, \u0026lsquo;To\u0026rsquo;, \u0026lsquo;My\u0026rsquo;, \u0026lsquo;Blog\u0026rsquo;, \u0026lsquo;Hello\u0026rsquo;, \u0026lsquo;World\u0026rsquo;] so X vector will be :\nX = [ [1,2,3,4], [5,6,0,0] ] valid_len = [3,2] and the mask must be :\n[ [1,1,1,0], [1,1,0,0] ] We are using brodcast mechanism to create mask. First torch.arange(max_len, dtype=torch.float32, device=X.device) will create a 1D array with shape (max_len,). In our example, it would be [0,1,2,3]. It must be [[0,1,2,3],[0,1,2,3]] right? But we will use brodcast mechanism to expand it to [[0,1,2,3],[0,1,2,3]]. For getting our broadcasted mask we need to apply operator to lengths of (max_len, 1) and (1, valid_len). If you do not know broadcast mechanism, you can read about it in PyTorch documentation.\nNow we came to point, for reshaping we use None in pytorch. torch.arange(max_len, dtype=torch.float32, device=X.device)[None, :] is equivalent to torch.arange(max_len, dtype=torch.float32, device=X.device).reshape(1, -1) and valid_len[:, None] is equivalent to valid_len.reshape(-1, 1).\nTo be honest, I would prefer reshape more readable so my version of this function is :\ndef sequence_mask_with_reshape(X, valid_len, value= -1e6): \u0026#34;\u0026#34;\u0026#34; X is 2D array (number_of_points, maxlen), valid_len is 1D array (number_of_points)\u0026#34;\u0026#34;\u0026#34; max_len = X.size(1) mask = torch.arange(max_len, dtype=torch.float32, device=X.device).reshape(1, -1) \u0026lt; valid_len.reshape(-1, 1) X[~mask] = value return X As you can see, it is more readable.\n","permalink":"https://demirbey05.github.io/posts/pytorch-reshaping/","summary":"PyTorch Reshaping with None","title":"PyTorch Reshaping with None"},{"content":"Hello 👋 I\u0026rsquo;m Okan, a master student in AI with interest in LLMs\nResearch Interests LLMs: Large language models reasoning, agents Contact GitHub: demirbey05 Email: huseyinokand@gmail.com Twitter: demirbey05 This site is built with Hugo and the PaperMod theme, deployed via GitHub Pages.\n","permalink":"https://demirbey05.github.io/about/","summary":"About me","title":"About"}]