Writing plans
Hand-writing plan.json: ticket sizing, scopes and rails, and what edges mean.
agb plan compiles a plan for you, but a plan is just JSON and you can write one by hand. This guide covers the rules agb validate enforces and the judgment calls it cannot make. The full field list is in the plan schema.
agb plan refuses to overwrite an existing plan.json unless you pass --force, precisely because a hand-written one may not be (overwrite guard).Skeleton
{
"repo": "/abs/path/to/repo",
"base": "main",
"gate": { "build": "npm run build", "test": "npm test" },
"prosecution": { "dryPasses": 1 },
"tickets": [
{
"id": "T1",
"title": "Short imperative title",
"body": "Self-contained spec: files to create, behavior, tests to write.",
"scope": ["src/feature/**", "test/feature.test.mjs"],
"rails": ["src/shared/types.ts"],
"edges": [{ "to": "T2" }],
"tier": "mid",
"pool_hint": "auto"
}
]
}Plan-level rules
These are checked by validatePlan, shared by agb validate, agb run and the plan compiler (validatePlan):
repois required; relative paths are resolved against the current directory (loadPlan).gatemust declarebuild,test, or both, and each must matchnpm testornpm run <script>. Put anything more complex in apackage.jsonscript.adlcBinis prohibited; the ADLC binary is resolved and authenticated by the runtime.basedefaults tomain(base default).agb runrefuses to start unless that branch is checked out inrepo.
Ticket rules
| Rule | Why |
|---|---|
id matches ^[A-Za-z0-9][A-Za-z0-9_.-]*$ and is unique case-insensitively | It becomes the worktree .worktrees/agb-<id> and branch agb/<id> in lowercase, so T1 and t1 would collide (id rules) |
body is a non-empty string | The builder sees only the body; an empty one renders an empty spec |
scope is a non-empty array of globs | An empty scope would disable the out-of-scope check (scope and rails) |
rails (optional) are valid pathspecs | Paths the ticket must not edit |
tier is cheap, mid or frontier (default mid) | Selects the candidate models |
pool_hint is gemini, claude, claude-gpt, gpt-oss or auto | Filters the tier's candidates; the combination must leave at least one (tier/hint check). gpt-oss passes the enum check but no tier has a GPT-OSS candidate, so it is always rejected |
Tier and hint combinations
cheap tier contains only Gemini models, so tier: "cheap" with pool_hint: "claude" is rejected at validation time (tier candidates, tierCandidates). Claude builders are reachable through mid (claude-sonnet-5-5-medium) and frontier (claude-sonnet-5-5-high, claude-opus-5-5-high). No tier lists a GPT-OSS model, so pool_hint: "gpt-oss" fails validation with every tier; GPT-OSS appears only as a prosecutor (PROSECUTORS).Edges
An edge { "to": "B" } on ticket A means A must merge before B starts. The scheduler turns each edge into a predecessor of its target (predecessors) and starts a ticket only when every predecessor has merged (ready check). There are no waves: independent tickets run as soon as a pool slot is free.
- Edges must name an existing ticket; self-edges and cycles are rejected (edge checks, cycle check).
- If a predecessor fails, every ticket downstream of it fails as
blocked by failed predecessorwithout running (blocked). - Add an edge whenever one ticket reads code another ticket writes. Two tickets with no edge between them run concurrently against the same base.
Scopes and rails
Scope is what a ticket may change; rails are what it must not. After the build, any change outside scope or inside a rail fails the attempt (scope check).
- Keep concurrent scopes disjoint.
agb preflightvetoes pairwise scope overlap between tickets that can run at the same time (preflight overlap). - Rail the foundation. When
Bdepends onA, listA's files inB'srailssoBcannot rewrite them. - Tests belong in scope. A builder told to write tests needs the test file in its scope.
Sizing
Each ticket gets two strikes: one regeneration with the failure appended, then it fails (two-strike rule). A ticket that cannot be finished in one focused builder session is too big. Practical heuristics:
- One coherent change per ticket: a module plus its tests, not a feature spread across layers.
- Put shared types, directory layout and dispatchers in a foundation ticket that everything else depends on.
- Prefer more, smaller tickets with clean edges over a few large ones; parallelism comes from width, and failures stay cheap.
- Write the
bodyso a fresh agent with no other context can execute it: name the files, the behavior and the tests. - Use
cheapfor mechanical work,midby default,frontierfor design-heavy or subtle work. See Pools and tiers.
Check before you run
agb validate plan.json # exit 2 if invalid, prints every error
agb preflight plan.json # scope overlap + coldstart on each ticket bodyThen follow the walkthrough from the run step.