Arrange Act Assert

Jag Reehal's thoughts on things, mostly product development

Your coding agents need a gardener

19 Oct 2026

Your coding agents repeat mistakes you corrected last week. A gardener reads what your team accepted on recent pull requests each week and proposes a lint rule or a sharper instruction when the evidence supports one.

Your coding agents need a gardener

The gardener opens each proposal as a PR with its evidence, and you decide whether it merges.

You correct an agent on Monday. On Thursday a fresh session makes the same mistake, because nothing it reads has changed. You spent your attention on that correction twice, and you’ll spend it again next week.

Ben Holmes described a useful split. Product engineering improves the product. Factory engineering improves the agents that improve the product. He credits @poteto with calling that second kind of work “gardening”. His example is an outer loop that scores agent conversations and uses the results to suggest skill changes.

Rachel Laycock’s The Conductor Developer argues that faster agents make human attention the scarce resource. In her account, a developer directs the work and judges what comes back.

Put the two together and you get the number I optimise for: how much of your attention the agents use. I count how often an agent asked something the repository answers, and how much context you needed before you could answer an escalation. A good hand-back names the decision and shows the evidence. I’d take a precise question about a real trade-off over a quiet guess, so I aim for better questions, which sometimes means more of them.

Three loops

---
alt: "Shepherd runs review rounds inside a coding agent, then repairs CI and reads Stamp’s approval verdict. Both record their work on GitHub and export traces. The weekly gardener reads those records and incidents traced to PRs Stamp approved, then proposes skill edits or checks for a person to review."
---
flowchart LR
  subgraph inner["Shepherd: in your coding agent"]
    S["Swarm<br/>reviews the diff"] --> T["Triage<br/>fixes or defers threads"]
    T --> SI["Simplify"]
    SI --> S
  end
  inner --> C["CI repair"]
  C --> ST["Stamp in CI<br/>gates + model review<br/>approves or refuses"]
  ST --> GH[("GitHub record<br/>threads, commits,<br/>statuses, verdicts")]
  inner --> GH
  inner --> TR[("Traces<br/>stages, retries, waits")]
  ST --> TR
  TR --> G["Gardener, weekly<br/>scores reviews, traces and incidents"]
  GH --> G
  G --> P["PRs that change<br/>skills and lint rules"]
  P -. next run .-> inner
Figure 1: Shepherd gets the PR ready, Stamp decides whether it gets approved, and the gardener improves the checks and instructions the next run uses.

My implementation has three parts, each with one kind of authority:

Shepherd fixes the PR, Stamp approves it covers the first two in detail.

Count what your team accepted

You can inspect the gardener’s evidence on GitHub after the coding-agent session ends. For each recent PR Shepherd touched, it collects the review threads, Shepherd’s commits and their trailers, any reverts of those commits, the shepherd commit status, and the swarm summary’s round count. I score that record with these signals:

Signal Raised when
reverted_fix someone reverted a commit Shepherd made
human_replied a person replied in a Shepherd thread
round_cap the review loop ran four rounds without settling
stopped_short the loop ended needing the author
loop_stalled Shepherd committed, then the head went a day with no final status
churn Shepherd made more than ten commits on one PR

For each signal, the garden skill reads the linked thread or commit before proposing an edit. It names the sentence in a skill that would change, or concludes that the skill did its job. A human_replied signal where the person agreed with the finding counts as Shepherd getting it right, though it never counts as an accepted fix. An escalation the author settled the way Shepherd framed it counts as Shepherd working, because asking at the right moment is part of the job.

A proposal is as good as its evidence, so the gardener sets a strict test for acceptance. Triage names each rule it fixes in a Shepherd-Fixes commit trailer. The gardener counts those trailers, taking them only from commits nobody reverted, on PRs by people who can push to the repository. A resolved thread on its own counts for nothing, because triage also resolves findings it judges mistaken.

Built this way, a proposal records what your team chose to do. Merge it, and the next agent session reads the revised instruction or runs the new check. A finding your team rejected gets no lint rule, however many threads triage resolved.

One run, one proposal

I ran the first garden over fourteen PRs on my test repository. Shepherd had touched eleven of them, which gave me plenty of signals to read.

The gardener compared each signal’s date with the history of Shepherd’s skills. Later skill changes covered most of what it found, so it dropped those. It proposed one edit: a sentence in triage’s rule for instructions from the person running Shepherd, backed by the thread that showed the need. It also named the reading under which a maintainer might decline it.

You can judge a proposal of that size in a minute. I wanted a gardener that proposes little, with evidence, over one that rewrites prompts every week.

Check or instruction

Before it writes anything, the gardener sorts each cause with the categories from Matt Pocock’s retro skill, which Shepherd ships unchanged:

Prefer the check. A lint rule runs the same way every time and costs nothing to read. An instruction asks a model to remember, and the model reads it alongside everything else in its context.

Some decisions need context. An interface with one implementation can be a boundary worth keeping, and a reviewer should weigh that argument case by case. A pattern one file’s syntax can decide works better as a lint rule. A machine can check for a class whose only method forwards to one collaborator, or a filter().map() chain, and then Shepherd’s checks report it before any model reads the diff.

The gardener proposes a lint rule when triage fixed the same finding on at least three PRs:

---
alt: "A model reviewer flags a pattern. A person accepts the fix, recorded as a Shepherd-Fixes trailer. After three accepted fixes on separate PRs, the gardener proposes a lint rule with the accepted cases it must flag and a counterexample it must leave alone. The rule starts as a warning in the repository's Oxlint plugin."
---
flowchart LR
  J["Model reviewer<br/>flags a pattern"] --> A["You accept the fix<br/>Shepherd-Fixes trailer"]
  A --> R["Three accepted fixes<br/>on separate PRs"]
  R --> L["Gardener proposes a lint rule<br/>cases it must flag,<br/>a counterexample it must allow"]
  L --> W["Rule ships as a warning"]

The proposal includes the accepted cases the rule must flag and a counterexample from the repository it must leave alone. New rules start as warnings in the repository’s vendored Oxlint plugin. A rule that needs the whole repository, such as “this interface has one implementer”, stays with the reviewers. Each warning takes a moment of each author’s attention, so keep a rule when it catches a mistake your team cares about and explains the fix.

An agent chasing a green build can add oxlint-disable above the line and move on. Shepherd and Stamp both list each suppression comment a PR adds. Stamp’s reviewer reads the code each one covers, refuses a suppression that hides a finding the change introduced, and approves a narrow one that gives its reason.

Learn from a regression Stamp approved

A PR can pass review and introduce a production bug. Shepherd’s /incident skill takes a failed build or a production error and tries to reproduce it in a test. It checks which PR introduced the failure, records whether Stamp approved that PR, and opens a fix PR. Without a reproducing test, Shepherd stops short of claiming a fix.

The gardener reads those incident issues as a separate escapes list. For each one, it inspects the culprit diff and Stamp’s review, then the fix and its reproducing test. It proposes a rule that flags the culprit change and allows the fix. A syntax check goes into the repository’s lint plugin; a check that needs judgement goes into an existing Stamp rule pack or Shepherd lens.

An escaped regression supplies a reproducing case, so the gardener can propose a check from that one incident. The three-PR threshold applies to recurring review findings. You can inspect the failure that prompted either proposal.

Some incidents come from combining branches that passed on their own. For those, the gardener recommends up-to-date branches or a merge queue. It proposes no review rule for a failure the reviewed diff could not show.

Shepherd can use executable-stories to keep the incident’s ticket beside the reproducing scenario. Its fix workflow compares full current and base runs, checks that the ticket’s scenarios pass, and flags skipped or shortened scenarios. Stamp refuses a PR that closes an incident issue without a test that reproduces the failure. You can follow the issue from the failure through the test, fix and proposed rule.

Keep the gardener’s power small

The weekly workflow splits reading from writing. The agent that reads the record has read-only tokens and writes its proposals to an outbox. A separate job on a fresh runner applies each patch to a clean clone with Git hooks off, accepts only changes outside .github and .git, and opens the PR.

Stamp reviews the gardener’s PRs under the repository’s existing policy. A PR that edits .stamp/ or a configured rule pack goes to a person, because later reviews trust those files. For other skill changes, Stamp’s reviewer checks for removed lenses, narrowed scope and weakened rules.

Your team controls Shepherd’s fix permissions and checks through .shepherd/config.yml. Shepherd reads that policy from the default branch. You can preview a proposed policy edit before merging it, including the commands, reviewers and steps that can write code or touch GitHub.

Find expensive or stalled runs

Shepherd and Stamp export traces through autotel when you set an OTLP endpoint. The gardener can add those traces to its GitHub evidence from OTLP JSON exports, Grafana Tempo or Datadog. It matches each trace to the repository, the PR and a commit on that PR, and counts a span once when exports overlap.

The report flags four measurements for investigation:

Signal Default trigger per PR
trace_stage_failed A recorded stage failed or never finished
trace_retries At least three recorded retries
trace_approval_wait At least five minutes and half the observed iteration time waiting for approval
trace_review_cost At least $5 of known Stamp review cost

You can change the retry, wait and cost thresholds. Each report records the thresholds it used and trace IDs you can inspect. Stamp records review cost; Shepherd records orchestration time. To measure what the coding agent spent, you need usage from its harness.

For a file export, run:

bunx @jagreehal/shepherd garden acme/service --since 2026-10-01 \
  --traces /var/lib/otel/garden.jsonl

For Tempo, configure GARDEN_TEMPO_URL and read-only headers in GARDEN_TEMPO_HEADERS. For Datadog, configure GARDEN_DATADOG_SITE, GARDEN_DATADOG_API_KEY and GARDEN_DATADOG_APP_KEY, with apm_read access. The setup guide covers each source. In the scheduled workflow, a collector step holds the backend credentials and hands the agent a report file through GARDEN_REPORT, so the agent reading untrusted PR text never holds them.

Sampling and retention leave gaps, and the report says where: unavailable reads, searches cut short, partial results from Datadog and unpriced reviews. It also counts PRs with traces that Shepherd did not work on. A missing price stays unknown, and the report labels its numbers as observed samples.

The gardener checks GitHub for the cause before proposing an edit. A long approval wait might call for a workflow change, or the author might have needed time to decide. Crossing the cost threshold flags a run for you to inspect. Accepted fixes need unreverted Shepherd-Fixes trailers and the author checks described above, whatever the traces show.

Next

Both tools use executable stories for tests of their review mechanics. Stamp’s scenarios check that it withdraws stale approvals and removes an older approval after a newer refusal. Shepherd’s scenarios check that a fix invalidates its previous review receipt and that skipping a test cannot verify a fix. Those tests protect the implementation. Next I want a set of representative agent tasks to rerun against a revised skill, so I can check that a new instruction changes what the agent does.

For a garden PR, I want the correction that prompted it, a case the new check catches and one it leaves alone. You decide whether those cases justify the change. After you merge it, Thursday’s session reads the instruction you had to explain on Monday.

aiagentsengineeringleadership