@defrex/autobuild 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,1781 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: guide
|
|
3
|
+
description: Authoritative reference for the autobuild system as installed in this repository - the build lifecycle (grooming, dispatch, plan, plan-review, implement, code-review, verify, finalize, reconcile, merge), the complete autobuild.toml configuration surface, how `ab init` and `ab upgrade` treat config and vendored skills, and what each installed ab-* skill is for. Use when asked about how autobuild works or why a build did what it did; when editing autobuild.toml; when adding or changing a verify or finalize step; when configuring roles, runners, models, policy limits, dispatch, or ticket sources; when reading, editing, or upgrading the installed ab-* skills; when a question mentions autobuild, autobuild.toml, or the `ab` CLI; or when writing the product's own name in prose, documentation, or a UI string.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Autobuild system guide
|
|
7
|
+
|
|
8
|
+
Reference material for an agent working on a repository that uses autobuild.
|
|
9
|
+
This skill describes the *system*. It drives no phase and changes no files.
|
|
10
|
+
|
|
11
|
+
## How to use this skill
|
|
12
|
+
|
|
13
|
+
For first-time or existing-repository installation, including the local versus
|
|
14
|
+
Vercel decision and readiness check, follow the canonical
|
|
15
|
+
[repository setup procedure](references/setup.md).
|
|
16
|
+
|
|
17
|
+
Two rules come first, because they govern everything below.
|
|
18
|
+
|
|
19
|
+
**The repository decides its own configuration; this guide only describes the
|
|
20
|
+
system.** For anything repository-specific — what verify steps run here, which
|
|
21
|
+
runner a role uses, what `test` actually invokes — read the repo, not this
|
|
22
|
+
document:
|
|
23
|
+
|
|
24
|
+
- `autobuild.toml` at the repo root — the live configuration.
|
|
25
|
+
- `.agents/skills/ab-*/SKILL.md` — the installed skills *as this repo edited
|
|
26
|
+
them*. They are editable by design, so the vendored copy may deliberately
|
|
27
|
+
differ from the distribution's default described here.
|
|
28
|
+
- The project's own `CLAUDE.md`, `README.md`, and docs.
|
|
29
|
+
|
|
30
|
+
When this guide and the repository disagree about the repository, the
|
|
31
|
+
repository wins. Quote its files, not this one, when answering "what does this
|
|
32
|
+
repo do?"
|
|
33
|
+
|
|
34
|
+
**Supply context for what the user asked; never prescribe what they didn't.**
|
|
35
|
+
This skill is background knowledge for the request in the current session. It
|
|
36
|
+
is not a mandate to audit or improve anything. If you notice a config you find
|
|
37
|
+
suboptimal — a missing verify step, a policy limit you'd set differently — that
|
|
38
|
+
is not a finding to act on. Make the change the user asked for, and nothing
|
|
39
|
+
else. Mentioning an unrelated observation at most belongs in a closing
|
|
40
|
+
sentence, never in a diff.
|
|
41
|
+
|
|
42
|
+
## The product's name
|
|
43
|
+
|
|
44
|
+
Autobuild is one word. Write it `Autobuild` in prose and as a proper noun, and
|
|
45
|
+
`autobuild` in identifiers, commands, paths, config keys, and other technical
|
|
46
|
+
contexts. Never split or hyphenate it — not in UI strings, documentation,
|
|
47
|
+
comments, commit messages, or tests. The one exception is a verbatim quotation
|
|
48
|
+
of a name owned by an external system: that system's spelling is its own to
|
|
49
|
+
choose, not this project's to correct.
|
|
50
|
+
|
|
51
|
+
## The lifecycle
|
|
52
|
+
|
|
53
|
+
A groomed ticket becomes a build, and the build walks a **fixed pipeline**:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
spec → plan ⇄ plan-review → implement ⇄ code-review → verify:* → finalize
|
|
57
|
+
epilogue: (pr.conflicted → reconcile → verify:*)* → merged or closed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- **Grooming and dispatch.** Work enters at Triage. Ingesters *propose*
|
|
61
|
+
tickets; a human grooms them to the complete
|
|
62
|
+
[spec standard](references/spec-standard.md) — what and why but never how,
|
|
63
|
+
verifiable acceptance criteria, explicit out-of-scope, and evidence — and
|
|
64
|
+
dispatches. Generated work cannot leave Triage un-groomed. The dispatcher
|
|
65
|
+
claims tickets that pass the `[tickets]` ready
|
|
66
|
+
gate, chooses a short immutable slug from the final conforming spec, and
|
|
67
|
+
starts builds up to the top-level `capacity` bound.
|
|
68
|
+
- **`spec`** — the ticket's spec becomes the build's contract. The `ab-spec`
|
|
69
|
+
skill is the human-interactive surface for producing it, and it runs *before*
|
|
70
|
+
a build exists.
|
|
71
|
+
- **`plan ⇄ plan-review`** — the planner turns the spec into a plan and may
|
|
72
|
+
select configured optional verify steps in its opening front matter; a fresh
|
|
73
|
+
reviewer (no memory of prior rounds, by design) reviews both together and
|
|
74
|
+
approves, asks for a revision, or escalates. The loop repeats until approval
|
|
75
|
+
or a policy limit.
|
|
76
|
+
- **`implement ⇄ code-review`** — the implementer executes the approved plan,
|
|
77
|
+
committing locally; a fresh reviewer reads the diff against spec and plan.
|
|
78
|
+
Same loop shape.
|
|
79
|
+
- **`verify:*`** — each step in `[verify].steps` runs as its own phase, in
|
|
80
|
+
order. A failing step sends the build back to `implement` with the report;
|
|
81
|
+
an explicit skip records why the step did not apply and advances.
|
|
82
|
+
- **`finalize`** — the agent writes the PR description; the kernel opens the
|
|
83
|
+
PR. `[finalize].steps` run afterward and are failure-tolerant. A
|
|
84
|
+
content-producing step commits selected files locally and finishes clean;
|
|
85
|
+
the runner regular-pushes a descendant head to extend the open PR branch.
|
|
86
|
+
- **Epilogue.** If main moves and the PR conflicts, `pr.conflicted` sends the
|
|
87
|
+
build to `reconcile` (merge base *into* the branch — never a rebase), then
|
|
88
|
+
back through `verify:*`. This can repeat. The build terminates **merged** or
|
|
89
|
+
**closed**.
|
|
90
|
+
|
|
91
|
+
**Review severity is proportion to the spec.** Both review loops rate a
|
|
92
|
+
finding by what the defect costs against the spec's acceptance criteria and
|
|
93
|
+
the realistic operating conditions of the work under review, not by how
|
|
94
|
+
certainly it is a defect: `blocking` names an acceptance criterion the defect
|
|
95
|
+
defeats, `important` names a criterion or stated invariant it puts at material
|
|
96
|
+
risk. A true defect that puts no acceptance criterion at risk, breaks no
|
|
97
|
+
stated invariant, and is unreachable under realistic input is `ab observe`,
|
|
98
|
+
not a finding, and work carrying such observations is approvable. A reviewer
|
|
99
|
+
does not raise a bar the spec set: where the spec bounds a failure model or an
|
|
100
|
+
operating condition, a stricter model is not a defect, and hostile or
|
|
101
|
+
pathological input that the surface's contract does not promise to handle is
|
|
102
|
+
`minor` or an observation, unless a security boundary, an acceptance
|
|
103
|
+
criterion, or a stated invariant makes it material.
|
|
104
|
+
|
|
105
|
+
**The grammar is fixed.** `verify:*` and `finalize:*` are the *only* extension
|
|
106
|
+
points. There are no custom phases, no DAGs, no reordering — a repo extends
|
|
107
|
+
autobuild by configuring verify and finalize steps, never by inventing stages.
|
|
108
|
+
If a request seems to need a new phase, say so rather than improvising one.
|
|
109
|
+
|
|
110
|
+
**Observation harvest is not a build phase.** On each dispatch tick, dispatch
|
|
111
|
+
measures both unclaimed observation count and drift: distinct other builds
|
|
112
|
+
merged after the oldest unclaimed observation. It starts when count reaches
|
|
113
|
+
`[policy].harvestThreshold` **or** drift reaches
|
|
114
|
+
`[policy].harvestMaxDrift`; zero disables drift. The originating build is
|
|
115
|
+
excluded even if it later merges, and only durable same-repository `pr.merged`
|
|
116
|
+
facts count. Either trigger claims the whole current accumulation and records
|
|
117
|
+
`count`, `drift`, or `both` on `harvest.started`, then dispatch runs one
|
|
118
|
+
repository-scoped workflow: deterministic `scan`, agent `synthesize`
|
|
119
|
+
⇄ fresh adversarial `review`, then deterministic `file`. Only approved
|
|
120
|
+
spec-standard proposals are created, in `[tickets].proposalState` — the triage
|
|
121
|
+
state unless this repository has named another one. The scan context exposes
|
|
122
|
+
existence and resolution for each distinct observation-origin ticket. At filing,
|
|
123
|
+
matching-source origins are refreshed and every still-existing unresolved one
|
|
124
|
+
is unioned with the agent-declared `blockedBy` ids; missing, resolved, foreign,
|
|
125
|
+
and absent origins are dropped. Filing records declared and origin-derived
|
|
126
|
+
blockers separately, while duplicate ids appear only once on the created ticket.
|
|
127
|
+
If the ticket is already visible as ready while that create is still unfinished,
|
|
128
|
+
dispatch correlates its source creation key with the durable reservation and
|
|
129
|
+
withholds it without consuming capacity; it remains in queue depth with a
|
|
130
|
+
distinct durable diagnostic, then proceeds normally after filing settles or the
|
|
131
|
+
owning workflow stops. A repository journal, artifact stream, dedup ledger, and lease make every step
|
|
132
|
+
queryable and crash-safe without polluting `ab builds` or the fixed phase grammar. Claims
|
|
133
|
+
exclude observations until they are dispositioned or selectively released;
|
|
134
|
+
idle ticks launch no harvest agent. A non-retrying infrastructure failure parks
|
|
135
|
+
its run at the durable boundary. Before any new scan, dispatch recovers every
|
|
136
|
+
ordinary parked run in start order, even when later runs shadow it, with a
|
|
137
|
+
fixed two-attempt budget applied independently per run. Automatic and human
|
|
138
|
+
reopening share `harvest.resumed`; one human acknowledgement reopens every
|
|
139
|
+
ordinary parked run and acknowledges every exhaustion barrier. Claims,
|
|
140
|
+
artifacts, attempts, reservations, and filing facts stay intact, so completed
|
|
141
|
+
work is not repeated. After a run's bound, one atomic fact commits only
|
|
142
|
+
classifiable filed creates, still-valid frozen joins, and suppressions; missing
|
|
143
|
+
creates, tombstone/unknown joins, and otherwise unclassifiable members are
|
|
144
|
+
released as pending. A rejected store read remains retryable infrastructure,
|
|
145
|
+
while successfully read malformed content fails safe toward release. The same
|
|
146
|
+
fact raises a human-attention barrier. Acknowledging it permits a future scan
|
|
147
|
+
but never resurrects an exhausted run. Existing journals gain these semantics
|
|
148
|
+
on replay without migration; completed and deliberate escalated runs remain
|
|
149
|
+
terminal.
|
|
150
|
+
|
|
151
|
+
**Slug naming is not a phase.** For each new build, a tool-free one-shot call
|
|
152
|
+
proposes a lowercase kebab base of at most three meaningful spec-derived words.
|
|
153
|
+
The dispatcher owns a hard deadline, strict validation, and store-wide `-2`,
|
|
154
|
+
`-3`, … collision suffixes. Absence or any failure falls back to the first
|
|
155
|
+
three words of the kebab-cased title, so naming never blocks dispatch. The slug
|
|
156
|
+
and `ab/<slug>` branch are chosen once; existing builds are never renamed.
|
|
157
|
+
Naming inherits `[roles.default]` and can be overridden by `[roles.slug]`.
|
|
158
|
+
|
|
159
|
+
## Who does what
|
|
160
|
+
|
|
161
|
+
The distinctions that change an administrator's answer:
|
|
162
|
+
|
|
163
|
+
- **Agents supply judgment** — planning, reviewing, implementing, verifying,
|
|
164
|
+
and narrow non-phase proposals such as slug naming or skill-conflict text. An
|
|
165
|
+
agent never decides a transition or whether any proposal is valid enough to
|
|
166
|
+
apply.
|
|
167
|
+
- **The kernel owns determinism** — phase transitions, gating, deduplication,
|
|
168
|
+
convergence and stall detection. Outcomes come from the typed `ab` CLI, never
|
|
169
|
+
from parsing an agent's stdout.
|
|
170
|
+
- **The BuildStore is append-only event logs.** The kernel reduces build status
|
|
171
|
+
from each build stream; dispatcher settings and harvest state are reduced
|
|
172
|
+
independently from the repository journal. Snapshots are never authoritative.
|
|
173
|
+
Events record facts, never derived state.
|
|
174
|
+
- **The interactive dashboard is a Store-only frontend.** It owns the terminal
|
|
175
|
+
and supervises a private child that owns dispatcher ticks, build runners,
|
|
176
|
+
Harvest, config/plugins, and ticket/forge/workspace/runtime adapters. Effective
|
|
177
|
+
config, standing queue depth, diagnostics, reload outcomes, runner outcomes,
|
|
178
|
+
and child health are run-correlated repository facts. Controls append directly
|
|
179
|
+
to the Store, so a slow claim or agent turn cannot delay a keypress. Repository
|
|
180
|
+
reads advance incrementally. SIGINT, SIGHUP, SIGQUIT, and SIGTERM restore the
|
|
181
|
+
terminal immediately, drain and reap the child, and keep repeated signals
|
|
182
|
+
graceful until an open claim tick reaches a recoverable boundary; non-SIGINT
|
|
183
|
+
termination is replayed only after that drain. Durable normal/forced/abnormal
|
|
184
|
+
evidence retains available process detail, and parent-death detection prevents
|
|
185
|
+
a detached kernel. `--plain`/non-TTY stay on the direct path, and `--once`
|
|
186
|
+
remains one tick plus drain.
|
|
187
|
+
- **Live configuration** has two explicit contexts. A running dispatcher owns
|
|
188
|
+
the last valid snapshot from the **main checkout**; dispatch, setup, and each
|
|
189
|
+
pipeline step capture it at their action boundary, without interrupting an
|
|
190
|
+
agent turn or command already running. Missing, unreadable, malformed, and
|
|
191
|
+
routing-invalid candidates retain that snapshot with deduplicated operator
|
|
192
|
+
notice, and restoring a valid file resumes reload. Scoped phase CLI processes
|
|
193
|
+
still read the build worktree's branch-owned file.
|
|
194
|
+
- **Workspaces** are provisioned per build and remain the locally reachable
|
|
195
|
+
execution context for that build.
|
|
196
|
+
- **Ticket providers** (`linear`, `file`) sit behind one port; the dispatcher
|
|
197
|
+
does not know which is configured.
|
|
198
|
+
- **Forge operations and pushes are kernel-side plumbing.** Agents commit
|
|
199
|
+
locally and never push, never touch a remote, never open the PR. A push
|
|
200
|
+
happens at the implement terminal boundary or after the runner validates a
|
|
201
|
+
successful content-producing finalize post-step; neither path force-pushes.
|
|
202
|
+
`github` publishes through GitHub. `local-git` performs no remote/network/`gh`
|
|
203
|
+
operation: durable PR records live under private Git refs, the review locator
|
|
204
|
+
is `refs/heads/ab/<slug>`, and consent lands a local guarded, unsigned squash
|
|
205
|
+
whose message is the deposited PR description and whose author and committer
|
|
206
|
+
come from the repository's ordinary Git identity. If Git cannot resolve that
|
|
207
|
+
identity, the PR remains open with actionable `user.name` / `user.email`
|
|
208
|
+
guidance and later dispatcher ticks retry. Without consent it remains open for
|
|
209
|
+
ordinary `git diff` inspection; it has no review UI or image host.
|
|
210
|
+
|
|
211
|
+
## `autobuild.toml` reference
|
|
212
|
+
|
|
213
|
+
One declarative file at the repo root. **Declarative, not executable** —
|
|
214
|
+
commands are plain shell strings that the kernel runs; nothing in this file is
|
|
215
|
+
evaluated as config logic.
|
|
216
|
+
|
|
217
|
+
**Strictness:** unknown top-level keys/tables and unknown keys inside known
|
|
218
|
+
tables are **errors**, not warnings — a typo must not silently disable a
|
|
219
|
+
verifier. The open maps are `[commands]`, `[roles]`, `[workspace.config]`,
|
|
220
|
+
`[workspace.config.runtimeProvisioning]`, `[verify.<step>]`, and
|
|
221
|
+
`[finalize.<step>]`. Autobuild strictly validates the
|
|
222
|
+
repository-defined command, role, and step entries. `[workspace.config]` is
|
|
223
|
+
plugin-owned and passed through unchanged for plugins; the builtin `git-worktree` provider requires it to be
|
|
224
|
+
empty, while `vercel-sandbox`
|
|
225
|
+
interprets it as a closed typed table.
|
|
226
|
+
Every other known table is closed to unknown keys. The removed `[dashboardFrames]`, `[project]`,
|
|
227
|
+
`[dispatcher]`, `[harvest]`, and `[outer]` tables have no aliases or migration
|
|
228
|
+
shims; they fail as ordinary unknown top-level keys.
|
|
229
|
+
|
|
230
|
+
### Root scalars
|
|
231
|
+
|
|
232
|
+
Root scalars must appear before the first TOML table; TOML otherwise nests them
|
|
233
|
+
under the preceding table.
|
|
234
|
+
|
|
235
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
236
|
+
|---|---|---|---|
|
|
237
|
+
| `baseBranch` | `"main"` | nonempty string | The branch builds branch from and target with their PR; what `reconcile` merges into the build branch. |
|
|
238
|
+
| `capacity` | `1` | positive integer | Maximum concurrent builds for this repository. |
|
|
239
|
+
| `forge` | `"github"` | nonblank string | Selects shipped `github` or `local-git`, or a plugin-registered Forge adapter. `github` authenticates with `GITHUB_TOKEN`, then `GH_TOKEN`, then the gh CLI's stored login. |
|
|
240
|
+
| `plugins` | `[]` | array of unique nonblank module specifiers | Trusted Bun plugin modules loaded before dispatch, `ab ticket`, and scoped phase wiring. |
|
|
241
|
+
|
|
242
|
+
Repository-path specifiers resolve from the config-bearing root, which is the
|
|
243
|
+
immutable build worktree in scoped phases. npm package specifiers resolve from
|
|
244
|
+
the consuming main checkout's installed dependencies first, then from the
|
|
245
|
+
Autobuild installation the CLI runs from, so an extension installed next to
|
|
246
|
+
the CLI (`bun add -g @defrex/autobuild-<extension>`) loads without being added
|
|
247
|
+
to the repository; a repository copy wins over an installed one, and missing
|
|
248
|
+
packages fail and are never installed automatically. Specifier strings must be
|
|
249
|
+
unique: an exact repeat fails config
|
|
250
|
+
validation before resolution or evaluation, identifies the repeated value and
|
|
251
|
+
both positions, and tells the operator to remove or deduplicate it. Distinct
|
|
252
|
+
specifiers remain separate declarations; if they register the same adapter
|
|
253
|
+
name, the collision error still identifies that adapter and both plugin owners.
|
|
254
|
+
Modules are trusted, in-process Bun code and cannot shadow builtin or previously
|
|
255
|
+
registered names. All four registration maps have production selectors:
|
|
256
|
+
`[tickets].source`, the root `forge` scalar, `[workspace].provider`,
|
|
257
|
+
and `[roles.*].runtime`. BuildStore uses the remote HTTP protocol, not this
|
|
258
|
+
manifest, and TelemetrySource has no registration map.
|
|
259
|
+
|
|
260
|
+
For the complete author workflow—from choosing one of the four open ports,
|
|
261
|
+
through the strict versioned manifest and environment-only secrets, to the
|
|
262
|
+
unchanged shared contract suite and optional npm publication—read
|
|
263
|
+
[`references/plugin-authoring.md`](references/plugin-authoring.md).
|
|
264
|
+
|
|
265
|
+
### `[pr]`
|
|
266
|
+
|
|
267
|
+
Optional PR-projection settings. Omission leaves artifact attachments text-only.
|
|
268
|
+
|
|
269
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
270
|
+
|---|---|---|---|
|
|
271
|
+
| `imageHost` | — | optional `[pr.imageHost]` table | Opts attached images into temporary public hosting and inline PR rendering. |
|
|
272
|
+
|
|
273
|
+
### `[pr.imageHost]`
|
|
274
|
+
|
|
275
|
+
Optional and **off by default**. Any agent session may explicitly designate an
|
|
276
|
+
exact deposited artifact with `ab artifact put <kind> <file> --attach`. Every
|
|
277
|
+
attachment receives a pinned BuildStore download command in the PR summary.
|
|
278
|
+
When this table is configured, only attachments whose normalized media type is
|
|
279
|
+
`image/*` are copied to an existing public GitHub release and rendered inline;
|
|
280
|
+
non-images remain text-download-only. The release must already be published and
|
|
281
|
+
mutable; Autobuild creates no release or tag. A private source repository must
|
|
282
|
+
name a separate public asset repository because GitHub's image proxy cannot
|
|
283
|
+
fetch authenticated assets. This is an explicit temporary-public-disclosure
|
|
284
|
+
opt-in.
|
|
285
|
+
|
|
286
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
287
|
+
|---|---|---|---|
|
|
288
|
+
| `provider` | — | required literal `"github-release"` | Selects the only image host implemented in this release. |
|
|
289
|
+
| `repository` | — | required nonblank `"owner/repo"` pair | Public repository that owns the pre-existing release. |
|
|
290
|
+
| `releaseId` | — | required positive integer | Numeric GitHub release id used for upload and deletion. |
|
|
291
|
+
|
|
292
|
+
The `gh` identity running Autobuild needs Contents write permission on the host
|
|
293
|
+
repository. Obtain an existing release's numeric id with, for example,
|
|
294
|
+
`gh api repos/owner/repo/releases/tags/<tag> --jq .id`. Upload, validation, or
|
|
295
|
+
timeout failure records a follow-up observation and preserves every exact text
|
|
296
|
+
retrieval command; it never changes verification or blocks finalize. Hosted
|
|
297
|
+
copies are deleted after `build.completed` (404 is already clean), and failed
|
|
298
|
+
deletions remain durable and retry on later dispatcher ticks. Their inline URLs
|
|
299
|
+
therefore intentionally stop working after the review window. The authoritative
|
|
300
|
+
BuildStore artifacts remain queryable under the store's separate retention
|
|
301
|
+
policy.
|
|
302
|
+
|
|
303
|
+
### `[workspace]`
|
|
304
|
+
|
|
305
|
+
Optional and backward-compatible: omission selects the builtin `git-worktree`
|
|
306
|
+
provider. The selector envelope is strict; only `[workspace.config]` is an open,
|
|
307
|
+
plugin-owned table.
|
|
308
|
+
|
|
309
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
310
|
+
|---|---|---|---|
|
|
311
|
+
| `provider` | `"git-worktree"` | nonblank builtin or plugin-registered name | Selects the one provider used by dispatch, recovery, PR polling, and cleanup. |
|
|
312
|
+
| `config` | `{}` | open nested table; must be empty for `git-worktree` | Passed unchanged to the selected plugin factory. |
|
|
313
|
+
|
|
314
|
+
A selected plugin factory receives the nested config, process environment, and
|
|
315
|
+
absolute repository root. Unknown names fail before claims and list all
|
|
316
|
+
available providers. A handle's `path` is absolute in its execution environment,
|
|
317
|
+
`ref` is provider-scoped, and optional `localPath` alone promises dispatcher
|
|
318
|
+
reachability.
|
|
319
|
+
|
|
320
|
+
`provider = "vercel-sandbox"` runs the complete build in a persistent Vercel VM
|
|
321
|
+
and requires an HTTPS hosted Store/scoped token, GitHub HTTPS origin, GitHub
|
|
322
|
+
forge, a dispatcher-side push-capable `GITHUB_TOKEN` or `GH_TOKEN`, and either
|
|
323
|
+
`VERCEL_OIDC_TOKEN` or the `VERCEL_TOKEN`/`VERCEL_TEAM_ID`/
|
|
324
|
+
`VERCEL_PROJECT_ID` tuple. These credentials are validated before claims:
|
|
325
|
+
|
|
326
|
+
```toml
|
|
327
|
+
[workspace]
|
|
328
|
+
provider = "vercel-sandbox"
|
|
329
|
+
[workspace.config]
|
|
330
|
+
timeoutSeconds = 2700
|
|
331
|
+
image = "vercel/sandbox/universal:latest"
|
|
332
|
+
vcpus = 4
|
|
333
|
+
environmentVariables = ["AI_GATEWAY_API_KEY"]
|
|
334
|
+
# private repository only; dedicated contents-read/no-write identity:
|
|
335
|
+
gitUsernameEnv = "AB_GIT_READ_USER"
|
|
336
|
+
gitPasswordEnv = "AB_GIT_READ_TOKEN"
|
|
337
|
+
|
|
338
|
+
[workspace.config.runtimeProvisioning.pi]
|
|
339
|
+
install = "npm install --global --ignore-scripts @earendil-works/pi-coding-agent@0.84.4"
|
|
340
|
+
preflight = "test \"$(pi --version)\" = \"0.84.4\""
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`timeoutSeconds` is required (60–86400; Hobby currently caps at 2700), `vcpus`
|
|
344
|
+
is 1–32, and optional `region`/unique `failoverRegions` select placement. Only
|
|
345
|
+
the named runtime variables and scoped `AB_STORE`/`AB_TOKEN` enter guest
|
|
346
|
+
commands. Never list Store, Forge, Vercel, or private-clone credentials there.
|
|
347
|
+
Every effective primary/alternate runtime needs a strict
|
|
348
|
+
`runtimeProvisioning` entry with pinned `install` and exact-version `preflight`
|
|
349
|
+
commands; runtime names remain open for plugins. Install/preflight runs before
|
|
350
|
+
the marker in every fresh/replacement/readiness sandbox, and preflight reruns
|
|
351
|
+
before every child. Failures name the runtime, role/alternate, and field and
|
|
352
|
+
start no agent. Keep runtime installation out of `commands.setup`. Pi over
|
|
353
|
+
`vercel-ai-gateway/...` uses `AI_GATEWAY_API_KEY`; never copy OAuth tokens,
|
|
354
|
+
`~/.pi`, or interactive login state. The clone identity is upload-pack-only and
|
|
355
|
+
scrubbed from Git config before an agent starts. Agents cannot push. Remote phase terminals deposit a durable
|
|
356
|
+
publication request and park; that successful `ab done` is the one terminal and
|
|
357
|
+
must not be repeated. After VM stop and lease release, the local kernel
|
|
358
|
+
publishes the exact SHA/branch through a temporary narrow firewall transform,
|
|
359
|
+
verifies it, and performs PR work locally. Ordinary completion deletes the VM.
|
|
360
|
+
Branch config, installed skills, relative/package plugins, setup, and phase CLI
|
|
361
|
+
commands resolve from the guest checkout, never dispatcher paths.
|
|
362
|
+
|
|
363
|
+
### `[commands]`
|
|
364
|
+
|
|
365
|
+
An **open map** of name → shell string. Both the key and the value must be
|
|
366
|
+
nonempty strings. Keys are user-chosen: `setup`, `lint`, `type-check`, and
|
|
367
|
+
`test` are *conventions*, not required keys, and a repo may define any verb it
|
|
368
|
+
likes.
|
|
369
|
+
|
|
370
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
371
|
+
|---|---|---|---|
|
|
372
|
+
| `<name>` | — | nonempty string → nonempty shell string | Names a deterministic verb the kernel may run. Referenced by name from check steps in `[verify.<step>]` or `[finalize.<step>]`. |
|
|
373
|
+
|
|
374
|
+
`setup` is special by convention: it runs after workspace provision and after a
|
|
375
|
+
sandbox rehydrate. Failures are durable, remain visible in build status and the
|
|
376
|
+
dashboard, and retry up to `[policy].maxSetupAttempts`; exhaustion raises a
|
|
377
|
+
setup-targeted escalation without manufacturing a pipeline phase or verdict. A
|
|
378
|
+
human answer re-arms the budget, and a later successful `runner.attached` clears
|
|
379
|
+
the current error. Values are never evaluated as config — they are handed to a
|
|
380
|
+
shell as written.
|
|
381
|
+
|
|
382
|
+
Fresh init config deliberately leaves this map empty. The setup agent follows
|
|
383
|
+
the installed [repository setup reference](references/setup.md), derives commands
|
|
384
|
+
from the repository's actual toolchain, and asks the user about choices source
|
|
385
|
+
cannot answer; deterministic init code never guesses a language or package
|
|
386
|
+
manager.
|
|
387
|
+
|
|
388
|
+
### `[verify]`
|
|
389
|
+
|
|
390
|
+
`steps` orders the verify phases; each entry `"<step>"` becomes a
|
|
391
|
+
`verify:<step>` phase and needs its own `[verify.<step>]` subtable. Those
|
|
392
|
+
subtables are part of this section — their fields are listed here.
|
|
393
|
+
|
|
394
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
395
|
+
|---|---|---|---|
|
|
396
|
+
| `steps` | `[]` | array of nonempty strings | Ordered configured universe of verify phases. Each name must have a matching `[verify.<step>]` table. |
|
|
397
|
+
| `kind` | — | **required**, `"check"` \| `"agent"` | Discriminator. `check` is deterministic (command + pass/fail, never an agent); `agent` runs a skill that returns `pass`, `fail`, or `skip`. |
|
|
398
|
+
| `command` | — | **required when `kind = "check"`**, nonempty string | Ref into `[commands]` — the key, not a shell string. Pass/fail is the command's exit status. |
|
|
399
|
+
| `skill` | — | **required when `kind = "agent"`**, nonempty string | Installed skill name to run (e.g. a repository-authored `"verify-app-e2e"`). |
|
|
400
|
+
| `paths` | — | optional nonempty array of positive repository-relative globs | Makes either step kind apply when any changed path matches any selector. Omitted means unconditional. |
|
|
401
|
+
| `always` | — | optional boolean | `true` makes the step unconditional and mandatory (a plan cannot deselect it); `false` is equivalent to omission. |
|
|
402
|
+
|
|
403
|
+
A plan may begin with strict TOML front matter selecting the complete set of
|
|
404
|
+
optional verification it warrants:
|
|
405
|
+
|
|
406
|
+
```toml
|
|
407
|
+
+++
|
|
408
|
+
verifySteps = ["types", "e2e"]
|
|
409
|
+
+++
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Names must already exist in `[verify].steps` with matching tables; duplicates,
|
|
413
|
+
blanks, unknown names, malformed metadata, and omission of an `always = true`
|
|
414
|
+
step make the planner's `ab done` fail before `plan.completed` is appended.
|
|
415
|
+
|
|
416
|
+
An agent verify step selects `[roles.<step>]` by its **logical step name** —
|
|
417
|
+
the name in `[verify].steps`, not the skill it runs. An agent finalize
|
|
418
|
+
post-step follows the same rule.
|
|
419
|
+
|
|
420
|
+
```toml
|
|
421
|
+
[verify]
|
|
422
|
+
steps = ["e2e"]
|
|
423
|
+
|
|
424
|
+
[verify.e2e]
|
|
425
|
+
kind = "agent"
|
|
426
|
+
skill = "verify-app-e2e" # repository-authored skill
|
|
427
|
+
|
|
428
|
+
[roles.e2e] # the STEP name — not "verify-app-e2e"
|
|
429
|
+
runtime = "pi"
|
|
430
|
+
model = "openai-codex/gpt-5.6-sol"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
The step's configured skill name remains a deprecated alias for existing
|
|
434
|
+
configurations and will be removed in a future release. It applies only when
|
|
435
|
+
`[roles.<step>]` is undeclared, so the step name always wins when both are
|
|
436
|
+
present, and `ab dispatch` reports the alias with the step name to rename it to.
|
|
437
|
+
Written order never reorders execution: the event records the canonical config
|
|
438
|
+
order. No front matter means all configured steps, preserving old plans and
|
|
439
|
+
logs; an explicit empty list is valid only when all steps are optional. The
|
|
440
|
+
selection from the exact plan completion approved by `plan-review` is
|
|
441
|
+
authoritative. A spec restart replaces it with the newly approved plan; a
|
|
442
|
+
reconcile reuses it.
|
|
443
|
+
|
|
444
|
+
Cross-field rules the validator actually enforces — each is an **error**:
|
|
445
|
+
|
|
446
|
+
- A step listed in `steps` with no `[verify.<step>]` table.
|
|
447
|
+
- A `[verify.<step>]` table whose step is not listed in `steps` (add it to
|
|
448
|
+
`steps` or remove the table — a defined-but-unlisted step never runs, so this
|
|
449
|
+
is never silently tolerated).
|
|
450
|
+
- `command` naming a key that does not exist in `[commands]`.
|
|
451
|
+
- An empty or malformed `paths` list. Matching is case-sensitive over Git's
|
|
452
|
+
`/`-separated paths; supported syntax is literals, `*`, `?`, and `**` only as
|
|
453
|
+
a whole segment. Absolute/traversing/empty segments, negation, escapes,
|
|
454
|
+
character classes, brace expansion, extglobs, and malformed `**` are errors.
|
|
455
|
+
`always = true` does not hide selector errors.
|
|
456
|
+
|
|
457
|
+
For each step, the kernel evaluates the approved plan selection first, then
|
|
458
|
+
`paths`: both must include the step for it to run. A selection exclusion records
|
|
459
|
+
`excluded by approved plan selection (plan@<rev>): verify step "<step>" was not selected`.
|
|
460
|
+
A selected step's `paths` are evaluated immediately before the step against
|
|
461
|
+
current `HEAD`, relative to the initial branch-cut SHA or the refreshed base
|
|
462
|
+
from the latest completed reconcile. It uses a NUL-delimited, no-rename Git diff, so
|
|
463
|
+
adds/modifications/deletions and both rename sides participate. A reconcile
|
|
464
|
+
starts a fresh cycle and evaluation repeats; upstream-only merged paths are
|
|
465
|
+
excluded while build-owned resolutions remain visible. A miss starts no
|
|
466
|
+
command, agent, or session and records `skipped` with
|
|
467
|
+
`excluded by [verify.<step>].paths: no changed path matched <JSON paths>`.
|
|
468
|
+
Git/base failures fail closed as infrastructure rather than becoming skips.
|
|
469
|
+
|
|
470
|
+
Agent verifiers may also use `ab verdict pass`,
|
|
471
|
+
`ab verdict fail --report <file>`, or `ab verdict skip --reason <text>`. The
|
|
472
|
+
skip reason is required and
|
|
473
|
+
must be non-blank; no failure report is required. Either kind of skip satisfies
|
|
474
|
+
that step for the current cycle and advances, but it is not a pass and never
|
|
475
|
+
masks another step's failure. Only failures return the build to `implement` and
|
|
476
|
+
consume `[policy].maxVerifyAttempts`; skips retain the cycle's attempt number
|
|
477
|
+
without consuming that budget.
|
|
478
|
+
|
|
479
|
+
### `[finalize]`
|
|
480
|
+
|
|
481
|
+
`steps` orders the post-PR actions; every name requires a matching
|
|
482
|
+
`[finalize.<step>]` table. Finalize has its own narrow union: verify-only
|
|
483
|
+
`paths` and `always` fields are errors.
|
|
484
|
+
|
|
485
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
486
|
+
|---|---|---|---|
|
|
487
|
+
| `steps` | `[]` | array of nonempty step names | Ordered post-steps that run after the PR is opened. Each name must have a matching table. |
|
|
488
|
+
| `kind` | — | **required**, `"check"` \| `"agent"` | Selects deterministic command execution or an agent skill. |
|
|
489
|
+
| `command` | — | **required when `kind = "check"`**, nonempty string | Ref into `[commands]`. The kernel runs the resolved shell command in the workspace with no agent session. |
|
|
490
|
+
| `skill` | — | **required when `kind = "agent"`**, nonempty string | Exact installed skill name passed to the runtime; no implicit `ab-` prefix is added. |
|
|
491
|
+
|
|
492
|
+
```toml
|
|
493
|
+
[finalize]
|
|
494
|
+
steps = ["publish", "release-notes"]
|
|
495
|
+
|
|
496
|
+
[finalize.publish]
|
|
497
|
+
kind = "check"
|
|
498
|
+
command = "publish"
|
|
499
|
+
|
|
500
|
+
[finalize.release-notes]
|
|
501
|
+
kind = "agent"
|
|
502
|
+
skill = "ab-release-notes"
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
The logical step name selects `[roles.<step>]` for agent routing — the same
|
|
506
|
+
rule an agent verify step follows, so one convention covers both:
|
|
507
|
+
|
|
508
|
+
```toml
|
|
509
|
+
[verify]
|
|
510
|
+
steps = ["e2e"]
|
|
511
|
+
|
|
512
|
+
[verify.e2e]
|
|
513
|
+
kind = "agent"
|
|
514
|
+
skill = "verify-app-e2e" # repository-authored skill
|
|
515
|
+
|
|
516
|
+
[finalize]
|
|
517
|
+
steps = ["release-notes"]
|
|
518
|
+
|
|
519
|
+
[finalize.release-notes]
|
|
520
|
+
kind = "agent"
|
|
521
|
+
skill = "ab-release-notes"
|
|
522
|
+
|
|
523
|
+
[roles.e2e] # verify STEP name — not "verify-app-e2e"
|
|
524
|
+
runtime = "pi"
|
|
525
|
+
|
|
526
|
+
[roles.release-notes] # finalize step name
|
|
527
|
+
runtime = "pi"
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
For an agent *verify* step, the step's configured skill name remains a
|
|
531
|
+
deprecated alias for existing configurations and will be removed in a future
|
|
532
|
+
release. Finalize has never had such an alias.
|
|
533
|
+
|
|
534
|
+
Checks
|
|
535
|
+
write their completion and any follow-up with kernel attribution; agents retain
|
|
536
|
+
their session actor and transcript. Nonzero command exits, execution/launch
|
|
537
|
+
exceptions, and structured agent failures all produce `ok = false` plus a
|
|
538
|
+
follow-up observation. Either `ok = true` or `ok = false` drains the step, so
|
|
539
|
+
neither kind can turn an otherwise green build red.
|
|
540
|
+
|
|
541
|
+
Validation rejects a listed name with no table, a table whose name is not in
|
|
542
|
+
`steps`, a check command absent from `[commands]`, an unknown kind, or any
|
|
543
|
+
field outside the selected strict variant. Diagnostics name the logical step.
|
|
544
|
+
|
|
545
|
+
A post-step that produces repository content must select and commit its files
|
|
546
|
+
locally and leave a clean worktree. The runner proves the last published head
|
|
547
|
+
is an ancestor and performs the regular, non-force push through the Forge port;
|
|
548
|
+
the agent never pushes. An unchanged `HEAD` creates and pushes no commit. Dirty
|
|
549
|
+
output, rewritten history, Git errors, and publication failures record the
|
|
550
|
+
step as failed and file an observation while the green build continues.
|
|
551
|
+
|
|
552
|
+
### `[roles]`
|
|
553
|
+
|
|
554
|
+
An **open map** of role name → `{ runtime?, model?, args?, extensions?, sessionBudgetSeconds?, alternates? }`.
|
|
555
|
+
The three primary axes, session budget, and ordered alternate list inherit independently. The reserved `default` role must explicitly name a
|
|
556
|
+
runtime, is the raw inheritance base for every other role, and is **never
|
|
557
|
+
dispatched as a phase**. Its absence fails eager resolution before a session,
|
|
558
|
+
with a copyable fix and all registered runtime names. Three runtimes ship:
|
|
559
|
+
**`claude`** (local Claude Code CLI), **`codex`** (local Codex CLI; unqualified
|
|
560
|
+
`gpt-*` ids), and **`pi`** (local Pi CLI 0.84.3 or newer; provider-qualified ids
|
|
561
|
+
such as the independent `openai-codex/gpt-5.6-sol` route). Pi runs headlessly
|
|
562
|
+
through RPC and uses that local installation's login and model catalog;
|
|
563
|
+
Autobuild does not install it. `ab models [query]` lists the local catalog, and
|
|
564
|
+
`ab models --available` filters it to configured credentials. Claude and Codex
|
|
565
|
+
delegate an omitted model to their CLI default; Pi declares `kimi-coding/k3`.
|
|
566
|
+
Trusted plugins may register additional runtime names;
|
|
567
|
+
they use the same role inheritance, default-model compatibility validation,
|
|
568
|
+
session event attribution, and optional one-shot capability path as builtins.
|
|
569
|
+
|
|
570
|
+
The pipeline resolves `plan`, `plan-review`, `implement`, `code-review`,
|
|
571
|
+
`finalize`, and `reconcile`, plus each **agent** verify and finalize step by
|
|
572
|
+
its logical step name. A verify step's configured skill name remains a
|
|
573
|
+
deprecated alias for existing configurations and will be removed in a future
|
|
574
|
+
release. The repository workflow resolves `harvest` and `harvest-review`. Two
|
|
575
|
+
non-phase judgments use the same runtime/model/args resolver: `slug` optionally
|
|
576
|
+
proposes pre-build naming, and `upgrade` proposes a resolution only when a
|
|
577
|
+
vendored skill merge conflicts. Both are tool-free one-shots and receive the
|
|
578
|
+
role's effective `args`. Missing slug capability uses the deterministic title fallback;
|
|
579
|
+
unavailable upgrade judgment retains the conflict for manual resolution.
|
|
580
|
+
|
|
581
|
+
A declared key that none of those routes requests is resolved and validated
|
|
582
|
+
like any other, and then never used — `ab dispatch` reports it at startup,
|
|
583
|
+
naming the key and the keys valid for this configuration, and reports a
|
|
584
|
+
deprecated skill-name key with the step name to rename it to. Both stay
|
|
585
|
+
warnings; neither blocks a session or changes which runtime and model run. A
|
|
586
|
+
`kind = "check"` step starts no session and so is not a route: naming a role
|
|
587
|
+
after one does not make it consumed. It is reported only when that check step is
|
|
588
|
+
its sole apparent route — a check step named `plan` leaves `[roles.plan]`
|
|
589
|
+
consumed by the core `plan` phase, and nothing is reported.
|
|
590
|
+
|
|
591
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
592
|
+
|---|---|---|---|
|
|
593
|
+
| `runtime` | — | required on `default`; otherwise optional, nonempty string | Runtime for this role. A child that omits it inherits `[roles.default].runtime`. Must name a registered runtime. |
|
|
594
|
+
| `model` | — | optional, nonempty string | Model for this role. A role that omits it inherits `[roles.default].model`; only when neither names a model does the merged runtime supply its own default. |
|
|
595
|
+
| `args` | `[]` | optional, array of nonempty strings | Ordered extra CLI argv tokens for every phase and tool-free one-shot invocation. Omitted ⇒ inherit `[roles.default].args`; a set list, including `[]`, replaces it wholesale. Each string is one token, without shell parsing. Only model and parsed protocol/output-mode spellings are rejected eagerly. |
|
|
596
|
+
| `extensions` | no effect | deprecated optional array of nonempty strings | Compatibility-only field. Dispatch warns for each declaring role and directs migration to `args`. |
|
|
597
|
+
| `sessionBudgetSeconds` | policy fallback | optional positive integer | Wall-clock budget for each build phase session on this logical role. Omitted ⇒ inherit `[roles.default].sessionBudgetSeconds`; absent there too ⇒ `[policy].sessionBudgetSeconds`. One value covers the primary and every alternate. |
|
|
598
|
+
| `alternates` | `[]` | optional ordered array of strict `{ runtime?, model?, args?, extensions? }` entries | Failure-triggered targets. Omitted ⇒ inherit `[roles.default].alternates`; a role list, including `[]`, replaces it wholesale. Each entry overlays that role's effective primary axes and is eagerly validated. `extensions` remains only the deprecated no-op. |
|
|
599
|
+
|
|
600
|
+
Inheritance is mechanical and **independent per field**: merge each configured
|
|
601
|
+
role over the raw `default` entry, then validate the exact runtime/model pair.
|
|
602
|
+
The named runtime must serve the named model. A model-only role never searches
|
|
603
|
+
for another runtime, and an incompatible inherited model is never replaced by
|
|
604
|
+
the selected runtime's default. The one implicit fill-in is benign: if neither
|
|
605
|
+
the phase role nor `default` names a model, the merged runtime uses its own
|
|
606
|
+
default model. Compatibility failures name the role, runtime, model, and served
|
|
607
|
+
model families; all problems in `default` and every declared role are
|
|
608
|
+
aggregated by one eager resolver construction before judgment runs. Runtime
|
|
609
|
+
adapters reject only the model and parsed protocol/output-mode options they
|
|
610
|
+
emit: Claude rejects `-p`/`--print`, `--output-format`, and `--model`; Codex
|
|
611
|
+
rejects `--json` and `--model`/`-m`; Pi rejects `--mode` and `--model`. Exact
|
|
612
|
+
spellings, `--option=value` forms, and documented compact short aliases join
|
|
613
|
+
that same role-naming aggregate. Every other option passes through without
|
|
614
|
+
Autobuild validation, including sandbox, approval, session, tool, skill,
|
|
615
|
+
thinking, extension, lifecycle, and unknown options, even when it supplements
|
|
616
|
+
another adapter-emitted occurrence. Pi always
|
|
617
|
+
disables ambient extension discovery and explicitly loads the Autobuild bridge;
|
|
618
|
+
`--extension <path>` in `args` loads an explicit extension alongside it and
|
|
619
|
+
activates all tools that extension registers. With no such args, Pi loads no
|
|
620
|
+
operator-installed extensions. Effective args are recorded on session-start
|
|
621
|
+
facts. Dispatch
|
|
622
|
+
constructs it before any build launches; upgrade constructs it lazily on the
|
|
623
|
+
first conflict and converts a construction failure to the safe `conflicted`
|
|
624
|
+
outcome.
|
|
625
|
+
|
|
626
|
+
Each attempt starts from the primary. Overload, rate-limit, 5xx, timeout,
|
|
627
|
+
transport, unknown provider failures, and quota/usage/billing exhaustion try
|
|
628
|
+
alternates in order without consuming extra phase attempts. Authentication,
|
|
629
|
+
permission, and local runtime-configuration failures do not. Every target has
|
|
630
|
+
its own session/transcript; a continuation fallback starts fresh from durable
|
|
631
|
+
context. Selection is not sticky, so the next phase, continuation, or retry
|
|
632
|
+
starts from the primary. Only chain exhaustion writes the phase failure, and
|
|
633
|
+
the final failure controls retry behavior. Substitution starts and exhausted
|
|
634
|
+
failures retain the targets and verbatim errors durably. Core phases, agent
|
|
635
|
+
verify/finalize, and Harvest use the chain; tool-free one-shots do not.
|
|
636
|
+
|
|
637
|
+
A build phase session that reaches its captured budget is aborted and ended
|
|
638
|
+
best-effort. The kernel records retryable `phase.failed` with `phase session
|
|
639
|
+
budget expired after <seconds> seconds`, retries from the primary under the
|
|
640
|
+
existing phase-attempt cap, and raises an answerable policy escalation when
|
|
641
|
+
exhausted. Kernel expiry never selects an alternate. Operator cancellation does
|
|
642
|
+
not disable the captured deadline: cooperative adapters return promptly, while
|
|
643
|
+
the deadline releases a turn that ignores cancellation. If operator abort
|
|
644
|
+
arrived first, that release remains an abort control outcome with no alternate
|
|
645
|
+
or `phase.failed`. A typed terminal racing either boundary remains authoritative.
|
|
646
|
+
Agent finalize post-step expiry is failure-tolerant; Harvest and direct check
|
|
647
|
+
commands are outside this budget.
|
|
648
|
+
|
|
649
|
+
Mixing models across roles is **intentional**, not an inconsistency to clean
|
|
650
|
+
up: a reviewer that differs from the implementer catches more. The removed
|
|
651
|
+
legacy `[agent]` table is not an alias: config loading rejects it and directs
|
|
652
|
+
users to move its fields to `[roles.default]`.
|
|
653
|
+
|
|
654
|
+
### `[policy]`
|
|
655
|
+
|
|
656
|
+
Every field is a **positive integer** except `harvestMaxDrift`, which is a
|
|
657
|
+
nonnegative integer so zero can disable that trigger.
|
|
658
|
+
|
|
659
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
660
|
+
|---|---|---|---|
|
|
661
|
+
| `sessionBudgetSeconds` | `3600` | positive integer | Wall-clock bound for each build agent session unless its logical role overrides it. |
|
|
662
|
+
| `stallRounds` | `3` | positive integer | The same finding surviving this many review rounds auto-escalates to a human — the anti-loop guard. |
|
|
663
|
+
| `maxVerifyAttempts` | `3` | positive integer | Caps the `verify → implement → verify` cycle before escalation. |
|
|
664
|
+
| `maxSetupAttempts` | `3` | positive integer | Caps consecutive `[commands].setup` failures before a setup-targeted human escalation. |
|
|
665
|
+
| `maxInfrastructureAttempts` | `3` | positive integer | Caps consecutive remote provider lifecycle failures before a setup-targeted human escalation; a `retry` answer re-arms the budget. |
|
|
666
|
+
| `maxReconcileAttempts` | `3` | positive integer | Caps completed reconciles that leave the PR conflicted against an unchanged authoritative base. Moving-base races do not consume the budget. |
|
|
667
|
+
| `maxReviewRounds` | `6` | positive integer | Default `maxRounds` for the `plan ⇄ plan-review` and `implement ⇄ code-review` convergence loops. When that cap parks a build, `ab answer <slug> --review-round-ceiling <n>` may replace it for only the escalation's loop and current spec revision. |
|
|
668
|
+
| `harvestThreshold` | `5` | positive integer | Newly unclaimed `observation.recorded` occurrences required to start one repository harvest run. |
|
|
669
|
+
| `harvestMaxDrift` | `3` | nonnegative integer | Other builds merged after the oldest unclaimed observation required to start a run; `0` disables drift. |
|
|
670
|
+
|
|
671
|
+
For each repeat conflict, Autobuild compares the base merged by the latest
|
|
672
|
+
completed reconcile with a fresh authoritative base snapshot. An advanced base
|
|
673
|
+
proves the attempt lost a race and permits another reconcile regardless of the
|
|
674
|
+
attempt high-water. An unchanged base consumes `maxReconcileAttempts` and
|
|
675
|
+
escalates when the budget is exhausted. Every current policy escalation carries
|
|
676
|
+
a closed `policyCause`; reconcile no-progress uses `reconcile-no-progress`,
|
|
677
|
+
while `round` remains occurrence scope. Answering a runner-failure cause re-arms
|
|
678
|
+
only its matching phase and round. The phase-level `verify-failure-limit` cause
|
|
679
|
+
explicitly re-arms all rounds for its verify target, while
|
|
680
|
+
`reconcile-no-progress` and the setup-only cause reset no phase-runner failures.
|
|
681
|
+
The closed cause map forces every future policy condition to choose one of these
|
|
682
|
+
semantics instead of inheriting behavior from a missing `round`. Runner failures
|
|
683
|
+
while checking progress or refreshing the base therefore remain counted after a
|
|
684
|
+
roundless no-progress answer. An already exhausted no-progress budget raises its
|
|
685
|
+
matching cause separately before another reconcile. Cause-less historical
|
|
686
|
+
raises retain their former round-shaped reset behavior, and roundless
|
|
687
|
+
policy/reconcile raises retain their former no-progress meaning, on replay
|
|
688
|
+
without migration. Post-reconcile verification still runs in full and remains
|
|
689
|
+
independently bounded by `maxVerifyAttempts`.
|
|
690
|
+
|
|
691
|
+
`stallRounds` counts *persistence chains*, which reviewers mark and the kernel
|
|
692
|
+
only follows. A finding's `persists` ids name prior-round findings whose defect
|
|
693
|
+
is still present in the work under review; a new instance of a defect class
|
|
694
|
+
whose reported instance was fixed is fresh work and starts its own chain,
|
|
695
|
+
however closely it resembles its predecessor. So the counter measures a
|
|
696
|
+
producer/reviewer stalemate rather than a defect category the loop keeps
|
|
697
|
+
converging on, and the kernel decides only whether a marked chain has survived
|
|
698
|
+
the threshold — never whether two findings are the same disagreement.
|
|
699
|
+
|
|
700
|
+
Harvest is driven by pressure inside `ab dispatch`, not by a wall clock, and
|
|
701
|
+
remains independent of build `capacity` and repository intake. Count reaching
|
|
702
|
+
`harvestThreshold` or drift reaching `harvestMaxDrift` starts a run. Drift is
|
|
703
|
+
measured from the oldest unclaimed observation and counts distinct later
|
|
704
|
+
same-repository merges from other builds; aborted and closed-unmerged builds do
|
|
705
|
+
not count. Once either limit is reached, the run claims the whole current
|
|
706
|
+
accumulation. Dispatch
|
|
707
|
+
tracks it without blocking watch ticks; a repository lease provides
|
|
708
|
+
cross-process single-flight. Recovery has its fixed per-run two-attempt budget,
|
|
709
|
+
separate from the configurable policy fields above.
|
|
710
|
+
|
|
711
|
+
### `[tickets]`
|
|
712
|
+
|
|
713
|
+
Names the TicketSource and owns its readiness/lifecycle state vocabulary.
|
|
714
|
+
Declarative only. The table is required: `readyState` has no default, and an
|
|
715
|
+
absent table fails clearly at `tickets.readyState` rather than making every
|
|
716
|
+
state eligible.
|
|
717
|
+
|
|
718
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
719
|
+
|---|---|---|---|
|
|
720
|
+
| `source` | — | **required**, nonblank builtin or plugin registration name | Which provider backs all ticket operations. Unknown names report every available builtin and plugin source. |
|
|
721
|
+
| `readyLabels` | — (source-aware) | optional; array of nonempty strings | A ticket must carry **every** listed label to be dispatchable. `[]` = **no label gate**. Absent uses the source default below. |
|
|
722
|
+
| `readyState` | — | **required**, non-blank string | The one workflow state a ticket must sit in to be dispatchable. Linear matches exactly and case-sensitively; file canonicalizes it to a state directory (`ready` → `ready/`). There is no any-state mode. |
|
|
723
|
+
| `teamKey` | — | required by `linear`, forbidden by `file`, allowed for plugins | The Linear team key (e.g. `"ENG"`) or an existing plugin configuration field. |
|
|
724
|
+
| `claimedState` | — | optional nonempty string; forbidden by `file`, allowed for plugins | Workflow state `claim()` moves a ticket to when a build starts. |
|
|
725
|
+
| `createState` | — | optional, nonempty string | Default state for new tickets when a create does not name one. Absent = the provider's default (Linear: the team's default, e.g. Backlog; file: Triage). |
|
|
726
|
+
| `triageState` | — | optional, nonempty string | State the dispatcher hands tickets back to for human triage — spec-gate bounces, aborted builds, closed-unmerged PRs. Absent = Linear: Backlog; file/plugin: Triage. Must name a state the tracker actually has — a Linear team only has "Triage" when its triage feature is enabled. |
|
|
727
|
+
| `proposalState` | — | optional, nonempty string | State observation harvest files approved proposals into. Absent = the resolved `triageState`, which keeps the human grooming gate. Setting it to `readyState` waives that gate: harvested proposals dispatch without being read. Keep it distinct from `triageState` — see below. |
|
|
728
|
+
| `dir` | file: `.autobuild/tickets`; plugin: — | optional nonempty string; forbidden by `linear`, allowed for plugins | Root holding file state directories, or an existing plugin configuration field. |
|
|
729
|
+
|
|
730
|
+
`readyLabels` is the only source-aware readiness default. Dispatch resolves it
|
|
731
|
+
as follows:
|
|
732
|
+
|
|
733
|
+
| `[tickets].source` | `readyLabels` absent |
|
|
734
|
+
|---|---|
|
|
735
|
+
| `"linear"` | `["autobuild"]` — the historical label narrowing |
|
|
736
|
+
| `"file"` | `[]` — no label narrowing; `readyState` selects the directory |
|
|
737
|
+
| plugin | `[]` — no host-imposed label convention |
|
|
738
|
+
|
|
739
|
+
An explicit `readyLabels` value always wins for every source.
|
|
740
|
+
|
|
741
|
+
`proposalState` is the one supported way to waive the grooming gate, and it is
|
|
742
|
+
deliberately not `triageState`. Do not suggest pointing `triageState` at the
|
|
743
|
+
ready state to get auto-dispatched proposals: `triageState` is also where a
|
|
744
|
+
spec-gate bounce lands, so a nonconforming ticket would be claimed, bounced,
|
|
745
|
+
and reclaimed on every tick. `proposalState` moves only what harvest files.
|
|
746
|
+
|
|
747
|
+
Cross-field rules, each an **error**:
|
|
748
|
+
|
|
749
|
+
- `source = "linear"` without `teamKey`; or with `dir` set.
|
|
750
|
+
- `source = "file"` with `teamKey` or `claimedState` set. `dir` is **optional**
|
|
751
|
+
here — absent means the default above, which is why the schema leaves it
|
|
752
|
+
optional rather than giving it a `.default()`: that is what keeps the
|
|
753
|
+
linear-only rule above meaningful and lets the factory tell a defaulted `dir`
|
|
754
|
+
from an explicit one.
|
|
755
|
+
- Every other name is plugin-selected after loading. The factory receives the
|
|
756
|
+
existing ticket table, environment, and absolute repository root; no untyped
|
|
757
|
+
plugin-options table is added. The selected dispatch instance serves
|
|
758
|
+
readiness, dependencies, claim, comments/transitions, harvest creation, and
|
|
759
|
+
janitor completion. Every source-agnostic `ab ticket` command loads and uses
|
|
760
|
+
the same configured registration.
|
|
761
|
+
|
|
762
|
+
The file tracker is **directory-per-state**: `<dir>/<state>/<id>.md` over
|
|
763
|
+
`triage/ ready/ doing/ done/`. The directory *is* the state, so a transition or
|
|
764
|
+
claim is a rename — frontmatter carries no `state`/`claimedBy`, and a ticket
|
|
765
|
+
body survives byte-exactly because a move never rewrites the file. A malformed
|
|
766
|
+
record in any lifecycle directory is diagnosed with its path and validation
|
|
767
|
+
problem, left untouched, and excluded from listing/claim so unrelated valid
|
|
768
|
+
ready work can continue. `ab ticket list` sends those diagnostics to stderr and
|
|
769
|
+
keeps JSON stdout as one bare valid-ticket array. Tracker-wide safety checks
|
|
770
|
+
remain fatal: the same id in two state dirs is a loud error naming both paths,
|
|
771
|
+
as are root-level ticket files with no state and filesystem failures. When
|
|
772
|
+
`dir` is defaulted, the backlog writes its own `.gitignore` of `*`, so git never sees it; an
|
|
773
|
+
explicit `dir` is the user's and is left alone. Agents and operators drive it
|
|
774
|
+
through the source-agnostic `ab ticket` commands rather than running `mv` by
|
|
775
|
+
hand.
|
|
776
|
+
|
|
777
|
+
**Secrets never live in this file.** `LINEAR_API_KEY` and plugin-declared
|
|
778
|
+
`requiredEnv` credentials are environment variables (a local `.env` works).
|
|
779
|
+
Missing plugin credentials name both the selected source and every missing
|
|
780
|
+
variable. If a user asks you to put an API key in `autobuild.toml`, use the
|
|
781
|
+
environment variable instead and say why.
|
|
782
|
+
|
|
783
|
+
### `[orchestrator]`
|
|
784
|
+
|
|
785
|
+
Gates the operator-sandbox feature (AUT-340): a persistent, **credential-free**
|
|
786
|
+
environment per operator × repository that an operator agent drives through the
|
|
787
|
+
`sandbox.*` registry tools (`ab mcp`, and every later binding). The whole table
|
|
788
|
+
is **closed to unknown keys** and restart-classified: changing it requires a
|
|
789
|
+
dispatcher restart.
|
|
790
|
+
|
|
791
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
792
|
+
|---|---|---|---|
|
|
793
|
+
| `enabled` | `false` | boolean | Master gate. With the table absent or `enabled = false`, the sandbox tools are absent from every binding and no environment is ever provisioned. |
|
|
794
|
+
| `sandbox` | — | strict subtable | The sandbox behavior knobs; absence reads as the defaults below. |
|
|
795
|
+
|
|
796
|
+
`[orchestrator.sandbox]` fields:
|
|
797
|
+
|
|
798
|
+
| Field | Default | Allowed / constraints | Effect |
|
|
799
|
+
|---|---|---|---|
|
|
800
|
+
| `idleMinutes` | `30` | positive integer | Minutes without a sandbox tool call after which the dispatcher tick's janitor stops the environment, keeping its snapshot; the next tool call resumes it. |
|
|
801
|
+
| `environmentVariables` | `[]` | array of nonblank names | Names of non-secret host variables forwarded into the sandbox — the only non-toolchain environment it ever sees, during provisioning/setup and tool exec/start alike. Unset means none. |
|
|
802
|
+
|
|
803
|
+
The credential-free rule is structural, not advisory: the environment receives
|
|
804
|
+
no store, forge, ticket-provider, or model credential, and naming any of
|
|
805
|
+
`AB_STORE`, `AB_TOKEN`, `GITHUB_TOKEN`, `GH_TOKEN`, `LINEAR_API_KEY`,
|
|
806
|
+
`VERCEL_OIDC_TOKEN`, `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, `VERCEL_PROJECT_ID`, or
|
|
807
|
+
`AI_GATEWAY_API_KEY` in `environmentVariables` is a config error. The typed
|
|
808
|
+
operator tools remain the only route to build state from inside the sandbox.
|
|
809
|
+
|
|
810
|
+
Tool bounds an operator agent should know: `sandbox.exec` waits at most **300
|
|
811
|
+
seconds**; stdout and stderr are each truncated at **65,536 bytes** with a
|
|
812
|
+
`[truncated by autobuild: output exceeded 65536 bytes]` marker; `sandbox.reset`
|
|
813
|
+
and archiving an operator's last session are **destructive** (reset re-provisions
|
|
814
|
+
from the current base head); there is at most **one** environment per operator
|
|
815
|
+
and repository.
|
|
816
|
+
|
|
817
|
+
## Setup and upgrades
|
|
818
|
+
|
|
819
|
+
**`ab init <target> [--force]`** runs *outside* build sessions — it takes a
|
|
820
|
+
repo path, needs no `AB_*` environment, and is safe to re-run. It:
|
|
821
|
+
|
|
822
|
+
- Probes every registered runtime and reports whether it is usable, including a
|
|
823
|
+
diagnostic reason. Setup launch selection uses the fixed product order
|
|
824
|
+
`claude`, `codex`, then `pi`; it never depends on repository content or a user
|
|
825
|
+
questionnaire.
|
|
826
|
+
- If config is absent, writes a valid stack-neutral skeleton with empty
|
|
827
|
+
`[commands]`, no verify or finalize steps, a valid local ticket gate, and one
|
|
828
|
+
explicit runtime solely as a schema placeholder. It reads no package or
|
|
829
|
+
language manifest and invents no command.
|
|
830
|
+
- Vendors 11 skills before handoff, including the locally editable
|
|
831
|
+
[repository setup reference](references/setup.md) in the installed `ab-guide`
|
|
832
|
+
tree. On an interactive terminal it starts the selected coding-agent CLI in
|
|
833
|
+
the target repository with a short prompt to read that reference and
|
|
834
|
+
propagates that process's exit status. The direct child inherits terminal I/O
|
|
835
|
+
but no ambient `AB_*` identity, and creates no build, session, event,
|
|
836
|
+
transcript, or BuildStore record.
|
|
837
|
+
- Without an interactive terminal or usable shipped runtime, deterministic
|
|
838
|
+
installation still succeeds. Init prints every probe reason and the exact
|
|
839
|
+
setup prompt for the user to run in a coding agent.
|
|
840
|
+
- The setup agent inspects the repository, configures real commands and verify
|
|
841
|
+
steps, independently chooses pipeline role runtimes/models, configures the
|
|
842
|
+
ticket source and environment-only credentials, arranges repository-specific
|
|
843
|
+
end-to-end verification (authoring a repository-owned agent verifier when
|
|
844
|
+
judgment is warranted), and files one groomed dispatchable ticket.
|
|
845
|
+
- It never overwrites config once the file exists, even with `--force`; a rerun
|
|
846
|
+
launches or prints a prompt that asks the agent to review and improve it.
|
|
847
|
+
- Idempotently adds the exact `.autobuild/` rule to the target's `.gitignore`,
|
|
848
|
+
preserving every existing byte/rule and handling a missing trailing newline.
|
|
849
|
+
- **Copies each complete canonical skill directory** to
|
|
850
|
+
`.agents/skills/ab-<name>/` — `SKILL.md` plus references/supporting files,
|
|
851
|
+
not links. Every file is **editable**: per-repo customization is the point.
|
|
852
|
+
Unknown repository-local support files are left alone.
|
|
853
|
+
- Links `.claude/skills/ab-<name>` → the `.agents` directory, so Claude, Codex,
|
|
854
|
+
and Pi discover **one** editable tree rather than diverging copies. If the
|
|
855
|
+
`.claude/skills` and `.agents/skills` roots already resolve to the same
|
|
856
|
+
directory, that repository-level alias satisfies discovery: no per-skill
|
|
857
|
+
link or legacy `.claude` pristine migration is attempted.
|
|
858
|
+
- Records the **pristine** installed tree at
|
|
859
|
+
`.agents/skills/.ab-pristine/ab-<name>/` — repo-versioned, and the per-file
|
|
860
|
+
base for `ab upgrade`'s three-way merges.
|
|
861
|
+
- Rewrites frontmatter on install: `name` → `ab-<name>`, and
|
|
862
|
+
`disable-model-invocation: true` on every skill outside the model-invocable
|
|
863
|
+
set (`ab-spec`, `ab-tickets`, `ab-guide`).
|
|
864
|
+
|
|
865
|
+
The final report states `autobuild.toml: written|skipped`, aggregates all skill
|
|
866
|
+
counts, names only attention-worthy `kept` and `overwritten` skills, and reports
|
|
867
|
+
every runtime probe result. A distinct real `.claude/skills/ab-*` directory is
|
|
868
|
+
not mistaken for an alias: init processes the remaining skills, accumulates all
|
|
869
|
+
such conflicts with move/remove guidance, skips setup, and exits nonzero. The
|
|
870
|
+
interactive path otherwise names the selected setup runtime; the fallback
|
|
871
|
+
prints its manual-run instruction and exact prompt. Non-TTY output is plain
|
|
872
|
+
text.
|
|
873
|
+
|
|
874
|
+
**`ab --version`** reports the installed package version, Bun-recorded commit
|
|
875
|
+
when available, and plugin API version using local distribution metadata only.
|
|
876
|
+
It works outside a repository, needs no config or `AB_*` session, and performs
|
|
877
|
+
no network request.
|
|
878
|
+
|
|
879
|
+
**`ab upgrade <target> [--no-self-update | --version <semver>]`** updates only
|
|
880
|
+
when explicitly invoked. By default it resolves the latest published version on
|
|
881
|
+
the installation's channel — the npm registry for a registry install, or the
|
|
882
|
+
latest full GitHub Release from the repository recorded by a Bun forge install
|
|
883
|
+
(including a fork) — installs it with the matching local/global Bun mechanism,
|
|
884
|
+
then hands off to that replacement binary. The fresh process therefore supplies both skill
|
|
885
|
+
defaults and merge logic. `--version` selects an exact release, including an
|
|
886
|
+
older one; `--no-self-update` merges against the installed distribution. Source
|
|
887
|
+
checkouts are never mutated and still merge installed skills, even with an
|
|
888
|
+
exact selector. For latest, unknown/contradictory provenance and lookup/install
|
|
889
|
+
failures warn and continue. For an exact release, mechanism, resolution, or
|
|
890
|
+
install failure stops before skill merge. No other command checks for or
|
|
891
|
+
installs a release.
|
|
892
|
+
|
|
893
|
+
A local Bun update changes the dependency in its owning project's
|
|
894
|
+
`package.json` and the matching `bun.lock` resolution. An existing global
|
|
895
|
+
install changes Bun's global package-manager state instead. Before either can
|
|
896
|
+
write, upgrade captures the target repository's Git baseline and carries it to
|
|
897
|
+
the replacement binary.
|
|
898
|
+
|
|
899
|
+
After that distribution decision, upgrade three-way merges every distributed
|
|
900
|
+
skill file as *pristine base × local edits × new default*, with a standing bias
|
|
901
|
+
toward **the local customization** — upstream is adopted only where it doesn't
|
|
902
|
+
collide with what the repo deliberately changed. New upstream files are
|
|
903
|
+
delivered; an upstream-removed unedited file is removed, while a customized
|
|
904
|
+
copy becomes a preserved repository-local support file.
|
|
905
|
+
Outcomes:
|
|
906
|
+
|
|
907
|
+
| Outcome | Meaning for the repo's files |
|
|
908
|
+
|---|---|
|
|
909
|
+
| `current` | Local already matches the new default, or the repo's edit stands as-is. Nothing written. |
|
|
910
|
+
| `adopted` | Upstream's version taken; pristine advanced. |
|
|
911
|
+
| `merged` | Clean three-way merge; live file and pristine both advanced. |
|
|
912
|
+
| `resolved` | Merge conflicted; a local-biased agent proposal passed deterministic validation, so that live file was resolved and its pristine base advanced to incoming. |
|
|
913
|
+
| `conflicted` | Resolution was unavailable, failed, declined as ambiguous, or failed validation. Both sides of that file stay **byte-untouched** for a human — **conflict markers are never written into a live skill**. |
|
|
914
|
+
| `installed` | In the distribution but not yet in the repo — installed fresh, like init. |
|
|
915
|
+
| `removed` | A fixed retired distribution skill had pristine provenance and either its unreferenced live tree matched pristine and was removed with its owned discovery link, or its canonical live tree was already missing and obsolete provenance plus any owned dangling link were removed. |
|
|
916
|
+
| `kept` | A fixed retired skill was customized, still configured, or unsafe to remove, so its live copy remains local; or an otherwise removable canonical copy was deleted while a user-owned Claude discovery entry was preserved and remains discoverable. Obsolete pristine ownership is cleared. |
|
|
917
|
+
| `unknown` | An installed `ab-*` skill absent from the distribution. **Left alone** — local skill additions are legitimate. |
|
|
918
|
+
|
|
919
|
+
The `removed`/`kept` classifications apply only to the fixed retirements
|
|
920
|
+
`ab-setup` and `ab-verify-e2e`. A pristine record proves Autobuild provenance;
|
|
921
|
+
a same-named repository-authored skill without one is untouched. Upgrade keeps
|
|
922
|
+
a retired skill named by an agent verify or finalize step, and parses config
|
|
923
|
+
conservatively so an inspection failure also keeps it. A user-owned
|
|
924
|
+
`.claude/skills/<name>` discovery entry — a distinct real directory or foreign
|
|
925
|
+
symlink — is preserved byte-for-byte, enters the structured discovery-conflict
|
|
926
|
+
report, and makes upgrade exit nonzero even when the corresponding
|
|
927
|
+
canonical/pristine trees are retired. A preserved symlink keeps its exact link
|
|
928
|
+
text and target. Every terminal classification clears obsolete pristine
|
|
929
|
+
ownership, making the retirement report one-time and preventing later link
|
|
930
|
+
recreation, resurrection, or re-reporting.
|
|
931
|
+
|
|
932
|
+
After a conflict-free merge, upgrade makes one local commit by default. It
|
|
933
|
+
includes only each reported skill's canonical tree, pristine record, and Claude
|
|
934
|
+
discovery path, plus `package.json` and `bun.lock` when this run's local Bun
|
|
935
|
+
update wrote those files inside the target repository. A global update adds no
|
|
936
|
+
path, and no owned change means no commit. Additions, modifications, links, and
|
|
937
|
+
deletions are included; unrelated staged, unstaged, and untracked work remains
|
|
938
|
+
untouched. The message identifies `ab upgrade` and lists every byte-changing
|
|
939
|
+
skill with its reported outcome. `--no-commit` disables this behavior and is
|
|
940
|
+
preserved through self-update handoff. If a replacement child receives the
|
|
941
|
+
handoff marker without the pre-self-update baseline — possible when an older
|
|
942
|
+
parent launches a newer child — it suppresses the entire automatic commit,
|
|
943
|
+
names the cross-version compatibility reason, and leaves all upgrade-owned
|
|
944
|
+
changes uncommitted for the operator. Skill-upgrade results and the report's
|
|
945
|
+
existing exit code remain unchanged.
|
|
946
|
+
|
|
947
|
+
Any content conflict, discovery conflict, pre-existing dirt in an owned path,
|
|
948
|
+
non-Git target, changed HEAD/worktree identity, or in-progress merge, rebase, or
|
|
949
|
+
cherry-pick likewise suppresses the whole commit with a named warning. If upgrade
|
|
950
|
+
cannot snapshot the worktree's Git index, it warns and declines to stage. A
|
|
951
|
+
staging or commit failure restores that exact pre-attempt index and reports the
|
|
952
|
+
original Git failure without touching merged worktree files. The report's
|
|
953
|
+
existing exit code is unchanged; upgrade never pushes or rewrites history.
|
|
954
|
+
|
|
955
|
+
The agent gets a fixed per-file deadline of at least ten minutes. While it is
|
|
956
|
+
resolving and stdout is interactive, `ab upgrade` continuously redraws one line
|
|
957
|
+
with the skill and file path, elapsed time, and `Ctrl-C to cancel`. Ctrl-C then
|
|
958
|
+
aborts only that agent invocation, records the file as the usual byte-preserving
|
|
959
|
+
`conflicted` outcome, and continues through later files and skills. Ctrl-C when
|
|
960
|
+
no resolution is active retains ordinary process termination. Piped and other
|
|
961
|
+
non-TTY output has no indicator, cursor sequences, or changed bytes. Timeouts
|
|
962
|
+
and cancellations remain content conflicts with exit zero; only Claude
|
|
963
|
+
discovery conflicts make upgrade exit nonzero.
|
|
964
|
+
|
|
965
|
+
The agent output is only an untrusted proposal. Each merge uses unguessable
|
|
966
|
+
labels so marker-looking skill content cannot impersonate that merge's
|
|
967
|
+
structure. `ab upgrade` requires a
|
|
968
|
+
complete file with every region outside the uniquely labelled conflict hunks
|
|
969
|
+
unchanged and in order; `SKILL.md` additionally requires the same namespaced
|
|
970
|
+
frontmatter identity. Standard marker lines are rejected in agent-authored hunk
|
|
971
|
+
gaps but allowed when already protected as exact clean content. Validation
|
|
972
|
+
finishes before either file is written. A rejected proposal remains report-only alongside the marked merge diagnostic;
|
|
973
|
+
the CLI prints the exact pristine path to merge by hand. Distinct real Claude
|
|
974
|
+
skill directories are a separate discovery-conflict class: upgrade processes
|
|
975
|
+
all skills, reports every collision with move/remove guidance, and exits
|
|
976
|
+
nonzero, while content-level `conflicted` outcomes retain their exit-zero
|
|
977
|
+
contract. Local customization survives upgrades, and divergence is visible
|
|
978
|
+
instead of silent.
|
|
979
|
+
|
|
980
|
+
## Local state and store selection
|
|
981
|
+
|
|
982
|
+
By default, each Git repository owns one self-contained state tree at
|
|
983
|
+
`<main-repo>/.autobuild/`:
|
|
984
|
+
|
|
985
|
+
- `autobuild.sqlite` — build records, event logs, and repository journals.
|
|
986
|
+
- `blobs/` — content-addressed artifacts.
|
|
987
|
+
- `worktrees/` — autobuild-created Git worktrees.
|
|
988
|
+
- `tickets/` — the default location for the `file` ticket source.
|
|
989
|
+
|
|
990
|
+
The main checkout comes from Git's absolute repository/worktree topology, not
|
|
991
|
+
blindly from the current worktree's top level or the parent of its Git common
|
|
992
|
+
directory. Commands run in an autobuild-created linked worktree therefore read
|
|
993
|
+
the main checkout's state and file tickets, while submodules and
|
|
994
|
+
separate-Git-dir checkouts keep distinct roots beneath their own working trees.
|
|
995
|
+
There is no home-directory fallback: a repository with no state tree starts
|
|
996
|
+
empty.
|
|
997
|
+
|
|
998
|
+
Every command uses the same precedence: explicit `--store <ref>` > nonempty
|
|
999
|
+
`AB_STORE` > `<main-repo>/.autobuild`. Relative local overrides are normalized
|
|
1000
|
+
against the main checkout. A local override relocates the whole state tree,
|
|
1001
|
+
including `worktrees/` and default file tickets; an explicit `[tickets].dir`
|
|
1002
|
+
remains repo-relative. An HTTP(S) URL still selects the remote store unchanged,
|
|
1003
|
+
while worktrees and default file tickets remain under the repository-default
|
|
1004
|
+
`.autobuild/` directory. The dispatcher passes its normalized selection to
|
|
1005
|
+
every agent session as `AB_STORE`.
|
|
1006
|
+
|
|
1007
|
+
## Source-agnostic ticket operations
|
|
1008
|
+
|
|
1009
|
+
The sessionless `ab ticket` namespace constructs whichever TicketSource the
|
|
1010
|
+
repository's `[tickets]` table selects. These forms therefore work unchanged
|
|
1011
|
+
with Linear and the file tracker, without provider-specific API/MCP calls:
|
|
1012
|
+
|
|
1013
|
+
- `ab ticket create <title> --body <file> [--state <state>] [--labels a,b] [--blocked-by id,id] [--json]`
|
|
1014
|
+
files a ticket. Without `--state`, the source uses `[tickets].createState` or
|
|
1015
|
+
its own default. A named state is passed through unchanged and the selected
|
|
1016
|
+
source validates it before creating anything. Blocker ids belong to the same
|
|
1017
|
+
source and are also checked before the single create call.
|
|
1018
|
+
- `ab ticket update <id> [--title <title>] [--body <file>] [--labels a,b] [--json]`
|
|
1019
|
+
partially replaces editable fields. At least one editable flag is required;
|
|
1020
|
+
`--json` alone is not an update.
|
|
1021
|
+
- `ab ticket block <id> <blocker-id[,blocker-id...]> [--json]` adds one or more
|
|
1022
|
+
blocking relationships.
|
|
1023
|
+
- `ab ticket unblock <id> <blocker-id[,blocker-id...]> [--json]` removes one or
|
|
1024
|
+
more blocking relationships.
|
|
1025
|
+
- `ab ticket list [--state <state>] [--labels a,b] [--json]` lists tickets. With
|
|
1026
|
+
no filters it uses exactly dispatch's configured ready state and source-aware
|
|
1027
|
+
default labels. If either filter is present, only explicitly supplied
|
|
1028
|
+
criteria apply; every requested label must match.
|
|
1029
|
+
- `ab ticket show <id> [--json]` reads one complete ticket. Human output includes
|
|
1030
|
+
labeled metadata and the body verbatim, so it can read a stored spec back.
|
|
1031
|
+
- `ab ticket move <id> <state> [--json]` transitions one ticket and reports its
|
|
1032
|
+
post-transition value.
|
|
1033
|
+
|
|
1034
|
+
State names and ids are source-local. For block and unblock, the first id is the
|
|
1035
|
+
ticket being changed and the comma-separated second operand names its blockers.
|
|
1036
|
+
Create validates every `--blocked-by` id before filing. The relationship forms
|
|
1037
|
+
deduplicate their list and validate the target plus every blocker id before
|
|
1038
|
+
writing any edge; block also rejects a direct self-block. An unknown id fails
|
|
1039
|
+
the whole invocation without a partial write. Adding an existing relationship
|
|
1040
|
+
and removing an absent relationship to an existing ticket both succeed as
|
|
1041
|
+
no-ops, so either command is safe to retry.
|
|
1042
|
+
|
|
1043
|
+
Blockers gate a ticket when the dispatcher tries to claim it. Adding a blocker
|
|
1044
|
+
to a ticket that has already been claimed into a build does not stop that build.
|
|
1045
|
+
For dependent tickets, follow the
|
|
1046
|
+
[safe blocker-filing workflow](references/ticket-dependencies.md): determine the
|
|
1047
|
+
intended final destination, stage outside ready, verify all blocker relationships,
|
|
1048
|
+
and move to ready last. Direct and hosted Linear create the issue and its blockers
|
|
1049
|
+
in separate requests; one `create --blocked-by` call is not atomic publication.
|
|
1050
|
+
Harvest's creation reservations cover Harvest only, not interactive CLI creates.
|
|
1051
|
+
Dependency order and JSON id extraction are still required, but do not alone
|
|
1052
|
+
prevent an active dispatcher from claiming a partially created ready ticket.
|
|
1053
|
+
|
|
1054
|
+
Update is partial: omitted fields remain untouched, including labels, assignee,
|
|
1055
|
+
and provider metadata. `--labels` is a complete replacement and an explicitly
|
|
1056
|
+
empty value (`--labels ''`) clears the list. Supplied title/body values must be
|
|
1057
|
+
nonblank. State is intentionally not an update field — `transition()` remains its sole owner —
|
|
1058
|
+
and every validation or unknown-ticket error occurs before
|
|
1059
|
+
mutation and names the offending field or id. `--body` always names a file; a
|
|
1060
|
+
missing file fails before a source write.
|
|
1061
|
+
|
|
1062
|
+
Quote multiword Linear states, for example `"In Progress"`; file states are
|
|
1063
|
+
case-insensitive on input and canonical on output. The adapters validate
|
|
1064
|
+
transitions, so an invalid state fails with the
|
|
1065
|
+
source's known states. A missing id fails nonzero with an error naming both the
|
|
1066
|
+
id and configured source.
|
|
1067
|
+
|
|
1068
|
+
Human-readable output is the default and must not be parsed for ids. Every
|
|
1069
|
+
subcommand accepts `--json`, which emits one bare JSON value and no prose: a
|
|
1070
|
+
`Ticket[]` for `list`, and the complete resulting `Ticket` for `create`,
|
|
1071
|
+
`update`, `block`, `unblock`, `show`, or `move`.
|
|
1072
|
+
|
|
1073
|
+
## Retrieving build artifacts
|
|
1074
|
+
|
|
1075
|
+
`ab artifact download <build> <kind>[@rev] --output <file> [--store <ref>]`
|
|
1076
|
+
is the read-only binary retrieval form. Operators need no session identity: it
|
|
1077
|
+
works after a build is terminal, writes exact bytes (creating parent
|
|
1078
|
+
directories), verifies that the build belongs to this repository, and uses the
|
|
1079
|
+
standard explicit `--store` > nonblank `AB_STORE` > repository-local selection.
|
|
1080
|
+
Inside a phase, a complete ambient tuple permits only the ambient build; a
|
|
1081
|
+
foreign build or malformed/partial identity is rejected. Use the exact pinned
|
|
1082
|
+
`@rev` from a PR attachment command.
|
|
1083
|
+
|
|
1084
|
+
In a build session, `ab artifact put <kind> <file> --attach` atomically deposits
|
|
1085
|
+
the exact bytes and designates that revision for the PR. A later designation of
|
|
1086
|
+
the same kind replaces it; distinct kinds remain distinct attachments. The
|
|
1087
|
+
ordinary form without `--attach` remains a private BuildStore deposit, and
|
|
1088
|
+
in-session `put|get` still require ambient phase auth.
|
|
1089
|
+
|
|
1090
|
+
## Dispatch dashboard
|
|
1091
|
+
|
|
1092
|
+
On a TTY, `ab dispatch` renders one fixed interactive frame anchored at the
|
|
1093
|
+
terminal's first row. Its header remains on that top row as frame height changes
|
|
1094
|
+
and after a resize; unused rows remain below. On exit, the final frame is copied
|
|
1095
|
+
to the normal screen and remains in scrollback. Its always-present two-line
|
|
1096
|
+
process-global header has a selectable `Autobuild` summary with the repository
|
|
1097
|
+
basename followed by the compact counters
|
|
1098
|
+
`queue <depth> | active <current>/<limit> | observations <current>/<limit>`.
|
|
1099
|
+
`queue` is the ready-ticket queue depth; `active` is the current
|
|
1100
|
+
nonterminal-build count against root `capacity`; and `observations` is the count
|
|
1101
|
+
of recorded observation occurrences not yet claimed by a Harvest snapshot
|
|
1102
|
+
against `policy.harvestThreshold`. The Store-only interactive frontend refreshes
|
|
1103
|
+
that display value directly from the BuildStore; a failed refresh retains the
|
|
1104
|
+
last successful count, shows a diagnostic, and appends no transport event.
|
|
1105
|
+
Non-interactive dispatch performs no frontend-only sampling, and the separate
|
|
1106
|
+
drift trigger is not displayed. An indented controls line follows for
|
|
1107
|
+
`intake ON`/`intake OFF`, `auto merge ON`/`auto merge OFF`, and `harvest
|
|
1108
|
+
ON`/`harvest OFF`, plus a conditional yellow `repository PAUSED` segment while
|
|
1109
|
+
the durable repository-wide hold is set. The controls start in the title
|
|
1110
|
+
column. From the top of the frame through the body, the two-column marker lane
|
|
1111
|
+
stays empty except for the selected row's `> ` marker. These controls and the
|
|
1112
|
+
pause segment are durable repository projections and converge across
|
|
1113
|
+
dispatchers on the existing poll; harvest specifically reflects its
|
|
1114
|
+
acknowledged gate, not pending intent. Routine tick
|
|
1115
|
+
counts, dependency diagnostics, parked-build notices, harvest outcomes, and
|
|
1116
|
+
action confirmations are suppressed in the interactive frame. The latest true
|
|
1117
|
+
warning or error instead appears on a conditional row below the header, aligned
|
|
1118
|
+
to the title; when none exists, that row consumes no space. A blank line
|
|
1119
|
+
separates the global section from the first body row, and another separates the
|
|
1120
|
+
body from the legend or feedback controls. The duplicate startup banner is
|
|
1121
|
+
suppressed; `--plain` and non-TTY output remain line-oriented and unchanged. A
|
|
1122
|
+
satisfied verify skip carries the literal `skipped` qualifier, so it remains
|
|
1123
|
+
distinct from a pass without color. Every nonterminal build has a row,
|
|
1124
|
+
including `queued` builds that have not attached a runner. A queued row shows
|
|
1125
|
+
its ticket, slug, literal `QUEUED` status, and either the pending dispatch
|
|
1126
|
+
boundary or the latest durable `dispatch.failed` stage, attempt, and error.
|
|
1127
|
+
While the repository hold is set, only queued rows gain a yellow `(held)`
|
|
1128
|
+
modifier beside `QUEUED`; intake being off without that hold shows neither
|
|
1129
|
+
`repository PAUSED` nor `(held)`.
|
|
1130
|
+
|
|
1131
|
+
A build row previews each unresolved blocker and setup error as at most three
|
|
1132
|
+
rendered rows, preserving authored newlines and one blank row between
|
|
1133
|
+
paragraphs. Longer messages add the withheld-row count and name `Enter details`;
|
|
1134
|
+
that existing detail view shows the setup error and every blocker in full.
|
|
1135
|
+
Up/Down scrolls detail content, Left/Right selects a session, and Enter opens the
|
|
1136
|
+
selected transcript. Harvest failure detail and each process warning use the
|
|
1137
|
+
same three-row cap and count but advertise no unavailable expansion action.
|
|
1138
|
+
These are display-only transformations: stored event and status text is
|
|
1139
|
+
unchanged.
|
|
1140
|
+
|
|
1141
|
+
When the selected session carries a stream, Enter instead opens a read-only
|
|
1142
|
+
session view, whether that session is still open or has already ended. For an
|
|
1143
|
+
open session the view follows the stream live: the turn's prompt, the agent's
|
|
1144
|
+
reasoning and answer text, each tool call with a one-line input summary, tool
|
|
1145
|
+
output capped at eight rows (longer output names the withheld-row count), and
|
|
1146
|
+
step separators appear as they land, within one dashboard poll interval, while
|
|
1147
|
+
the view tracks the tail. For an ended session the view renders the session's
|
|
1148
|
+
finalized content, including error and truncation markers where the runner
|
|
1149
|
+
emitted them, and a final `Stream closed` line names the outcome. Up/Down
|
|
1150
|
+
scrolls; scrolling up from the bottom pauses tail-following — the legend says
|
|
1151
|
+
`paused` — and scrolling back to the bottom resumes it. The view is strictly
|
|
1152
|
+
read-only: no key inside it answers, pauses, resumes, or aborts anything, the
|
|
1153
|
+
abort confirmation cannot be opened from it, and its legend offers only
|
|
1154
|
+
Up/Down, Esc, and quit. Escape returns to build detail with the same session
|
|
1155
|
+
selected. Sessions without a stream keep the existing messages: the
|
|
1156
|
+
open-session unavailable note, the reclaimed note, the no-deposit note, and the
|
|
1157
|
+
existing transcript rendering.
|
|
1158
|
+
|
|
1159
|
+
Up/Down moves without wrapping through global first, optional `Harvest` second,
|
|
1160
|
+
then slug-sorted builds. Stable discriminated identity preserves selection
|
|
1161
|
+
through repaint, re-sort, and row appearance/disappearance. The legend is
|
|
1162
|
+
contextual: every row offers navigation and quit; global offers `h harvest`,
|
|
1163
|
+
`m auto-merge`, `i intake`, `p pause all`, and `r resume all`; `Harvest` offers `p resume` for ordinary
|
|
1164
|
+
failure or `p acknowledge` for exhaustion/escalation only when that action is
|
|
1165
|
+
available; runner-attached builds offer `m auto-merge` plus the state-specific
|
|
1166
|
+
`p pause`, `p cancel pause`, or `r resume` action, while queued builds offer
|
|
1167
|
+
`d discard`. `m` on `Harvest` is an explanatory build-only no-op.
|
|
1168
|
+
|
|
1169
|
+
A running build reads `PAUSING` after `p` appends its pause request and before
|
|
1170
|
+
the kernel acknowledges it at a safe boundary. During that window `p` appends
|
|
1171
|
+
the opposing resume request, cancels the stale pause, and projects the build
|
|
1172
|
+
back to `RUNNING`. An acknowledged paused build offers only `r resume` and reads
|
|
1173
|
+
`RESUMING` until the kernel acknowledges that request; there is no gesture to
|
|
1174
|
+
cancel a pending resume. Blocked takes visual precedence, including when the
|
|
1175
|
+
build is also operator-paused, and `r` opens the existing feedback field. Keys
|
|
1176
|
+
that are not offered for the current build state are no-ops and append no
|
|
1177
|
+
control event.
|
|
1178
|
+
|
|
1179
|
+
Global-row `p` is the repository-wide version — the same walk `ab pause --all`
|
|
1180
|
+
runs sessionless: it durably holds every `QUEUED` build, turns intake off, and
|
|
1181
|
+
appends `build.pause-requested` to every `RUNNING` build, so the repository
|
|
1182
|
+
comes to rest before a machine, terminal, or dispatcher restart. The hold is the `dispatcher.pause-set` repository fact, written before
|
|
1183
|
+
the intake fact; while it is set no dispatcher tick attaches a runner to a
|
|
1184
|
+
queued build, whether through interrupted-dispatch recovery, startup resume, or
|
|
1185
|
+
the lease sweep. The per-build walk skips `PAUSING` — the bulk control never
|
|
1186
|
+
cancels a pending pause, unlike build-row `p` — and skips `QUEUED`, `PAUSED`,
|
|
1187
|
+
`RESUMING`, and `BLOCKED` without error; queued builds are covered by the hold
|
|
1188
|
+
instead. Global-row `r` reverses all of it: the hold released,
|
|
1189
|
+
`build.resume-requested` on every `PAUSED` build (including builds paused by
|
|
1190
|
+
hand beforehand), and intake back on. A released queued build becomes
|
|
1191
|
+
launchable again on the next ordinary tick, with no further operator action. `r`
|
|
1192
|
+
does not touch `BLOCKED` builds, whose blockers still need the per-build `r`
|
|
1193
|
+
feedback field. Both report the affected build count, the hold, and the new
|
|
1194
|
+
intake value on the notice row. One carve-out remains: the harvest gate is
|
|
1195
|
+
independent, so a harvest session can still start after a bulk pause (`h` stops
|
|
1196
|
+
it). The janitor is likewise ungated, so an abort or discard on a held build is
|
|
1197
|
+
still settled. Build work in this repository is otherwise genuinely at rest.
|
|
1198
|
+
|
|
1199
|
+
`--intake` and `--no-intake` are mutually exclusive durable repository setters.
|
|
1200
|
+
Omitting both reuses stored state, falling back to ON only when no intake fact
|
|
1201
|
+
exists. Global-row `i` re-reads current state and appends the opposite value.
|
|
1202
|
+
Intake off skips only new ticket claims while janitor, stale-runner, harvest,
|
|
1203
|
+
and in-flight work continue — it deliberately does **not** set the repository
|
|
1204
|
+
hold, so a build the repository has already accepted still starts and finishes.
|
|
1205
|
+
Every dispatcher samples the current value on each tick, so a change in one
|
|
1206
|
+
process gates all dispatchers for that repository.
|
|
1207
|
+
|
|
1208
|
+
The repository hold has no launch flag and no setter of its own: `p` and `r` on
|
|
1209
|
+
the dashboard's global row and the `ab pause --all` / `ab resume --all` walk
|
|
1210
|
+
they share are the only ways to set or clear it, and each of those moves intake
|
|
1211
|
+
with it. Each dispatcher frame reads the hold from the repository journal
|
|
1212
|
+
itself, so an already-paused repository shows `repository PAUSED` and `(held)`
|
|
1213
|
+
on its first paint; clearing the hold removes both on the next poll. The hold
|
|
1214
|
+
survives a restart and is not defeated by re-running `ab dispatch`.
|
|
1215
|
+
|
|
1216
|
+
`--auto-merge` and `--no-auto-merge` similarly set the durable repository
|
|
1217
|
+
claim-time default; omission reuses stored state, falling back to OFF only when
|
|
1218
|
+
no fact exists. The forms are mutually exclusive independently of the intake
|
|
1219
|
+
pair. Global-row `m` re-reads current state, appends the opposite value, and
|
|
1220
|
+
posts a dispatcher notice. When on, each fresh dispatcher claim records the
|
|
1221
|
+
existing human-authored `build.auto-merge-requested` fact immediately after
|
|
1222
|
+
`build.created` and before runner launch. The first visible build frame therefore
|
|
1223
|
+
carries `auto merge`. This is a creation-time seed, not policy: changes never
|
|
1224
|
+
touch existing builds, resumed/adopted logs or other creation paths never sample
|
|
1225
|
+
it, and build-row `m` remains independent (a seeded build can be cancelled while
|
|
1226
|
+
the global default stays on). Both repository settings are independently
|
|
1227
|
+
last-write-wins by event sequence. They are stored in the BuildStore, not in
|
|
1228
|
+
`autobuild.toml`; propagation uses polling, not a push channel.
|
|
1229
|
+
|
|
1230
|
+
Global-row `h` re-reads the repository journal and appends the existing human
|
|
1231
|
+
pause/resume request. The newest pending command determines the next target, so
|
|
1232
|
+
rapid presses issue opposing requests; the token changes only after the kernel's
|
|
1233
|
+
`harvest.paused`/`harvest.resumed` acknowledgement.
|
|
1234
|
+
|
|
1235
|
+
`Harvest` uses the same marker, right-aligned status column, and status colors
|
|
1236
|
+
as builds; its internal run id is not shown. One row selects the oldest
|
|
1237
|
+
unresolved ordinary failure, exhaustion, or escalation attention before the
|
|
1238
|
+
oldest open run, so later running/completed work cannot hide it. Completed runs,
|
|
1239
|
+
an idle paused repository, acknowledged recovery exhaustion, and dismissed
|
|
1240
|
+
escalation have no row. A paused open run freezes and reads `PAUSED`. Ordinary
|
|
1241
|
+
failure reads red `FAILED`, marks the stopped step, shows automatic progress,
|
|
1242
|
+
and offers `p resume`. Exhaustion remains red, says `recovery exhausted — human
|
|
1243
|
+
attention required`, shows stopped step/pending count, and offers `p
|
|
1244
|
+
acknowledge`; escalation also offers `p acknowledge`. A request awaiting
|
|
1245
|
+
acknowledgement has no duplicate action. Row `p` never pauses the gate. When the
|
|
1246
|
+
gate is off it writes nothing and directs the operator to global `h`; the
|
|
1247
|
+
eventual shared human acknowledgement reopens every ordinary parked run and
|
|
1248
|
+
settles every exhaustion barrier. Exhaustion and escalation remain terminal.
|
|
1249
|
+
Escalation's row is dismissed only by a post-terminal human resume request
|
|
1250
|
+
followed by its later kernel acknowledgement, never by the request alone.
|
|
1251
|
+
|
|
1252
|
+
A build with auto-merge off has no auto-merge token. Requested, enabled, and
|
|
1253
|
+
cancelling states all read `auto merge`: cyan means requested locally but not
|
|
1254
|
+
yet applied on GitHub, green means native auto-merge is enabled, and yellow
|
|
1255
|
+
means cancellation is in flight. The token disappears when cancellation lands.
|
|
1256
|
+
Merge conflicts recover automatically: the gate defers, the pipeline re-enters
|
|
1257
|
+
reconcile, and verify re-runs before consent is re-examined — the reconcile
|
|
1258
|
+
step on the build row is the only indication, and uncomputed mergeability
|
|
1259
|
+
resolves the same way on a later poll. The auto-merge gate records a durable
|
|
1260
|
+
observation only for a deferral a person must fix (disabled repository
|
|
1261
|
+
auto-merge, a dirty local checkout, a missing Git identity, an unproven gate
|
|
1262
|
+
state, a plan limitation) — never for a conflict the pipeline resolves itself.
|
|
1263
|
+
A recorded deferral stops being shown once the PR merges or closes, or once a
|
|
1264
|
+
reconcile or re-finalize supersedes it, until the gate re-examines consent.
|
|
1265
|
+
|
|
1266
|
+
### Durable build controls: CLI and dashboard
|
|
1267
|
+
|
|
1268
|
+
The CLI and dashboard are two surfaces over the same store-backed control
|
|
1269
|
+
operations. Use the CLI from a non-TTY, a script, or an assistant; use the
|
|
1270
|
+
keys while watching the interactive dashboard. Both append human-authored facts
|
|
1271
|
+
to the build's event log and apply the same write-time checks.
|
|
1272
|
+
|
|
1273
|
+
| Intent | CLI | Dashboard | Durable event(s) |
|
|
1274
|
+
|---|---|---|---|
|
|
1275
|
+
| Discard interrupted dispatch | — | Select a queued build and press `d`. | `build.discard-requested` |
|
|
1276
|
+
| Pause | `ab pause <slug> [--store <ref>]` | Select a `RUNNING` build and press `p`; press `p` again while `PAUSING` to cancel the pending pause. | `build.pause-requested`; cancellation reuses `build.resume-requested` |
|
|
1277
|
+
| Resume | `ab resume <slug> [--store <ref>]` | Select a `PAUSED` build and press `r`. | `build.resume-requested` |
|
|
1278
|
+
| Pause all | `ab pause --all [--store <ref>] [--json]` | Select the global row and press `p`. | `dispatcher.pause-set` (first), `dispatcher.intake-set`, then one `build.pause-requested` per `RUNNING` build |
|
|
1279
|
+
| Resume all | `ab resume --all [--store <ref>] [--json]` | Select the global row and press `r`. | `dispatcher.pause-set` (first), `dispatcher.intake-set`, then one `build.resume-requested` per `PAUSED` build |
|
|
1280
|
+
| Enable/disable auto-merge | `ab auto-merge <slug> on\|off [--store <ref>]` | Select the build and press `m` to toggle. | `build.auto-merge-requested` / `build.auto-merge-cancelled` |
|
|
1281
|
+
| Answer blockers with guidance | `ab answer <slug> <text> [--store <ref>]` | Select a blocked build, press `r`, enter text, then Enter. | One `escalation.answered` with `resolution: guidance` per applicable blocker. |
|
|
1282
|
+
| Retry blockers without guidance | `ab answer <slug> [--store <ref>]` | Open the same `r` field and press Enter empty or whitespace-only. | One `escalation.answered` with `resolution: retry` per applicable blocker. |
|
|
1283
|
+
| Dismiss cited finding chains | `ab answer <slug> --dismiss [--store <ref>]` | CLI only; build detail names the command. | One `escalation.answered` with `resolution: dismiss-finding` per blocker that cites a real review finding. |
|
|
1284
|
+
| Revise spec from a file | `ab answer <slug> --revise-spec <file> [--store <ref>]` | CLI only; build detail names the command. | Human `escalation.answered` events naming the exact replacement revision, then kernel `spec.revised`. |
|
|
1285
|
+
| Re-import revised ticket spec | `ab answer <slug> --revise-spec-from-ticket [--store <ref>]` | CLI only; build detail names the command. | The same `revise-spec` answer and `spec.revised` sequence. |
|
|
1286
|
+
| Abort | `ab abort <slug> [--store <ref>]` | Select any non-terminal build and press `a`, then Enter to confirm (Escape cancels). | `build.abort-requested` |
|
|
1287
|
+
|
|
1288
|
+
The two `--all` forms and the global-row keys are one walk behind two surfaces.
|
|
1289
|
+
The repository pause is written first and intake second, both absolutely —
|
|
1290
|
+
setters, not toggles, so the values the command names are the values the
|
|
1291
|
+
repository holds afterwards — and each per-build request is a compare-and-set
|
|
1292
|
+
against a freshly reduced log, so a rerun adds no second pending pause and
|
|
1293
|
+
never cancels one already in flight. Pause first is what makes every surviving
|
|
1294
|
+
prefix of a failed walk at least as quiesced as asked: once it lands, no queued
|
|
1295
|
+
build is launchable, whatever else fails after it. Output names every build that
|
|
1296
|
+
received a request, the hold, and the intake value written; `--json` emits that
|
|
1297
|
+
same summary for a script. A walk that fails partway exits nonzero and reports
|
|
1298
|
+
which of the two repository facts landed, which builds were requested, which one
|
|
1299
|
+
failed, and which were never attempted. Neither form starts a dispatcher, so
|
|
1300
|
+
reaching for one to quiesce the repository does not launch the work it is trying
|
|
1301
|
+
to park.
|
|
1302
|
+
|
|
1303
|
+
Discard is dashboard-only and queued-only. After any launching execution has
|
|
1304
|
+
released its process-tree lease, the dispatcher releases a partial workspace,
|
|
1305
|
+
returns the ticket to `[tickets].readyState`, and completes the old build as
|
|
1306
|
+
`discarded`; a later tick may claim the ticket into a fresh build. This is not
|
|
1307
|
+
abort: abort returns the ticket to Triage.
|
|
1308
|
+
|
|
1309
|
+
Abort accepts queued, running, paused, or blocked builds. A live turn receives a
|
|
1310
|
+
cancellation signal and the runner acknowledges the request. Its execution
|
|
1311
|
+
supervisor gracefully stops and reaps the complete process tree, force-terminating
|
|
1312
|
+
it after the bounded stop delay when necessary, and only then releases the lease.
|
|
1313
|
+
The dispatcher defers cleanup while that lease is live, then performs the
|
|
1314
|
+
checkpointed, retry-safe cleanup: close an open unmerged PR, release the workspace,
|
|
1315
|
+
delete the exact remote and local build branches, preserve existing ticket labels
|
|
1316
|
+
while adding `autobuild:aborted`, and return the ticket to configured Triage before
|
|
1317
|
+
completing as `abandoned`. Missing
|
|
1318
|
+
Forge cleanup capabilities or provider outages leave cleanup pending with an
|
|
1319
|
+
actionable diagnostic for the next tick.
|
|
1320
|
+
|
|
1321
|
+
On the dashboard, `r` on a blocked build replaces the bottom legend with a
|
|
1322
|
+
multi-line composer panel: the build being resumed, a note that guidance is
|
|
1323
|
+
optional, the build's unresolved blocker questions, the field itself, and its
|
|
1324
|
+
key bindings. All printable keys edit the field instead of triggering dashboard
|
|
1325
|
+
actions. `Enter` submits (an empty field resumes without guidance), `Ctrl-J`
|
|
1326
|
+
inserts a line break — as does `Shift+Enter` in terminals that report it
|
|
1327
|
+
distinctly — `Backspace` deletes at the caret, `Left`/`Right`/`Up`/`Down` and
|
|
1328
|
+
`Home`/`End` move the caret rather than the dashboard's row selection, and
|
|
1329
|
+
`Escape` cancels without writing. Line breaks survive submission, so the
|
|
1330
|
+
guidance the agent receives keeps the operator's line structure. Pasting
|
|
1331
|
+
multi-line text inserts all of it in one step in terminals that support
|
|
1332
|
+
bracketed paste; where they do not, a paste still submits at its first line
|
|
1333
|
+
break.
|
|
1334
|
+
|
|
1335
|
+
`ab answer` answers every escalation that is open when the command runs,
|
|
1336
|
+
regardless of `agent`, `stall`, or `policy` source. Its text is joined, trimmed,
|
|
1337
|
+
and delivered as authoritative guidance. With no text (or only whitespace), it
|
|
1338
|
+
requests a bare retry and supplies no agent guidance. Verify has two explicit
|
|
1339
|
+
answer routes: guidance answering an agent verifier's own `ab escalate` returns
|
|
1340
|
+
to that same `verify:<step>` on `verify.started.feedback` and materializes as
|
|
1341
|
+
`.ab/guidance.json`; the cited start carries it durably across any pre-launch
|
|
1342
|
+
recovery, and the matching `session.started` consumes it once after launch.
|
|
1343
|
+
Guidance answering the policy escalation after exhausted failed verify reports
|
|
1344
|
+
instead goes to `implement` and outranks the pending report. A bare retry on either path carries
|
|
1345
|
+
no guidance. The dashboard captures the blocker ids when its field opens, then
|
|
1346
|
+
answers only those still open at submission; Escape cancels without writing. If
|
|
1347
|
+
the build is also paused, both surfaces append all answers first and
|
|
1348
|
+
`build.resume-requested` last. A plain
|
|
1349
|
+
`ab resume` does not answer blockers; use `ab answer` for a blocked build.
|
|
1350
|
+
|
|
1351
|
+
An answer is not only recorded — it is delivered to the agent that can act on
|
|
1352
|
+
it. An escalation from `plan` or `plan-review` feeds the next `plan` round.
|
|
1353
|
+
An escalation from `implement` or `code-review` feeds the next `implement`
|
|
1354
|
+
round. An agent verifier's own escalation feeds the next run of that same
|
|
1355
|
+
`verify:<step>`. When a failed verify report exhausts policy, guidance answering
|
|
1356
|
+
that policy escalation instead feeds the next `implement` round and takes
|
|
1357
|
+
precedence over the failed report. When several answers are waiting for one
|
|
1358
|
+
engine-routed destination (`plan`, `code`, or the same exact verifier), only the
|
|
1359
|
+
newest is eligible: it durably supersedes older answers, which do not resurface
|
|
1360
|
+
after the winner is delivered. Destinations remain independent, and a new
|
|
1361
|
+
answer recorded after an earlier delivery is eligible for the next appropriate
|
|
1362
|
+
run. `finalize` and `reconcile` receive their own answers on their next attempt.
|
|
1363
|
+
In each of these routed cases, `ab context`
|
|
1364
|
+
writes the answer to `.ab/guidance.json` in the build's workspace, and the
|
|
1365
|
+
receiving skill treats it as authoritative feedback for that round or attempt.
|
|
1366
|
+
A producer round's feedback is exclusive: on a guidance round
|
|
1367
|
+
`.ab/guidance.json` replaces the reviewer's findings or a routed-back verify
|
|
1368
|
+
failure, so the answer has to carry what the agent needs. A bare retry writes no
|
|
1369
|
+
`.ab/guidance.json` and displaces nothing: the resumed round still gets whatever
|
|
1370
|
+
the loop was already routing, such as the last review round's findings.
|
|
1371
|
+
|
|
1372
|
+
`--dismiss` is per escalation. It answers only blockers that cite at least one
|
|
1373
|
+
real plan-review or code-review finding; blockers with no such citation remain
|
|
1374
|
+
open. A dismissed finding id means a human retired that disagreement. The next
|
|
1375
|
+
reviewer receives `.ab/dismissed-findings.json`, must not re-raise the finding,
|
|
1376
|
+
and must use a fresh id without `persists` if a genuinely different problem
|
|
1377
|
+
touches the same code. Dismissal settles a chain, not a file or the reviewer's
|
|
1378
|
+
ability to report new problems.
|
|
1379
|
+
|
|
1380
|
+
Spec revision requires an open escalation. `--revise-spec` reads a conforming
|
|
1381
|
+
replacement from a file; `--revise-spec-from-ticket` re-imports the build's
|
|
1382
|
+
recorded ticket after an operator amends it. The human `escalation.answered`
|
|
1383
|
+
fact names the exact deposited revision it authorizes, then kernel
|
|
1384
|
+
`spec.revised` publishes it. If interrupted between those facts, a retry
|
|
1385
|
+
completes the recorded body and does not use a newly supplied one. A
|
|
1386
|
+
non-conforming body records nothing. The build restarts from `plan` on the new
|
|
1387
|
+
revision and keeps any open PR. A build's spec can therefore change under it:
|
|
1388
|
+
phase runs after the revision read the new revision, while historical runs
|
|
1389
|
+
remain readable at the revision they actually saw.
|
|
1390
|
+
|
|
1391
|
+
Every command requires the target to exist in this repository and be active
|
|
1392
|
+
(`running`, `paused`, or `blocked`), except abort also accepts `queued`;
|
|
1393
|
+
`ab answer` additionally requires an open escalation. A stale, missing, done,
|
|
1394
|
+
aborted, or otherwise inapplicable target is an error and gets no new event. Attribution uses `USER`, then `USERNAME`, then the
|
|
1395
|
+
same stable fallback on both surfaces. All five commands run sessionless and
|
|
1396
|
+
accept `--store <ref>` with the usual explicit flag > `AB_STORE` > repository
|
|
1397
|
+
local precedence. If nonblank `AB_SESSION` and matching `AB_BUILD` identify the
|
|
1398
|
+
caller's own phase build, the command refuses it; a phase cannot pause, resume,
|
|
1399
|
+
auto-merge, answer, or abort itself. `ab pause --all` and `ab resume --all`
|
|
1400
|
+
accept `--store` on the same precedence and are refused for the same reason
|
|
1401
|
+
whenever `AB_SESSION` and `AB_BUILD` are both nonblank — the caller's own build
|
|
1402
|
+
is inside the walk.
|
|
1403
|
+
|
|
1404
|
+
These commands request normal kernel work; they do not wake a runner, operate
|
|
1405
|
+
the forge, or bypass the lease sweep. Resume is therefore an attempt, not a
|
|
1406
|
+
guarantee: if the condition still fails, a phase may raise a new escalation and
|
|
1407
|
+
block again. Every open escalation, including all policy and setup causes,
|
|
1408
|
+
survives `ab dispatch` and `ab dispatch --once` startup until a human answers
|
|
1409
|
+
it. Startup still recovers actionable builds and delivers pending operator
|
|
1410
|
+
commands, but it never records an answer or re-arms a budget.
|
|
1411
|
+
|
|
1412
|
+
Durable repository intake and the claim-time auto-merge default have launch-flag
|
|
1413
|
+
setters and global-row toggles but no standalone sessionless control commands.
|
|
1414
|
+
Abort is available through both its CLI command and the confirmed build-row/detail
|
|
1415
|
+
`a` key. Global-row `h` owns the durable harvest gate. Global-row `p`/`r` and
|
|
1416
|
+
`ab pause --all` / `ab resume --all` are two surfaces over the one walk described
|
|
1417
|
+
above: from the same repository state they write the same events. Neither the
|
|
1418
|
+
repository hold nor intake has a standalone sessionless setter of its own, but
|
|
1419
|
+
the bulk commands write both as part of quiescing — the hold first. On the
|
|
1420
|
+
optional repository-scoped
|
|
1421
|
+
`Harvest` run row, `p` only resumes or acknowledges the represented run; `i`
|
|
1422
|
+
and `h` are no-ops, and `m` remains an explanatory build-only no-op. On build
|
|
1423
|
+
rows, `i` and `h` are also no-ops.
|
|
1424
|
+
|
|
1425
|
+
## Checking build status
|
|
1426
|
+
|
|
1427
|
+
The read-only status commands answer "what is happening?" without inspecting
|
|
1428
|
+
SQLite, worktrees, or OS processes. Operators need no phase-session `AB_*`
|
|
1429
|
+
identity; those invocations remain repository-wide. Inside a phase, a complete
|
|
1430
|
+
ambient tuple permits `ab build status` only for the ambient build and denies
|
|
1431
|
+
`ab builds` because it requires repository-wide collection access. Any partial,
|
|
1432
|
+
malformed, shared-only, or mixed build/Harvest identity is rejected rather than
|
|
1433
|
+
falling back to operator access. The commands append no events, take no leases,
|
|
1434
|
+
and are safe to run at any time.
|
|
1435
|
+
|
|
1436
|
+
**`ab repository status [--json] [--store <ref>]`** reports the three durable
|
|
1437
|
+
dispatcher settings in one answer: ticket intake (`intake`, default `true`), the
|
|
1438
|
+
repository-wide pause (`paused`, default `false`), and the claim-time auto-merge
|
|
1439
|
+
default (`defaultAutoMerge`, default `false`). Those defaults also describe a
|
|
1440
|
+
repository with no journal row; the read does not create one. Human output is
|
|
1441
|
+
the default. `--json` emits one bare object with those exact fields plus the
|
|
1442
|
+
canonical `repo`, and `--store` follows the usual explicit flag > `AB_STORE` >
|
|
1443
|
+
repository-local precedence. The command uses the same reducer as the
|
|
1444
|
+
dispatcher and dashboard, writes no state, claims no ticket, attaches no runner,
|
|
1445
|
+
and starts no dispatcher work.
|
|
1446
|
+
|
|
1447
|
+
**`ab builds`** summarizes this repository's builds, one row each. It reports
|
|
1448
|
+
**active** builds by default — `running`, `paused`, `blocked` — because those
|
|
1449
|
+
are the ones something can still be done about. `HEARTBEAT` is mutable runner
|
|
1450
|
+
liveness; `PROGRESS` is the age of the latest durable event. A literal
|
|
1451
|
+
`diverged` suffix means a nonterminal build still holds a live lease but its
|
|
1452
|
+
heartbeat is at least one hour newer than its latest event. It is a visibility
|
|
1453
|
+
signal, not a stalled/dead verdict.
|
|
1454
|
+
|
|
1455
|
+
| Flag | Effect |
|
|
1456
|
+
|---|---|
|
|
1457
|
+
| `--queued` | Also include `queued` builds — dispatched but not yet attached to a runner. |
|
|
1458
|
+
| `--all` | Every status, including `queued`, `done`, and `aborted`. Subsumes `--queued`. |
|
|
1459
|
+
| `--json` | The projection as JSON: no ANSI, no prose. Use this when scripting or parsing. |
|
|
1460
|
+
| `--store <ref>` | Read a different store — a path or an `http(s)` URL, same reference behavior `ab dispatch` takes. |
|
|
1461
|
+
|
|
1462
|
+
An empty result names the filter in effect, so "no active builds" means the
|
|
1463
|
+
filter matched nothing, not that the command failed. Widen it with `--queued`
|
|
1464
|
+
or `--all` before concluding a build doesn't exist.
|
|
1465
|
+
|
|
1466
|
+
**`ab build status <slug>`** details one build: unresolved escalations, open
|
|
1467
|
+
sessions, chronological durable observations, verify progress for the current
|
|
1468
|
+
cycle, PR lifecycle, latest event, durable progress age, heartbeat, and lease.
|
|
1469
|
+
For an open PR, the current work-owner decision reads the build-owned
|
|
1470
|
+
configuration from the store's deposited `build-runner-effective-config`
|
|
1471
|
+
artifact — deposited at every runner launch and refreshed on effective-config
|
|
1472
|
+
changes — falling back to the local workspace's `autobuild.toml` only when no
|
|
1473
|
+
deposit exists; the decision is reported unavailable only when neither source
|
|
1474
|
+
can supply a configuration, and that message names the missing artifact kind
|
|
1475
|
+
rather than a workspace path.
|
|
1476
|
+
Observations are shown without `--events`; for `forge = "local-git"`, this is where a deferred landing
|
|
1477
|
+
names uncommitted work that collides with the squash. Autobuild leaves that work
|
|
1478
|
+
untouched and later dispatcher ticks retry automatically after the operator
|
|
1479
|
+
resolves it. `--events <n>` appends the newest `n` event envelopes in
|
|
1480
|
+
chronological order — the fastest way to see what a build actually just did.
|
|
1481
|
+
|
|
1482
|
+
Verify progress covers the **current cycle** — the results since the latest
|
|
1483
|
+
code-review approve or reconcile. Implement and reconcile change the code, so a
|
|
1484
|
+
new cycle re-runs from the first step and earlier results describe code that no
|
|
1485
|
+
longer exists. An empty step list next to `attempt 1` therefore means the cycle
|
|
1486
|
+
restarted, not that verify never ran. A skip renders as `SKIP` with its reason;
|
|
1487
|
+
JSON exposes `outcome: "skipped"` and `reason`, never a synthetic pass.
|
|
1488
|
+
`--json` and `--store <ref>` work the same here.
|
|
1489
|
+
|
|
1490
|
+
Use `ab builds` to find the build; use `ab build status` to understand it.
|
|
1491
|
+
|
|
1492
|
+
The open-session list is literal durable state. Historical logs with a
|
|
1493
|
+
`session.started` but no ending remain readable and show that session as open;
|
|
1494
|
+
Autobuild does not reinterpret or repair them. When a runner actually takes a
|
|
1495
|
+
build over, it first records each session open at the resume boundary as
|
|
1496
|
+
`reclaimed`, without inventing a transcript, and then records its attachment.
|
|
1497
|
+
The rerun opens one fresh session, so after attachment only work that could
|
|
1498
|
+
still be alive appears in `ab build status`. Dashboard session history keeps the
|
|
1499
|
+
reclaimed entry visibly distinct and reports that its transcript is unavailable.
|
|
1500
|
+
|
|
1501
|
+
Each session row also shows its live-view stream: the stream id and
|
|
1502
|
+
whether that stream is `open` or `closed` — for example `stream st_1f2e (open)`.
|
|
1503
|
+
A session that never streamed (a plugin runtime without the streaming
|
|
1504
|
+
capability, or a historical log) shows no stream segment. While a session
|
|
1505
|
+
runs, its stream is the live view of the turn; once the session ends the
|
|
1506
|
+
stream finalizes into an artifact, and `ab artifact download <slug>
|
|
1507
|
+
stream:<id>` retrieves the closed session's finalized document.
|
|
1508
|
+
|
|
1509
|
+
Harvest sessions stream the same protocol into *repository-scoped* streams:
|
|
1510
|
+
the harvest runner's synthesize and review brackets each get one stream
|
|
1511
|
+
(`phase` spelled `harvest:<step>`), with `harvest.session.started` carrying
|
|
1512
|
+
the stream id. Find them via that `stream` field or the harvest journal
|
|
1513
|
+
(`ab watch --repository`); a closed stream finalizes to a repository-scope
|
|
1514
|
+
`stream:<id>` artifact. The harvest run id is not on the stream — recover it
|
|
1515
|
+
from the journal event that names the stream.
|
|
1516
|
+
|
|
1517
|
+
**`ab watch [<slug>...] [--repository] [--event <glob>]... [--since <cursor>]
|
|
1518
|
+
[--timeout <dur>] [--interval <dur>] [--count <n>] [--json] [--store <ref>]`**
|
|
1519
|
+
streams matching events as they land instead of answering once. With no slug it
|
|
1520
|
+
follows every nonterminal build in the repository — a build created while the
|
|
1521
|
+
watch runs joins it without a restart — and naming one or more slugs watches
|
|
1522
|
+
exactly those builds, ending the watch when all of them reach a terminal status.
|
|
1523
|
+
`--repository` additionally follows the repository journal; without it,
|
|
1524
|
+
repository events are never emitted.
|
|
1525
|
+
|
|
1526
|
+
The default filter is the attention set — the events something must wake up
|
|
1527
|
+
for. For builds that is exactly: `escalation.raised`, `phase.failed`,
|
|
1528
|
+
`infrastructure.failed`, `runner.setup-failed`, `dispatch.failed`,
|
|
1529
|
+
`publication.lost`, `finalize.completed`, `pr.conflicted`, `pr.merged`,
|
|
1530
|
+
`pr.closed`, `build.completed`, `build.aborted`. For the repository journal:
|
|
1531
|
+
`harvest.escalated`, `harvest.failed`, `dispatcher.tick-failed`,
|
|
1532
|
+
`dispatcher.config-rejected`, `dispatcher.harvest-runner-failed`. A repeatable
|
|
1533
|
+
`--event <glob>` (`escalation.*`, `pr.*`, `*` — `*` matches any run of
|
|
1534
|
+
characters, `?` one) replaces that filter, and every glob must match at least
|
|
1535
|
+
one known event type or the command fails before reading the store.
|
|
1536
|
+
|
|
1537
|
+
With `--json`, stdout is newline-delimited JSON — exactly one object per
|
|
1538
|
+
matching event, flushed as soon as it is written — with fields `build` (slug,
|
|
1539
|
+
or null for a repository event), `repo` (present only for repository events),
|
|
1540
|
+
`event` (the complete durable envelope: build, seq, ts, actor, type, payload),
|
|
1541
|
+
`state` (for build events, the reduction after that event: `status`, `phase`,
|
|
1542
|
+
`round`, `openEscalations` with their ids and questions, `pr`, `prState`,
|
|
1543
|
+
`outcome`; null for repository events), and `cursor`. Without `--json` each
|
|
1544
|
+
record is one plain human-readable line. On every exit the last `--json` line
|
|
1545
|
+
is a bare `{"cursor": …}` object reflecting the final position even when no
|
|
1546
|
+
record matched. Cursors are opaque; resume with `--since <cursor>` and every
|
|
1547
|
+
matching event after that cursor is delivered exactly once, in per-stream
|
|
1548
|
+
sequence order — including events that landed while no watch was running. A
|
|
1549
|
+
cursor from a different repository or store is rejected.
|
|
1550
|
+
|
|
1551
|
+
The watch ends with exit 0 when `--timeout` elapses (30 minutes by default;
|
|
1552
|
+
`0` unbounded), after `--count <n>` records, or when named builds have all
|
|
1553
|
+
gone terminal; with no slugs named, an empty set of active builds does not end
|
|
1554
|
+
it. Usage errors, scope violations, unknown slugs, and cursor rejection exit 1
|
|
1555
|
+
before any record. A store read that fails after the watch has started is
|
|
1556
|
+
reported once on stderr and retried without duplicating or skipping any event.
|
|
1557
|
+
Against a local store the default cadence polls every 1 second. Against an
|
|
1558
|
+
`http(s)` store each tracked stream long-polls instead — one held request per
|
|
1559
|
+
stream waits up to 25 seconds for the next event, requests run concurrently,
|
|
1560
|
+
and an appended matching event is delivered within about a second of its
|
|
1561
|
+
append rather than at the next tick — while `--interval` (default 5 seconds)
|
|
1562
|
+
still bounds the spacing between a stream's request starts, so a quiet stream
|
|
1563
|
+
costs roughly one request per 25 seconds and a chatty one no more than one
|
|
1564
|
+
request per interval. The command is read-only — it appends no
|
|
1565
|
+
event, takes no lease, creates no record, and starts no work — and deciding
|
|
1566
|
+
what to do about a blocker or a merge stays with the caller, not the command.
|
|
1567
|
+
Inside a phase, only the ambient build may be watched.
|
|
1568
|
+
|
|
1569
|
+
Two recipes cover the common harnesses. First, a line-per-notification tool
|
|
1570
|
+
that re-arms periodically (Claude Code's Monitor tool expires after at most 30
|
|
1571
|
+
minutes): the cursor is what makes each re-arming lossless — persist the final
|
|
1572
|
+
`{"cursor": …}` line and pass it back through `--since` next time:
|
|
1573
|
+
|
|
1574
|
+
```sh
|
|
1575
|
+
last=$(cat .ab-watch.cursor 2>/dev/null || true)
|
|
1576
|
+
ab watch --json --timeout 30m ${last:+--since "$last"} \
|
|
1577
|
+
| awk '/^\{"cursor":/ { cursor = $0; next } { print } \
|
|
1578
|
+
END { if (cursor != "") print cursor > ".ab-watch.cursor" }'
|
|
1579
|
+
```
|
|
1580
|
+
|
|
1581
|
+
Second, a plain shell loop — the same resume discipline wrapped in `while`:
|
|
1582
|
+
|
|
1583
|
+
```sh
|
|
1584
|
+
while :; do
|
|
1585
|
+
last=$(cat .ab-watch.cursor 2>/dev/null || true)
|
|
1586
|
+
ab watch --json --timeout 30m ${last:+--since "$last"} \
|
|
1587
|
+
| while IFS= read -r line; do
|
|
1588
|
+
case $line in
|
|
1589
|
+
'{"cursor":'* ) printf '%s\n' "$line" > .ab-watch.cursor ;;
|
|
1590
|
+
* ) echo "$line" ;; # one JSON record per matching event
|
|
1591
|
+
esac
|
|
1592
|
+
done
|
|
1593
|
+
sleep 1
|
|
1594
|
+
done
|
|
1595
|
+
```
|
|
1596
|
+
|
|
1597
|
+
**`ab wait [<slug>...] (--for <condition> | --event <glob>)... [--since <cursor>]
|
|
1598
|
+
[--timeout <dur>] [--interval <dur>] [--json] [--store <ref>]`** is the blocking
|
|
1599
|
+
sibling of `ab watch`: it blocks until a targeted build satisfies a condition,
|
|
1600
|
+
prints exactly one record, and exits with a code that tells the caller what
|
|
1601
|
+
happened. Target selection matches `ab watch` — no slug targets every
|
|
1602
|
+
nonterminal build (builds created while waiting join; an empty or entirely
|
|
1603
|
+
terminal set keeps waiting for a build to appear), named slugs target exactly
|
|
1604
|
+
those builds, and an unknown slug is an error before waiting begins. Inside a
|
|
1605
|
+
phase, only the ambient build may be waited on.
|
|
1606
|
+
|
|
1607
|
+
At least one `--for <condition>` or `--event <glob>` is required. The condition
|
|
1608
|
+
vocabulary is closed: `blocked`, `paused`, `running`, `done`, `aborted`,
|
|
1609
|
+
`terminal` (done or aborted), `pr=open`, `pr=merged`, `pr=closed`,
|
|
1610
|
+
`pr=conflicted`, `phase=<phase>` (a core phase — `plan`, `plan-review`,
|
|
1611
|
+
`implement`, `code-review`, `finalize`, `reconcile` — or `verify:<step>`), and
|
|
1612
|
+
`attention` (any event in the `ab watch` attention set). Repeatable `--event`
|
|
1613
|
+
globs follow the `ab watch` rules. Multiple values are alternatives: the first
|
|
1614
|
+
one satisfied ends the wait — all `--for` values in supply order, then all
|
|
1615
|
+
globs in supply order — and the record names what matched.
|
|
1616
|
+
|
|
1617
|
+
A status, PR, or phase condition is satisfied when a targeted build's reduction
|
|
1618
|
+
has that state — the same projections `ab build status` reports, so `blocked`
|
|
1619
|
+
means an open escalation exactly as everywhere else. In the manner of `kubectl
|
|
1620
|
+
wait`, a condition that already holds when the command starts returns
|
|
1621
|
+
immediately with that build's latest event (`--since` does not suppress this);
|
|
1622
|
+
otherwise the first later event whose reduction produces it ends the wait.
|
|
1623
|
+
`attention` and `--event` conditions fire only on events appended after the
|
|
1624
|
+
command started, or on a cursor-resumed backlog after `--since`.
|
|
1625
|
+
|
|
1626
|
+
The record is the `ab watch` record plus a `condition` field naming the
|
|
1627
|
+
satisfied `--for` value or `--event` glob. Unlike `ab watch`, there is no
|
|
1628
|
+
trailing cursor object: the record's own `cursor` is the final position.
|
|
1629
|
+
|
|
1630
|
+
Exit codes: 0 — satisfied, exactly one record printed; 1 — usage errors, scope
|
|
1631
|
+
violations, unknown slugs, or a store that cannot be opened at start; 2 —
|
|
1632
|
+
slugs were named and every named build went terminal without satisfying any
|
|
1633
|
+
condition (one record with `condition` null, showing how the build ended); 3 —
|
|
1634
|
+
`--timeout` elapsed (30 minutes by default; `0` unbounded; with `--json`,
|
|
1635
|
+
stdout is a bare `{"cursor": …}` object for the final position, plus one line
|
|
1636
|
+
on stderr); 4 — interrupted by SIGINT before any condition was satisfied (same
|
|
1637
|
+
cursor behavior as 3). The default poll interval and `--interval` match
|
|
1638
|
+
`ab watch`. A store read that fails while waiting is reported once on stderr
|
|
1639
|
+
and retried without missing or duplicating a condition. The command is
|
|
1640
|
+
read-only — it appends no event, takes no lease, and creates no record — and
|
|
1641
|
+
deciding what to do about a match stays with the caller's skill.
|
|
1642
|
+
|
|
1643
|
+
Two recipes cover the common harnesses. First, a single-wake-up harness
|
|
1644
|
+
(Claude Code's background Bash mode re-invokes the agent exactly once, when the
|
|
1645
|
+
command exits): run `ab wait` as a background command and let the exit code
|
|
1646
|
+
plus the one record be the whole wake-up payload —
|
|
1647
|
+
|
|
1648
|
+
```sh
|
|
1649
|
+
ab wait --for blocked --json my-build # exit 0: record says why; 2: it ended; 3: re-arm
|
|
1650
|
+
```
|
|
1651
|
+
|
|
1652
|
+
Second, a plain blocking call — Codex, Pi, and the shell only expose "run a
|
|
1653
|
+
command and wait": run it in the turn and read the record when it returns.
|
|
1654
|
+
|
|
1655
|
+
The loop pattern ties the two together: wait, act on the record (answer the
|
|
1656
|
+
escalation, inspect the build, merge the PR), then wait again, passing the
|
|
1657
|
+
record's `cursor` back through `--since` so no event is ever examined twice:
|
|
1658
|
+
|
|
1659
|
+
```sh
|
|
1660
|
+
last=$(cat .ab-wait.cursor 2>/dev/null || true)
|
|
1661
|
+
record=$(ab wait --for attention --json --timeout 30m ${last:+--since "$last"} my-build)
|
|
1662
|
+
code=$?
|
|
1663
|
+
cursor=$(printf '%s' "$record" | grep -o '"cursor":"[^"]*"' | cut -d'"' -f4)
|
|
1664
|
+
[ -n "$cursor" ] && printf '%s' "$cursor" > .ab-wait.cursor
|
|
1665
|
+
# exit 0: $record names the condition in its `condition` field — act on it.
|
|
1666
|
+
# exit 2: the build ended without the condition; exit 3: deadline, re-arm.
|
|
1667
|
+
```
|
|
1668
|
+
|
|
1669
|
+
**`ab harvest status [--events N] [--json] [--store <ref>]`** projects the
|
|
1670
|
+
**`ab harvest status [--events N] [--json] [--store <ref>]`** projects the
|
|
1671
|
+
durable repository gate and an ordered collection of every unresolved failed
|
|
1672
|
+
run plus relevant open/latest context from the same journal the runner resumes.
|
|
1673
|
+
Each section distinguishes recoverable from terminal and shows that run's
|
|
1674
|
+
automatic attempts/limit, stopped step/round, attention, exact retained snapshot
|
|
1675
|
+
before exhaustion or released observation/proposal keys afterward, workflow
|
|
1676
|
+
occurrences, review rounds, and filed ticket refs. Repository event history is
|
|
1677
|
+
shown once. It is read-only and also reports an idle or paused repository with
|
|
1678
|
+
no run. The dispatch header always shows its acknowledged `harvest ON/OFF` gate
|
|
1679
|
+
and global `h` controls it. The optional, non-color-only `Harvest` row omits the
|
|
1680
|
+
internal run id and represents one deterministically selected open run or
|
|
1681
|
+
unresolved attention; its contextual `p` issues the repository-wide resume or
|
|
1682
|
+
acknowledgement, and `m` remains the build-only no-op.
|
|
1683
|
+
|
|
1684
|
+
### Status, durable progress, and lease health are separate
|
|
1685
|
+
|
|
1686
|
+
These are **three independent axes**, and reading one as another is the mistake
|
|
1687
|
+
worth avoiding.
|
|
1688
|
+
|
|
1689
|
+
**Status** is reduced from the event log (§15.5) — authoritative, and the same
|
|
1690
|
+
projection the engine itself routes on. **Durable progress** is the timestamp of
|
|
1691
|
+
the latest reduced event; its age says how long the log has been silent.
|
|
1692
|
+
**Lease health** comes from the mutable lease columns (§15.2.6), because
|
|
1693
|
+
liveness is not an event: nothing appends when a sandbox dies, so a build whose
|
|
1694
|
+
runner is gone still reduces to `running` forever. That gap is exactly why all
|
|
1695
|
+
three are reported separately.
|
|
1696
|
+
|
|
1697
|
+
`diverged` is shown only when a nonterminal build has an unexpired lease and its
|
|
1698
|
+
heartbeat is at least one hour newer than its latest event. Finished, aborted,
|
|
1699
|
+
no-lease, and expired-lease builds are never marked. The marker does not alter
|
|
1700
|
+
status, lease health, routing, or recovery.
|
|
1701
|
+
|
|
1702
|
+
| Lease | Meaning |
|
|
1703
|
+
|---|---|
|
|
1704
|
+
| `held` | A runner holds an unexpired lease. The runner is live; inspect `PROGRESS` to see when it last wrote a durable fact. |
|
|
1705
|
+
| `expired` | The lease ran out — the runner is gone. `running` + `expired` is the **stale** case: the status is not lying, it simply has no "runner died" fact to record. Re-attachment depends on the current engine decision: the lease sweep re-attaches actionable runner work, while a merged or closed PR awaits repository-level completion with no runner re-attachment pending. |
|
|
1706
|
+
| `no-lease` | **Not necessarily dead.** A build that has not yet claimed its first lease reads this way, and the lease sweep deliberately grants an absent lease a first-claim grace window before acting. A freshly launched build is the common case — read it together with progress, not alone. |
|
|
1707
|
+
|
|
1708
|
+
So `running` + `held` confirms liveness, while its progress age says whether
|
|
1709
|
+
the durable log is moving. For `running` + `expired`, inspect the build detail
|
|
1710
|
+
to distinguish actionable work that will return through the lease sweep from
|
|
1711
|
+
an ended PR awaiting repository-level completion. `no-lease` with recent
|
|
1712
|
+
progress is usually a runner still starting up.
|
|
1713
|
+
|
|
1714
|
+
## Serving the operator tools over MCP
|
|
1715
|
+
|
|
1716
|
+
`ab mcp [--store <ref>] [--repo <id>]` serves Autobuild's operator tool
|
|
1717
|
+
registry as an MCP (Model Context Protocol) server over stdio. Every tool it
|
|
1718
|
+
advertises mirrors an operator CLI command or an operator API route — same
|
|
1719
|
+
input schemas, same annotations, same results — and mutating tools attribute
|
|
1720
|
+
their writes to the sessionless operator identity (USER/USERNAME, else
|
|
1721
|
+
"dashboard"), exactly like the other operator commands. The server fails
|
|
1722
|
+
closed inside a phase session: repository-wide operator authority cannot be
|
|
1723
|
+
narrowed to the ambient build, so a complete or malformed `AB_*` phase tuple
|
|
1724
|
+
refuses to start it.
|
|
1725
|
+
|
|
1726
|
+
**The `ab` CLI stays the primary local interface** for agents that have a
|
|
1727
|
+
shell. Use `ab mcp` when a local agent cannot run commands, for dogfooding the
|
|
1728
|
+
operator tools, or for parity testing of the tool registry itself — the
|
|
1729
|
+
served tools are generated from the same closed table the CLI and operator API
|
|
1730
|
+
are measured against.
|
|
1731
|
+
|
|
1732
|
+
| Flag | Effect |
|
|
1733
|
+
|---|---|
|
|
1734
|
+
| `--store <ref>` | Override the BuildStore path or URL, same precedence as the other sessionless commands. |
|
|
1735
|
+
| `--repo <id>` | Constrain the server to one repository; calls naming any other repository are refused. |
|
|
1736
|
+
|
|
1737
|
+
Register it with Claude Code (from an Autobuild installation root):
|
|
1738
|
+
|
|
1739
|
+
```sh
|
|
1740
|
+
claude mcp add autobuild -- bun <install-root>/bin/ab.ts mcp
|
|
1741
|
+
```
|
|
1742
|
+
|
|
1743
|
+
Register it with Codex by adding a server entry to `~/.codex/config.toml`:
|
|
1744
|
+
|
|
1745
|
+
```toml
|
|
1746
|
+
[mcp_servers.autobuild]
|
|
1747
|
+
command = "bun"
|
|
1748
|
+
args = ["<install-root>/bin/ab.ts", "mcp"]
|
|
1749
|
+
```
|
|
1750
|
+
|
|
1751
|
+
The server runs until its client disconnects (stdio EOF) and needs no
|
|
1752
|
+
daemon: each client session starts one short-lived `ab mcp` process.
|
|
1753
|
+
|
|
1754
|
+
## The installed skills
|
|
1755
|
+
|
|
1756
|
+
Each is invoked as `ab-<name>`, with its editable copy at
|
|
1757
|
+
`.agents/skills/ab-<name>/SKILL.md` (read that copy, not the distribution
|
|
1758
|
+
default, when you need to know what this repo's version says).
|
|
1759
|
+
|
|
1760
|
+
| Skill | Place in the lifecycle | Purpose |
|
|
1761
|
+
|---|---|---|
|
|
1762
|
+
| `ab-spec` | Before a build exists | Design a feature spec-first through conversation, or flesh out a ticket to the spec standard. The human-interactive surface; takes a ticket, not a build slug. **Model-invocable.** |
|
|
1763
|
+
| `ab-tickets` | Before a build exists | Drive this repo's local file tracker: create a ticket, report the backlog, groom or move one between `triage/ ready/ doing/ done/`. The agent-facing surface on the tracker — use it instead of `mv`. **Model-invocable.** |
|
|
1764
|
+
| `ab-guide` | Outside the pipeline | This skill: reference for the lifecycle, config surface, setup/upgrade behavior, and the installed skills. Its editable `references/setup.md` guides the init handoff. **Model-invocable.** |
|
|
1765
|
+
| `ab-harvest` | harvest `synthesize` step | Continue the producer across review rounds: cluster the claimed structured observations and author typed spec-standard create/join/suppress proposals. Runner-only. |
|
|
1766
|
+
| `ab-harvest-review` | harvest `review` step | Fresh adversarial reviewer for proposal coverage, semantic dedup, spec quality, and evidence; returns `approve`/`revise`/`escalate`. Runner-only. |
|
|
1767
|
+
| `ab-plan` | `plan` phase | Turn the spec into a plan another agent can implement without re-deriving the reasoning. Writes no product code. |
|
|
1768
|
+
| `ab-plan-review` | `plan-review` phase | Fresh skeptic: review the plan against the spec, verdict `approve`/`revise`/`escalate`. |
|
|
1769
|
+
| `ab-implement` | `implement` phase | Execute the approved plan as local commits plus deposited notes. Never pushes. |
|
|
1770
|
+
| `ab-code-review` | `code-review` phase | Fresh skeptic: review the implementation diff against spec and plan, same verdict vocabulary. |
|
|
1771
|
+
| `ab-reconcile` | `reconcile` phase (epilogue) | Resolve a conflicted PR with one merge commit, base merged *into* the build branch. Never rebases. |
|
|
1772
|
+
| `ab-finalize` | `finalize` phase | Write the PR description for a green build; the kernel opens the PR. |
|
|
1773
|
+
|
|
1774
|
+
Autobuild installs 11 skills. Everything except `ab-spec`, `ab-tickets`, and
|
|
1775
|
+
`ab-guide` is **runner-invoked** by the kernel and carries
|
|
1776
|
+
`disable-model-invocation: true` — do not invoke a phase skill yourself, and do
|
|
1777
|
+
not remove that key to make one convenient to call. A model starting a pipeline
|
|
1778
|
+
phase by pattern-matching a description is exactly what the flag prevents. The
|
|
1779
|
+
three exceptions drive no phase, which is the criterion for membership (§16.3):
|
|
1780
|
+
`ab-spec` and `ab-tickets` are human/agent-facing pre-build surfaces, and
|
|
1781
|
+
`ab-guide` is read-only reference including the setup handoff guidance.
|