<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Ashr Blog</title>
        <link>https://ashr.io/blog</link>
        <description>Ashr Blog</description>
        <lastBuildDate>Tue, 14 Apr 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Building Ash: A Background Coding Agent That Fixes AI Agents]]></title>
            <link>https://ashr.io/blog/building-ash</link>
            <guid>https://ashr.io/blog/building-ash</guid>
            <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How we built Ash, a background coding agent that autonomously detects and fixes issues in AI agent systems.]]></description>
            <content:encoded><![CDATA[<p>We're building ashr, a platform that helps teams build, evaluate, and monitor AI agents, catching errors before production. The same failure patterns kept showing up across our users' repos: broken tool calls, mismanaged context, and agents begging for tools that they didn't have access to.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-building-a-good-harness">The Problem: Building a Good Harness<a href="https://ashr.io/blog/building-ash#the-problem-building-a-good-harness" class="hash-link" aria-label="Direct link to The Problem: Building a Good Harness" title="Direct link to The Problem: Building a Good Harness" translate="no">​</a></h2>
<p>As many engineers have noticed, the art behind building a good agent lies in the harness, with the best agent engineers operating under the assumption that any foundation model-level issues will simply be resolved by model providers with time.</p>
<p>The harness is such an elusive part of the agent engineering experience that even model providers struggle to figure it out: Claude Code performs worse on coding benchmarks<sup><a href="https://ashr.io/blog/building-ash#user-content-fn-1-a9dbd6" id="user-content-fnref-1-a9dbd6" data-footnote-ref="" aria-describedby="footnote-label" class="anchorTargetStickyNavbar_Vzrq">1</a></sup> than coding agents that are built on top of the exact same models. Even Anthropic doesn't know what the best Opus coding harness is, and they made the model.</p>
<p>As a testing platform, we saw how our customers tried to solve this problem. The most AI forward of them were running expensive agents overnight, using ashr to test fixes. This process scratched the surface of prompting errors, but was unable to fix broader context management and tooling errors, while also being far too costly to scale.</p>
<p>We decided to solve this problem by building Ash: the on-call AI engineer for your harness, taking into account observability tools, ashr evals, and other information to optimize agents autonomously. The goal is to operate as the AI engineering version of the Weights and Biases sweep<sup><a href="https://ashr.io/blog/building-ash#user-content-fn-2-a9dbd6" id="user-content-fnref-2-a9dbd6" data-footnote-ref="" aria-describedby="footnote-label" class="anchorTargetStickyNavbar_Vzrq">2</a></sup>, which runs parameter optimizations based off Weights and Biases experiments. Ultimately, the best teams treat their harness as an ongoing experiment, and our goal is to make it as easy as possible to test as many high-quality hypotheses as possible.</p>
<p>Ash can either run fully autonomously, picking up subtle signals and regressions from data sources, or go in specific directions based off instructions sent on Slack, Linear, and Github Comments.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-five-tiers-of-llm">Architecture: Five Tiers of LLM<a href="https://ashr.io/blog/building-ash#architecture-five-tiers-of-llm" class="hash-link" aria-label="Direct link to Architecture: Five Tiers of LLM" title="Direct link to Architecture: Five Tiers of LLM" translate="no">​</a></h2>
<p>Ash runs a tiered architecture where the expensive model thinks and the cheap model works. Each tier has a single job and a constrained tool set.</p>
<p><strong>Tier 0 — Triager (Haiku).</strong> Every incoming signal — a Slack message, a cron scan, a webhook — first hits a fast classifier. One word: IGNORE, SIMPLE, or COMPLEX. Simple tasks route to Sonnet for planning. Complex tasks get Opus. The triager's system prompt is deliberately aggressive about defaulting to COMPLEX — we'd rather overspend on planning than under-diagnose a hard problem. This single routing decision saved us roughly 40% on LLM costs without sacrificing quality.</p>
<p><strong>Tier 1 — Planner (Sonnet or Opus).</strong> The planner reads the codebase, queries production data, searches the web for current docs, and produces a fix plan. It doesn't edit files; it thinks. Its output is a structured plan with exact old/new text replacements, or a subtask decomposition for parallel execution.</p>
<p><strong>Tier 2 — Worker (Haiku).</strong> The worker takes the plan and executes it mechanically by editing files, running commands, and searching code. It doesn't decide <em>what</em> to fix; it follows the plan. This separation means the worker model can be cheap and fast. If the worker finishes without making any edits, Ash nudges it: "You haven't made any file edits yet. USE edit_file RIGHT NOW." This sounds crude, but it solved a real problem where cheaper models would describe changes instead of making them.</p>
<p><strong>Tier 3 — Reviewer (Haiku).</strong> A quick sanity check on the diff. Does this change actually address agent <em>behavior</em>, not just code correctness? Is it surgical or did it refactor half the repo? The reviewer responds APPROVED or REVISE with specific instructions that get fed back to the worker.</p>
<p><strong>Tier 4 — Validator (Sonnet).</strong> The final gate. The validator can run tests, score before/after states, and inspect the codebase. It returns VALIDATED, FAILED, or UNCERTAIN — a FAILED blocks the PR entirely. It chooses its own verification strategy: code logic changes get the test suite, prompt changes get the scorer, config changes get a parse check.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-signal-pipeline">The Signal Pipeline<a href="https://ashr.io/blog/building-ash#the-signal-pipeline" class="hash-link" aria-label="Direct link to The Signal Pipeline" title="Direct link to The Signal Pipeline" translate="no">​</a></h2>
<p>Ash doesn't start from a user typing "fix this." It starts from signals — normalized observations from multiple sources. A GitHub <code>@ash</code> mention, a Slack message, a scheduled cron scan that queries <code>trace_metrics</code> for error spikes, a proactive scan that looks for degradation patterns. Every signal gets normalized into an <code>AshSignal</code> with a problem description, evidence array, tenant ID, repo, and source metadata.</p>
<p>Before the agent runs, the context builder assembles everything it needs: past fix patterns from project memory, repo metadata, and per-repo agent config. Each data source is fetched independently — a single failure never blocks the pipeline.</p>
<p>The loop: normalize → build context → run coding agent → create PR → log the run → record the fix pattern to memory for future runs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sandboxes-and-the-warm-pool">Sandboxes and the Warm Pool<a href="https://ashr.io/blog/building-ash#sandboxes-and-the-warm-pool" class="hash-link" aria-label="Direct link to Sandboxes and the Warm Pool" title="Direct link to Sandboxes and the Warm Pool" translate="no">​</a></h2>
<p>Every Ash run executes inside a Daytona sandbox, a full VM with the repo cloned, dependencies installed, and a database running if the repo needs one. Setup is automatic: detect package manager, install deps, run migrations, inject env vars, reset git state.</p>
<p>Cold-starting a sandbox takes 30–60 seconds, which is too slow for a tool triggered from Slack. So we built a warm pool that maintains pre-provisioned sandboxes per repo. When a run grabs one, the pool resets it and kicks off background replenishment. This cut our median start time to under 5 seconds.</p>
<p>The trickiest part was orphan cleanup. When a Modal container dies mid-run, the <code>finally: sandbox.destroy()</code> block never fires. We run a cron every 10 minutes that lists all sandboxes and nukes anything older than 30 minutes that isn't actively in use. When disk quota fills up, we force-clean everything except the youngest sandboxes before retrying.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="durable-execution-via-checkpointing">Durable Execution via Checkpointing<a href="https://ashr.io/blog/building-ash#durable-execution-via-checkpointing" class="hash-link" aria-label="Direct link to Durable Execution via Checkpointing" title="Direct link to Durable Execution via Checkpointing" translate="no">​</a></h2>
<p>Long-running agent sessions (some complex fixes take 15+ minutes and hundreds of tool calls) are vulnerable to container restarts. We solve this with Postgres-backed checkpointing: every 5 tool calls, the full conversation history is serialized to JSON and upserted into <code>ash_run_checkpoints</code>, keyed by Slack thread timestamp. The serializer handles SDK message objects, content block arrays, and nested tool results — anything that isn't a plain dict gets <code>model_dump()</code>'d or <code>__dict__</code>-extracted.</p>
<p>On restart, <code>run_coding_agent()</code> checks for a resumable checkpoint before doing anything else. If one exists and is under 30 minutes old, the agent restores the message history, cost accumulator, and tool call counter, then continues from the exact tier (planner or worker) where it left off. Stale checkpoints are discarded. Successful runs clear their checkpoint. The whole system is non-fatal, if checkpoint save or load fails, the run continues normally.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="parallel-workers-and-the-experiment-loop">Parallel Workers and the Experiment Loop<a href="https://ashr.io/blog/building-ash#parallel-workers-and-the-experiment-loop" class="hash-link" aria-label="Direct link to Parallel Workers and the Experiment Loop" title="Direct link to Parallel Workers and the Experiment Loop" translate="no">​</a></h2>
<p>For complex tasks, the planner can decompose work into subtasks with dependency declarations using an XML format: <code>&lt;subtask id="1" type="write" depends_on=""&gt; ... &lt;/subtask&gt;</code>. The orchestrator parses this, builds execution waves via topological sort (cycle detection falls back to a single wave), and fans out workers across isolated Daytona sandboxes created in parallel via <code>create_sandbox_pool()</code>. Write subtasks get isolated sandboxes; read subtasks share the parent. Each worker gets its own budget and timeout.</p>
<p>Failed workers retry once with their full conversation history injected as context. If they fail again, the subtask degrades gracefully — it gets spawned as a completely separate Ash run via the API. Diffs from successful isolated workers are merged into the parent sandbox using <code>git apply --3way</code>, with merge conflicts tracked and reported.</p>
<p>When a repo has a scorer configured, Ash enters experiment mode. It scores a baseline, executes the plan, scores again, and compares. If the delta is below a noise threshold it reverts all changes with <code>git checkout . &amp;&amp; git clean -fd</code> and asks the planner for a different approach. The best-scoring variant's file contents are stashed before revert and re-applied at the end. The experiment history (approach description, score delta, kept/reverted) is fed back to the planner so it doesn't repeat failed strategies.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conversation-compaction">Conversation Compaction<a href="https://ashr.io/blog/building-ash#conversation-compaction" class="hash-link" aria-label="Direct link to Conversation Compaction" title="Direct link to Conversation Compaction" translate="no">​</a></h2>
<p>Both planner and worker conversations can grow past 50+ messages during complex runs. We compact them: keep the first message (task description) and last 3 exchanges, summarize everything in between with a Haiku call, and replace the middle with the summary. A subtle bug we hit: after compaction, assistant messages sometimes contained citation blocks referencing web search results that were in the now-removed middle. The API would reject these with "Could not find search result for citation index." We now strip all <code>citations</code>, <code>server_tool_use</code>, and <code>web_search_tool_result</code> blocks from the tail messages after compaction.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tool-analysis-at-the-boundary">Tool Analysis at the Boundary<a href="https://ashr.io/blog/building-ash#tool-analysis-at-the-boundary" class="hash-link" aria-label="Direct link to Tool Analysis at the Boundary" title="Direct link to Tool Analysis at the Boundary" translate="no">​</a></h2>
<p>Every tool call passes through a static analysis layer (<code>analyze_input</code> before execution, <code>analyze_output</code> after) that returns one of three actions: pass, inject, or block. This catches things prompt engineering alone can't.</p>
<p><strong>Blocked inputs:</strong> destructive git operations (<code>git push --force</code>, <code>git reset --hard</code>, <code>rm -rf /</code>, <code>git branch -D main</code>), reading a directory path with <code>read_file</code> (redirected to <code>list_files</code>). <strong>Injected input guidance:</strong> package installations during work phase get a warning that lockfile changes will pollute the diff; placeholder-looking scorer inputs get flagged.</p>
<p><strong>Injected output guidance:</strong> empty <code>query_data</code> results get debugging suggestions (table empty? filters too strict? no eval data?); empty <code>search_code</code> results suggest trying different terms; file reads over 15,000 characters warn about context usage; test failures get "read the error, fix the root cause, don't just re-run" guidance; score results get "record this, compare before/after, &gt;5% delta is meaningful" instructions.</p>
<p>The insight was that guardrails belong at the tool boundary, not in the system prompt. The model can ignore prompt instructions. It can't ignore a blocked tool call.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-knowledge-layer">The Knowledge Layer<a href="https://ashr.io/blog/building-ash#the-knowledge-layer" class="hash-link" aria-label="Direct link to The Knowledge Layer" title="Direct link to The Knowledge Layer" translate="no">​</a></h2>
<p>Ash maintains a knowledge layer so the planner doesn't start from zero every time. A knowledge layer, especially in an evolving field with many parameters, is often very hard to get right. Working with quick-moving teams guarantees that knowledge gets stale quickly, and adding in the complexity of different prompting guidelines, agent frameworks, and development patterns makes the knowledge layer one of the most vital features we needed to develop for ash.</p>
<p><strong>Ecosystem knowledge.</strong> A scraper indexes documentation for every tool in our catalog. It fetches pages, strips boilerplate, and distills them into structured sections — quickstart, API reference, configuration, troubleshooting. Content hashing avoids redundant re-indexing. The planner gets current docs for every tool it's reasoning about, not whatever was in the training data six months ago.</p>
<p><strong>Project memory.</strong> Every successful PR records a fix pattern: what the problem was, what worked. The next time Ash sees a similar issue in that repo, the planner gets that history in context. Over time, Ash develops repo-specific intuition about what tends to break and how to fix it.</p>
<p><strong>Global patterns.</strong> Successful fixes are abstracted across the fleet (with tenant isolation) so Ash learns from everyone. Insights such as "when you see a <code>tool_arg_error</code> on a search tool, tightening the schema's enum constraints worked 73% of the time." enrich ash to truly be the best AI engineer across organizations.</p>
<p><strong>Insights.</strong> Persistent, evolving hypotheses about a repo's agent behavior. Created from proactive scans, auto-closed when fixes land, reopened if the problem recurs. Each insight carries its evidence trail and what's already been tried, so the planner doesn't waste cycles repeating failed approaches.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-we-learned">What We Learned<a href="https://ashr.io/blog/building-ash#what-we-learned" class="hash-link" aria-label="Direct link to What We Learned" title="Direct link to What We Learned" translate="no">​</a></h2>
<p><strong>Research before you touch anything.</strong> Ash's planner is instructed to web search before every prompt edit, tool schema change, or behavior modification. Training data is months old. The field moves fast. This single instruction — "search first, implement second" — was the biggest quality improvement we made.</p>
<p><strong>The expensive model should never touch a file.</strong> The moment your planning model starts editing files, it gets distracted by implementation details and loses the thread on strategy. Strict separation of planning and execution made both tiers better at their jobs.</p>
<p><strong>Nudge, don't hope.</strong> Cheaper models need explicit behavioral corrections at runtime, not just better prompts. The worker nudge ("you haven't edited anything — do it NOW"). Runtime steering is infinitely more valuable than subtle prompting fixes for the dumber worker agents.</p>
<p><strong>Dog-food everything.</strong> Ash emits observability traces into the same ashr observability system it monitors for users. Its own runs show up in the dashboard with per-step timing, token usage, and cost breakdowns. When Ash's quality degrades, Ash can theoretically detect and fix itself. We haven't fully closed that loop yet, but the infrastructure is there.</p>
<!-- -->
<section data-footnotes="" class="footnotes"><h2 class="anchor anchorTargetStickyNavbar_Vzrq sr-only" id="footnote-label">Footnotes<a href="https://ashr.io/blog/building-ash#footnote-label" class="hash-link" aria-label="Direct link to Footnotes" title="Direct link to Footnotes" translate="no">​</a></h2>
<ol>
<li class="anchorTargetStickyNavbar_Vzrq" id="user-content-fn-1-a9dbd6">
<p><a href="https://www.tbench.ai/leaderboard/terminal-bench/2.0" target="_blank" rel="noopener noreferrer" class="">Terminal Bench 2.0 Leaderboard</a> — an independent coding agent benchmark where third-party agents built on the same foundation models routinely outperform the model providers' own coding agents. <a href="https://ashr.io/blog/building-ash#user-content-fnref-1-a9dbd6" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li class="anchorTargetStickyNavbar_Vzrq" id="user-content-fn-2-a9dbd6">
<p><a href="https://docs.wandb.ai/guides/sweeps" target="_blank" rel="noopener noreferrer" class="">W&amp;B Sweeps</a> — Weights and Biases' automated hyperparameter optimization tool that runs parameter searches based on experiment results. <a href="https://ashr.io/blog/building-ash#user-content-fnref-2-a9dbd6" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
            <category>Engineering</category>
            <category>AI Agents</category>
        </item>
        <item>
            <title><![CDATA[Why's This Shit Broken?]]></title>
            <link>https://ashr.io/blog/why-testing-is-broken</link>
            <guid>https://ashr.io/blog/why-testing-is-broken</guid>
            <pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[If you were a fly in our Dogpatch apartment during the late weeks of December, you would hear my cofounder and I barking the same sentence back and forth to each other. "Why's this shit broken?"]]></description>
            <content:encoded><![CDATA[<p>If you were a fly in our Dogpatch apartment during the late weeks of December, you would hear my cofounder and I barking the same sentence back and forth to each other. <em>"Why's this shit broken?"</em></p>
<p>Turns out we would decide to dedicate our 20s to trying to answer that question for other engineers.</p>
<p>Ashr started similarly to many of the AI startups reading this post, with the thesis that AI was a fundamental driving force that would revolutionize the way entire sectors do business. We worked with SMBs: automating workflows, scheduling, and payroll.</p>
<p>The whole way through this process, we struggled. Clients wouldn't let us use their employees' payroll information as a testing ground for an AI system, current clients wouldn't be representative of every case or the scale at which we wanted to operate, and no existing testing suite was malleable enough to fit our seemingly esoteric use-case.</p>
<p>We thought this was due to the novelty of our space; there had been no other AI-native service primarily targeting this side of the SMB space.</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_BuS1"><p>When we generated test suites with Claude Code, Cursor, or Codex, they would test basic schema-matching, but would <strong>collapse at catching subtle silent fails</strong> and would not warn us when the tooling wasn't set up for the agent to retrieve information.</p></div></div>
<p>Further, these agents completely failed to validate the quality of responses. For any good agentic service, calling the right tools and delivering a truly high-quality response go hand-in-hand to pushing a good product.</p>
<p>Then we ventured out, talking to founders. Voice agent companies testing their software manually, driving themselves crazy by speaking to their agent to test every tool path. Companies working in banking, legal, and healthcare without the ability to test features not currently encompassed by the limited data they have access to.</p>
<p>We knew one thing: <strong>current testing infrastructure is structurally broken and doesn't match the speed of development.</strong></p>
<p>What's more: quality assurance was completely absent. With no evaluation metric or North Star to look towards or compare their results with, teams are left in the dark as to the true utility or accuracy of their product.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>So, in week 3 of YC, we took a step forward, and <strong>completely pivoted</strong>.</p></div></div>
<p>We want to work to help other founders improve their products before customers complain. We're providing testing and evaluation as a service, because <em>the speed of testing and quality validation should match the speed at which we now code</em>.</p>]]></content:encoded>
            <category>Founders</category>
            <category>Testing</category>
            <category>AI Agents</category>
        </item>
    </channel>
</rss>