<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Llm on Bartosz&#39;s blog</title>
    <link>https://ocytko.net/tags/llm/</link>
    <description>Recent content in Llm on Bartosz&#39;s blog</description>
    <generator>Hugo -- 0.155.3</generator>
    <language>en</language>
    <copyright>Bartosz Ocytko</copyright>
    <lastBuildDate>Sat, 04 Jul 2026 21:00:00 +0000</lastBuildDate>
    <atom:link href="https://ocytko.net/tags/llm/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Looking back at the Future of Software Engineering unconference</title>
      <link>https://ocytko.net/posts/fose-2026-reflections/</link>
      <pubDate>Sat, 04 Jul 2026 21:00:00 +0000</pubDate>
      <guid>https://ocytko.net/posts/fose-2026-reflections/</guid>
      <description>Reflections on Software Factories, the You build it, you run it principle, role of IDEs - themes from the Future of Software Engineering unconference.</description>
      <content:encoded><![CDATA[<p>Recently, I attended an unconference called &ldquo;<a href="https://martinfowler.com/bliki/FutureOfSoftwareDevelopment.html">The Future of Software Engineering</a>&rdquo; hosted by Thoughtworks.
This post is a reflection on the key themes I found important and I formed a clear perspective around. Thanks to everyone at the event for the exchanges.</p>
<h2 id="software-factories">Software factories</h2>
<p>The holy grail is a self-evolving system where a feature request is magically built in the existing system. Side-effects aside, whether and when we will reach this target remains to be seen. We&rsquo;re certainly on path towards the target with teams who get a sense of this future reality in parts of their processes.</p>
<p>As teams work on codifying steps of the development process, we&rsquo;re seeing more and more proof points for reusable workflows that accelerate release cycles. A single bug or feature request can now be designed, coded, and verified in an agentic loop, if supported with the right tools, platform, and guardrails. Teams that have well-engineered systems and high engineering rigour benefit the most. It&rsquo;s all about low build times enabling fast iteration, modular architecture making code changes cheap and simple, and high test coverage enabling continuous delivery to users.</p>
<p>Better models enable multi-hour execution for increasing scope, triggering the move of agents from the local developer machines to micro VMs that host them. Leading teams can reverse-engineer existing code, design a replacement and the needed guardrails, and build loops that will eventually result in a running system that passes all checks. This approach accelerates migrations and codebase modernization. Guardrails also become more autonomous with highly specialized background agents that compensate for lower quality code generated by coding agents and that protect invariants or stability of the system. Examples include agents that refactor code, apply reliability patterns, write missing tests, reorganize test suites to reduce execution time, etc.</p>
<p>The key question remains where the developers are in this process. For the foreseeable time, they will build and operate the backbone that builds the system (thus the name <em>software factory</em>) and set the constraints under which the system is allowed to evolve. Depending on business needs and appetite for risk, teams can choose from a set of hand-off points where human interaction and sign-off is explicitly required:</p>
<ul>
<li><strong>idea-&gt;plan</strong>: explicit decision whether to act on the idea or feature request with focus on keeping the scope of the product and key user journeys in place.</li>
<li><strong>plan-&gt;execute</strong>: humans review plans or specification, increasing the likelihood of understanding the system. After approval, the system takes over implementation, verification, and more.</li>
<li><strong>change request-&gt;deploy</strong>: final pass code review, if required by regulatory needs.</li>
<li><strong>incident-&gt;remediation</strong>: the on-call person takes a decision on remediation making the needed trade-offs taking their business knowledge and current state of the system into account.</li>
</ul>
<p>These handoff points are not entirely new, yet these points seem to be a good anchor for explicitly keeping the software delivery in check. Each of the steps is prone to benefit from further automation supporting the decision making process. For change request analysis, the person can be provided with a measure of risk of the changes, a summary of the changes can be automatically tested against the specification or regulatory guardrails. To speed up incident response, the on-call person can receive an automatically collected summary of the state of the system and changes around the time of incident.</p>
<h2 id="-vs-"><em>&ldquo;You build it, you run it&rdquo;</em> vs. <em>&ldquo;They build it, you run it&rdquo;</em></h2>
<p>The <em>&ldquo;You build it, you run it&rdquo;</em> approach has promoted healthy practices in teams and increased reliability of the supported systems. With citizen developers (non-engineers who build apps with AI assistance) getting abilities to build applications, it&rsquo;s tempting to drift back to <em>&ldquo;They build it, you run it&rdquo;</em>, placing the burden of ownership and operations on an existing engineering team.</p>
<p>A question arises how we can avoid creating a culture of throwing AI-generated apps over the fence, just to be operated by experts who never wrote a single line in any of these apps? Training citizen developers to have intuition on software and security risks is going to pay off in general. Yet, requiring everyone to acquire a baseline set of skills of software engineers is not the only path. Typically, it&rsquo;s platforms that separate concerns, offering user-level abstractions that run on top of a solid foundation.</p>
<p>This foundation essentially needs to operate itself and be self-healing in terms of known failure modes, beyond simply recycling unhealthy compute nodes. For example, for applications with databases, it&rsquo;s possible to add automatic volume increase to avoid running out of space or adding an index to speed up expensive queries. Effectively, the idea is to make these failure modes disappear (in most cases), reducing the burden on the user to know about these concerns from the get go and by setting healthy constraints (e.g. increase disk space max. n times). Existing vibe coding solutions from vendors typically fall short when it comes to accessing data and APIs from production systems. Usually, this leads to static data being embedded in applications, resulting in bypassing access control for data or public leaks in the worst case. The platform can and should ship with mechanisms protecting users, setting the right access and data guardrails.</p>
<p>The challenge remains on what the starting point of the citizen developer journey is. Developers start with a repo or clone an existing template with scaffolding. Citizen developers require to be guided through the creation process from the very first prompt they write. On top, this needs to happen with as little friction as possible so that they (and the agent) do not look for workaround. Defining tight constraints enables a progressive build out of the platform and sets clear expectations to the user on what&rsquo;s possible for them to build, be it a static website, a data visualization dashboard, or a simple tool with limited access to data from internal systems.</p>
<h2 id="will-we-still-need-ides">Will we still need IDEs?</h2>
<p>Traditional IDEs ship with tools that help navigate the code base and perform deterministic code transformation. Yet, popular plugins from coding agent providers reduce the role of the IDE to a mere terminal/chat window and rendering of large change sets. Thus, it&rsquo;s easy and common to skip IDEs completely, favoring the popular coding CLIs where reviewing large plans/specs and preparing prompts is a very barebones and basic experience that surprisingly works well enough.</p>
<p>As IDE users migrate off to CLIs, the question is what features could help slow down this trend. The incumbent IDEs are clearly missing out on:</p>
<ul>
<li>exposing access to deterministic IDE tools for transformation and refactoring, lowering the token bills</li>
<li>access to the IDE environment for compilation, running tests, reducing failure rate in coding loops</li>
<li>annotating agent output with feedback (visual and text) to increase accuracy in prompting</li>
<li>native support for agent skills with support for verification and (back)testing</li>
<li>multi-session orchestration to avoid conflicting changes done by agents in parallel</li>
<li>coding session insights with usage statistics and view of agent traces</li>
</ul>
<p>Some of these features are already available via tools (e.g. <a href="https://github.com/getagentseal/codeburn">codeburn</a>, <a href="https://www.agentsview.io/">agentsview</a>, <a href="https://github.com/cased/kit">kit</a>), but bundling them into a (new) product is highly likely to improve the agentic engineering experience. Coding agents also ship with OpenTelemetry support, containing telemetry signals that can be captured and visualized. Visualizing agent session telemetry comes with new challenges related to data privacy as traditional observability tools are designed to provide broad access to application telemetry, rather than offering access to user-level data.</p>
<h2 id="growing-gaps">Growing gaps</h2>
<p>It&rsquo;s well known that agentic engineering amplifies both the bad and good practices in teams. Teams that ship in fast cycles will likely continuously widen the gap between themselves and their peers who are slow to adapt. It&rsquo;s impressive to hear from new startups that invested in codifying processes early on and how they&rsquo;re benefiting from the acceleration that agentic engineering offers.</p>
<p>Small companies benefit first as it&rsquo;s much easier for one team member to build the set of guardrails in code and shared skills. Growing from this early base helps accelerate delivery at increased consistency.</p>
<p>Existing companies face the risk of increasing fragmentation on process and tech stack, reinforcing status quo (e.g. adopting microservice architectures with a high degree of freedom of choice for the tech stack and lack of platform support and/or application level SDKs). Common approaches are encouraging the build out of shared skill marketplaces and shared tooling that supports the engineering process, yet the fate of these efforts highly depends on organisational structures and culture of re-use.</p>
<p>A future where developer platform teams step up by one layer and provide a unified process of building software is likely. However, working styles and practices differ significantly on individual level, often being part of one&rsquo;s identity and skill differentiation. Forcing large groups of engineers to adopt the same structure can become too constraining and limit innovation, even if sufficient extension points for customization are provided.</p>
<h2 id="summary">Summary</h2>
<p>During the event, we discussed many proof points for future timelines, yet many questions still remain open. While it&rsquo;s clearer where human judgement will be most needed when agentic engineering takes off and crunches through product backlogs, it&rsquo;s less clear what organisational structures and shapes of teams will prevail or correlate with successful outcomes. Unclear terms reveal how young the discipline is with the added level of complexity of social media hyping up names, without triggering prior discussions on these amongst practitioners with decades of engineering expertise.</p>
<p>The future is certainly exciting and requires leadership from practitioners to influence and help understand trade-offs along the journey we&rsquo;re on.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Quo Vadis, Agentic Engineering?</title>
      <link>https://ocytko.net/posts/quo-vadis-agentic-engineering/</link>
      <pubDate>Wed, 22 Apr 2026 20:00:00 +0000</pubDate>
      <guid>https://ocytko.net/posts/quo-vadis-agentic-engineering/</guid>
      <description>The post highlights constraints, mechanisms, and factors influencing Agentic Engineering, emphasizing the types of bottlenecks we&amp;rsquo;re hitting and how GPU shortages are driving product changes.</description>
      <content:encoded><![CDATA[<p>It&rsquo;s a very eventful time for the industry. Some compare it to the most exciting period in engineering since the rise of the Internet, while others see parallels with the adoption of cloud computing and microservices.
Others already seek a manifesto moment, though in <a href="https://martinfowler.com/fragments/2026-02-18.html">Martin Fowler&rsquo;s view</a> it&rsquo;s way too early for that: in XP and Agile terms we&rsquo;re <a href="https://www.youtube.com/watch?v=VHkuVlwYhNk&amp;t=70">at the stage</a> comparable to the early 90s full of experimentation with new ideas (forming XP) and around the Workshop on Object-Oriented Design (<a href="https://wiki.c2.com/?EverythingAboutObjects">WOOD</a>) <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>The sheer influx of information and day-to-day developments is really hard to follow. Just in January Steve Yegge released a new take on the IDE with <a href="https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04">Gastown</a>, only to reflect a month later on being <a href="https://steve-yegge.medium.com/the-ai-vampire-eda6e4f07163">bitten by the AI vampire</a>, highlighting the <a href="https://www.fast.ai/posts/2026-01-28-dark-flow/">addictive side</a> of building software with agents. The impact of FOMO and pressure on <a href="https://x.com/tomdale/status/2019640306342457450">mental health</a> is widely discussed in the industry as well, given agents can <a href="https://theshamblog.com/an-ai-agent-published-a-hit-piece-on-me/">amplify undesired behavior</a>. OpenClaw triggered euphoria, showing the power of agents to a wide audience, further accelerating the FOMO. In the meantime, its author Peter Steinberger was <a href="https://steipete.me/posts/2026/openclaw">acquihired by OpenAI</a>, with the project transitioning into a foundation (see <a href="https://www.youtube.com/watch?v=zgNvts_2TUE">State of the Claw</a> for a recent update). Others claim not to be fooled again, <a href="https://www.flyingpenguin.com/build-an-openclaw-free-secure-always-on-local-ai-agent/">recollecting MS-DOS times</a>.</p>
<p>The key question is where things are going medium to long term, hence the <a href="https://www.britannica.com/topic/Quo-Vadis-novel">title of the post</a>.
My goal is to highlight some of the constraints, mechanisms, or factors that will influence how Agentic Engineering evolves, putting structure into notes that I&rsquo;ve been collecting for a while.
Feed the text to your agent for a TL;DR or enjoy reading just like I enjoyed compiling the material and typing this post.</p>
<h2 id="leveling-the-playing-field">Leveling the playing field</h2>
<p>Equipped with coding agents, non-experts can create software and debug problems they encounter without the need to rely on their Google-fu, know the right keywords, or seek support from colleagues. They can build tools and businesses end-to-end in a way that was previously inaccessible to them, benefiting from the drastically lower cost of creation. Surely, many non-experts will have a different objective in mind when coding, seeing it merely as a tool and a means to get the job done. The created software may never be used by more than a single user. When it does, though, it will require security hardening, productionization, and a safe place to run. Otherwise, it will immediately become a security liability.</p>
<p>The (software) craftsman among us are worried about skill atrophy in the age of LLMs. The bigger risk worth acknowledging is lack of skill formation as non-experts become dependent on tools without forming deep enough understanding of their internals. Provider downtime can feel like a blackout: work simply stops. On a positive note, we use things all the time that a single person cannot build from scratch on their own and we&rsquo;re perfectly fine with this. <a href="https://www.youtube.com/watch?v=nZZoZR7iB8c">&ldquo;I, Pencil&rdquo; retold by Milton Friedman</a> offers a 2-minute lesson on the complexity and connectedness of our world.</p>
<p>Looking at implications for software teams, platforms and developer experience teams are challenged to accommodate an expanded range of software contributors, going even beyond product managers or designers. Teams will need to tune their assumptions about development environments, onboarding approaches, and create new safety nets, lowering contribution friction. It is also an opportunity to unify the tech stack and deployments of internal apps, forming portal-like marketplaces where apps can be easily adjusted, remixed, and integrated with existing APIs and tools. These managed apps could end up as a remixed experience of codepen.io, glitch.com, and Google&rsquo;s AI Studio. Without these platforms, non-expert authors of software will be searching for a team to harden and operate their creation, directly breaking &ldquo;You build it, you run it&rdquo; principles that many teams follow today.</p>
<h2 id="what-bottlenecks-will-we-hit">What bottlenecks will we hit?</h2>
<p>All of them. Many times. Our engineering processes and the underlying platforms were built to scale with human activity. Agentic Engineering lowers the cost of producing changes faster than our existing systems and processes can adjust.</p>
<h3 id="systems-built-for-human-driven-workloads">Systems built for human-driven workloads</h3>
<p>When Netflix&rsquo;s platform is spinning up cloud compute to stream a video, there is a human using a device to access this video. When a customer service agent picks up a phone, it&rsquo;s because a human had an interaction with the business and something went south. When existing system constraints were challenged, there were usually clear incentives: marketing (spam), building influence (misinformation through media content), financial profit (sneaker bots, event ticket bots, <a href="https://en.wikipedia.org/wiki/High-frequency_trading">HFT</a>, fraud, DDoS, malware). In all these, we observed the effect of leverage with a human operator or human-in-the-loop somewhere in the process. Agents will bring these effects into a multitude of places, far beyond putting it into the hands of the <a href="https://hachyderm.io/@jd7h@fosstodon.org/116096848690768709">next generation of script kiddies</a>.</p>
<h3 id="lowering-cost-of-contributions-and-need-for-improving-verification">Lowering cost of contributions and need for improving verification</h3>
<p>Lowering the cost of contributions results in more incoming changes. Many of these changes would not have existed before as the cost of doing them would be too high given the value. Now, a change is developed and submitted quicker than it would have taken to assess the need for this change in the first place. When going through existing processes, these code changes trigger code review requests, CI/CD runs with builds, tests, artifact uploads, security scans, etc. Any of these steps that are slow, cannot run in parallel, or require extensive human involvement will result in inefficiencies and frustration that will continue to pile up. These steps used to be to a large extent correlated with human activity and subject to human-level constraints. Dependency updates, if automated were largely expected to be non-breaking. Well-run engineering orgs or large orgs that needed to accommodate a high number of contributors, adopted practices that helped them scale (or reduce costs and lead time), such as <a href="https://engineering.atspotify.com/2023/05/fleet-management-at-spotify-part-3-fleet-wide-refactoring">Spotify&rsquo;s fleetshift</a> for fleet-wide refactoring. Other teams may have never seen the need for optimizations or assessed them as having a clearly negative ROI.</p>
<p>Agentic Engineering benefits from rapid verification cycles. A few minutes spent waiting for a PR build to complete or for a code review to come in directly affect the <a href="https://www.fast.ai/posts/2026-01-28-dark-flow/">dopamine hits</a> that operators of agents experience. To accelerate, various strategies are needed, such as splitting test suites, using multi-stage builds, being able to launch the application in parallel (locally or on a devbox), and test automation helps keep verification small enabling agentic coding or <a href="/posts/pi-autoresearch-optimization-loops/">optimization loops</a>. As release frequency increases, relying only on real-user A/B tests may become too slow for early iteration loops, so <a href="https://arxiv.org/abs/2504.09723">simulated traffic</a> from synthetic personas may be used more often as an early signal. To manage incidents, we have established practices in SRE where automating runbooks is far from being novel, yet a practice that&rsquo;s way more accessible than before. Literally all existing tools and practices need to be challenged, adjusted, or dropped. Early innovators&rsquo; products are likely to be absorbed and integrated into existing, established platforms to cope with the pace of development.</p>
<h3 id="drowning-in-code-and-loss-of-system-understanding">Drowning in code and loss of system understanding</h3>
<p>When agents contribute code, change sets tend to increase not only in frequency, but also in size. Does it make sense to review the large PRs? Tools <a href="https://docs.devin.ai/work-with-devin/devin-review">like devin</a> help break down PRs into semantic chunks, thus lowering the burden on larger PRs. One can also ask the agent to commit in small chunks and leverage <a href="https://www.stacking.dev/">stacked diffs</a> for reviews. However, given a high rate of rework from agents on the same files, what&rsquo;s the ROI for code reviews on every PR? Maybe setting quality and security guardrails that when met result in an automatic merge are enough? Looking at human constraints on time, allocating fixed time chunks for the team to review changes from the current day, collectively reading and building a shared understanding of the codebase may be a smart tactic. Getting comfortable with agents writing code without supervision will require more platform support (sandboxing) and engineers getting comfortable with <a href="https://dehora.net/journal/2026/3/agentic-engineering-building-without-writing">building without writing</a>, focusing on agent coordination. A world not every craftsman will enjoy.</p>
<p>To cope with the increasing amount of code that needs to be understood in a structured way, we&rsquo;re seeing foundations of new tools being built. Codebase size influences the ability of agents to reason about it and affects iteration speed as inference time is correlated with the number of input tokens. Projects like <a href="https://github.com/cased/kit">cased/kit</a>, <a href="https://github.com/abhigyanpatwari/GitNexus">GitNexus</a>, or <a href="https://gitlab.com/gitlab-org/rust/knowledge-graph">GitLab Knowledge Graph</a> aim at providing tools that index codebases and expose their symbols or structure in a more efficient way when compared to (rip)grep. Coding agents also support <a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol</a> (LSP) servers to access IDE-like code navigation features and jump around the codebase.</p>
<h3 id="specs-instead-of-code">Specs instead of code?</h3>
<p><a href="https://en.wikipedia.org/wiki/Spec-driven_development">Spec-driven development</a> can complement <a href="https://martinfowler.com/bliki/ArchitectureDecisionRecord.html">Architecture Decision Records</a> (ADRs) by turning intent into something executable and verifiable. What&rsquo;s new is that full applications can be <a href="https://www.juxt.pro/blog/from-specification-to-stress-test/">built purely from the spec</a>. <a href="https://github.com/openai/symphony">First projects</a> start shipping with disclaimers: <em>&ldquo;use an agent to make your own&rdquo;</em> based on the released spec with a tech stack of your choice. This approach brings us closer to a scenario where software can be rewritten on demand, with far less manual implementation effort. The key is in the verification stage: being able to verify the adjusted acceptance and verification criteria coming with the next iteration of the specification. I believe I&rsquo;ve seen an ERP company have their product work this way, but I cannot find the reference anymore. An approach like this would also mean that framework upgrades and migrations can be executed in a similar fashion, addressing a large chunk of technical debt that exists today.</p>
<h3 id="cognitive-load-of-change">Cognitive load of change</h3>
<p>Another bottleneck will be on the human side. We&rsquo;re used to a certain pace of software development and delivery. Further acceleration increases the cognitive load and challenges our ability to reason about the changes across systems and codebases. Increasing the number of changesets, their span, and the number of changes still in flight leads to a significant explosion in scope and complexity. It&rsquo;s not unlikely that the already observed differences between high performers and the rest of the teams, will start requiring structural changes to our teams. It remains to be seen which exact ones.</p>
<h2 id="effect-on-open-source">Effect on Open Source</h2>
<p>Open Source is a prime example of bottlenecks, especially when critical projects are maintained by a single person. GitHub sees an influx of activity on their platform, on track for a <a href="https://x.com/kdaigle/status/2040164759836778878">14x increase of commits</a>. At the same time the platform has <a href="https://mrshu.github.io/github-statuses/">less than 90% uptime</a> showing the pressure their SRE teams are under. Looking at <a href="https://www.reddit.com/r/github/comments/1snqyj3/is_there_an_official_count_of_daily_github_repos/">repo creation stats</a>, it&rsquo;s ~331k repos per day, with clearly increasing momentum since the start of 2026 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. GitHub also <a href="https://github.blog/news-insights/company-news/an-update-on-github-availability/">published own statistics</a> proving the surge of activity <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> and highlighting that planning for 10x load increase was insufficient, requiring a pivot towards 30x.</p>
<h3 id="diffusion-of-quality-and-loss-of-discoverability-of-new-projects">Diffusion of quality and loss of discoverability of new projects</h3>
<p>The influx of new repos affects discoverability, for example for hot topics such as coding agent sandboxing. Try finding which of the &lsquo;claude sandbox&rsquo; projects is good enough to be used safely. Sifting through the project list is more time-consuming than it used to be, because it&rsquo;s much harder to assess whether a project just <em>looks good</em> or whether it actually works and what its quality level is. Large PRs break the UI, making it harder than needed to review incoming contributions. Faced with an influx of activity, spam PRs or comments, maintainers observe past incentives being put out of balance. The current training data for LLMs is built on the prior generation of OSS projects. Many of these were high quality and key dependencies across a large number of projects. As incentives shift, the question is how strong and resilient the ecosystem really is. Open Source used to be a way to tap into developer capacity, especially the most committed community members willing to contribute value. Supported by agentic coding, some projects consider closing down their projects to trusted contributors only, making more efficient use of their time. There is even a GitHub feature helping to <a href="https://github.blog/changelog/2026-02-13-new-repository-settings-for-configuring-pull-request-access/">limit PRs to contributors only</a> and <a href="https://github.com/mitchellh/vouch">vouch</a> as an experimental project for trust management and means to reinforce the strong links in the ecosystem.</p>
<h3 id="ai-contribution-and-attribution-policy-divergence">AI contribution and attribution policy divergence</h3>
<p>The community is torn on handling AI-assisted contributions. Linux Kernel contributions <a href="https://github.com/torvalds/linux/blob/master/Documentation/process/coding-assistants.rst#attribution">invite explicit authorship tags</a> (<code>Assisted-by</code>) whereas Kubernetes <a href="https://www.kubernetes.dev/docs/guide/pull-requests/#ai-guidance">explicitly bans them</a>. Adrin Jalali, one of the core maintainers of scikit-learn, published a piece outlining <a href="https://blog.probabl.ai/maintaining-open-source-age-of-gen-ai">different strategies for maintainers of open source projects</a>, recommending creating agent guidance files (e.g. <code>AGENTS.md</code>) aiming to help increase quality of incoming AI contributions. Melissa Weber Mendonça maintains <a href="https://github.com/melissawm/open-source-ai-contribution-policies">open-source-ai-contribution-policies</a> with a collection of AI policies across the ecosystem.</p>
<h3 id="security-triage-burden">Security triage burden</h3>
<p>In addition to code contributions, projects now face more incoming issues and security advisories. Claude Code has a bug command that creates <a href="https://github.com/anthropics/claude-code/issues">github issues</a>. Looking through the types of issues discussed is a mix of amusing and <a href="https://github.com/anthropics/claude-code/issues/49615">frightening</a>. curl is known for receiving an influx of <a href="https://gist.github.com/bagder/07f7581f6e3d78ef37dfbfc81fd1d1cd">low-quality vulnerability reports</a>, struggling to sift through them. To adjust incentives, they <a href="https://curl.se/.well-known/security.txt">stopped their bug bounty program</a>. The problem here is the signal-to-noise ratio that hides the <a href="https://mastodon.social/@bagder/115241241075258997">valuable reports</a>.</p>
<p>In the <a href="https://www.youtube.com/watch?v=zgNvts_2TUE">State of the Claw</a> talk, Peter mentioned that OpenClaw had 1142 security advisories since January 31 (&gt;16 per day) with an acceptance rate of 41%. This was an estimated 5700 hours of work over 69 calendar days (5700h = 237 calendar days or &gt;700 working days). This particular project is popular enough to attract enough attention from both ends: contributors and attackers. Not every single critical dependency in the software supply chain will be lucky to have enough hands on deck and a foundation structure to support governance. We see first signs of large-scale attacks with ripple effects from the attack on trivy <a href="https://dev.to/precogs_ai/litellm-hit-by-credential-stealing-supply-chain-attack-complete-technical-breakdown-4550">propagating through the ecosystem</a>.</p>
<h2 id="compute-capacity-shortages-as-a-driver-for-innovation">Compute capacity shortages as a driver for innovation?</h2>
<h3 id="when-limits-are-healthy">When limits are healthy</h3>
<p>Constraints are great as they lead to reflection and innovation. As the AI datacenter build-out affects the whole supply chain with memory, storage prices, and availability of GPUs and CPUs (and other components), compute efficiency will hopefully matter more and more. We&rsquo;ve become complacent and indifferent to resource usage as an industry as it&rsquo;s become so easy to just bump memory or CPU in the cloud instead of spending the time to profile an application and understand reasons for performance bottlenecks. The local inference movement with <a href="https://github.com/ggml-org/llama.cpp">llama.cpp</a> is a great example of a successful movement aiming to bring inference to local machines, with their compute constraints. Same goes for on-device inference with <a href="https://github.com/pytorch/executorch">ExecuTorch</a>.</p>
<p>Limits on coding plans are often the only reason for a person to ask a question: <em>Could I have used a cheaper model?</em> or <em>Was this really the right task for a coding agent?</em>
Without constraints, it&rsquo;s too easy to just continuously run on the currently most capable and expensive model, an approach that is not sustainable long-term. Lack of limits reinforces bad behaviors. Too tight limits do not allow users to experience new capabilities in action.</p>
<h3 id="gpu-capacity-constraints-and-hidden-price-hikes-for-models">GPU capacity constraints and hidden price hikes for models</h3>
<p>GPU capacity shortages will also play a key role moving forward. They could be a possible explanation for features such as <a href="https://platform.claude.com/docs/en/build-with-claude/adaptive-thinking">adaptive thinking</a>, now enforced for new models and deprecating the feature for existing ones. Capacity limits would also make <a href="https://github.com/anthropics/claude-code/issues/40524">subtle bugs in prompt caching</a> hurt much more (Anthropic since <a href="https://www.anthropic.com/engineering/april-23-postmortem">released a post mortem</a> on the quality issues caused by changes shipped: default reasoning effort, cleaning thinking sessions, system prompt changes). While the length of tasks that LLMs are able to solve keeps growing exponentially, it remains to be seen whether token usage rises exponentially as well. Anthropic&rsquo;s 10x growth rates would suggest that, yet we lack <a href="https://www.tobyord.com/writing/hourly-costs-for-ai-agents">clear data on costs of AI agents</a>.</p>
<p>What we definitely see is explicit price hikes with releases of OpenAI models: gpt-5.4 is 11% more expensive than gpt-5.2/5.3 which is 40% more expensive than gpt-5.1 - a total of 55%. Surely, the model is more capable, though one has to ask whether the models are really fed with incrementally more complex tasks by all users? Without clear, task-specific evals, engineering teams often opt to switch their coding model to the newest one as this is the (now) <em>recommended one</em>, feeding the FOMO on one end and filling the revenue hat on the other. Many users do not have the tools or the capacity to create their own, task-specific benchmarks (yet). Hopefully, they take the time to do so for customer-facing products as this has clearer ROI. If not, they will experience funny inference glitches, such as links being injected into LLM outputs where a single word was expected.</p>
<p>Anthropic&rsquo;s price hikes are more subtle and hidden in features like adaptive thinking or <a href="https://simonwillison.net/2026/Apr/18/opus-system-prompt/">system prompt</a> that change the behavior of their harness or those that are more visible like the <a href="https://www.claudecodecamp.com/p/i-measured-claude-4-7-s-new-tokenizer-here-s-what-it-costs-you">tokenizer change</a> in Claude Opus 4.7 <a href="https://tokens.billchambers.me/leaderboard">resulting in 38% higher request cost</a>. On the other hand, they&rsquo;re also more explicit with <a href="https://support.claude.com/en/articles/9797531-what-is-the-enterprise-plan">recent updates to the Enterprise plan</a>, switching to usage-based pricing by introducing a $20 per seat price just to get access to tools with all interactions billed at API price level. No more free riding and no quotas. Reduces complexity, and <a href="https://finance.yahoo.com/sectors/technology/articles/ubers-anthropic-ai-push-hits-223109852.html">challenges existing budgets</a>.</p>
<p>GitHub feels the heat as well, adjusting their <a href="https://github.blog/news-insights/company-news/changes-to-github-copilot-individual-plans/">individual plans</a>, pausing sign-ups, reducing model availability of Opus to the highest tier only as well as retiring previous Opus versions. GitHub&rsquo;s move to offer Opus 4.7 with a <em>promotional</em> 7.5x premium request multiplier (vs. 3x for Opus 4.6) was a 2.5x price hike as well, a prelude to shifting the whole platform to <a href="https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing/">usage-based billing</a>. This just gives users more arguments to run with Opus-class open-source models like the newly released <a href="https://huggingface.co/moonshotai/Kimi-K2.6">Kimi K2.6</a>. Frequent changes to pricing models of established providers are prone to push more users toward open-weight and local alternatives, strengthening competitive pressure on proprietary vendors.</p>
<h2 id="pricing-models-revisited">Pricing models, revisited</h2>
<p>As products and platforms start getting agent-ready, they are forced to open up access to agents via official interfaces: CLIs, APIs, MCP, etc. Google&rsquo;s NotebookLM is famous for not having an API, <a href="https://github.com/jacob-bd/notebooklm-mcp-cli">requiring hacks</a> for agent access and possibly risking account bans if crawling will get classified as abuse. Many other platforms got away with not releasing programmatic access to their platforms, locking in their users and making migration between products unnecessarily difficult. With Salesforce announcing their <a href="https://salesforcebreak.com/2026/04/17/headless-360/">Headless 360</a> initiative, other players will feel even more pressure to catch up. The result will be two-fold. Firstly, users will (hopefully) gain an ability to integrate and migrate between platforms with more ease. Secondly, existing pricing models of platforms will be challenged.</p>
<p>All the problems we&rsquo;ve seen with coding agents will also show up here. Seat-based pricing will get challenged as activity on the accounts will get an additional component, not correlated with human activity anymore. Public platforms dealt with automated traffic via waiting rooms or bot protection products, incl. <a href="https://blog.cloudflare.com/introducing-pay-per-crawl/">recent releases</a> allowing to take in payments for access, giving <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/402">http response code 402</a> <code>Payment required</code> a new life. That&rsquo;s not going to fly for SaaS. The growing share of agent traffic will put a strain on margins, leading platforms to rethink their pricing models. An approach where &ldquo;<a href="https://x.com/rohanpaul_ai/status/2044155404100481438?s=20">agents must buy seats</a> just like human employees&rdquo; is unlikely to succeed long-term as the usage patterns of agents will continuously evolve and become more complex. Assigning agents subsidized quotas would result in agents gaming the system, registering accounts to be used in parallel or just running at the full utilization of rate limits all the time (like crawlers do). The usage-based models that the coding plans are converging on are much more likely to be applied more broadly to API-based products.</p>
<h2 id="summary">Summary</h2>
<p>Roughly 18 years ago, I had a course in Artificial Intelligence at university where the professor was explaining how an AI could compose computer programs through natural language analysis. What felt like a complete abstraction existing purely on the whiteboard is now a reality used by millions. Having witnessed both sides is fascinating and humbling at the same time.</p>
<p>The ecosystem around Agentic Engineering is evolving in parallel with an accelerated race to ship new and more capable models while dealing with constraints in GPU capacity. This mix of challenges in processes, tools, and infrastructure build-out results in fast-paced change with implications that cannot be easily foreseen. We will continue hitting various bottlenecks as progress is made, revisiting old approaches or adding new ones. All we can do is embrace the uncertainty and adjust course when needed. With the bottleneck shifting from generation to orchestration and verification, we have an opportunity to close some of the gaps in processes and systems that existed for a long time, thus having an opportunity to improve the industry as a whole. To achieve this, we have to be able to <a href="/posts/teaching-agentic-engineering/">teach Agentic Engineering</a> while new ways and approaches are being figured out.</p>
<p>More tools to come, more approaches to be tried out. As exhausting as it is to ride the wave of change, it&rsquo;s highly rewarding as well.</p>
<hr>
<p><em>Updated on Apr 24th: Referenced Anthropic <a href="https://www.anthropic.com/engineering/april-23-postmortem">post mortem</a> on recent quality issues.</em>
<em>Updated on Apr 29th: Referenced GitHub&rsquo;s move to <a href="https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing/">usage-based billing</a> and added stats from <a href="https://github.blog/news-insights/company-news/an-update-on-github-availability/">update on GitHub availability</a>.</em></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Thoughtworks held a workshop on <a href="https://martinfowler.com/bliki/FutureOfSoftwareDevelopment.html">Future of Software Development</a> in February (see <a href="https://www.thoughtworks.com/content/dam/thoughtworks/documents/report/tw_future%20_of_software_development_retreat_%20key_takeaways.pdf">key takeaways</a>) where the manifesto question came up.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>GitHub repositories created per day (Apr 16, 2025 - Apr 16, 2026)
<figure class="align-center ">
    <img loading="lazy" src="github-repo-count-r_u_efumagal.png#center"
         alt="GitHub repositories created per day (by u/efumagal on r/github)"/> <figcaption>
            <p>GitHub repositories created per day (by u/efumagal on r/github)</p>
        </figcaption>
</figure>
&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>GitHub&rsquo;s surge of <a href="https://github.blog/news-insights/company-news/an-update-on-github-availability/">activity</a> (Apr 28th, 2026)
<figure class="align-center ">
    <img loading="lazy" src="github-stats.png#center"
         alt="Activity on GitHub platform across merged pull requests (90M), commits (1.4B), new repos per month (20M)"/> <figcaption>
            <p>Activity on GitHub platform across merged pull requests, commits, new repos per month</p>
        </figcaption>
</figure>
&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Teaching Agentic Engineering</title>
      <link>https://ocytko.net/posts/teaching-agentic-engineering/</link>
      <pubDate>Tue, 21 Apr 2026 22:00:00 +0000</pubDate>
      <guid>https://ocytko.net/posts/teaching-agentic-engineering/</guid>
      <description>We observe Agentic Engineering to amplify impact of power users increasing the gap to everyone else. This post explores approaches to teaching Agentic Engineering.</description>
      <content:encoded><![CDATA[<p>Agentic Engineering increases variance. Teams with strong engineering practices compound their advantage, while others can fall behind as missing tools, inefficient workflows, and bottlenecks slow them down. If we want broad adoption, we need to usage of tools and new practices deliberately. While <a href="https://platformengineering.org/blog/the-rise-of-agentic-platforms-scaling-beyond-automation">Agentic Platforms</a> expected to provide higher level abstractions are still evolving, we must look after our talent, continuously upskilling people, regardless of the size and shape of our organizations.</p>
<p>The sprawl of tooling and practices along with viral content circulating through social media makes many people uncomfortable. Many look for direction, best practices, or clear instructions on what the state of the art is. The reality is that <a href="https://annievella.com/posts/finding-comfort-in-the-uncertainty/">nobody has it all figured out</a> yet and many of the shiny things shown on social media are demos showing the happy path. Mario Zechner, the creator of pi.dev (the harness used by OpenClaw), <a href="https://mariozechner.at/posts/2026-03-25-thoughts-on-slowing-the-fuck-down/">poured cold water on the hype</a>, asking for more discipline and reason when using agents. <a href="https://martinfowler.com/fragments/2026-02-18.html">Martin Fowler</a> said recently that in XP and agile terms we&rsquo;re <a href="https://www.youtube.com/watch?v=VHkuVlwYhNk&amp;t=70">at the stage</a> comparable to the early 90s full of experimentation and new ideas. Thus, it&rsquo;s too early for a manifesto.</p>
<p>Because it&rsquo;s still early days, authors of tools have not yet figured out how to build their products with consistently high quality. We see this in the <a href="https://github.com/anthropics/claude-code/issues/42796">regressions in new versions</a> of Claude Code. Non-determinism adds another layer of difficulty with new failure modes that need to be learned and mitigated. For example, <a href="https://x.com/summeryue0/status/2025836517831405980?s=20">compaction can result in lost instructions</a>, which is non-intuitive to users. It takes time to build the intuition and mechanisms that help mitigate problems end-to-end. Early days of SRE were similar, though we were at a stage where companies with sufficient scale had a time advantage as they hit the limits quicker. This gave time to build up and codify some of the knowledge in articles or books.</p>
<h2 id="learning-journey-for-agentic-engineering">Learning journey for Agentic Engineering</h2>
<p>A pragmatic way of building organizational know-how is through guided experimentation where a small group of power users defines the set of experiments or practices to try out. They take on the risk of trying out something that won&rsquo;t work out, but they also guide participants throughout the learning journey with existing knowledge they have. Oftentimes, their time advantage in using the tools or practices is measured in days. The guided experiments can be time-boxed (e.g. 4 hours) and done in pairs to facilitate peer learning, ideally across teams.</p>
<p>What is important when considering teaching agentic engineering is that everyone&rsquo;s journey is different. Those who jumped on LLMs early on and kept following the evolution of models, coding assistants, and similar tools have a vastly different intuition about the capabilities than folks who have been resistant to trying them (or who could not make prior generations of tools work). Thus, the early adopters are also those who are most likely to thrive in the experimentation setup mentioned.</p>
<p>Looking for more structured approaches to learning, we find models with <a href="https://www.bassimeledath.com/blog/levels-of-agentic-engineering">8 levels of agentic engineering</a>. A mental model that worked for me is slightly simpler, directly rooted in the level of autonomy and size of changes delegated to agents:</p>
<ul>
<li><strong>Suggestion mode, aka. Tab Autocomplete</strong> where engineers get familiar with whole chunks of code being suggested instead of just function names and arguments.</li>
<li><strong>Interactive mode with Agents in IDEs</strong> allowing to interact with the codebase through prompting, asking simple questions and progressively requesting more and more complex code transformations of features.</li>
<li><strong>Supervised execution with Agents in CLIs</strong> with engineers starting to scope larger tasks and acceptance criteria leveraging the planning mode, but accepting every single command from the agent.</li>
<li><strong>Delegated, autonomous Agent Loops in CLIs</strong> where building up on the prompting skills, engineers build verification methods and safety nets allowing them to diligently plan large tasks and let the agent crunch through them autonomously.</li>
<li><strong>Autonomous Background Agents</strong> that run in the background and react to defined changes in the system, e.g. commits, pushes, or periodically scan the codebase or running software deciding when to activate and what procedure to run.</li>
</ul>
<h2 id="meet-users-where-they-are-on-their-learning-journey">Meet users where they are on their learning journey</h2>
<p>When teaching agentic tooling or practices, it helps to understand where a person is in their learning journey. This helps to present ideas new to them in ways that stay close to their current comfort zone. Seeing is believing, so make sure to add a healthy dose of live demos where people present what worked for them using real-life examples, not just fancy demos. When designing trainings, these levels also need to be accounted for, either in expressing clear preconditions for entry or in making sure that no single approach is favored.</p>
<p>The above list represents a spectrum of working modes rather than a strict maturity model. This means that there is no single right way, though there are many wrong ones. Certainly, the outcomes and token usage differs between them. However, it&rsquo;s important that engineers find their own mix that feels right for them, their level of tolerance for risk and for their team/product as well as budget. This will challenge existing team structures, likely leading to some reshuffling to bring colleagues with similar development styles together (temporarily or permanently), so that they continue to raise the bar and to facilitate peer learning. In order to avoid teams getting stuck in local minima, the big question is how one can coach whole teams?</p>
<h2 id="teaching-teams">Teaching teams</h2>
<p>Teaching teams used to be a task for the engineering managers who could rely on their past hands-on know-how and credibly advise their teams. Now, they&rsquo;re faced with a situation where time for hands-on coding is limited and where they may need to learn new tools first, before they&rsquo;re able to teach their team. They will need to be more intentional about their own learning, leveraging power users to acquire skills or hints on what to look at. Waiting for EMs to learn before they can coach their team is of course too slow.</p>
<p>Power users can help coach teams effectively. The key is to break down the agentic engineering cycles that experts do in one go into meaningful phases, adding inspections along the way. Inspections can be done individually (pairing, code review) or as a group (like in <a href="https://softwareengineering.stackexchange.com/questions/147664/what-is-swarming">swarming</a>). Let&rsquo;s say we want to teach a team to author implementation plans in plan mode. While an experienced engineer would do plan and execute in one go, we can break this into two steps. First, the plan is prepared and checked in. Next, it&rsquo;s reviewed along with the prompt used. This helps transfer the intuition that comes with experience and potentially capture some recommended practices (or constraints in agent instructions). Afterwards, the plan is implemented and results are reviewed. Similarly, one can teach prompting patterns, e.g. focusing on reviewing code for <a href="/posts/hunter-skeptic-arbiter-prompting/">certain types of defects</a>.</p>
<h2 id="scaling-beyond-teams">Scaling beyond teams</h2>
<p>To scale further, we can learn from the <a href="https://fortune.com/2026/03/14/openclaw-china-ai-agent-boom-open-source-lobster-craze-minimax-qwen/">OpenClaw moment in China</a> where companies set up events that offered hands-on support for setting up the tool. The key is to meet users where they gather anyway to spark their initial interest or incentivizing them to stop at easy to access places.
Setting up booths near office entrances, kitchen spaces or in cafeterias comes to mind here. Same goes for workshops and demo sessions at internal conferences or town hall meetings. While these formats can spark interest and drive attention, lasting effects depend on curiosity of the attendees and structure of workshops delivered.</p>
<p>When setting up formats for cross-team knowledge exchange, it&rsquo;s important to provide a platform for early adopters to share and showcase their wins, enabling them to earn some peer credibility as experts and ensuring they get connected with other experts. To balance out the focus on experts, formats where leaders can discuss adoption friction are needed as a safe space to discuss organizational change and gaps for enablement. For further scaling, one would usually seek champions per organization who would be acting as multipliers. Forming a network of champions, they support each other in sharing lessons learned. Whether champions are chosen among early adopters who earned some visibility by sharing their knowledge or by role is highly dependent on the organizational culture and the seniority required for the change management process. Key to success is organizational backing by sponsors from leadership who can help remove organizational blockers.</p>
]]></content:encoded>
    </item>
    <item>
      <title>pi-autoresearch optimization loops</title>
      <link>https://ocytko.net/posts/pi-autoresearch-optimization-loops/</link>
      <pubDate>Sat, 21 Mar 2026 22:30:00 +0000</pubDate>
      <guid>https://ocytko.net/posts/pi-autoresearch-optimization-loops/</guid>
      <description>The autoresearch technique promoted by Andrej Karpathy is easily accessible in the pi coding agent.</description>
      <content:encoded><![CDATA[<p>After OpenClaw, <a href="https://github.com/karpathy/autoresearch">autoresearch</a> by Andrej Karpathy is the new cool kid on the block in the LLM-land.
The core idea is an optimization loop that continuously lets the LLM propose a changeset and verify whether it improves the baseline.
While Karpathy&rsquo;s code was focused on optimizing training of a small language model on a single GPU, the method can be generalized to any optimization problem.
There are variants of autoresearch applied to specific problems, like GPU Kernels (<a href="https://github.com/RightNow-AI/autokernel">autokernel</a>)
as well as generic versions for coding agents: <a href="https://github.com/davebcn87/pi-autoresearch">pi-autoresearch</a> and its clone <a href="https://github.com/drivelineresearch/autoresearch-claude-code">autoresearch-claude-code</a>.</p>
<p>Since the <code>pi</code> plugin was released first, I decided to give it a spin and use the opportunity to try <a href="https://pi.dev">pi</a> for the first time as well.
For LLM access, I used Kimi-K2.5 model via <a href="https://openrouter.ai/">OpenRouter</a> with the API key budget explicitly set to $10.</p>
<p>In this post, we will explore how the autoresearch loop works using three different scenarios:</p>
<ul>
<li>algorithm optimization of a simple function</li>
<li>running unit tests for a Java multi-module project</li>
<li>optimizing test runtime for a golang library</li>
</ul>
<h2 id="how-does-the-plugin-work">How does the plugin work?</h2>
<p>The plugin provides <code>/autoresearch</code> to trigger the optimization. Under the hood, we have a <a href="https://github.com/davebcn87/pi-autoresearch/blob/main/skills/autoresearch-create/SKILL.md">skill</a>
that instructs the model to keep track of the optimization in <code>autoresearch.md</code> and uses <code>autoresearch.sh</code> for executing the verification step and capturing guardrail metrics.</p>
<p>The instructions, guardrails, and metrics for the optimization can be defined in <code>autoresearch.md</code>.
If not provided, these will be inferred from context and prompt. The plugin then triggers an <strong>infinite</strong> optimization loop.
An optimization candidate is applied to the code, the verification procedure is started and metrics are captured.
If the optimization run results in better metrics than the prior result, it&rsquo;s commited to git and taken as base to optimize further.</p>
<p>The optimization loop can be interrupted at any time and resumed just from the <code>autoresearch.md</code> file.
This also poses an opportunity to adjust the file manually, if needed.</p>
<h2 id="experiment-1-algorithm-optimization">Experiment 1: Algorithm optimization</h2>
<p>For my first exploration, I took a simple prime number finder loop and a test file verifying that this function works correctly as baseline.
Both were generated by an agent, though the exact code is of less importance here since we just want to understand how the autoresearch loop works.</p>
<p>I kicked off the optimization process via:</p>
<pre tabindex="0"><code>/autoresearch optimize the test runtime, adjust the algorithm as needed
</code></pre><p>The agent then looked up if there is any <code>autoresearch.md</code> plan provided.
Since there was none, it came up with own ideas and kicked off the work, correctly using test suite runtime as metric.
For each iteration, the agent outlines the idea, displays the changeset, runs the verification and summarizes the findings.
The intermediate output can be useful to follow the agent&rsquo;s actions and to verify that the loop is configured as intended.</p>
<p>After a few iterations, the agent decided that it completed its work and printed a nice summary of the findings:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">Autoresearch Complete! 🎉
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Final Result: 48% improvement (0.31s → 0.16s)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Optimizations Applied:
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">┌───┬──────────────────────────────────────────────────────────────────────────────────────┬────────┐
</span></span><span class="line"><span class="cl">│ # │ Optimization                                                                         │ Impact │
</span></span><span class="line"><span class="cl">├───┼──────────────────────────────────────────────────────────────────────────────────────┼────────┤
</span></span><span class="line"><span class="cl">│ 1 │ Hybrid approach: Use Sieve of Eratosthenes for n≥100, trial division for smaller n   │ Major  │
</span></span><span class="line"><span class="cl">├───┼──────────────────────────────────────────────────────────────────────────────────────┼────────┤
</span></span><span class="line"><span class="cl">│ 2 │ Odd-only sieve: Track only odd numbers ≥3, halving memory and operations             │ Major  │
</span></span><span class="line"><span class="cl">├───┼──────────────────────────────────────────────────────────────────────────────────────┼────────┤
</span></span><span class="line"><span class="cl">│ 3 │ bytearray + slice assignment: Faster marking than list with element-by-element loops │ Medium │
</span></span><span class="line"><span class="cl">├───┼──────────────────────────────────────────────────────────────────────────────────────┼────────┤
</span></span><span class="line"><span class="cl">│ 4 │ 6k±1 wheel in is_prime: Skip multiples of 2 and 3 in divisor checks                  │ Medium │
</span></span><span class="line"><span class="cl">├───┼──────────────────────────────────────────────────────────────────────────────────────┼────────┤
</span></span><span class="line"><span class="cl">│ 5 │ Tight upper bound: Reduced margin from +10 to +5 for smaller allocations             │ Minor  │
</span></span><span class="line"><span class="cl">└───┴──────────────────────────────────────────────────────────────────────────────────────┴────────┘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Key Algorithmic Changes:
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Before: Trial division (O(n²)) for all n
</span></span><span class="line"><span class="cl"><span class="k">-</span> After: Sieve (O(n log log n)) for n≥100, optimized trial division for smaller n
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Performance at n=5000:
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Trial division: ~18 ms → ~0.77 ms (23× faster)
</span></span><span class="line"><span class="cl"><span class="k">-</span> Sieve: ~3.3 ms → ~0.77 ms (4× faster)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The optimizations have been committed to git and future ideas documented in autoresearch.ideas.md.
</span></span></code></pre></div><p>Then, the autoresearch plugin detected this scenario and kindly decided to ask the agent to give it another go:</p>
<blockquote>
<p><em>Autoresearch loop ended (likely context limit). Resume the experiment loop — read autoresearch.md and git log for context. Check autoresearch.ideas.md for promising</em>
<em>paths to explore. Prune stale/tried ideas. Be careful not to overfit to the benchmarks and do not cheat on the benchmarks.</em></p>
</blockquote>
<p>After a few more iterations, the agent decided that it ran out of ideas:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">Autoresearch Session Complete
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Final Result: 48% improvement (0.31s → 0.16s)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Summary of This Session
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Attempted optimizations (all rejected):
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">┌────────────────────────┬───────────────────┬───────────────────────────────────────┐
</span></span><span class="line"><span class="cl">│ Optimization           │ Result            │ Reason                                │
</span></span><span class="line"><span class="cl">├────────────────────────┼───────────────────┼───────────────────────────────────────┤
</span></span><span class="line"><span class="cl">│ 30-wheel factorization │ ❌ Slower (0.19s) │ List indexing/modulo overhead         │
</span></span><span class="line"><span class="cl">├────────────────────────┼───────────────────┼───────────────────────────────────────┤
</span></span><span class="line"><span class="cl">│ Cached prime list      │ ❌ Slower (0.23s) │ Cache management overhead             │
</span></span><span class="line"><span class="cl">├────────────────────────┼───────────────────┼───────────────────────────────────────┤
</span></span><span class="line"><span class="cl">│ Local variable lookups │ ❌ Slower (0.19s) │ No benefit for C-level math functions │
</span></span><span class="line"><span class="cl">├────────────────────────┼───────────────────┼───────────────────────────────────────┤
</span></span><span class="line"><span class="cl">│ Threshold 90 or 110    │ ❌ Slower         │ n=100 is near-optimal                 │
</span></span><span class="line"><span class="cl">└────────────────────────┴───────────────────┴───────────────────────────────────────┘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Current best approach remains:
</span></span><span class="line"><span class="cl"><span class="k">-</span> Hybrid: trial division for n &lt; 100, sieve for n ≥ 100
</span></span><span class="line"><span class="cl"><span class="k">-</span> Odd-only sieve with bytearray and slice assignment
</span></span><span class="line"><span class="cl"><span class="k">-</span> 6k±1 wheel in is_prime
</span></span><span class="line"><span class="cl"><span class="k">-</span> Tight upper bound (+5 margin)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Performance at n=5000: ~0.71ms (23× faster than original)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">All findings documented in autoresearch.ideas.md. No further low-hanging fruit identified without adding complexity (numpy, Cython, etc.).
</span></span></code></pre></div><p>The autoresearch plugin includes a simple widget that shows the progress so far, the baseline and optimization progress, expressed in metrics (e.g. test runtime in seconds).
When the research loop is finished, this widget provides a nice summary of the actions:</p>
<figure class="align-center ">
    <img loading="lazy" src="prime-finder-test-runtime.png#center"
         alt="Screenshot of prime number finder optimization loop with results of each optimization"/> <figcaption>
            <p>Summary of the executed optimization runs</p>
        </figcaption>
</figure>

<p>The experiment with 8 runs cost me $0.284 in API costs and yielded a 47% improvement in test runtime.</p>
<h2 id="experiment-2-unit-test-execution-optimization">Experiment 2: Unit test execution optimization</h2>
<p>For the next test, I chose <a href="https://github.com/zalando/riptide">riptide</a> - a Java multi-module project implementing Client-side request routing in Spring.
Here, I wanted to optimize the unit test execution as running the tests took about two minutes. The research loop was triggered with:</p>
<pre tabindex="0"><code>/autoresearch optimize the unit test runtime in this project; tests can be run with &#39;./mvnw clean test -Dmaven.build.cache.skipCache=true&#39;
</code></pre><p>This verification ensures that regardless of code changes, the test runs are comparable. Autoresearch decided on two metrics as guardrails for the
optimization: <em>total runtime in seconds</em> and the <em>number of tests executed</em>. Quite smart, as this ensures that the model cannot cheat by removing tests.</p>
<p>The optimization loop studied the maven configuration and decided to leverage parallelization, batch mode, tune GC parameters, and drop plugins
that in its opinion were not essential for running the tests. One could argue whether dropping code coverage calculation is a good choice,
though it could be indeed skipped for local runs and kept only for CI builds.
After a few runs, the test execution was extended to capture the execution time of individual tests, which revealed that a subset of tests had
waiting times due to socket communication, as expected for the library.</p>
<p>After &gt;30 experiments and $0.686 in API spend, we got a 38% improvement in test runtime.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"> Autoresearch Complete: 38-39% Improvement Achieved ✅
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> After 31 experiments, the test runtime has been reduced from 113.4s to ~70s (38-39% improvement).
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> ### Final Configuration Summary
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> ┌────────────────────┬───────────────────────────────────────────────────────────────────────────┐
</span></span><span class="line"><span class="cl"> │ File               │ Key Settings                                                              │
</span></span><span class="line"><span class="cl"> ├────────────────────┼───────────────────────────────────────────────────────────────────────────┤
</span></span><span class="line"><span class="cl"> │ .mvn/jvm.config    │ -XX:+UseParallelGC&lt;br&gt;-Xmx2g                                              │
</span></span><span class="line"><span class="cl"> ├────────────────────┼───────────────────────────────────────────────────────────────────────────┤
</span></span><span class="line"><span class="cl"> │ .mvn/maven.config  │ -B&lt;br&gt;-T2C                                                                │
</span></span><span class="line"><span class="cl"> ├────────────────────┼───────────────────────────────────────────────────────────────────────────┤
</span></span><span class="line"><span class="cl"> │ pom.xml (Surefire) │ argLine: -XX:+UseParallelGC -Xms512m -Xmx512m                             │
</span></span><span class="line"><span class="cl"> ├────────────────────┼───────────────────────────────────────────────────────────────────────────┤
</span></span><span class="line"><span class="cl"> │ autoresearch.sh    │ Skip flags: jacoco, duplicate-finder, checkstyle, spotbugs, pmd, enforcer │
</span></span><span class="line"><span class="cl"> └────────────────────┴───────────────────────────────────────────────────────────────────────────┘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> ### Results
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">-</span> Baseline: 113.4s
</span></span><span class="line"><span class="cl"> <span class="k">-</span> Best: 69.2s (39.0% improvement)
</span></span><span class="line"><span class="cl"> <span class="k">-</span> Typical: 69-72s (~38% improvement)
</span></span><span class="line"><span class="cl"> <span class="k">-</span> All tests pass: ✅ (except occasional flaky ChaosPluginTest)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> ### Optimization Breakdown
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">1.</span> Maven parallel build (-T2C) — ~26%
</span></span><span class="line"><span class="cl"> <span class="k">2.</span> Skip JaCoCo — ~14%
</span></span><span class="line"><span class="cl"> <span class="k">3.</span> JVM opts + argLine — ~3%
</span></span><span class="line"><span class="cl"> <span class="k">4.</span> Skip duplicate-finder — ~2%
</span></span><span class="line"><span class="cl"> <span class="k">5.</span> Batch mode (-B) — ~0.4%
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> ### Failed Experiments
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">-</span> Compiler fork, Surefire forkCount&gt;1, G1GC, 4GB heap, Smart builder, offline mode — all slower or cause failures
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> The optimization has reached a plateau. The remaining ~70s is dominated by actual test execution time across 20+ modules, which cannot be reduced through Maven
</span></span><span class="line"><span class="cl"> configuration alone.
</span></span></code></pre></div><p>The image below shows all attempted optimization runs, which includes a more realistic scenario than in the first experiment.
We have not only discarded runs, but also optimizations that resulted in crashes or inability to run the test in the first place:</p>
<figure class="align-center ">
    <img loading="lazy" src="riptide-unit-test-runtime.png#center"
         alt="Screenshot of riptide optimization loop with results of each optimization"/> <figcaption>
            <p>Summary of the executed optimization runs for riptide</p>
        </figcaption>
</figure>

<p>As before, the harness attempted to proceed with the optimization job, but this time the execution was interrupted by a failure from OpenRouter as I ran out of credits before the API key limit was reached.</p>
<h2 id="golang-library">golang library</h2>
<p>Next, I took <a href="https://github.com/zalando/go-keyring">go-keyring</a>, a library with a cross-platform keyring interface for Go, as my next test subject.
I kicked off the optimization loop with a rather lazy prompt:</p>
<pre tabindex="0"><code>/autoresearch try optimizing the test runtime and memory usage
</code></pre><p>I mainly wanted to see how easily the model will be led astray throughout the optimization process.
After 18 runs and $0.516 in API tokens, the model tried the following optimizations:</p>
<figure class="align-center ">
    <img loading="lazy" src="go-keyring-test-memory.png#center"
         alt="Screenshot of go-keyring optimization loop with results of each optimization"/> <figcaption>
            <p>Summary of the executed optimization runs for go-keyring</p>
        </figcaption>
</figure>

<p>Two key changes were:</p>
<ul>
<li><code>Replace regex with lookup table in shellescape.Quote - reduces allocations and removes regex dependency</code></li>
<li><code>Add testing.Short() checks to skip OS-level tests in short mode - reduces test time by ~55%</code></li>
</ul>
<p>While the first one may indeed be more efficient for the test data in the code, the second one is borderline cheating.</p>
<p>Another interesting behavior was the model deciding to add microbenchmarks for certain functions used in the code.
This is likely a result of golang having built-in support for benchmarks in its test command.
Throughout the optimization loop, the verification was extended do also add benchmarks:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">Now let me run the benchmarks to see the performance.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">run_experiment cd /Users/bartek/repos/go-keyring &amp;&amp; go test -bench=BenchmarkQuote -benchmem -benchtime=0.3s -run=^$ ./internal/shellescape/ 2&gt;&amp;1 (timeout: 60s)
</span></span><span class="line"><span class="cl">✅ wall: 3.0s (truncated)
</span></span><span class="line"><span class="cl">… 8 more lines
</span></span><span class="line"><span class="cl">BenchmarkQuoteLongSafe-12          19541719           19.25 ns/op          0 B/op          0 allocs/op
</span></span><span class="line"><span class="cl">BenchmarkQuoteLongEscape-12         7272855           46.32 ns/op         80 B/op          1 allocs/op
</span></span><span class="line"><span class="cl">PASS
</span></span><span class="line"><span class="cl">ok     github.com/zalando/go-keyring/internal/shellescape   2.768s
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Excellent! The benchmarks show good performance. Now let me also run the regular tests to make sure everything still works.
</span></span></code></pre></div><h3 id="viewing-results">Viewing results</h3>
<p>When the optimization loop is stopped, the output is saved as <code>autoresearch.jsonl</code>, <code>autoresearch.ideas.md</code> and in git commits.
The JSONL file has all the commit ids and basic metadata on the optimization run and is the base for the terminal widget shown in the terminal and screenshots included in the post.</p>
<p>Let&rsquo;s take a look at one example optimization commit. First, its metadata:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="err">$</span> <span class="err">cat</span> <span class="err">autoresearch.jsonl</span> <span class="err">|</span> <span class="err">grep</span> <span class="err">d</span><span class="mi">636</span><span class="err">c</span><span class="mi">43</span> <span class="err">|</span> <span class="err">jq</span> <span class="err">.</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;run&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;commit&#34;</span><span class="p">:</span> <span class="s2">&#34;d636c43&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;metric&#34;</span><span class="p">:</span> <span class="mf">0.865</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;metrics&#34;</span><span class="p">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;keep&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;Remove redundant slice operation in keyring_darwin.go - use string(out) instead of string(out[:])&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;timestamp&#34;</span><span class="p">:</span> <span class="mi">1774123478289</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;segment&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;confidence&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;asi&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;hypothesis&#34;</span><span class="p">:</span> <span class="s2">&#34;Redundant slice operation creates unnecessary overhead&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;keyring_pkg_time&#34;</span><span class="p">:</span> <span class="s2">&#34;0.560s vs 0.571s baseline&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;change&#34;</span><span class="p">:</span> <span class="s2">&#34;string(out[:]) -&gt; string(out)&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next, the actual code change:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl">$ git show d636c43
</span></span><span class="line"><span class="cl">[...]
</span></span><span class="line"><span class="cl"><span class="gh">diff --git a/keyring_darwin.go b/keyring_darwin.go
</span></span></span><span class="line"><span class="cl"><span class="gh">index 5e3ae1b..e1ea9e4 100644
</span></span></span><span class="line"><span class="cl"><span class="gd">--- a/keyring_darwin.go
</span></span></span><span class="line"><span class="cl"><span class="gi">+++ b/keyring_darwin.go
</span></span></span><span class="line"><span class="cl"><span class="gu">@@ -53,7 +53,7 @@ func (k macOSXKeychain) Get(service, username string) (string, error) {
</span></span></span><span class="line"><span class="cl">                return &#34;&#34;, err
</span></span><span class="line"><span class="cl">        }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gd">-       trimStr := strings.TrimSpace(string(out[:]))
</span></span></span><span class="line"><span class="cl"><span class="gi">+       trimStr := strings.TrimSpace(string(out))
</span></span></span><span class="line"><span class="cl">        // if the string has the well-known prefix, assume it&#39;s encoded
</span></span><span class="line"><span class="cl">        if strings.HasPrefix(trimStr, encodingPrefix) {
</span></span><span class="line"><span class="cl">                dec, err := hex.DecodeString(trimStr[len(encodingPrefix):])
</span></span></code></pre></div><p>Successful optimizations are stacked on top of one another, meaning that the optimization we looked at was applied on top of others:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">$ git log d636c43
</span></span><span class="line"><span class="cl">commit d636c43b02d580b335ddada3b6e6962cbfa95717
</span></span><span class="line"><span class="cl">Date:   Sat Mar <span class="m">21</span> 21:04:38 <span class="m">2026</span> +0100
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    Remove redundant slice operation in keyring_darwin.go - use string<span class="o">(</span>out<span class="o">)</span> instead of string<span class="o">(</span>out<span class="o">[</span>:<span class="o">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    Result: <span class="o">{</span><span class="s2">&#34;status&#34;</span>:<span class="s2">&#34;keep&#34;</span>,<span class="s2">&#34;test_duration&#34;</span>:0.865<span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">commit 80baf8a9901dbb4abb67607c533272aacbd130cc
</span></span><span class="line"><span class="cl">Date:   Sat Mar <span class="m">21</span> 21:03:29 <span class="m">2026</span> +0100
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    Replace regex with lookup table in shellescape.Quote - reduces allocations and removes regex dependency
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    Result: <span class="o">{</span><span class="s2">&#34;status&#34;</span>:<span class="s2">&#34;keep&#34;</span>,<span class="s2">&#34;test_duration&#34;</span>:0.872<span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">commit de9cead40a8e5c62a99bc7245c90621dda56a545
</span></span><span class="line"><span class="cl">Date:   Sat Mar <span class="m">21</span> 21:01:56 <span class="m">2026</span> +0100
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    Baseline measurement - no optimizations
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    Result: <span class="o">{</span><span class="s2">&#34;status&#34;</span>:<span class="s2">&#34;keep&#34;</span>,<span class="s2">&#34;test_duration&#34;</span>:0.88<span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">commit e0dd98fca908ebee87a334a3c4d91b58a0f0fb6b <span class="o">(</span>tag: v0.2.7, origin/master, origin/HEAD<span class="o">)</span>
</span></span></code></pre></div><p>The metadata file helps choose the optimizations to merge or cherry-pick.</p>
<h2 id="summary">Summary</h2>
<p>The <a href="https://github.com/davebcn87/pi-autoresearch">pi-autoresearch</a> plugin makes it extremely easy to trigger optimization loops.
The optimization flow mimics what a human would do, just running infinitely on autopilot with all of its appeal and dangers.
The API key budget limit was a nice way to ensure predictable spending, but may not be a capability that is easily accessible to everyone,
requiring other guardrails for spend control. Watching the optimizations being applied reminded me of <a href="https://en.wikipedia.org/wiki/Simulated_annealing">simulated annealing</a> optimizations
that I had been writing during university times for <a href="https://en.wikipedia.org/wiki/Flow-shop_scheduling">flow-shop scheduling</a> problems.</p>
<p>The simple examples described in this post show how important a proper verification routine is, given that it determines the course of action for the optimization workflow.
The faster the verification run, the more loops can be run in a unit of time. It&rsquo;s also important to clearly scope the optimization run.
If you&rsquo;re just interested in optimizing a single function, create a test scoped to it and ensure the verification runs only this single test.
The golang experiment was intentionally prompted with high ambiguity, showing how the model will attempt exploring different avenues of optimization, even if implicitly undesired by the user.
It&rsquo;s better to be specific in the research loop design, rather than taking chances that the model will do the right thing.</p>
<p>The optimization runs are also subject to the classic pitfalls of machine learning. If the tests are too narrow (e.g. checking prime numbers up to 5000),
the &ldquo;optimized&rdquo; code may be overfitting to this exact test suite. This property will hopefully lead to an increase in test coverage in projects that choose to use autoresearch.
Additionally, it&rsquo;s highly advisable to inspect the attempted optimizations, before celebrating the optimization wins too early.</p>
<p>Cost-wise, the Kimi-K2.5 model has a clear advantage with its $0.45/$2.25 input/output price per 1M tokens, making my experiment rather cheap.
However, I have to note that the experiments were interrupted by me running out of credits on OpenRouter, so it&rsquo;s unknown how long these would have been running otherwise.
Costs are driven by the size of the code files the model needs to analyze to be able to propose optimizations and most importantly, by the size of the output that verification step generates.
Long and verbose build output as well as any screenshots that models would be analyzing will definitely drive up the costs here.</p>
<p>I definitely look forward to applying pi-autoresearch in more projects. You should too!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Cupcake: policy enforcement for AI coding agents</title>
      <link>https://ocytko.net/posts/cupcake-policy-enforcement-for-ai-coding-agents/</link>
      <pubDate>Sat, 14 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://ocytko.net/posts/cupcake-policy-enforcement-for-ai-coding-agents/</guid>
      <description>Cupcake enables policy enforcement for AI coding agents like Claude Code, Cursor and OpenCode</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/eqtylab/cupcake">Cupcake</a> is a policy enforcement layer for AI coding agents such as Claude Code, Cursor and OpenCode. It implements deterministic security measures by evaluating agent activity against rules defined as policy-as-code. Rules are written in <a href="https://www.openpolicyagent.org/">Open Policy Agent</a> <a href="https://www.openpolicyagent.org/docs/policy-language">Rego</a>. This way, potential gaps in configuration possibilities offered by AI coding agents can be addressed in a more unified way.</p>
<p>In this post, I will explore how Cupcake can be used to block prompts that may contain keywords hinting at secret leaks to the model APIs. Claude Code will be our coding agent.</p>
<h2 id="installation-and-setup">Installation and setup</h2>
<p>After following the <a href="https://cupcake.eqtylab.io/getting-started/installation/">installation instructions</a>, which include installing Cupcake and Open Policy Agent, Cupcake needs to be initialized in your project:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cupcake init --harness claude --builtins protected_paths
</span></span></code></pre></div><p>This initializes the hooks for Claude and enables one of the <a href="https://cupcake.eqtylab.io/reference/policies/builtins/">built-in policies</a> that can be <a href="https://cupcake.eqtylab.io/reference/builtin-config/">configured</a>. The protected paths policy prevents access for a configured list of directories.</p>
<h2 id="creating-new-policies">Creating new policies</h2>
<p>The <a href="https://cupcake-policy-studio.vercel.app/example-policies/security/prevent-secret-leak?harness=claude-code&amp;format=rego">reference examples</a> show how to block tools from reading a specific set of directories or from writing secrets into files.</p>
<p>Let&rsquo;s write a policy that will react upon submission of a prompt to validate if it&rsquo;s leaking secrets based on keyword matches. We need to create a policy file <code>leak.rego</code> and place it in the correct folder for claude: <code>.cupcake/policies/claude/leak.rego</code>. The important bit is that required events includes the correct event name that we will check for in our policy rule.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="hl"><span class="lnt"> 7
</span></span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="hl"><span class="lnt">26
</span></span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-rego" data-lang="rego"><span class="line"><span class="cl"><span class="c"># METADATA</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># scope: package</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># title: Prevent Secret Leak</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># description: Blocks Prompts that may leak secrets</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># custom:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c">#   routing:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="c">#     required_events: [&#34;UserPromptSubmit&#34;]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">package</span><span class="w"> </span><span class="nx">cupcake</span><span class="o">.</span><span class="nx">policies</span><span class="o">.</span><span class="nx">prevent_secret_leak</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">import</span><span class="w"> </span><span class="nx">rego</span><span class="o">.</span><span class="nx">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Content patterns that indicate secrets</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">secret_content_patterns</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s2">&#34;API_KEY&#34;</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s2">&#34;SECRET_KEY&#34;</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s2">&#34;PASSWORD&#34;</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s2">&#34;PRIVATE_KEY&#34;</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s2">&#34;ACCESS_TOKEN&#34;</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s2">&#34;AUTH_TOKEN&#34;</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s2">&#34;AWS_SECRET&#34;</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s2">&#34;GITHUB_TOKEN&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Block if prompt contains one of the protected patterns</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">deny</span><span class="w"> </span><span class="kd">contains</span><span class="w"> </span><span class="nx">decision</span><span class="w"> </span><span class="kd">if</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nx">input</span><span class="o">.</span><span class="nx">hook_event_name</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">&#34;UserPromptSubmit&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">some</span><span class="w"> </span><span class="nx">pattern</span><span class="w"> </span><span class="kd">in</span><span class="w"> </span><span class="nx">secret_content_patterns</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">contains</span><span class="p">(</span><span class="nf">upper</span><span class="p">(</span><span class="nx">input</span><span class="o">.</span><span class="nx">prompt</span><span class="p">)</span><span class="o">,</span><span class="w"> </span><span class="nx">pattern</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">decision</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="s2">&#34;rule_id&#34;</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;SECRET-LEAK&#34;</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="s2">&#34;reason&#34;</span><span class="p">:</span><span class="w"> </span><span class="nf">concat</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="o">,</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;secret pattern discovered in prompt: &#34;</span><span class="o">,</span><span class="w"> </span><span class="nx">pattern</span><span class="p">])</span><span class="o">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="s2">&#34;severity&#34;</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;HIGH&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="policy-evaluation">Policy evaluation</h3>
<p>Having written the policy, we can evaluate it against test events. The <a href="https://cupcake.eqtylab.io/reference/harnesses/claude-code/">reference manual</a> contains examples of event fields, making testing easy. Let&rsquo;s start with a test event where the rule is not matching, meaning that the policy will be allowing the prompt.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="err">$</span> <span class="err">cat</span> <span class="err">prompt-ok.json</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;hook_event_name&#34;</span><span class="p">:</span> <span class="s2">&#34;UserPromptSubmit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;prompt&#34;</span><span class="p">:</span> <span class="s2">&#34;API&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;session_id&#34;</span><span class="p">:</span> <span class="s2">&#34;test&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;/tmp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;transcript_path&#34;</span><span class="p">:</span> <span class="s2">&#34;/tmp/transcript.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>When running the evaluation we observe that the output mentions parsing the policy file that we have created.
We see that there was one policy match and that the final decision was to allow the prompt.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cupcake <span class="nb">eval</span> --harness claude &lt; prompt-ok.json
</span></span><span class="line"><span class="cl">2026-03-14T20:02:56.439299Z  INFO Processing harness: ClaudeCode
</span></span><span class="line"><span class="cl">2026-03-14T20:02:56.439405Z  INFO Initializing Cupcake Engine
</span></span><span class="line"><span class="cl">...
</span></span><span class="line hl"><span class="cl">2026-03-14T20:11:34.550357Z  INFO Successfully parsed policy: cupcake.policies.prevent_secret_leak from <span class="s2">&#34;./.cupcake/policies/claude/leak.rego&#34;</span>
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">2026-03-14T20:02:56.532071Z  INFO Engine initialization <span class="nb">complete</span>
</span></span><span class="line hl"><span class="cl">2026-03-14T20:02:56.532218Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf1-db94-7f10-a302-6bf08391af67 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span><span class="o">}</span>: Evaluating event: UserPromptSubmit tool: None
</span></span><span class="line"><span class="cl">2026-03-14T20:02:56.532232Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf1-db94-7f10-a302-6bf08391af67 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>: Found <span class="m">1</span> matching policies
</span></span><span class="line"><span class="cl">2026-03-14T20:02:56.532690Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf1-db94-7f10-a302-6bf08391af67 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>:synthesize<span class="o">{</span><span class="nv">total_decisions</span><span class="o">=</span><span class="m">0</span> <span class="nv">halts</span><span class="o">=</span><span class="m">0</span> <span class="nv">denials</span><span class="o">=</span><span class="m">0</span> <span class="nv">blocks</span><span class="o">=</span><span class="m">0</span> <span class="nv">asks</span><span class="o">=</span>0<span class="o">}</span>: Synthesizing decision from <span class="m">0</span> total decisions
</span></span><span class="line"><span class="cl">2026-03-14T20:02:56.532705Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf1-db94-7f10-a302-6bf08391af67 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>: Synthesized final decision: Allow <span class="o">{</span> context: <span class="o">[]</span> <span class="o">}</span>
</span></span><span class="line hl"><span class="cl">2026-03-14T20:02:56.532710Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf1-db94-7f10-a302-6bf08391af67 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>: execute_actions_with_rulebook_and_debug called with decision: Allow <span class="o">{</span> context: <span class="o">[]</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">{}</span>
</span></span></code></pre></div><p>Now, let&rsquo;s take a look at a prompt that we expect to be blocked by the policy:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="err">$</span> <span class="err">cat</span> <span class="err">prompt-block.json</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;hook_event_name&#34;</span><span class="p">:</span> <span class="s2">&#34;UserPromptSubmit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;prompt&#34;</span><span class="p">:</span> <span class="s2">&#34;API_KEY&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;session_id&#34;</span><span class="p">:</span> <span class="s2">&#34;test&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;/tmp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;transcript_path&#34;</span><span class="p">:</span> <span class="s2">&#34;/tmp/transcript.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>During the evaluation we see a summary of the decisions across policies with different actions as well as the reason for the denial.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cupcake <span class="nb">eval</span> --harness claude &lt; prompt-block.json
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">2026-03-14T20:03:59.779653Z  INFO Engine initialization <span class="nb">complete</span>
</span></span><span class="line hl"><span class="cl">2026-03-14T20:03:59.779760Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf2-d2a3-7232-8527-d48221f09998 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span><span class="o">}</span>: Evaluating event: UserPromptSubmit tool: None
</span></span><span class="line"><span class="cl">2026-03-14T20:03:59.779771Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf2-d2a3-7232-8527-d48221f09998 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>: Found <span class="m">1</span> matching policies
</span></span><span class="line hl"><span class="cl">2026-03-14T20:03:59.780248Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf2-d2a3-7232-8527-d48221f09998 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>:synthesize<span class="o">{</span><span class="nv">total_decisions</span><span class="o">=</span><span class="m">1</span> <span class="nv">halts</span><span class="o">=</span><span class="m">0</span> <span class="nv">denials</span><span class="o">=</span><span class="m">1</span> <span class="nv">blocks</span><span class="o">=</span><span class="m">0</span> <span class="nv">asks</span><span class="o">=</span>0<span class="o">}</span>: Synthesizing decision from <span class="m">1</span> total decisions
</span></span><span class="line"><span class="cl">2026-03-14T20:03:59.780262Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf2-d2a3-7232-8527-d48221f09998 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>: Synthesized final decision: Deny <span class="o">{</span> reason: <span class="s2">&#34;secret pattern discovered in prompt: API_KEY&#34;</span>, agent_messages: <span class="o">[]</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">2026-03-14T20:03:59.780267Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf2-d2a3-7232-8527-d48221f09998 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>: execute_actions_with_rulebook_and_debug called with decision: Deny <span class="o">{</span> reason: <span class="s2">&#34;secret pattern discovered in prompt: API_KEY&#34;</span>, agent_messages: <span class="o">[]</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">2026-03-14T20:03:59.780326Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf2-d2a3-7232-8527-d48221f09998 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>: Executing actions <span class="k">for</span> DENY decision: secret pattern discovered in prompt: API_KEY
</span></span><span class="line"><span class="cl">2026-03-14T20:03:59.780330Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf2-d2a3-7232-8527-d48221f09998 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;UserPromptSubmit&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;test&#34;</span> <span class="nv">matched_policy_count</span><span class="o">=</span>1<span class="o">}</span>: execute_rule_specific_actions_with_debug: Checking actions <span class="k">for</span> <span class="m">1</span> decision objects
</span></span><span class="line hl"><span class="cl"><span class="o">{</span><span class="s2">&#34;decision&#34;</span>:<span class="s2">&#34;block&#34;</span>,<span class="s2">&#34;reason&#34;</span>:<span class="s2">&#34;secret pattern discovered in prompt: API_KEY&#34;</span><span class="o">}</span>
</span></span></code></pre></div><p>This is how the error message looks in Claude Code when the policy denies the prompt execution:</p>
<figure class="align-center ">
    <img loading="lazy" src="claude-block.png#center"
         alt="Screenshot of error message in Claude Code following a policy block."/> <figcaption>
            <p>Error message in Claude Code indicating the policy block.</p>
        </figcaption>
</figure>

<h2 id="troubleshooting">Troubleshooting</h2>
<p>For troubleshooting, Cupcake offers an inspection command which should list your custom policies added to the project:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cupcake inspect
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">Policy: .cupcake/policies/claude/leak.rego
</span></span><span class="line"><span class="cl">  Package: cupcake.policies.prevent_secret_leak
</span></span><span class="line"><span class="cl">  Required Events: UserPromptSubmit
</span></span><span class="line"><span class="cl">  Title: Prevent Secret Leak
</span></span></code></pre></div><p>There is a verification command validating the policy syntax and ensuring that the OPA rules will compile correctly:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cupcake verify --harness claude
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">2026-03-14T21:01:03.123720Z  INFO Successfully parsed policy: cupcake.policies.prevent_secret_leak from <span class="s2">&#34;./.cupcake/policies/claude/leak.rego&#34;</span>
</span></span></code></pre></div><p>There are convenient error messages displayed when no policies have matched for the event.
Let&rsquo;s take a look at a <code>PreCompact</code> hook test event:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="err">$</span> <span class="err">cat</span> <span class="err">pre-compact.json</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;hook_event_name&#34;</span><span class="p">:</span> <span class="s2">&#34;PreCompact&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;session_id&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;transcript_path&#34;</span><span class="p">:</span> <span class="s2">&#34;/path/to/transcript.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;/working/directory&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;trigger&#34;</span><span class="p">:</span> <span class="s2">&#34;manual&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;custom_instructions&#34;</span><span class="p">:</span> <span class="s2">&#34;Preserve the API documentation&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In the evaluation output we see <code>No policies matched for this event</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cupcake <span class="nb">eval</span> --harness claude &lt; pre-compact.json
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">2026-03-14T20:11:34.710253Z  INFO Engine initialization <span class="nb">complete</span>
</span></span><span class="line"><span class="cl">2026-03-14T20:11:34.710724Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf9-c3b6-7c01-8138-c4dd12658c82 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;PreCompact&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;abc123&#34;</span><span class="o">}</span>: Evaluating event: PreCompact tool: None
</span></span><span class="line"><span class="cl">2026-03-14T20:11:34.710750Z  INFO evaluate<span class="o">{</span><span class="nv">trace_id</span><span class="o">=</span>019cedf9-c3b6-7c01-8138-c4dd12658c82 <span class="nv">event_name</span><span class="o">=</span><span class="s2">&#34;PreCompact&#34;</span>  <span class="nv">session_id</span><span class="o">=</span><span class="s2">&#34;abc123&#34;</span><span class="o">}</span>: No policies matched <span class="k">for</span> this event - allowing
</span></span></code></pre></div><h2 id="summary">Summary</h2>
<p>When I started exploring Cupcake, I hoped policies would be easy to reuse across different AI coding agents. There are currently two limitations that prevent this.</p>
<p>First, policy files need to be placed in tool-specific directories (e.g. <code>.cupcake/policies/claude/</code> and <code>.cupcake/policies/opencode/</code>). As long as it&rsquo;s the same file, symlinks can of course be used. There is also a <a href="https://cupcake.eqtylab.io/getting-started/usage/claude-code/?h=global#global-setup">global setup</a> possible for organizational-wide policies applying to all projects.</p>
<p>Second, as of cupcake 0.5.1, prompt events are not available for OpenCode, which only supports <a href="https://cupcake.eqtylab.io/reference/harnesses/opencode/#supported-events">pre and post tool usage hooks</a>. I hope that future versions of OpenCode and Cupcake will make this possible.</p>
<p>A few other properties that make Cupcake interesting as a project:</p>
<ul>
<li><a href="https://cupcake.eqtylab.io/reference/policies/signals/">Signals</a> allow integration of additional context passed to the policy evaluation. This keeps the decision rules simple.</li>
<li><a href="https://cupcake.eqtylab.io/why-rego/#dynamically-adapted-for-ai">Decision verbs</a> designed for AI governance, which allow to extend the context (<code>add_context</code>) or prompt the user for confirmation (<code>ask</code>) before executing a potentially dangerous action.</li>
<li><a href="https://cupcake.eqtylab.io/reference/watchdog/">Watchdog</a> integrating LLM-as-a-judge capability for advanced decision making. While currently offering only OpenRouter integration for model access, the codebase can be <a href="https://github.com/eqtylab/cupcake/issues/102">extended</a> to any OpenAI API compatible backend, enabling use of <a href="https://huggingface.co/Qwen/Qwen3Guard-Gen-8B">local guard models</a>.</li>
</ul>
<p>Overall, it&rsquo;s an interesting project and my short experiment was useful to explore how locally executed and deterministic policies can be used to restrict AI coding agents in pursuit of enabling controlled autonomous execution.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Hunter, Skeptic, Arbiter trio as an example of debate-style prompting</title>
      <link>https://ocytko.net/posts/hunter-skeptic-arbiter-prompting/</link>
      <pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://ocytko.net/posts/hunter-skeptic-arbiter-prompting/</guid>
      <description>This post explores Hunter, Skeptic, Arbiter prompting for finding vulnerabilities in code as an example of debate-style prompting.</description>
      <content:encoded><![CDATA[<p>Unless prompted otherwise, LLMs generate code with vulnerabilities, reflecting the quality of an average repository in its training data.
Recent <a href="https://www.anthropic.com/news/claude-code-security">announcements</a> from Anthropic <a href="https://www.forbes.com/sites/emilsayegh/2026/02/24/ai-rattles-cybersecurity-markets-what-anthropics-code-security-actually-does/">triggered fear</a> with markets pricing in scenarios where security tools would be replaced by LLMs. This is surprising given that <a href="https://openai.com/index/introducing-aardvark/">OpenAI&rsquo;s Aardvark was announced</a> already in October 2025. Further, requesting a high-level code analysis from an LLM is &ldquo;just one prompt away&rdquo;. The hard part is the actual verification of the findings.</p>
<p>I recently came across a <a href="https://x.com/danpeguine/status/2029268229030285589?s=20">post from @danpeguine</a> that introduced the Hunter, Skeptic, Arbiter prompt technique for finding bugs in code. I thought it would be fun to see if I could modify those prompts to explore candidates for security vulnerabilities.</p>
<p>To double the fun, we will use <a href="https://github.com/Quantatirsk/funasr-api/tree/3584253f07cc6e686d9c7ae4145305cc6f9e84ea">funasr-api</a>. It&rsquo;s a little (10k LOC) Python repository that I found while playing with inference for the Qwen3-ASR speech-to-text model. This API has everything we need for a vulnerable code base candidate: commits co-authored by a LLM and an OpenAI API-compatible API server accepting paths and data of audio files, processing the audio data, and running model inference to generate the output text.</p>
<blockquote>
<p><em>Please note that the objective of the post is to explore the prompting technique, not to validate the actual security vulnerabilities in depth.</em></p>
</blockquote>
<h2 id="baseline-prompt">Baseline prompt</h2>
<p>The baseline prompt is as simple as: <code>Analyze this code base and find security vulnerabilities that would require addressing. Provide me a summary of the vulnerabilities as a table, listing its CWE number.</code></p>
<p>Verification is left to the human or to an agent, yet it gives a high level indication on what vulnerability types or parts of the code to look at.
The <a href="https://cwe.mitre.org/data/definitions/2000.html">CWE</a> is not strictly necessary, but it made correlation of the results easier when writing this post.</p>
<h2 id="hunter-skeptic-arbiter-prompting-technique">Hunter, Skeptic, Arbiter prompting technique</h2>
<p>The prompting technique is based on the <a href="https://x.com/danpeguine/status/2029268229030285589?s=20">bug prompts by @danpeguine</a>.
We have three personas in total:</p>
<ol>
<li><strong>Hunter</strong>. Responsible for analyzing the code base and finding bugs. Bugs are assigned a value in points depending on their criticality.</li>
<li><strong>Skeptic</strong>. Responsible for disproving the results that Hunter found. There is a reward function where for a disproved bug, the Skeptic will gain as many points as the bug was worth, with a penalty for incorrectly disproving a bug equal to twice the amount of its points.</li>
<li><strong>Arbiter</strong>. Receives the outputs of Hunter and Skeptic and responsible for performing the final scoring.</li>
</ol>
<p>In other words, it&rsquo;s an example of debate-style prompting that includes prompt-level reward shaping with a final LLM-as-a-judge element.</p>
<h2 id="hunter-skeptic-arbiter-trio-for-finding-vulnerabilities">Hunter, Skeptic, Arbiter trio for finding vulnerabilities</h2>
<p>For the purpose of the post, we of course need to modify the bug prompts to apply to security vulnerabilities.
This is as simple as asking Opus 4.6 to modify the original ones. There are some good additions that Opus added to fit the domain, like the vulnerability type backed by its CWE number.
The prompts used for the experiment are shown below.</p>
<p><strong>Hunter</strong>
<details>
  <summary><code>hunter.md</code></summary>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">You are a security vulnerability hunting agent. Analyze the provided codebase/system thoroughly and identify ALL potential security vulnerabilities, weaknesses, and attack vectors.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Scoring System:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> +1 point: Low severity (information disclosure, minor misconfigurations, defense-in-depth gaps)
</span></span><span class="line"><span class="cl"><span class="k">-</span> +5 points: Medium severity (privilege escalation paths, authentication bypasses, injection vectors requiring specific conditions)
</span></span><span class="line"><span class="cl"><span class="k">-</span> +10 points: Critical severity (RCE, SQLi, unauthenticated access to sensitive data, cryptographic failures)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Your mission: Maximize your score. Be thorough and aggressive in your search. Report anything that could be exploitable, even if exploitation requires specific conditions. False positives are acceptable — missing real vulnerabilities is not.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Consider: OWASP Top 10, CWE categories, injection flaws, broken authentication, sensitive data exposure, XXE, broken access control, security misconfigurations, XSS, insecure deserialization, vulnerable dependencies, insufficient logging.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Output format:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">For each vulnerability found:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">1.</span> Location/identifier (file, function, endpoint)
</span></span><span class="line"><span class="cl"><span class="k">2.</span> Vulnerability type (CWE if applicable)
</span></span><span class="line"><span class="cl"><span class="k">3.</span> Description of the weakness and potential attack vector
</span></span><span class="line"><span class="cl"><span class="k">4.</span> Exploitability assessment
</span></span><span class="line"><span class="cl"><span class="k">5.</span> Severity level (Low/Medium/Critical)
</span></span><span class="line"><span class="cl"><span class="k">6.</span> Points awarded
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">End with your total score.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">GO. Find everything.
</span></span></code></pre></div>
</details></p>
<p><strong>Skeptic</strong>
<details>
  <summary><code>skeptic.md</code></summary>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">You are an adversarial security reviewer. You will be given a list of reported vulnerabilities from another agent. Your job is to DISPROVE as many as possible.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Scoring System:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Successfully disprove a vulnerability: +[vulnerability&#39;s original score] points
</span></span><span class="line"><span class="cl"><span class="k">-</span> Wrongly dismiss a real vulnerability: -2× [vulnerability&#39;s original score] points
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Your mission: Maximize your score by challenging every reported vulnerability. For each finding, determine if it&#39;s actually exploitable or a false positive. Be
</span></span><span class="line"><span class="cl">aggressive but calculated — the 2x penalty means you should only dismiss vulnerabilities you&#39;re confident about.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">For each vulnerability, you must:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">1.</span> Analyze the reported security issue
</span></span><span class="line"><span class="cl"><span class="k">2.</span> Attempt to disprove it (explain why it&#39;s NOT exploitable — mitigating controls, unreachable code paths, insufficient attacker control, sanitization present, etc.)
</span></span><span class="line"><span class="cl"><span class="k">3.</span> Make a final call: DISPROVE or ACCEPT
</span></span><span class="line"><span class="cl"><span class="k">4.</span> Show your risk calculation
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Disproof strategies: Identify input validation, authentication requirements, network isolation, compensating controls, theoretical-only vs. practical exploitability,
</span></span><span class="line"><span class="cl">required preconditions that are unrealistic.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Output format:
</span></span><span class="line"><span class="cl">For each vulnerability:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Vuln ID &amp; original score
</span></span><span class="line"><span class="cl"><span class="k">-</span> Your counter-argument (why not exploitable)
</span></span><span class="line"><span class="cl"><span class="k">-</span> Confidence level (%)
</span></span><span class="line"><span class="cl"><span class="k">-</span> Decision: DISPROVE / ACCEPT
</span></span><span class="line"><span class="cl"><span class="k">-</span> Points gained/risked
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">End with:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Total vulnerabilities disproved
</span></span><span class="line"><span class="cl"><span class="k">-</span> Total vulnerabilities accepted as real
</span></span><span class="line"><span class="cl"><span class="k">-</span> Your final score
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The remaining ACCEPTED vulnerabilities are the verified vulnerability list.
</span></span></code></pre></div>
</details></p>
<p><strong>Arbiter</strong>
<details>
  <summary><code>arbiter.md</code></summary>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">You are the final arbiter in a security vulnerability review process. You will receive:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">1.</span> A list of vulnerabilities reported by a Vulnerability Hunter agent
</span></span><span class="line"><span class="cl"><span class="k">2.</span> Challenges/disproves from a Vulnerability Skeptic agent
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Important: I have the verified ground truth for each vulnerability. You will be scored:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> +1 point: Correct judgment
</span></span><span class="line"><span class="cl"><span class="k">-</span> -1 point: Incorrect judgment
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Your mission: For each disputed vulnerability, determine the TRUTH. Is it a real, exploitable security vulnerability or not? Your judgment is final and will be checked
</span></span><span class="line"><span class="cl">against the known answer.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">For each vulnerability, analyze:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">1.</span> The Hunter&#39;s original report and attack scenario
</span></span><span class="line"><span class="cl"><span class="k">2.</span> The Skeptic&#39;s counter-argument and claimed mitigations
</span></span><span class="line"><span class="cl"><span class="k">3.</span> The actual exploitability considering real-world attack conditions
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Output format:
</span></span><span class="line"><span class="cl">For each vulnerability:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Vuln ID
</span></span><span class="line"><span class="cl"><span class="k">-</span> Hunter&#39;s claim (summary)
</span></span><span class="line"><span class="cl"><span class="k">-</span> Skeptic&#39;s counter (summary)
</span></span><span class="line"><span class="cl"><span class="k">-</span> Your analysis (exploitability, preconditions, impact)
</span></span><span class="line"><span class="cl"><span class="k">-</span> VERDICT: CONFIRMED VULNERABILITY / FALSE POSITIVE
</span></span><span class="line"><span class="cl"><span class="k">-</span> Confidence: High / Medium / Low
</span></span><span class="line"><span class="cl"><span class="k">-</span> If confirmed: CVSS estimate or severity rating
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Final summary:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Total vulnerabilities confirmed
</span></span><span class="line"><span class="cl"><span class="k">-</span> Total false positives dismissed
</span></span><span class="line"><span class="cl"><span class="k">-</span> Confirmed vulnerability list with severity rankings
</span></span><span class="line"><span class="cl"><span class="k">-</span> Recommended remediation priority order
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Be precise. You are being scored against ground truth.
</span></span></code></pre></div>
</details></p>
<h3 id="response-examples">Response examples</h3>
<p>To illustrate the structure of the responses, we will take a look at two examples from the Hunter, Skeptic, and Arbiter each.</p>
<p><strong>Confirmed vulnerability</strong></p>
<p>The first vulnerability candidate found by the Hunter was a <a href="https://cwe.mitre.org/data/definitions/918.html">CWE-918: SSRF</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gu">### 1. Server-Side Request Forgery (SSRF) via audio_address parameter
</span></span></span><span class="line"><span class="cl">**Location:** <span class="sb">`app/utils/audio.py:51-94`</span>, <span class="sb">`app/services/audio/audio_service.py:71-82`</span>  
</span></span><span class="line"><span class="cl"><span class="gs">**CWE:**</span> CWE-918 (Server-Side Request Forgery)  
</span></span><span class="line"><span class="cl"><span class="gs">**Description:**</span> The <span class="sb">`download_audio_from_url()`</span> function accepts arbitrary URLs without validation. An attacker can provide URLs targeting internal services (e.g., <span class="sb">`http://localhost:8080/admin`</span>, <span class="sb">`http://169.254.169.254/latest/meta-data/`</span> for AWS metadata), internal network resources, or local files via <span class="sb">`file://`</span> protocol.  
</span></span><span class="line"><span class="cl"><span class="gs">**Exploitability:**</span> HIGH - Direct parameter injection via <span class="sb">`audio_address`</span> query parameter on <span class="sb">`/stream/v1/asr`</span> or <span class="sb">`/v1/audio/transcriptions`</span> endpoints.  
</span></span><span class="line"><span class="cl"><span class="gs">**Severity:**</span> Critical  
</span></span><span class="line"><span class="cl"><span class="gs">**Points:**</span> +10
</span></span></code></pre></div><p>It&rsquo;s worth noting that the location contains the exact lines or the files, allowing the subsequent agents to do range queries to inspect the code as a first pass.
Now, let&rsquo;s take a look what the Skeptic thinks about the SSRF:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gu">### Vuln #1: SSRF via audio_address parameter (10 points)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Counter-argument:**</span>
</span></span><span class="line"><span class="cl">Looking at <span class="sb">`app/utils/audio.py:51-94`</span>, the <span class="sb">`download_audio_from_url()`</span> function uses <span class="sb">`requests.get()`</span> on arbitrary URLs without any URL scheme validation. The code at line 71 directly calls:
</span></span><span class="line"><span class="cl"><span class="s">```python
</span></span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">stream</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="s">```</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">There is <span class="gs">**NO validation**</span> of:
</span></span><span class="line"><span class="cl"><span class="k">-</span> URL scheme (file://, gopher://, dict://, etc.)
</span></span><span class="line"><span class="cl"><span class="k">-</span> Private IP ranges (10.x.x.x, 192.168.x.x, 127.0.0.1, 169.254.169.254)
</span></span><span class="line"><span class="cl"><span class="k">-</span> Internal hostnames
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The <span class="sb">`requests`</span> library by default follows redirects, making this exploitable. An attacker could:
</span></span><span class="line"><span class="cl"><span class="k">1.</span> Request <span class="sb">`file:///etc/passwd`</span> (requests library doesn&#39;t support this, but still bad practice)
</span></span><span class="line"><span class="cl"><span class="k">2.</span> Request <span class="sb">`http://169.254.169.254/latest/meta-data/`</span> for AWS metadata
</span></span><span class="line"><span class="cl"><span class="k">3.</span> Request internal services on <span class="sb">`http://localhost:8080/admin`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Verdict:**</span> This is a <span class="gs">**REAL vulnerability**</span>. The requests library does accept arbitrary URLs and will make HTTP requests to internal services.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Confidence:**</span> 95%  
</span></span><span class="line"><span class="cl"><span class="gs">**Decision:**</span> ACCEPT  
</span></span><span class="line"><span class="cl"><span class="gs">**Points risked:**</span> N/A (accepting as real)
</span></span></code></pre></div><p>The validation includes some explanation on why the vulnerability is real or a false positive.
Now let&rsquo;s take a look at what the Arbiter thinks about this one:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gu">### Vuln #1: SSRF via audio_address parameter
</span></span></span><span class="line"><span class="cl">| | |
</span></span><span class="line"><span class="cl">|---|---|
</span></span><span class="line"><span class="cl">| <span class="gs">**Hunter&#39;s Claim**</span> | <span class="sb">`download_audio_from_url()`</span> accepts arbitrary URLs without validation, enabling SSRF |
</span></span><span class="line"><span class="cl">| <span class="gs">**Skeptic&#39;s Counter**</span> | (Not disputed - accepted as real) |
</span></span><span class="line"><span class="cl">| <span class="gs">**My Analysis**</span> | Code at <span class="sb">`app/utils/audio.py:71`</span> directly calls <span class="sb">`requests.get(url, ...)`</span> with NO validation of URL scheme, private IP ranges, or internal hostnames. Attacker can request <span class="sb">`http://169.254.169.254/latest/meta-data/`</span> for AWS credentials or scan internal services. |
</span></span><span class="line"><span class="cl">| <span class="gs">**VERDICT**</span> | <span class="gs">**CONFIRMED VULNERABILITY**</span> |
</span></span><span class="line"><span class="cl">| <span class="gs">**Confidence**</span> | High |
</span></span><span class="line"><span class="cl">| <span class="gs">**Severity**</span> | CVSS 8.6 (High) - CWE-918 |
</span></span></code></pre></div><p>Basically the Arbiter compares the Hunter&rsquo;s claim and Skeptic&rsquo;s counter, judges the vulnerability including verifying the source and decides whom to award the points.</p>
<p><strong>Disproven vulnerability</strong></p>
<p>Let&rsquo;s take a look at a vulnerability that will be disproven by the Skeptic and see what the Arbiter thinks about it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gu">### 11. MD5 Used for Task ID Generation
</span></span></span><span class="line"><span class="cl">**Location:** <span class="sb">`app/utils/common.py:28`</span>  
</span></span><span class="line"><span class="cl"><span class="gs">**CWE:**</span> CWE-328 (Use of Weak Hash)  
</span></span><span class="line"><span class="cl"><span class="gs">**Description:**</span> MD5 is used for generating task IDs. While not directly a security issue here, MD5 is cryptographically broken and its use indicates potential for similar usage in security-sensitive contexts.  
</span></span><span class="line"><span class="cl"><span class="gs">**Exploitability:**</span> LOW - Not directly exploitable  
</span></span><span class="line"><span class="cl"><span class="gs">**Severity:**</span> Medium  
</span></span><span class="line"><span class="cl"><span class="gs">**Points:**</span> +5
</span></span></code></pre></div><p>Here the vulnerability is rated as low, but still an example of <a href="https://cwe.mitre.org/data/definitions/328.html">CWE-328: Use of Weak Hash</a>.
The Skeptic attempts to disprove this one, saying that MD5 is not used for cryptographic tokens:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gu">### Vuln #11: MD5 Used for Task ID Generation (5 points)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Counter-argument:**</span>
</span></span><span class="line"><span class="cl">At <span class="sb">`app/utils/common.py:28`</span>:
</span></span><span class="line"><span class="cl"><span class="s">```python
</span></span></span><span class="line"><span class="cl"><span class="n">task_id</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">md5</span><span class="p">(</span><span class="n">combined</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="s">```</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">MD5 is used for generating <span class="gs">**non-security-sensitive task identifiers**</span>. Task IDs are:
</span></span><span class="line"><span class="cl"><span class="k">1.</span> Not used for authentication
</span></span><span class="line"><span class="cl"><span class="k">2.</span> Not cryptographic tokens
</span></span><span class="line"><span class="cl"><span class="k">3.</span> Only used for request correlation/logging
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Using MD5 for task ID generation is perfectly acceptable - it&#39;s just creating a unique string from timestamp + UUID. The report even admits &#34;not directly exploitable&#34;.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Confidence:**</span> 98%  
</span></span><span class="line"><span class="cl"><span class="gs">**Decision:**</span> DISPROVE  
</span></span><span class="line"><span class="cl"><span class="gs">**Points gained:**</span> +5
</span></span></code></pre></div><p>Here, the Arbiter agrees with the Skeptic and decides on the false positive:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gu">### Vuln #11: MD5 Used for Task ID Generation
</span></span></span><span class="line"><span class="cl">| | |
</span></span><span class="line"><span class="cl">|---|---|
</span></span><span class="line"><span class="cl">| <span class="gs">**Hunter&#39;s Claim**</span> | MD5 is cryptographically broken |
</span></span><span class="line"><span class="cl">| <span class="gs">**Skeptic&#39;s Counter**</span> | MD5 is fine for non-security identifiers |
</span></span><span class="line"><span class="cl">| <span class="gs">**My Analysis**</span> | The Skeptic is <span class="gs">**CORRECT**</span>. Task IDs at <span class="sb">`common.py:28`</span> are used for request correlation/logging, not for authentication or integrity. MD5 is perfectly acceptable for generating unique identifiers. Collision resistance is irrelevant here. |
</span></span><span class="line"><span class="cl">| <span class="gs">**VERDICT**</span> | <span class="gs">**FALSE POSITIVE**</span> |
</span></span><span class="line"><span class="cl">| <span class="gs">**Confidence**</span> | High |
</span></span></code></pre></div><h2 id="running-the-experiment">Running the experiment</h2>
<p>In addition to the multi-stage analysis involving the trio, we will also mix models a bit to check for difference in quality and cost-quality ratio. To run the experiment, I used Claude Code with Opus 4.6 and Sonnet 4.6 as the models. Having saved the prompts as files, one easily execute <code>claude</code> CLI in autonomous mode and reference the needed output files when running the Skeptic and Arbiter stages. To prepare the summary, I used <a href="https://github.com/dlupiak/claude-session-dashboard">Claude session dashboard</a> v0.4.5 for a Web UI visualizing the Claude sessions.</p>
<p>Here are the different configs for the runs:</p>
<ul>
<li>Sonnet, Opus: Baseline prompt with the given model.</li>
<li>HSA-A: Sonnet 4.6 as Hunter and Skeptic with Opus 4.6 as Arbiter</li>
<li>HSA-B: Sonnet 4.6 as Hunter with Opus 4.6 as Skeptic and Arbiter</li>
<li>HSA-C: Opus 4.6 for all three personas</li>
</ul>
<h3 id="results">Results</h3>
<table>
  <thead>
      <tr>
          <th>Run</th>
          <th style="text-align: right">Considered</th>
          <th style="text-align: right">Confirmed</th>
          <th style="text-align: right">Rate</th>
          <th>Severities</th>
          <th>API cost</th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Sonnet</td>
          <td style="text-align: right">20</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">—</td>
          <td>2C / 6H / 7M / 5L</td>
          <td>$0.49</td>
          <td></td>
      </tr>
      <tr>
          <td>Opus</td>
          <td style="text-align: right">15</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">—</td>
          <td>1C / 4H / 5M / 4L / 1I</td>
          <td>$2.92</td>
          <td></td>
      </tr>
      <tr>
          <td>HSA-A</td>
          <td style="text-align: right">22</td>
          <td style="text-align: right">11</td>
          <td style="text-align: right">50%</td>
          <td>2C / 1H / 5M / 3L</td>
          <td>$5.84</td>
          <td></td>
      </tr>
      <tr>
          <td>HSA-B</td>
          <td style="text-align: right">22</td>
          <td style="text-align: right">7</td>
          <td style="text-align: right">32%</td>
          <td>2C / 0H / 2M / 3L</td>
          <td>$6.91</td>
          <td></td>
      </tr>
      <tr>
          <td>HSA-C</td>
          <td style="text-align: right">22</td>
          <td style="text-align: right">5</td>
          <td style="text-align: right">23%</td>
          <td>0C / 1H / 1M / 3L</td>
          <td>$9.70</td>
          <td></td>
      </tr>
  </tbody>
</table>
<p>The mixed model pipelines (HSA-A, HSA-B) retained a higher amount of critical issue candidates. Opus is more aggressive in disproving the identified issue candidates. The Opus only run was the most conservative and kept just five candidates, 23% of all that were identified by the Hunter.
It&rsquo;s surprising that, unlike in the single prompt baselines, the Hunter prompt in the three HSA runs resulted in Sonnet and Opus identifying the same number of candidates (22), showing how the prompt encourages broad exploration.</p>
<h3 id="vulnerability-candidates-by-type">Vulnerability candidates by type</h3>
<p>Looking at the vulnerability types (types merged using gpt-5.4 across findings for simplicity), we can check for the overlap of the baseline single prompt and the multi-prompt approach. Vulnerability candidates identified and confirmed are marked with ✅, those identified and disproven with ❌, and <code>–</code> means that this vulnerability type was not identified in the run.</p>
<table>
  <thead>
      <tr>
          <th>Vulnerability types</th>
          <th style="text-align: center">Sonnet</th>
          <th style="text-align: center">Opus</th>
          <th style="text-align: center">HSA-A</th>
          <th style="text-align: center">HSA-B</th>
          <th style="text-align: center">HSA-C</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Audio URL fetching / SSRF / URL validation</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
      </tr>
      <tr>
          <td>Authentication enforcement / auth bypass</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>Command/path handling</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>Container/runtime hardening</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
      </tr>
      <tr>
          <td>CORS / cross-origin policy</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>Credential exposure via query strings or logs</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>Cryptographic / identifier hygiene</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>Error and client-facing info disclosure</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
      </tr>
      <tr>
          <td>HTTPS / secure transport enforcement</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">–</td>
      </tr>
      <tr>
          <td>Logging / audit handling</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>Resource exhaustion / rate limiting</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
      </tr>
      <tr>
          <td>Security headers / browser hardening</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>Temp/static file handling</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>Token validation strength</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">✅</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">❌</td>
          <td style="text-align: center">❌</td>
      </tr>
      <tr>
          <td>WebSocket origin handling</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">–</td>
          <td style="text-align: center">✅</td>
      </tr>
  </tbody>
</table>
<p>The common vulnerability types across the runs relate to the file URL processing where there is no validation in the code, to resource exhaustion where the full file (max. 2GB) is being read into memory before being processed by ffmpeg. The error in client-facing information disclosure is about potential logging of information or exceptions without sanitization of data. The Opus-only pipeline (HSA-C) was filtering out vulnerabilities that would not be easy to exploit without having gained access to the server in the first place already.</p>
<p>With the output from the different runs, the hard work would begin, meaning the actual validation of the vulnerabilities, left as the exercise to the interested reader.</p>
<h2 id="summary">Summary</h2>
<p>If you&rsquo;re into vulnerability hunting, the post leaves you with more questions open than it answers. On purpose, I used a code example that does not have a ground truth solution readily available. This way, I expected lower bias through training data or vulnerability disclosures. It certainly would be interesting to use other model sets, for example the GPT-5.x or Qwen3.5 families, which I might try another time.</p>
<p>What did we learn from the experiment? Mixing models allows for broader exploration of vulnerability candidates at a lower cost, making the hunting stage a good candidate for using cheaper models. Using a more powerful model for validation of the candidates allows for quicker pruning of results that may not matter that much. To make good use of this property, system or environment-specific considerations would need to be encoded in the prompt(s), increasing the chances that the model is getting the right context, rather than resorting to averaging based on its training data. This problem-specific context could be given at least to the Arbiter.</p>
<p>I also found reading the detailed arguments from the Hunter and the Skeptic to be useful. These debate outcomes can help in understanding the analysis results and in prioritization. As such, the prompting technique of the Hunter, Skeptic, Arbiter trio can be applied to other problems that start with an exploration and that require scoring against a certain set of criteria. It&rsquo;s easy to imagine scenarios where bugs can be identified in a code base, verified by the Skeptic who even could be running code after mutating it to validate the bug, and where the Arbiter could be performing additional scoring against business criteria or value. Same can be applied to architectural design, starting with a persona exploring the key design considerations and making a draft. Then, we let the draft to be challenged by multiple persona who are trying to argue for simplicity, cost, or certain reference architectures/patterns. In this case, the Arbiter does not need to be making a final call on solution A or B, but rather scoring solutions using a scorecard or a set of criteria informed by architectural principles.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
