AS
All work

Project 01 · Terrace, 2026

Autonomous Coding Agent

An autonomous coding agent that turns tickets into pull requests

A model writing a patch is the easy part. The hard part is a two-hour, side-effectful job that has to survive a pod rollout, a revoked API key, a flaky integration test and a reviewer who asks for changes three days later. In its best stretches it ranked among the company's top three contributors by merged lines of code.

GoLLM AgentsRAGTemporalKubernetesDockerMCPPlaywrightCI/CDObservability
At peak
Top 3 contributor by merged lines of code
Codebase
115k lines of Go, hexagonal
Multi-model
Claude · Codex · Gemini · Kimi · Grok · GLM · Composer
Bounded
5 review rounds, 1 CI fix, then a human

What it is

115k lines of Go: a coding agent that runs as a service rather than in a terminal. You assign it a Linear ticket or mention it in Slack; it clones the repository, implements the change, validates it against the repo's own build, lint and test commands, opens a pull request, reviews its own diff and pushes it through CI. Then it waits for human comments and revises, unwatched.

The model behind it is swappable, and that is the point. Claude, Codex, Gemini, Kimi, Grok, GLM and Cursor's Composer all run the same work; a ticket can pin one with a label, and if it is unavailable the run moves to another by itself. Nothing above the adapter layer knows which vendor ran.

Linear poller30s tick · label-gatedSlack socketoutbound · no ingressdedup gateDurable workflow · ~40 activities1. Setup1.6 red testsClone · Analyze · Inject prompt · Baseline coverage + tests2. Agent2.5 test agentRun the coding CLI · graceful deadline inside a hard timeout3. Validationearly shipBuild · Lint · Unit · Integration · Coverage, then draft PR4. Ship≤5 roundsSimplify · Review · Fix loop · Mark ready · Watch CI · Fix CI6. Feedback≤10 roundsWait for human comments · Apply · Re-review · PushPull requestdraft at phase 3ready at phase 4
One run, end to end: four phases plus a bounded feedback loop.

Multi-model, and what makes models substitutable

Seven model families do the same work here. Making that swap mean anything is the actual engineering, because the vendors agree on almost nothing that matters: not on how they authenticate, not on the instruction filename, not on whether they can see an image, and not on what a model is even called.

  • The shared port covers only the three things every CLI genuinely does. Every difference stays inside its own adapter, because widening the interface to cover the rest is how you get an abstraction that lies.
  • Model ids do not cross vendors, so a task asks for a tier and a concrete id is resolved only for whichever family ends up running. A model someone pinned by hand cannot survive the move, so it is dropped and the ticket says what ran instead.
  • A configured key proves nothing, so every backend gets a real cheap call, and the verdict, classified as quota, auth, a failed turn, an upstream error or unreachable, is what drives the fallback. The ping names no model on purpose, so a retired id cannot condemn a healthy backend.
  • Adapters declare their capabilities. Reasoning effort is normalised once and mapped to each vendor's knob; an adapter that cannot see images has them stripped and runs text-only rather than erroring.
  • Tiers double as the cost lever: short structured calls resolve to the cheapest tier the running backend serves, implementation gets the expensive one, and a per-model rate table turns that into USD per task.
  • Token usage is pulled out of each provider's own streamed response format, which is different for every one of them, so cost and token accounting exist per request rather than as a monthly surprise on an invoice.

Context, and grounding it in code that exists

Two questions have to be answered from a sentence someone typed in a hurry: which repository this is about, and what the agent needs to know that is not in the code it is about to change. Asked without that second part, a model produces a plausible file at a plausible line number.

  • Repo detection scores four signals, from an explicit declaration down to a name mentioned in prose, and on low confidence it asks in a comment instead of proceeding. A cheap-tier pass then confirms or corrects the guess.
  • Context is written into the clone as a file the CLI reads, not prepended to a prompt, and it arrives in four layers: a generic prompt, org runbooks narrowed to the ticket, per-repo hints, and facts read off the repo. They stay separate because they have different owners and go stale at different rates.
  • Retrieval seeds come out of the ticket and the selected expert dossiers by regex, capped at eight, and are searched against GitHub's own code index. There is no vector store: an identifier is a lexical object, and exact match answers that better than a similarity score.
  • The pack is bounded at twelve searches, six files and twelve thousand bytes. Byte ceilings rather than token budgets on purpose, since there is no tokenizer in the service and an exact cap beats an estimate that has to be recomputed per model family.
  • Every reference the agent may cite is created by the retrieval tool and never by the model, so the citable set is closed by construction. Whatever it cites anyway is resolved against the default branch, and anything that does not exist is flagged rather than shipped as a confident wrong line.

Grading the work

Tests passing proves a diff is internally consistent, not that it solved the ticket. An agent that patches the symptom produces green everything and the wrong change, so the work is delegated across implementer, test-writer and reviewer agents, and graded in three separate places.

  • Before implementation, a red phase derives failing tests from the ticket, so a definition of done exists that predates the patch.
  • After implementation, a separate test-writer agent runs, because the model that just convinced itself the change is correct is its worst reviewer. Splitting the roles is the point: the same model in a different seat, with different instructions and no memory of arguing for the patch.
  • After the PR exists, a simplify pass seeds a fix-and-re-review loop bounded at five rounds, because an unbounded self-review loop spends money until someone notices.
  • CI gets exactly one repair attempt, and when the failing check is the static-analysis gate the prompt carries the exact violations, rule ids and thresholds, verified to belong to the commit being fixed. A log tail alone says something failed without saying which rule broke.
  • Frontend repos get a browser through MCP, since the interesting failures there are visual: navigate, click, snapshot the accessibility tree, read the console. The server cannot intercept traffic, so request stubbing needs an ephemeral Playwright script, and knowing where that boundary sits matters more than adding tools: an agent that thinks it stubbed a request writes a test that passes for the wrong reason.

The platform underneath

One run is hours long and the pod running it is rolled on every deploy, so the problem was never orchestration, it was durability. On a job queue a rollout mid-run loses the clone, the branch and everything the model was paid to produce.

  • A run is roughly forty Temporal activities, each with its own timeout and retry, and a worker that dies resumes from the last completed one instead of re-billing the model.
  • Deduplication falls out of the same engine. Pod-local claims are empty after a rollout, so the first tick after a deploy would open a second PR; the guard asks the workflow engine's visibility index instead, the one piece of state that outlives the pod.
  • One image, two binaries, two Kubernetes Deployments: the bot polls Linear and holds the Slack socket, the worker runs activities, so agent capacity scales without touching ingestion. Nothing calls in, so there is no Ingress; the single HTTP port exists for the kubelet's probes and the Prometheus scrape.
  • Vendor CLIs are pinned into the image, which makes upgrading a coding agent a deploy and rolling one back an image tag. Provider keys arrive as Vault-injected secrets, so no credential lives in the manifest or the image.
  • A second MCP server exposes Kubernetes logs, metrics and traces to the agents themselves, so an agent debugging a failure can read the trace instead of guessing from a stack trace pasted into a ticket.
  • Integration tests run Docker-in-Docker, because the things most likely to break are the ones that need a real container: the repo's own test suite, the vendor CLIs, and the clone.
  • Prometheus carries duration per phase, review-loop iterations, tokens in and out, USD per task, coverage delta, CI results, and backend resolutions labelled by reason. That last one is what I would add first to any agent system: it is how you find out half your runs quietly moved to a fallback provider a week ago.

Two decisions worth defending

No agent framework

The vendor CLIs already do tool use and context management, and they are the thing being evaluated, so wrapping them in a second loop is a worse copy of the easy part. What was left over, durability across a restart and a provider abstraction with live probing, is exactly what a framework does not offer: its execution model is in-process and ephemeral, where the requirement was durable and resumable. The layering is ports and adapters so the cost of a new provider is knowable in advance: an adapter, a factory case, a probe and some catalog entries, never the port or the resolver.

Test-first, for a specific reason

The failure modes are combinatorial and expensive to reach live: provider times mode times availability times vision support. Verifying that a revoked key falls back to another vendor, drops the now-invalid model label and posts the reason to the ticket should not require revoking a key, so the adapters expose seams for the command runner and the login step and the whole matrix runs in unit tests in seconds.

What I would tell the next person building one

  • Reliability is the product. The model writes the patch; everything else is making a multi-hour job survive a rollout, a revoked key, a flaky test and a red pipeline.
  • Bound every loop and name the bound. Five review rounds, ten feedback rounds, one CI repair. An agent with no stopping condition spends money and credibility at the same rate.
  • Instrument the decisions, not just the outcomes. Knowing a run succeeded is far less useful than knowing which backend it resolved to and why.

Next project

Hand Rehabilitation Platform