Skip to content

Repository files navigation

level-five (l5)

tests License: MIT

A level 3 agentic harness built by following Agentic Programming by Jerod W. Wilkerson. The harness is a story execution system: stories enter with an approved plan, move through implementation, testing, documentation, and verification, retry when verification fails, and end completed or escalated — or pause, when capacity runs out, and continue where they stopped.

About the name. The name is aspirational. What you'll find here is a level 3 harness, but level five is where the ladder leads, and the repository is built to grow in that direction.

Companion to the book

This repository tracks Agentic Programming through level 3. Part 3 (Chapters 12–19) explains how an agentic harness works, and Appendix A, "Building a Sample Level 3 Harness," builds this one from an empty directory to a working system, including the real escalations that happened along the way.

The book is at agenticprogrammingbook.com.

The appendix is the starting point, not the finish line. It stops at a deliberately small harness so the essential structure stays readable, and then hands you a roadmap: harden first, following Chapter 18, then scale, following Chapter 19. This repository is walking that roadmap. Every improvement arrives the way the book says it should — as a story the harness plans, executes, verifies, and documents itself.

The appendix state

The exact harness Appendix A describes is tagged appendix-a. If you are reading the appendix and want to check your own build against it, or want to start where the appendix stops, use that tag:

git clone https://github.com/jerodw/level-five.git
cd level-five
git checkout appendix-a

main has moved past it. The appendix's code excerpts — the workflow definition, the implementer prompt, the coordinator's routing loop — match the tag, and the differences on main are the point rather than drift.

What has changed since

Each story below is a step the book's roadmap calls for, or a failure the build hit that the roadmap did not anticipate. The story artifacts are committed in .harness/stories/. Run directories are execution state rather than source, so .harness/runs/ is gitignored and does not travel with a clone; the runs worth keeping — four escalations, and three runs preserved for what they showed — are copied into .harness/runs-archive/.

Story Change Where the book argues for it
001–002 l5-status; per-stage changed-files records Appendix A (appendix-a)
003–004 One shared harness layer; machine-readable artifact schemas Ch. 16, prompt layering; Ch. 14, artifact contracts
005–007 Schema-directed story parser and pre-flight validation; one reader of a story artifact; coordinator-enforced stage output ownership Ch. 15, governance boundaries; Ch. 18, hardening
008–009 The story schema and the workflow's stage rules injected into the planner prompt Ch. 16, injection over restatement
010–012 attempts/attempt-N/ archives, execution-history.json, retry-history.json Ch. 17–18, retry evidence
013–017 Verification hardening: the suite re-run in a clean clone, assertions that can be shown to fail, the schema inventory moved out of tests/, the coordinator's output contract asserted directly, an implementer's test edits decided by reverting them Failures this build hit; Ch. 18 in spirit
018–020 Story artifacts validated at plan time; the revert check reverting to what the stage found rather than to HEAD; escalated runs made resumable, committing their work when they stop Ch. 18, hardening; Ch. 17, retry evidence
021–024 A run commits only what it produced; required outputs must be written by the attempt that ran; l5-plan commits the artifact it caused; escalation-summary.md carries the finding rather than a pointer to it Ch. 18, hardening
025–027 Plan time validates the artifact it just wrote; one resolution of a story's own commit range; a re-run onto a branch already holding finished work refused Ch. 18, hardening
028–031 Retries routed to the stage that owns the defect; loading code retired out of git history and the rule enforced mechanically; a story branched from a declared base; mutation controls that mutate the working tree, never a pinned revision Ch. 17, retry routing; Ch. 18
032–035 A plan refused when it assigns work a stage cannot own; cloning over the normal transport instead of copying a live object store; a resume guard that works when the harness is its own target; stages granted the read-only tools they need, with mutation denied at the door by a hook Ch. 15, governance boundaries; Ch. 18
036–038 A stage that failed mechanically runs again in place, on its own budget; a stage's baseline is what that stage first found; a test module named for what it checks Ch. 18, hardening
039–043 Every configurable value proven configurable; no target-stack literal in harness source; the verification runner no longer assumed to be Python; a plan may assign an existing file to the implementer; an undeclared config key refused Ch. 15, governance; portability the appendix assumes
044–046 The documenter records what it changed; the documenter runs before verification, so its output is judged; the test location comes from configuration Ch. 18, hardening
047–051 The tester writes fixture-based tests, asking whether a shipped artifact is an assertion's subject or its input; every test that needed a workflow as an input now builds one, leaving only the modules the shipped definition is genuinely about; a verifier verdict can say that retrying cannot finish the work, ending the run without spending the budget; retry guidance declares what would satisfy each instruction, so guidance that sanctions the outcome it then fails is caught and rewritten rather than charged to the stage; and a documented claim about a story with no merged work is reported, because the run directories and request files a documenter writes from are untracked and reach no clone Ch. 18, hardening
052–053 A new test may not resolve this repository's own git history, so a test's result stops depending on what has been committed since; the modules that still did are declared under a ceiling that only a conversion lowers, and the conversion took that ceiling to zero — the texts those tests needed are committed fixtures now, read from the tree rather than out of the commit graph Ch. 18, hardening
054–058 A documented quantity counts whether it is written in digits or in words; a finding too small to fail a run has somewhere to go, re-entering the workflow as a correction pass that spends no retry budget and may change words but never behaviour, beside a shared prose layer reaching every stage that writes for a reader; a story's commit range ends where the story ended rather than where it escalated; a plan assigning a stage a path it is restricted from is refused when the session ends, naming the grant that would make it legal; and a check that re-runs the suite says so before it starts, so a silent console no longer reads as a hang Ch. 18, hardening; Ch. 15, governance boundaries
059–063 Budgets, resumes, and what a stopped run keeps: l5-plan offers to run the story it just committed and skips without reading when nothing can answer; every stage declares a self-route budget, so a mechanical failure runs again in place rather than ending the run; a crashed run's resume archives the interrupted attempt before re-running the stage, and refuses rather than overwrites; a resume restores the attempt allowance so a run that escalated at its ceiling does not resume with nothing to spend; and a run has a cost ceiling — one for the run and one per stage execution, declared with the reasoning behind each figure, with every invocation recorded in cost.json Ch. 17, retry evidence; Ch. 18, hardening; Ch. 13, budgets
064–068 The coordinator runs the test suite, not the agents — an eleven-minute command stops being something a ten-minute agent turn has to fit around: the implementer runs only the tests its change touches, since the revert check, the coordinator and the clean-clone check each run after it; a story that escalated and resumed leaves a completion its merge cannot drop; the coordinator runs the target's configured suite and the tester only authors, so the verdict is an exit code rather than a stage's account of one and the full output is kept; a correction pass costs a correction rather than a re-run; and a plan may declare that a change forces an existing test to adapt, so plan-time validation refuses only what a run would actually refuse Ch. 18, hardening
069–072 A second workflow, and the work choosing it: a story artifact selects the workflow definition its run loads, so the choice belongs to the work rather than to the target's configuration; refactor-workflow.json drops the create restriction and the revert check — which assume every legitimate test edit is forced by a change elsewhere — and guards the implementer with a suite census instead, because a refactor's threat is weakening the validation that already exists rather than authoring the validation that judges it; a prompt's filename says which workflow owns it; and the planner proposes the workflow it plans for, every definition declaring an applies_when, with a headless invocation that can name no workflow and ask nobody refused before anything is invoked Ch. 18, hardening
073–076 Enforcement where a prompt paragraph used to stand: a stage that runs no suite has the invocation denied at the door by a deny-only Bash guard that reduces a command to its program and targets; the denial is recorded as a filter over the spellings agents reach for rather than a boundary an invocation cannot cross, with the measurement that makes it worth having; a superseded attempt keeps its evidence, the archive collecting each check's result off the shape of the declaration that carries it and following a record's own output_path; and the workflow proposal delivers its answer to a path it is permitted to write, keeping the transcript of a classifying turn a developer needs to read Ch. 18, hardening
077–080 A cheaper check, a suite that behaves on real hardware, and a run that waits rather than ends: a writing stage may nominate the test that fails without its change and the revert check decides on that test alone — passing where the stage left the tree and failing with the governed edits reverted — because one failing test proves a failing suite while no number of passing tests proves a passing one; a pty teardown stops overwriting the exit status its test asserts; the suite runs in parallel with no fixed worker count, from dependencies declared in a tracked file; and a capacity stop pauses the run rather than ending it, bounded by a wait the harness was told rather than one it guessed, because a budget ceiling is a reason to stop while capacity exhaustion is only a reason to wait Ch. 18, hardening; Ch. 13, budgets
081–085 Evidence that outlives the run directory it was written in: .harness/history/*.jsonl are versioned append-only records declared in one schema, summaries rather than copies, that no routing decision reads; every terminal path appends its record before the commit that path leaves, so the only writer's own output is never left untracked; every coordinator suite run records the scope it was narrowed by, so a whole-suite green and a one-test green stop being indistinguishable; each run keeps a result-and-output pair keyed by stage, attempt and try, so a rerun no longer writes over the evidence the self-route that caused it cited; and a later pass supersedes an earlier failure only where its scope is a subset, so a rerun narrowed until the answer is convenient clears nothing Ch. 19, scaling; Ch. 15 and Ch. 18, evidence
086–088 Who authorized the work, and who may edit what judges it: .harness/stories/ is blocked to every stage, so no stage can rewrite the artifact its own work is judged against; a story artifact carries a required mandate and the coordinator refuses at pre-flight — above the run directory, the branch and every invocation — to run work whose mandate does not resolve to a human, permitting only on positive evidence and reporting each failure distinctly; and the approval behind it is observed rather than inferred, l5-plan asking the developer directly and stamping only what the answer gave it, where before the evidence was that a terminal was attached and a file appeared Ch. 16, governance; Ch. 18, evidence
089–093 Filing to an issue tracker without letting the tracker break a run — the outbox: anything bound for a system outside the harness goes into a durable local queue first, written through a temporary file replaced into place and keyed by a digest of the identity alone, so no failure on the far side becomes a failure of the run that produced the item; enqueue is total over whatever it is handed, since a value json could not render would otherwise have stopped a story, and a refused item is reported rather than coerced; one configured command files an entry, through which the harness knows nothing else about the tracker — stdin, a key, a reference never parsed, and an exit code saying landed, retry or stop; the sweep drains at three points chosen so failure is free and alone among the pre-flights may not refuse; and the same shape run backwards asks what is already filed, carrying nothing known and nothing filed as different answers Ch. 17, external systems; Ch. 18, hardening
094–095 The Inspector reads a target's own code and files story briefs — pre-planning artifacts carrying intent, evidence and a severity, which nothing executes and which become stories only through l5-plan's interview, so there is nothing to refuse; as the outbox's first producer it pays what a producer owes, its dedupe identity carrying the mechanically stable parts and never the prose a model rephrases between runs; and that dedupe gains its free tier, the local queue read as a record of what this harness already filed, both sources asked every time with neither a fallback for the other — a landed entry suppressing, a pending one reported as queued rather than filed, and a failed one suppressing nothing, because it is terminal and the finding it carries reached nobody Ch. 18, inspection
096–099 A brief becomes something a person uses end to end — written in conversation, filed, and planned from: what the planning entry point takes in, what it tells you it is doing, what a brief is allowed to be about, and where one a developer asked for goes. The link that makes the chain a loop: l5-plan --brief <key> fetches a filed brief through the same configured command the dedupe question is asked of — asked this time by key, so one brief comes back in full rather than a bounded summary of a set — and hands the planner the brief's own prose instead of a paraphrase of it, with the brief's workflow standing in for the selector's proposal. Planning stays interactive: the interview happens, the developer approves, and the mandate is still stamped from an observed answer, because "plan from a brief" and "plan headlessly" sound like the same feature and are not. The brief is a plan-time input alone, leaving one trace — its key in the story's description, as prose — and the filing side is widened so the round trip is lossless, since a title and a body throw away the fields a fetched brief is held to. And every message a developer decides on now names the story's title beside its id — the approval prompt, which named no story at all where "approve this plan?" omitted the act the whole mandate mechanism rests on, the run offer, each skip line, and the fresh run's workflow-started announcement; the title is bounded where it is printed rather than trusted, because it is prose an agent wrote and nothing in the story contract constrains its length or forbids it a newline. And a brief stops being only a defect the Inspector found: severity is redefined as how much the work matters and confidence as how sure the writer is of the judgement the brief makes, each given a defect reading and a work-to-be-done reading at every level, and category gains feature and refactor — appended, because the identity a brief is filed under carries the category as a string, so the ten already filed keep their keys. The Inspector still files defects and nothing else, since everything it can find is one; the widening is for the producer that is a person, for whom leaving a feature nobody built alone has no consequence to weigh and a feature just asked for cannot be imagined. And the harness's second producer of briefs finally has somewhere to put one. It wrote them already and stopped there — the brief existed as text in a terminal and reached nothing. It now goes into the same outbox the Inspector files into, deliberately rather than into a directory of files: a second producer writing files would rebuild the identity, the key, the transport and the landed/pending/failed record beside the queue that already has them, and dedupe would never see those briefs, so the Inspector would go on filing findings a developer had already filed by hand. What a brief is filed under moves into a module of its own, because an identity derived twice is a duplicate filed on every inspection. The seam is a skill rather than a command, since the filing is part of a conversation: the harness ships a plugin directory, l5-assist loads it for the session, and an assist session in any target has it with nothing installed into that target. The judgement half is the skill's — the slug rule, the bare paths, showing the developer the whole brief and filing on their word — and the deterministic half is a module the suite drives. Nothing here plans, confers a mandate, or changes anything already filed; a brief is inert, and a brief nobody wants costs a human reading it and deciding no Ch. 18, inspection; Ch. 17, external systems; Ch. 16, governance
100 The Inspector stops being something somebody has to remember to run. When a story's run completes, the coordinator inspects what that story touched and files briefs for what it finds. It adds no judgement — what a good finding is stays in the Inspector's prompt, and the dedupe, the validation, the identity, the brief cap and the filing stay where they already were — and what is new is run integration. The scope is the files the run changed plus, for each, the files git tracks directly beside them: a changed file alone cannot reveal duplication or a parity gap, because both live in the file the story did not change, while the containing directories in full re-inspect a subsystem on every story that touches it. That expansion is computed before any agent is invoked, so it is testable and its cost is known in advance, and a file cap trims the neighbours before it trims what the story touched. It runs after the completion commit, so a slow inspection cannot delay the durability of the work, and before the completion sweep, so the briefs it enqueues are filed by that same sweep. And it may never block, delay or refuse a run — the outbox sweep's rule, inherited for the sweep's reason, expressed as a total function with no return value, no exception and no parameter by which a caller could be told to stop, so a later reader cannot make it consistent with the refusing pre-flights around it and delete the guarantee by accident. A configuration it cannot obey is named in its record rather than refused, because a function with no way to refuse cannot be given one for a bad bound. What an inspection did is recorded durably: the run directory is gitignored and reaches no clone, so the harness declares the cross-run log its own schema had reserved for exactly this producer since story-081 and commits the record in a second commit that stages the record path by name and never the whole tree — an inspection invokes an agent inside the repository, and a commit the harness makes must not be able to absorb what that agent touched Ch. 18, inspection; Ch. 19, scaling
101 An inspection records what it cost: the Inspector ran under a cost ceiling and nothing accumulated what an inspection actually spent, so there was no corpus to tune that ceiling against — and since story-100 an inspection fires on every completed story, several times a day, each invocation unnoticed on its own. Both modes now append one line per inspection to inspection-log.jsonl, carrying the cost, the mode, the scope size and what came back, so one read answers what inspection has cost; a narrow-mode inspection also appends its cost to that run's cost.json, because its inspection is part of that run's life. The figure is carried rather than re-derived — run_agent already parses it off the result event, and reading the agent log back would be a second parser of the harness's own output Ch. 18, evidence
102 A run stands on its branch before it writes anything: the checkout moved above the run directory, the cross-run history and every re-entry a resume makes, because those writes landed on whatever branch l5-run was typed from — and .harness/history/ is tracked and differs between branches, so git refused the checkout and the run died with an unhandled error after archiving the entry, zeroing the counters and appending three records. It had been latent in every resume: a fresh run takes checkout -b, which carries local modifications along, and only an existing branch refuses. A failed checkout is now a refusal naming the paths rather than a traceback, and the invariant is statable — nothing writes the target's tracked files until the run stands on its branch Ch. 18, hardening
103 The harness leg checks the tree the run will read the harness from: story-102 repointed the resume guard's branch comparison at the story branch, and where the harness and the target are one checkout the guard had been deferring to that comparison for the harness question too — but a branch reference says nothing about the tree you are standing in, and the clean-tree check beside it says the tree is clean rather than that it is that branch's. So fixing the harness on the base and resuming was refused as nothing having changed, which is the recovery story-101 had needed two hours earlier. The leg now requires the tree to be standing on the branch the earlier check spoke about, and gives up refusing a pointless off-branch resume to do it — permitting one is the direction this guard's bias already takes; refusing a needed one is not. Found by the post-story inspection of story-102, in the change it was inspecting Ch. 18, hardening
104 A question to the developer is about consequences: the planner interviews the developer and the assist agent explains things to them, and both did it in the vocabulary of the code — while the developer, who is not reading the file as they answer, was being asked to decide something they could not evaluate without opening three of them. The shared prose layer that governs how every agent writes had scoped itself to artifacts, so a question in an interview and a line in a terminal fell outside it, and l5-assist never loaded that layer at all — the agent the developer talks to most was the one governed least. The layer now reaches what an agent says in a session, and asks for the consequence first, what each option costs, and what happens by default, with the citation beneath rather than as the lead; neither half is a licence, since a decision no plan can be built on is worse than the question it replaced and a person who asks where something is still gets the path and the line Ch. 16, prompt layering
105 The outbox reaches the project board: this deployment had a GitHub project and eighteen briefs the harness had written that reached nothing — the sync command was configured, the query command was not, and the sync script skipped the board because it had no project to file into. No new script was written: both reference implementations already shipped, and what was missing was the wiring, the second call that lands an item in a named column rather than in the field's default, and one repair. The board call had been unreachable on retry — the script found the existing issue and returned above the board block, so an issue whose creation succeeded and whose board call failed was never added while the sweep reported it landed. Turning the query side on is what makes a second inspection stop refiling what the first one filed Ch. 17, external systems
106 A tester edit outside the test directory is governed, and a refused edit is reverted rather than escalated: the stage that writes the validation could edit the thing being validated, and nothing watched it — the code stage carried a path restriction and a revert check, the tester carried neither, and story-104's tester changed the very file its own assertions were about while the run kept no way to see what it changed. A stage may now declare the prefixes its writes stay inside, and the revert check that comes with the declaration brings the stage baseline with it, which is the half that closes the evidence gap. A refused edit is undone from that baseline and recorded rather than stopping the run, since a stopped run over a case already proved harmless buys nothing. And the rule is derived once — plan time and run time had been reading it in two places that could disagree Ch. 15, governance boundaries; Ch. 18, evidence
107 The queue holds work and the index holds receipts: one directory held two different things under one name — work not yet filed, and a permanent record of work that had been. Landing already drops the payload, so what a landed entry leaves behind is a receipt saying which identity was filed and where, and nothing about it is waiting to happen; meanwhile every caller opened and validated every file in the directory, at a cost growing with everything ever filed rather than with what was left to file. The two are separate now, with the derivation both readers share moved beside the reads it is built from, because landed still suppresses a finding while pending is reported as queued and failed suppresses nothing — three answers from two places, and one function that gives them. The index is gitignored like the queue and the cost is stated: receipts do not survive a clone, because a versioned index would write into a tracked directory mid-run and the next run's clean-tree check would refuse it. Migration is lazy — a landed entry moves when a sweep meets it — so a deployment that never runs a migration step cannot silently lose dedupe Ch. 17, external systems
108 A forgotten file is not a failure: the coordinator already recorded why it was re-entering a stage, and spent one budget for every reason — so a stage that left the suite red and a stage that did the work and forgot to write a JSON file cost the same. In nine stories the bookkeeping re-entry happened six times and twice decided a run: story-101 escalated with its work complete and its suite green, and story-107 spent two in a row and finished only because nothing else went wrong. A stage may now declare a second budget spent only on a required artifact that is missing or stale, with the failure budget untouched by it, and the tester's failure budget returns to the two it was before it was raised by hand as a hedge against this. A stage declaring no split is unchanged, down to the wording of its escalation Ch. 18, hardening
109 A test may not time the machine: three tests asserted that a real operation finished inside a sub-two-second window, which under a parallel suite is a claim about how fast the hardware is at that moment rather than about the code — and one of them ended story-101 at $23.88 with its work complete and its suite green on 4767 of 4768, reporting as a failure a precondition its own message said proved nothing. Each of those tests has two halves and only one is load-sensitive: killing an operation for exceeding a bound is a claim about the harness that a loaded machine only makes more true, while finishing inside one is a claim about the machine. The halves are separated, the fast ones no longer assert a deadline, widening the constant was rejected as the same defect with a better number, and the rule is enforced rather than written down — every module that reads a clock is classified in both directions, one route reports that the machine was the obstacle, and a ceiling keeps that report expensive Ch. 18, hardening
110 A filed brief carries its classification: every brief the Inspector writes is classified — a category, a severity, a confidence, an effort and the workflow to plan it under — and none of it reached a person looking at the tracker, because it was filed in an encoded blob only the fetch reads. A brief on the board was prose and one label, so a developer choosing what to plan could not tell a correctness defect from documentation drift without opening each one. The sync command now adds a category label beside the one it already applied and sets five project fields from what the payload already carries. Both, because they answer different questions: a field sorts and groups on the board, by the option order the field declares rather than alphabetically, and is invisible outside it; a label travels with the issue and is searchable everywhere but has no order. A label the tracker lacks is created, a field it lacks costs that field and nothing else — because the command can make one and not the other Ch. 17, external systems
111 A stage records every file it touched: story-101's tester changed a file and left it out of its own changed-files record, nothing caught it, and nothing could fix it — the record is another stage's output under a blocked path, so the correction pass declined it and the run ended with an account known to be wrong and structurally unfixable. That matters more than one missing line, because those records are what the governance layer reads: what a run touched, which stage created a governed path, what the revert check and the baseline merge turn on, and what a post-story inspection takes into scope. The coordinator now signs the tree before a stage runs and compares afterwards over tracked and untracked paths alike, sharing blocked-path resolution with the readers that already exist so the check and the enforcement cannot disagree, and routes an omission back to the one stage that may correct it Ch. 15, governance boundaries; Ch. 18, evidence
112 A stage can check its own outputs: a stage learned it had left a required output unwritten only after its turn ended, when the coordinator checked and re-entered it — a whole invocation to produce one file, six times in nine stories. Its prompt already said so as forcefully as prose can, which is the move this replaces. A stage may now run the check itself at any point in its turn, and a turn-end hook catches what it would have caught; the hook blocks at most once, fails open on every malformed payload and unreadable file, and decides nothing, because the coordinator's own check remains the authority and reads nothing the hook wrote. One derivation answers both, since two spellings of written freshly and valid would drift silently in the worst direction — a stage told it was complete and re-entered anyway Ch. 18, hardening
113 A refile over a failure is reported: a terminally-failed queue entry stops suppressing a finding, so the next inspection files it again — and the re-enqueue overwrites the entry at the same key, discarding the attempt count and the last error, which are the only lasting record that filing this identity keeps failing. So a misconfigured transport looked exactly like a flaky one, on every inspection, forever. The local index now keeps the keys held in the terminal failed state and says so twice: as a count on the queue line, reported even when it is zero, and as a note beneath each brief filed over one. The failed set is evidence and never a suppression source, and no retry ceiling was added — stopping after so many failures is the suppression that loses a finding nobody ever saw Ch. 17, external systems; Ch. 18, evidence
114 What the Inspector files is not noise: a promotion policy written against output a developer had read rather than against a prediction of it — 46 open briefs from thirteen post-story inspections, none closed and none planned. Two things the request predicted did not survive that reading, so the policy is narrower than it would have been: briefs are not thin, the median being 313 words with file:line evidence and its workflow choice argued, and the cap is inert, having dropped nothing on any inspection ever. What the corpus shows instead is arithmetic — filing runs at about three and a half briefs per completed story and planning drains none of them — so the lever is a severity floor rather than a quality bar, one number applied where the cap already lives so both producers get it from the same call. The prompt is told the floor as well as the filter that enforces it, which is not a duplicated fact: the filter decides, and the prompt exists so an invocation does not spend three hundred words on a brief that will be dropped on arrival Ch. 18, inspection
116 Every brief is titled for the behaviour it produces, defects included: a brief's title is the line a developer reads in a tracker column, and the three places describing one said only what it is not — not part of what a brief is filed under — while the schema taught a split, what is wrong for a defect and what should exist for work to be done. So defects were titled for today's state and a column read as a list of complaints, every line describing something that will not exist once the work lands. One rule now: a title states the finished behaviour in the present tense, as though it already holds. That something is broken now is carried by category, by severity and by the body's opening line, and does not need a fourth statement. Planned from a brief the Inspector filed and l5-plan --brief fetched back, which closes that loop end to end Ch. 18, inspection

Still ahead, in the order Chapters 18 and 19 recommend: per-agent logs and a watcher, a fuller hook-based tool policy in place of the static allowed_tools allowlist, an adjudicator, git worktrees and parallel story execution, and a real initialization library.

The harness stays at level 3. Epics and products (Chapters 20–22) are a different unit of coordination, and the book is explicit that the story workflow earns that step through a track record rather than a feature list.

Prerequisites

  • Claude Code CLI (claude) with an active subscription
  • Python 3 (3.10+)
  • Git

The harness itself has no third-party runtime dependency — it uses only the Python standard library. Running its test suite needs what requirements-dev.txt declares; see Tests.

Scripts

All harness capabilities are invoked through l5- scripts in scripts/:

Script Purpose
l5-init Initialize a .harness/ structure in a target repository
l5-plan Plan a story interactively with the planner agent, from request text or from a filed brief named with --brief; commits and pushes the story artifact the session produced, then offers to run it
l5-run Execute an approved story through the story workflow
l5-status Show a snapshot of story runs (status, current stage, retries), or one run's detail
l5-assist Launch the interactive assist agent with harness context, and with the skills the harness ships — among them filing a story brief into the outbox
l5-check Report whether a stage's required outputs are present in the run directory, were written by the invocation running now, and satisfy their declared schemas — the check the coordinator makes after a stage's turn, reachable while the turn is still open. Every stage is invited to run it on itself; it decides nothing
l5-sync Drain the outbox — the durable local queue of work to file externally — reporting what landed, what is still pending, and what no sweep will clear on its own

Example:

scripts/l5-plan "Add a --dry-run flag to l5-run"
scripts/l5-plan --brief <the key the tracker reported for a filed brief>
scripts/l5-run story-001
scripts/l5-status

Layout

workflows/       workflow definitions (stages, artifact routes, retry rules):
                 story-workflow.json, and since story-070
                 refactor-workflow.json for behaviour-preserving work
schemas/         JSON Schemas for the structured artifacts, plus their manifest
prompts/         reusable agent prompt templates ({{placeholder}} injection)
plugin/          the skills the harness loads into the sessions it starts,
                 for the session rather than installed into a target
orchestration/   the Story Coordinator and its supporting modules
rules/           execution rules enforced by the coordinator
scripts/         thin l5- entry points
hooks/           the deny-only tool guard each stage invocation carries,
                 and the turn-end check that asks a stage for the outputs
                 it owes before letting the turn end
templates/       starter files l5-init copies into a new target repository
tests/           the coordinator's test suite, run without model calls
.harness/        target-repository state: config, standards, stories, and
                 docs/ARCHITECTURE.md; plus runs, logs, requests and the
                 outbox queue, which are gitignored execution state

The harness pieces (workflows/, schemas/, prompts/, plugin/, orchestration/, rules/, scripts/, hooks/, templates/) are reusable across target repositories. The .harness/ directory is target-repository state; run l5-init to create it in any other repository you want the harness to work on.

This repository is both the harness repository and its own first target repository. Every demo story is a real harness feature, so the harness participates in building itself from the start.

Looking for the architecture document? It is .harness/docs/ARCHITECTURE.md, not a top-level docs/. Its location is a configuration value rather than a fixed part of the layout: architecture_docs in .harness/config.yaml names it, and the coordinator injects whatever that key names into the implementer's context on every run. It sits beside .harness/standards/ because both are agent context, and a target repository is free to keep them elsewhere.

How a story runs

  1. l5-plan runs an interactive planning session and writes an approved story artifact to .harness/stories/, then validates, commits and pushes it and offers to run it: Enter starts l5-run for the story just planned, anything else skips and prints the command that would have started it. When stdin is not a terminal the offer is not made at all and the command is printed, so a scripted invocation cannot hang on a prompt nothing can answer. Unless --workflow names one, the session runs in two phases: a first classifying turn proposes the workflow the request should run under and shows its reasoning, and the interview begins only once that proposal is confirmed or overridden — because a workflow's stage list is injected before the interview, so the choice cannot be made partway through it. With no terminal and no --workflow there is nobody to confirm a proposal, and the invocation is refused before anything is invoked, written or committed. Since story-096 the request may be a filed brief rather than text: --brief <key> fetches it through the configured filed-query command, renders it as the request the session is given, and takes the brief's workflow as the proposal, so the classifying turn is not made at all. A brief and request text are mutually exclusive, and every way the fetch can fail refuses above the session, the snapshot and the commit, saying what happened and that the request can be passed as text instead.
  2. l5-run hands the story to the Story Coordinator, which creates a story branch and a run directory under .harness/runs/<story-id>/.
  3. The coordinator advances the workflow stage by stage — implement → test → document → verify under story-workflow.json, or implement → document → verify under refactor-workflow.json, whose correctness claim is that behaviour is unchanged and whose implementer is guarded by a suite census rather than by the create and revert checks. A second workflow is not a second unit of work: the story is still the unit, and which definition its run loads is a field on the story artifact. The coordinator assembles each stage's context, injects it into the stage prompt, and invokes the agent headlessly (claude -p). A stage that runs no suite cannot invoke one: the deny-only Bash guard each invocation carries reduces a command to its program and the targets it was pointed at, and refuses it when that matches the same reduction of the target's configured test command. Every stage is invited to run l5-check on itself, and a stage that tries to end its turn with a required output missing, stale or invalid is blocked once by a Stop hook naming what is unwritten — so the artifact is written in the turn that earned it rather than in a re-entry that costs the run an invocation. Both are reports: they are optional, they block at most once, and the coordinator's own check after the turn is what decides, unchanged. The documenter runs before verification so that what it writes is judged rather than taken on trust.
  4. The verifier writes verification-result.json. The coordinator routes from that artifact: advance, retry, or escalate. A retry goes to the stage that owns the defect — named by the verifier as a category the workflow defines, with no default route — carrying structured guidance in retry-guidance.json. A verdict may also report that retrying cannot finish the work at all, which escalates immediately and leaves the retry budget unspent. On a passing verdict the coordinator re-runs the suite in a fresh clone with the story committed, because the working tree is the one place that commit does not yet exist.
  5. A stage that fails mechanically — rather than being judged wrong — runs again in place, on a separate per-stage budget that retries do not share.
  6. A stage invocation stopped because capacity ran out — a provider rate limit, an exhausted plan quota — is not a failure of the work, so the run pauses rather than escalating: status paused, exit code 3 (completion is 0 and escalation is 2), no escalation summary, and every counter left where it stood. The pause commits what the run left in the working tree and then continues at the same stage — waiting in place when the signal named a reset time within max_pause_wait_seconds, and otherwise exiting for l5-run <story-id> to resume, which continues at the stage it paused on with nothing reset and nothing archived. The state and the commit land before any waiting begins, so a process killed while it waits loses nothing.
  7. A run that completes is then inspected, where inspect_after_story_max_files is configured: the Inspector reads what the story changed plus the files git tracks directly beside them — one level, because duplication and parity gaps live in the file the story did not change — and files what it finds as story briefs into the outbox. It happens after the completion commit, so a slow inspection cannot delay the durability of the work, and before the completion sweep, so the briefs it enqueues are filed by that same sweep. It may never block, delay or refuse a run: it returns nothing, raises nothing, and a value it cannot obey is named in its record rather than refused. A completed run therefore leaves a second commit where an inspection was made, carrying its record in .harness/history/inspection-log.jsonl and staging that path by name and nothing else. Delete the key and no run inspects anything. That record says what the inspection cost: the figure the invocation itself reported, beside which mode it was, how many files were in scope, how many invocations the figure covers and the three finding counts — and a broad l5-inspect invocation writes and commits a line of its own shape into the same file, so one read of one file answers what inspection has cost across both modes. The cost of a completed run's inspection is also appended to that run's cost.json beside the stage invocations, and is deliberately never charged to the allowance max_run_cost_usd is compared against: the inspection happens after the completion commit, and charging it there could put a completed run over a cap it had already honoured. An invocation that reported no cost records that it reported none rather than a zero, and a record that cannot be written costs the record and nothing else.
  8. Every run leaves its state (state.json), the same events in two renderings (events.log and execution-history.json), a record of any retry (retry-history.json), and the artifacts each stage produced.

See .harness/docs/ARCHITECTURE.md for the full architecture.

Tests

The Story Coordinator is deterministic and fully unit-tested without any model calls (a fake runner plays back scripted stage artifacts). Run the suite with:

.venv/bin/python -m pytest tests/ -q -n auto

That is test_command in .harness/config.yaml verbatim, and running it verbatim is the point: the harness's own gates — the revert check, the coordinator's suite run in the tree, and the clean-clone check — execute the configured command, so what a developer runs and what the gates run cannot drift apart. -n auto reads the core count of whatever machine it lands on, so no core count is written down anywhere.

The revert check reaches that command second. A writing stage may nominate the test that fails without its change, and the check then runs test_selection_command — the same configuration's selector, with the nominated test substituted at {test} — on the tree the stage left, where it must pass, and again with the governed edits reverted, where it must fail. Pass-then-fail decides the check on that one test; anything else falls through to the configured suite command above, so a bad nomination costs one selector run and changes no verdict.

The dependencies that command needs are declared in requirements-dev.txt, and it has to be installed into both interpreters this repository configures — the one you run the suite in and the one verification_runner names:

.venv/bin/pip install -r requirements-dev.txt
.venv310/bin/pip install -r requirements-dev.txt

Install it into only the first and your local suite is green while the clean-clone check dies on an unrecognized argument, because that check runs the same command under the second interpreter. That is the failure this instruction exists to prevent.

Contributing and feedback

This repository tracks the book through level 3, so its scope is what Part 3 and Appendix A describe. Small fixes — genuine bugs, or errors in the code and its docs — are welcome via pull request. Improvements the book's roadmap calls for are welcome as issues; they are best planned and executed through the harness itself, which is the whole point of it. Changes that would take the harness past level 3, or in a direction the book does not argue for, are out of scope here.

Found a bug in the harness code? Open a GitHub issue. For anything about the book's content — typos, unclear passages, errata — please use the feedback form at agenticprogrammingbook.com/feedback rather than GitHub Issues.

License

MIT — see LICENSE. Copyright © 2026 Jerod W. Wilkerson.

About

A level-3 agentic harness — the companion code to Appendix A of the book Agentic Programming.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages