@defrex/autobuild 0.9.0 → 0.10.1

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.
Files changed (85) hide show
  1. package/README.md +97 -113
  2. package/SPEC.md +17 -4
  3. package/docs/architecture.md +109 -0
  4. package/docs/configuration.md +26 -7
  5. package/docs/remote-store-protocol.md +35 -0
  6. package/docs/website.md +4 -0
  7. package/package.json +6 -4
  8. package/packages/core/src/cli/dashboard/facts.ts +215 -0
  9. package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
  10. package/packages/core/src/cli/dashboard/model.ts +85 -147
  11. package/packages/core/src/cli/dashboard/poll.ts +7 -2
  12. package/packages/core/src/cli/dispatch-process.ts +1 -13
  13. package/packages/core/src/cli/dispatch.ts +80 -37
  14. package/packages/core/src/cli/help.ts +15 -1
  15. package/packages/core/src/cli/init-validation.ts +18 -0
  16. package/packages/core/src/cli/init.ts +3 -2
  17. package/packages/core/src/cli/installation.ts +50 -8
  18. package/packages/core/src/cli/self-update.ts +77 -2
  19. package/packages/core/src/cli/status.ts +1 -1
  20. package/packages/core/src/cli/ticket.ts +146 -1
  21. package/packages/core/src/cli/wait.ts +10 -2
  22. package/packages/core/src/cli/watch.ts +10 -2
  23. package/packages/core/src/config/live.ts +1 -0
  24. package/packages/core/src/config/pipeline-source.ts +17 -5
  25. package/packages/core/src/config/schema.ts +21 -0
  26. package/packages/core/src/integration/harness.ts +113 -1
  27. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
  28. package/packages/core/src/kernel/auto-merge-default.ts +43 -9
  29. package/packages/core/src/kernel/auto-merge.ts +99 -22
  30. package/packages/core/src/kernel/dispatch-settings.ts +42 -17
  31. package/packages/core/src/kernel/dispatch-status.ts +23 -0
  32. package/packages/core/src/kernel/engine.ts +9 -438
  33. package/packages/core/src/kernel/generators/build-log.ts +259 -0
  34. package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
  35. package/packages/core/src/kernel/generators/session-log.ts +72 -0
  36. package/packages/core/src/kernel/harvest.ts +62 -18
  37. package/packages/core/src/kernel/incremental-contract.ts +75 -0
  38. package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
  39. package/packages/core/src/kernel/incremental.ts +53 -0
  40. package/packages/core/src/kernel/log-index.ts +620 -0
  41. package/packages/core/src/kernel/pr-attachments.ts +164 -46
  42. package/packages/core/src/kernel/reducer-registry.ts +164 -0
  43. package/packages/core/src/kernel/reducer.ts +216 -74
  44. package/packages/core/src/operator/query.ts +68 -28
  45. package/packages/core/src/operator/read-view.ts +98 -0
  46. package/packages/core/src/orchestrator/prompt.ts +3 -1
  47. package/packages/core/src/ports/tickets/fake.ts +5 -1
  48. package/packages/core/src/ports/tickets/file.ts +169 -48
  49. package/packages/core/src/ports/tickets/linear.ts +33 -1
  50. package/packages/core/src/ports/tickets/operations.ts +1 -1
  51. package/packages/core/src/ports/tickets/remote.ts +8 -1
  52. package/packages/core/src/ports/types.ts +32 -1
  53. package/packages/core/src/processes/build-execution-state.ts +31 -15
  54. package/packages/core/src/processes/build-runner.ts +184 -90
  55. package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
  56. package/packages/core/src/processes/dispatcher.ts +270 -176
  57. package/packages/core/src/processes/execution-settlement.ts +72 -39
  58. package/packages/core/src/processes/harvest-execution-state.ts +41 -29
  59. package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
  60. package/packages/core/src/processes/orchestrator-tick.ts +44 -3
  61. package/packages/core/src/processes/publication-state.ts +164 -52
  62. package/packages/core/src/processes/repo-view.ts +1436 -0
  63. package/packages/core/src/processes/sandbox-state.ts +26 -2
  64. package/packages/core/src/processes/tick-state.ts +33 -0
  65. package/packages/core/src/store/adapter.ts +10 -1
  66. package/packages/core/src/store/build-scope.ts +26 -0
  67. package/packages/core/src/store/contract.ts +337 -0
  68. package/packages/core/src/store/digest.ts +71 -18
  69. package/packages/core/src/store/local/schema.ts +17 -0
  70. package/packages/core/src/store/local/store.ts +111 -4
  71. package/packages/core/src/store/memory.ts +68 -1
  72. package/packages/core/src/store/phase-session.ts +23 -0
  73. package/packages/core/src/store/remote/client.ts +50 -1
  74. package/packages/core/src/store/remote/index.ts +2 -0
  75. package/packages/core/src/store/remote/protocol.ts +15 -0
  76. package/packages/core/src/store/repo-state-events.ts +72 -7
  77. package/packages/core/src/store/session-handle.ts +30 -0
  78. package/packages/core/src/store/session-reducer.ts +52 -11
  79. package/packages/core/src/store/snapshots.ts +88 -0
  80. package/packages/core/src/store/ticket-assets.ts +41 -8
  81. package/packages/core/src/store/types.ts +38 -0
  82. package/skills/guide/SKILL.md +38 -21
  83. package/skills/guide/references/remote-store-protocol.md +35 -0
  84. package/skills/operate/SKILL.md +146 -19
  85. package/skills/tickets/SKILL.md +57 -5
package/README.md CHANGED
@@ -2,72 +2,29 @@
2
2
 
3
3
  # Autobuild
4
4
 
5
- **Tickets in, PRs out. No babysitting required.**
5
+ **Tickets in, Product out.**
6
6
 
7
7
  </div>
8
8
 
9
- Every build runs headless, so ten tickets in flight cost the same
10
- attention as one. You groom tickets in, you review pull requests out.
11
- Agents do the work, and deterministic code keeps them honest.
9
+ Autobuild runs coding agents through a fixed pipeline that plans, implements,
10
+ reviews, and verifies every ticket before it merges. You groom tickets in and
11
+ review pull requests out; agents do the work, and deterministic code keeps
12
+ them honest.
12
13
 
13
14
  ![The autobuild dispatch dashboard with four builds in flight, each partway
14
15
  through the pipeline](docs/assets/headline-wide.png)
15
16
 
16
- ## Groomed ticket in, reviewed PR out
17
-
18
- Running several coding agents by hand doesn't scale well. Every session wants
19
- prompts, permissions, and attention, and your focus becomes the ceiling.
20
- With Autobuild, each build runs the whole loop headlessly and escalates only
21
- when it is truly blocked. With attention off the critical path, throughput
22
- is only limited by how fast you can write good tickets.
23
-
24
- Headless is safe because the pipeline is deterministic code, not model
25
- judgment. State lives in a typed, append-only event log, phase transitions
26
- are owned by tested code, and every build leaves a queryable paper trail. Your
27
- merge gates are never bypassed: a PR lands when your required checks and your
28
- consent say it lands.
29
-
30
- The loop also feeds itself. While building, agents record observations —
31
- latent bugs, worthwhile refactors, follow-ups they noticed but rightly left
32
- alone. A harvester distills them into proposed tickets and files them for
33
- triage; approve one and it runs the same loop.
34
-
35
- Every seam is an adapter: ticket sources (the hosted service with database or
36
- Linear backend, direct Linear, local files, or a configured plugin), agent runtimes (Claude, Codex, Pi, or a configured plugin), the forge
37
- (GitHub via `gh`, credential-free local Git, or a configured plugin), workspaces,
38
- and the build store all sit behind
39
- narrow interfaces. Trusted Bun plugins declared in `autobuild.toml` can register
40
- third-party ticket, runtime, workspace, and forge adapters against the versioned
41
- `@defrex/autobuild/plugin-sdk` surface. Repositories may also opt into the shipped
42
- `vercel-sandbox` workspace provider: the complete build runs in an isolated
43
- Vercel VM while the local supervisor retains credentialed branch/PR publication.
44
- The default remains a local Git worktree. See the [workspace configuration and
45
- security contract](docs/configuration.md#vercel-sandbox).
46
- Teams that need one durable store across hosts
47
- can deploy the optional [hosted service](packages/hosted-store-service/README.md),
48
- which also serves a GitHub-authenticated browser dashboard, can own ticket
49
- persistence and external tracker credentials, and can run the dispatcher itself on a schedule (see [the hosted dispatcher](docs/hosted-dispatcher.md)). Repository identity across the
50
- Store, dispatcher, query commands, and that dashboard is the repository's
51
- normalized origin URL (e.g. `https://github.com/defrex/autobuild`), never a
52
- checkout path; pre-origin records are not migrated. The web UI uses a server-managed
53
- session; automation clients can use the versioned [operator API](docs/operator-api.md)
54
- for the same controls and projections without exposing service credentials, and
55
- external MCP agents connect through the [hosted MCP server](docs/mcp.md).
56
-
57
17
  ## Quickstart
58
18
 
59
19
  You need [Bun](https://bun.sh), `git`, and the local prerequisites for the
60
- agent runtime you select: Claude Code, Codex CLI, or Pi. Choosing the GitHub
61
- forge also requires an authenticated [`gh` CLI](https://cli.github.com)
62
- (`gh auth login`) or an exported `GITHUB_TOKEN`/`GH_TOKEN`; the local-git forge needs no
63
- account or remote.
64
- Init suggests only runtimes whose executable and/or provider authentication is
65
- usable on your machine.
20
+ agent runtime you select: Claude Code, Codex CLI, or Pi. The GitHub forge also
21
+ needs an authenticated [`gh` CLI](https://cli.github.com) or an exported
22
+ `GITHUB_TOKEN`/`GH_TOKEN`; the local-git forge needs no account or remote.
66
23
 
67
24
  <!-- release-install:start -->
68
25
 
69
26
  ```sh
70
- bun add -g @defrex/autobuild@0.9.0
27
+ bun add -g @defrex/autobuild@0.10.1
71
28
  ```
72
29
 
73
30
  <!-- release-install:end -->
@@ -78,44 +35,36 @@ Then, from the repository you want built:
78
35
  ab init
79
36
  ```
80
37
 
81
- Vendors the editable `ab-*` skills, writes a stack-neutral config, and hands
82
- repository-aware setup to a coding agent. Follow the canonical
83
- [installation and setup procedure](docs/setup.md) to keep local worktrees or
84
- explicitly configure Vercel, then commit the setup and check its real execution
85
- environment without creating build history:
86
-
87
- ```sh
88
- ab init --validate
89
- ```
90
-
91
- See the [configuration reference](docs/configuration.md) for every field. Only
92
- a clean readiness result should be followed by:
38
+ This vendors the editable `ab-*` skills, writes a stack-neutral config, and
39
+ hands repository-aware setup to a coding agent. The
40
+ [configuration reference](docs/configuration.md) covers every field. Then
41
+ start the dispatcher and its live dashboard:
93
42
 
94
43
  ```sh
95
44
  ab dispatch
96
45
  ```
97
46
 
98
- Starts the dispatcher, with the live dashboard on a TTY.
99
-
100
- In your selected coding agent, invoke the ticket-grooming skill:
47
+ Then groom your first ticket in your coding agent:
101
48
 
102
49
  ```text
103
- /ab-spec I want to build a feature!
50
+ /ab-spec add a retry budget
104
51
  ```
105
52
 
106
- The agent asks the questions needed to produce a quality ticket and adds it
107
- directly to your ticketing system. Using the skill to create tickets then
108
- largely becomes your input workflow.
53
+ The agent asks the questions needed to produce a buildable ticket and files it
54
+ in your tracker. From then on, that skill is how work goes in.
109
55
 
110
56
  ## How it works
111
57
 
112
- Every build moves through the same fixed pipeline:
58
+ ### Every ticket runs the pipeline
113
59
 
114
60
  ```text
115
61
  spec → plan ⇄ plan-review → implement ⇄ code-review → verify:* → finalize
116
62
  epilogue: (pr.conflicted → reconcile → verify:*)* → merged or closed
117
63
  ```
118
64
 
65
+ Each ticket moves through deterministic phases inside its own build-runner.
66
+ Every phase is a fresh agent session, with artifacts knitting them together.
67
+
119
68
  1. **spec** — the dispatcher claims a ready ticket, establishes the final
120
69
  spec, and cuts a branch. The spec is the contract for everything after.
121
70
  2. **plan ⇄ plan-review** — a planner writes an implementation plan; an
@@ -125,54 +74,88 @@ spec → plan ⇄ plan-review → implement ⇄ code-review → verify:* → fin
125
74
  you instead of looping forever.
126
75
  4. **verify:\*** — your verification steps, in the order you declare them:
127
76
  shell commands judged by exit code, or agent verifiers that return a
128
- verdict.
129
- 5. **finalize** — the PR opens with an agent-written description and a summary
130
- of explicitly attached evidence, then any post-PR steps you've configured
131
- (changelogs, release notes) run failure-tolerant. A content-producing step
132
- commits selected files locally; the runner extends the open PR branch with
133
- a regular push. A no-op adds no commit, and a publication failure becomes a
134
- follow-up observation rather than failing the green build. Agent verifiers
135
- attach an exact screenshot, trace, or other artifact with
136
- `ab artifact put <kind> <file> --attach`; the PR always gets a pinned
137
- retrieval command, and configured public image hosting can also render
138
- images inline.
77
+ verdict and can attach evidence such as screenshots.
78
+ 5. **finalize** — the PR opens with an agent-written description and the
79
+ attached evidence, then any post-PR steps you've configured (changelogs,
80
+ release notes) run.
139
81
  6. **epilogue** — the dispatcher watches the open PR. Conflicts route back
140
82
  through reconcile and re-verify; the build ends `merged` or `closed`.
141
83
 
142
- Each phase is an agent session, but the pipeline itself is not agentic:
143
- agents never decide what phase comes next, and outcomes are never inferred
144
- from what a model printed. Every phase reports through a typed CLI into an
145
- append-only event log, and tested code decides the transition. That log is
146
- the build — kill the process at any point and the dispatcher resumes from
147
- durable state, and every decision along the way stays queryable after the
148
- fact.
84
+ ### A dispatcher spawns builds
85
+
86
+ The dispatcher claims ready tickets and starts a build-runner for each.
87
+ Runners are isolated, and all state is logged. Agents never decide what phase
88
+ comes next, and outcomes are never inferred from what a model printed: every
89
+ phase reports through a typed CLI into the event log, and tested code decides
90
+ the transition. That log is the build. Kill the process at any point and the
91
+ dispatcher resumes from durable state.
149
92
 
150
93
  The pipeline grammar is fixed on purpose; `verify:*` and `finalize:*` are the
151
- extension points, declared per-repo in `autobuild.toml`. Post-step agents may
152
- commit locally but never push or call the forge; publication stays
153
- kernel-owned. For the seams and the reasoning behind them, see
154
- [`docs/architecture.md`](docs/architecture.md) and [`SPEC.md`](SPEC.md).
155
-
156
- ### Observation harvesting
157
-
158
- Builds notice things they shouldn't fix. An implementer that spots a latent
159
- bug, a worthwhile refactor, or a missing follow-up outside its spec records a
160
- structured observation (`ab observe`) and moves on — the insight is kept, and
161
- scope creep stays out of the PR.
162
-
163
- Observations accumulate per repository, and once enough pile up the
164
- dispatcher runs a separate outer workflow — scan → synthesize ⇄ review →
165
- file — that distills them into proposed tickets, deduplicated against work
166
- already filed. By default, proposals land in triage with the reserved
167
- `autobuild:proposal` label and never dispatch themselves: the label means
168
- observation harvest created the ticket for human triage, not that Autobuild has
169
- groomed or claimed it. Autobuild does not use this label as a readiness gate or
170
- remove it; it is distinct from any configured or historical `autobuild` ready
171
- label. You groom and ready proposals like any ticket you wrote yourself. Agents
172
- propose; humans dispatch.
94
+ extension points, declared per repository in `autobuild.toml`. For the seams
95
+ and the reasoning behind them, see [`docs/architecture.md`](docs/architecture.md)
96
+ and [`SPEC.md`](SPEC.md).
97
+
98
+ ## All local, all remote, or anywhere between
99
+
100
+ Every seam is an adapter you can customize for your project. The build
101
+ pipeline stays the same.
102
+
103
+ | Seam | Decides | Adapters |
104
+ | ---------- | --------------------- | ------------------------------------------ |
105
+ | tickets | where work comes from | local files, Linear, hosted, plugin |
106
+ | dispatcher | what starts builds | local process, scheduled (hosted) |
107
+ | workspace | where a build runs | git worktree, Vercel Sandbox, plugin |
108
+ | runtime | who does the thinking | Claude Code, Codex, Pi, plugin |
109
+ | forge | where code lands | local git, GitHub, plugin |
110
+ | store | where state lives | SQLite, Postgres, any HTTP store |
111
+ | operators | how you watch | terminal, web UI, MCP |
112
+
113
+ Plugins are trusted Bun modules declared in `autobuild.toml` against the
114
+ versioned `@defrex/autobuild/plugin-sdk`. Teams that want one durable store
115
+ across hosts can deploy the optional
116
+ [hosted service](packages/hosted-store-service/README.md), which adds a
117
+ browser dashboard, a [scheduled dispatcher](docs/hosted-dispatcher.md), an
118
+ [operator API](docs/operator-api.md), and an [MCP server](docs/mcp.md).
119
+
120
+
121
+ ## Built for throughput, not latency
122
+
123
+ One build may take longer than a chat with an agent. The trade is far more
124
+ changes landing every day, with much greater reliability.
125
+
126
+ - **File it and walk away.** Nothing in the pipeline waits on you. You don't
127
+ approve tool calls, paste errors back, or watch a terminal. A build
128
+ escalates to you only when it is truly blocked.
129
+ - **As many builds as you can groom.** Builds run side by side, each in its
130
+ own workspace, up to the limit you set. Your attention stops being the
131
+ ceiling; how fast you can write good tickets becomes the limit.
132
+
133
+ Headless is safe because the pipeline is deterministic code, not model
134
+ judgment. State lives in a typed, append-only event log, phase transitions
135
+ are owned by tested code, and every build leaves a queryable paper trail. Your
136
+ merge gates are never bypassed: a PR lands when your required checks and your
137
+ consent say it lands.
138
+
139
+ ## Let the queue fill itself
140
+
141
+ Once tickets are the interface, anything that can write a ticket can put work
142
+ in front of you.
143
+
144
+ Builds feed the queue themselves. An implementer that spots a latent bug, a
145
+ worthwhile refactor, or a missing follow-up outside its spec records an
146
+ observation (`ab observe`) and moves on: the insight is kept, and scope creep
147
+ stays out of the PR. Once enough pile up, the dispatcher distills them into
148
+ proposed tickets, deduplicated against work already filed, and by default
149
+ files them into triage. You groom them like any ticket you wrote yourself.
150
+ Agents propose; humans dispatch.
151
+
152
+ Support threads, meeting notes, and error telemetry can feed it the same way.
153
+ An agent with the `ab` CLI or the MCP server can file tickets and answer
154
+ routine escalations, leaving you only the real product calls.
173
155
 
174
156
  ## Learn more
175
157
 
158
+ - [autobuild.run](https://www.autobuild.run) — the project site.
176
159
  - [`docs/spec-standard.md`](docs/spec-standard.md) — what makes a ticket
177
160
  buildable: the standard every dispatched spec must meet.
178
161
  - [`docs/configuration.md`](docs/configuration.md) — the complete strict
@@ -181,7 +164,8 @@ propose; humans dispatch.
181
164
  configuration, as a worked example of the config surface.
182
165
  - [`docs/architecture.md`](docs/architecture.md) — how the design maps to the
183
166
  codebase: kernel, ports, processes, and stores.
184
- - [`packages/postgres-store/README.md`](packages/postgres-store/README.md) — serverless PostgreSQL persistence with S3-compatible or Vercel Blob storage.
167
+ - [`packages/postgres-store/README.md`](packages/postgres-store/README.md) —
168
+ serverless PostgreSQL persistence with S3-compatible or Vercel Blob storage.
185
169
  - [`docs/remote-store-protocol.md`](docs/remote-store-protocol.md) — the
186
170
  complete HTTP server contract and BuildStore conformance instructions.
187
171
  - [`SPEC.md`](SPEC.md) — the source of truth for the design and its
package/SPEC.md CHANGED
@@ -241,6 +241,16 @@ same BuildStore. Consequences, by design:
241
241
 
242
242
  - **State is a reduction of events.** Any state snapshot is a cache, never
243
243
  the source of truth. Resumability falls out.
244
+ - **Reducer snapshots are a cache the log regenerates, never an authority.**
245
+ The store may keep, per scope (one build log or the repository journal) and
246
+ reducer, that reducer's serialized state as of a cursor, keyed by scope,
247
+ reducer, reducer version, and cursor, so a process with no hot memory loads
248
+ it, reads only the newer events, and advances. A missing snapshot, one
249
+ written by a different reducer version, or one ahead of the log is ignored
250
+ and the state is rebuilt by full replay with identical results; stored
251
+ cursors never decrease; deleting every snapshot changes no observable
252
+ output. A store without snapshot storage behaves as if every snapshot were
253
+ missing.
244
254
  - **The UI layer is a subscriber** plus a command channel back. TUI, web —
245
255
  same adapter pattern. The shipped interactive terminal owns a supervised
246
256
  kernel child, and the BuildStore is their only changing-state boundary.
@@ -491,8 +501,11 @@ stream read's ([docs/remote-store-protocol.md](docs/remote-store-protocol.md),
491
501
 
492
502
  #### 7.1.2 The embedded orchestrator (turns)
493
503
 
494
- The orchestrator is the agent that files tickets, watches builds, unblocks
495
- them, and discusses direction — played in the hosted service by a stateless
504
+ The orchestrator is a role, not a deployment: the agent that files tickets,
505
+ watches builds, unblocks them, and discusses direction. Any agent can take it
506
+ on — a coding-agent session at the repository root using the `ab` CLI or
507
+ `ab mcp` needs no initialization — and the canonical `ab-operate` skill is its
508
+ manual in either setting. In the hosted service it is played by a stateless
496
509
  turn runner over the operator tool registry, not by an agent living in a
497
510
  sandbox (parity with MCP; no credential plumbing) and not by an external
498
511
  workflow engine (durability comes from the store; the tick pattern already
@@ -2413,8 +2426,8 @@ installs into a repo:
2413
2426
  non-agent-invocable: they are invoked explicitly by the runner or a human,
2414
2427
  never auto-triggered by a model pattern-matching a description — a model
2415
2428
  must not start a pipeline phase by accident. The model-invocable exceptions
2416
- (`ab-spec`, `ab-tickets`, `ab-guide`) are exactly the three skills that
2417
- **drive no phase**; membership is decided by that criterion, not taste.
2429
+ (`ab-spec`, `ab-tickets`, `ab-guide`, `ab-operate`) are exactly the four
2430
+ skills that **drive no phase**; membership is decided by that criterion, not taste.
2418
2431
 
2419
2432
  When `.agents/skills` and `.claude/skills` resolve to the same filesystem
2420
2433
  entry, that repository-level alias already satisfies harness discovery. Init
@@ -131,6 +131,115 @@ session's status, open turn, pending approval, and wake cursors from its own
131
131
  closed catalog. No decision anywhere
132
132
  consults a snapshot in place of the append-only log.
133
133
 
134
+ **Incremental reducers.** Every pure projection of an event array is a fold plus
135
+ a final derivation, defined once with `defineReducer` in
136
+ `packages/core/src/kernel/incremental.ts`: `initial()` builds an empty
137
+ accumulator, `advance(acc, events)` folds in only the events newer than the
138
+ accumulator, and `finish(acc)` derives the public state. The whole-array
139
+ functions (`reduceBuild`, `reduceHarvest`, `reduceSession`, and the rest) are
140
+ wrappers over the same fold, so results cannot diverge, and a property test
141
+ checks every split point of fixture and generated logs. The accumulator is plain
142
+ JSON, so it survives a serialize-and-parse round trip. `advance` never mutates
143
+ its input: it clones, then folds, so a state derived earlier is never disturbed
144
+ by a later advance. Each reducer publishes a `version`; bump it whenever the
145
+ accumulator shape or the fold semantics change, because a cached accumulator is
146
+ only valid for the version that wrote it. `kernel/reducer-registry.ts` lists
147
+ every reducer with its version, and its snapshot test fails when an accumulator
148
+ shape changes without a bump. Most callers still read whole logs (the terminal
149
+ dashboard poll advances a carried build accumulator by per-build deltas; the
150
+ dispatcher view below restores from stored accumulators).
151
+
152
+ **Reducer snapshots.** The `BuildStore` port can keep, per scope (one build log
153
+ or the repository journal) and registered reducer, that reducer's accumulator as
154
+ of a cursor: `getReducerSnapshot(scope, reducer, version)` and
155
+ `putReducerSnapshot(scope, reducer, {version, cursor, state})`
156
+ (`packages/core/src/store/snapshots.ts`). The log stays the only authority. The
157
+ store enforces the invariants: the stored cursor never decreases (a write whose
158
+ cursor does not advance is a no-op, so concurrent writers cannot undo each
159
+ other), a row of another reducer version reads as absent and is replaced once a
160
+ writer advances past it, and a cursor beyond the log's tail is refused and never
161
+ returned. Every consumer treats a missing, mismatched, ahead-of-log, malformed
162
+ or unreadable snapshot as a miss and replays the log, with identical results;
163
+ deleting all snapshots changes no observable output (the integration harness
164
+ audits this for every scenario at cleanup). Memory, SQLite, Postgres and the
165
+ remote client implement it, and the hosted store service serves it on
166
+ `GET|PUT /builds|repos/{id}/snapshots/{reducer}`
167
+ (`docs/remote-store-protocol.md`). SQLite creates the `reducer_snapshots` table
168
+ at open. Postgres keeps it outside the versioned schema: `reducer_snapshots` is
169
+ created by the idempotent `SNAPSHOT_SCHEMA_DDL` in `migratePostgres`, with no
170
+ marker, version or checksum change, so a deployment at the current schema keeps
171
+ serving across the upgrade, a new client against a database that lacks the table
172
+ reads every snapshot as missing and replays (SQLSTATE `42P01` is never an
173
+ error), and an older client never asks. Run `bun run postgres:migrate` once to
174
+ gain the table; until then the dispatcher and every other reader replay in full.
175
+
176
+ **Dispatcher repository view.** A dispatcher process builds its view of the
177
+ repository once and refreshes it by deltas
178
+ (`packages/core/src/processes/repo-view.ts`). `RepoViewStore` decorates the
179
+ `BuildStore` port: the first `refresh()` reads the journal's high-water mark
180
+ (`getRepoHighWater`), the bounded journal subset, the build listing and digests,
181
+ and the full log of every build that still has work (the same predicate the tick
182
+ loops use); every later `refresh()` reads only events newer than its cursors —
183
+ one journal delta, one delta per held build, plus the listing and digest reads
184
+ that discover new builds. Every tick stage reads and writes through the view, so
185
+ an event a stage appends is folded in at once and later stages in the same tick
186
+ see it with no re-read; a foreign append interleaved with ours leaves a seq gap
187
+ that marks the log dirty, a lost compare-and-set does the same, and any delta
188
+ that is not contiguous from its cursor discards the log and re-reads it in full,
189
+ so no event is skipped. Every state transition of one source (a build, the
190
+ journal) runs on that source's single promise chain — snapshot and delta reads,
191
+ window opening and widening, the fold after an own append, a lost
192
+ compare-and-set's invalidation — and readers queue on it too, so overlapping
193
+ readers join the repair in flight instead of observing half-applied state.
194
+ Decisions that deliberately re-read at the last moment
195
+ (`refreshBuild`) take an explicit delta. Cursor-bearing readers such as the
196
+ orchestrator wake pass get per-source windows that hold only events above the
197
+ lowest session cursor. A `--once` invocation builds the view cold and reads each
198
+ log once; a long-running dispatcher pays that replay at its first tick and not
199
+ again.
200
+
201
+ The view restores from reducer snapshots on cold start. The journal comes from
202
+ the `journalView` reducer (the journal pruned on arrival: durable types plus the
203
+ tail from the latest `dispatcher.run-started`); each work build comes from one
204
+ `work` snapshot row — the `build`, `logIndex`, `openExecution`,
205
+ `openBuildWorkspace`, `publicationState`, `lastExecutionOutcome`, `buildDigest`,
206
+ `dashboardFacts` and `currentDeferralObservation` accumulators at a single
207
+ cursor. One row is one atomic write on every adapter, so a reader in another
208
+ process sees the old bundle or the new, never a mix; the bundle version is
209
+ derived from the component reducer versions, so a stale bundle misses and the
210
+ build is replayed once. The view
211
+ reads only the events newer than that cursor and holds, for a snapshot-backed
212
+ build, just that delta plus the reduced state of everything before it, so a tick
213
+ with no new events reads zero event rows (apart from the discovery listing and
214
+ digests) and one with M new events reads exactly M. Tick stages that run for every
215
+ build each tick take `buildFacts(slug)` (state, log index, open execution, open
216
+ workspace, publication view) instead of the event array, and read raw history
217
+ only on the branches that act on it. `persistSnapshots()` writes the advanced
218
+ state back at the end of each tick and on shutdown — best effort, throttled in a
219
+ resident dispatcher, always run by a cold single-tick process — and never writes
220
+ a cursor that was not read from the log.
221
+
222
+ **Operator reads.** The web dashboard, the repository and Harvest status
223
+ routes, the operator build listings, and the `ab watch`/`ab wait` discovery
224
+ passes derive from that same view instead of replaying the log
225
+ (`packages/core/src/operator/read-view.ts`). A dashboard row renders from the
226
+ `BuildState` plus the `dashboardFacts` reducer
227
+ (`packages/core/src/cli/dashboard/facts.ts`: queued-row dispatch text, session
228
+ brackets, phase intervals, pause time, and the per-round loop-output seqs) and
229
+ the auto-merge deferral ledger; `projectBuild(…, events)` is the same projection
230
+ over the whole-array reduction, so the snapshot and replay paths cannot differ.
231
+ Within one serving process, passes are single-flight per repository: requests
232
+ arriving while a pass runs join the one trailing pass, which restores from the
233
+ snapshot the previous pass persisted. A poll at idle therefore reads no event
234
+ rows beyond snapshot lookups and build discovery, and a poll after M new events
235
+ reads those M rows once, however many tabs or watchers the process serves.
236
+ Serving processes do not coordinate: with P of them, each may read the same M
237
+ rows once, so duplicate reads are bounded by P and never by history, tabs, or
238
+ watchers. A settled build listed under `--all` is restored from the snapshot the
239
+ dispatcher left at settlement, or read once and then persisted. The first
240
+ operator poll after upgrading replays each work build once, because the stored
241
+ bundle layout changed.
242
+
134
243
  **Session Store authority.** `packages/core/src/cli/binary.ts` validates a complete build or
135
244
  Harvest ambient tuple before opening its phase Store through
136
245
  `packages/core/src/cli/store-opening.ts`. `packages/core/src/cli/env.ts` also classifies optional ambient
@@ -202,7 +202,7 @@ These fields hot-reload:
202
202
  | `[workspace]` provider and config (including `provisioning`) | Next launch of a build whose OWN branch carries it. A base-branch reload never re-provisions an in-flight build. |
203
203
  | `[roles]` | Next agent invocation for that role. Runtime, model, args, alternates, and the session budget are captured together and delivered live. |
204
204
  | `[policy]` | Next policy, convergence, or agent-session boundary, delivered live. A running session keeps its captured budget. |
205
- | `tickets.readyLabels`, `tickets.readyState` | Next ready-ticket scan. |
205
+ | `tickets.readyLabels`, `tickets.readyState`, `tickets.readyAssignee` | Next ready-ticket scan. |
206
206
  | `tickets.triageState`, `tickets.proposalState` | Next dispatcher handback or harvest filing boundary. |
207
207
 
208
208
  Adapter selectors and factory inputs are constructed once and require a
@@ -1167,6 +1167,7 @@ state. Within a present table, `source` and `readyState` are required.
1167
1167
  | `source` | — | required nonblank builtin or plugin registration name | Select the ticket adapter after configured plugins load. |
1168
1168
  | `readyLabels` | source-aware | optional array of nonempty strings; `[]` allowed | Require every listed label in addition to the state gate. |
1169
1169
  | `readyState` | — | required nonblank string | The one workflow state eligible for dispatch. |
1170
+ | `readyAssignee` | — | optional; `"me"` or an email address; Linear only (rejected for file, hosted, and plugin sources) | Require the ticket to be assigned to one Linear user, in addition to the state and label gates. |
1170
1171
  | `teamKey` | — | required for Linear and hosted; forbidden for file; optional for plugins | Backend team key such as `"ENG"`, or an existing plugin config field. |
1171
1172
  | `claimedState` | `"In Progress"` for direct Linear; hosted backend default otherwise | optional nonempty string; forbidden for file; allowed for plugins | Workflow state entered when a ticket is claimed. |
1172
1173
  | `createState` | provider default | optional nonempty string | Default state for newly created tickets when `ab ticket create` omits `--state`. |
@@ -1174,13 +1175,33 @@ state. Within a present table, `source` and `readyState` are required.
1174
1175
  | `proposalState` | the resolved `triageState` | optional nonempty string | State harvest files its synthesized proposals into. Setting it to `readyState` waives the human grooming gate, not dependency eligibility. |
1175
1176
  | `dir` | file: selected local state root's `tickets/`; plugin: omitted | optional nonempty path; forbidden for Linear; allowed for plugins | Root containing file-source state directories, or an existing plugin config field. Relative file paths resolve from the repository. |
1176
1177
 
1178
+ `readyAssignee = "me"` resolves to the Linear user who owns `LINEAR_API_KEY`,
1179
+ so one committed value makes each person's local dispatcher take only the work
1180
+ assigned to them; an email names a specific user, for a shared or bot key. The
1181
+ gate is conjunctive with `readyState` and `readyLabels`, and a ticket with no
1182
+ assignee is never ready while it is set. It applies to every ready surface
1183
+ (dispatch, the queued count, and `ab ticket list` without filters); blockers
1184
+ still prevent claim. It is empty or blank, or neither `"me"` nor an email,
1185
+ fails validation. `ab init --validate` prints the user the gate resolves to and
1186
+ fails when an email matches no workspace user. The field reloads without a
1187
+ restart.
1188
+
1177
1189
  When `readyLabels` is absent, direct Linear uses `["autobuild"]`; hosted, file,
1178
1190
  and plugin sources use `[]`, meaning no host-imposed label gate. An explicit value always
1179
1191
  wins. A nonempty list is
1180
1192
  conjunctive: every configured label must be present. `readyState` remains
1181
1193
  mandatory regardless of labels. Linear compares state and label names exactly
1182
- and case-sensitively. The file source accepts state names case-insensitively and
1183
- canonicalizes them to `triage/`, `ready/`, `doing/`, or `done/`.
1194
+ and case-sensitively. The file source matches state names case-insensitively against the directories
1195
+ under the tracker root: the four lifecycle directories (`triage/`, `ready/`,
1196
+ `doing/`, `done/`, always present) plus any directory the repository adds, such
1197
+ as `icebox/`. A name matching no directory is an error listing those that
1198
+ exist, so creating a state stays an explicit act: `ab ticket state create <name>
1199
+ [--about <text>]` makes one, and `ab ticket states` lists every state with its
1200
+ roles, ticket count, and purpose. A purpose is stored as a one-line `.about` file
1201
+ inside the state directory, so it travels with a git-tracked `dir` and is never
1202
+ read as a ticket. `createState`, `readyState`,
1203
+ `triageState`, and `proposalState` may each name any existing directory;
1204
+ `ab ticket list` with no filter still lists only `readyState`.
1184
1205
 
1185
1206
  Source-specific validation is strict:
1186
1207
 
@@ -1551,10 +1572,8 @@ pre-attempt index and reports the original Git failure; merged worktree files
1551
1572
  remain in place and the merge's exit status is unchanged. Upgrade never pushes
1552
1573
  or rewrites history.
1553
1574
 
1554
- Autobuild now installs 12 skills; only `ab-spec`, `ab-tickets`, and `ab-guide`
1555
- are model-invocable. (`ab-operate` — the embedded orchestrator's operating
1556
- manual — also carries `disable-model-invocation: true`; it is read by the
1557
- hosted turn runner, never invoked by a model in a build session.) The setup reference is an ordinary support file in the
1575
+ Autobuild now installs 12 skills; only `ab-spec`, `ab-tickets`, `ab-guide`,
1576
+ and `ab-operate` are model-invocable. The setup reference is an ordinary support file in the
1558
1577
  editable/pristine `ab-guide` tree and participates in the same three-way
1559
1578
  upgrade merge as every other vendored file.
1560
1579
 
@@ -297,6 +297,8 @@ it to exist. An unknown build returns `404 not-found`.
297
297
  | `appendIfCurrent` | `POST /builds/{slug}/events/conditional` | `{"expectedSeq": nonnegative integer, "event": event write}` | appended: `201` + build event envelope; stream advanced: `200 null` |
298
298
  | `getEvents` | `GET /builds/{slug}/events?since={n}&wait={s}` | optional `since` query value, parsed below; absence defaults to `0`. Optional `wait` in whole seconds, parsed per the event-wait rules below | `200` + envelopes whose `seq` is strictly greater than parsed `since`, in increasing sequence order |
299
299
  | `appendWithArtifacts` | `POST /builds/{slug}/deposits` | atomic deposit request | `201` + `{event, artifacts}` |
300
+ | `getReducerSnapshot` | `GET /builds/{slug}/snapshots/{reducer}?version={n}` | required `version` query value, a positive integer; `reducer` is a registry name (`^[A-Za-z][A-Za-z0-9._-]{0,63}$`) | `200` + `{"version", "cursor", "state"}` or `200 null` when absent, written by another reducer version, or ahead of the build log's tail; an unknown build is `404`, which the shipped client also maps to `null` |
301
+ | `putReducerSnapshot` | `PUT /builds/{slug}/snapshots/{reducer}` | `{"version": int >= 1, "cursor": int >= 0, "state": JSON}`, at most 16 MiB (`413` above) | `200 {"written": boolean}`; the stored cursor never decreases, so a write at an equal or lower cursor (or an equal cursor at an equal or lower version) and one past the log's tail answer `written: false` |
300
302
  | `putArtifact` | `POST /builds/{slug}/artifacts` | artifact input | `201` + build artifact metadata |
301
303
  | `getArtifact` | `GET /builds/{slug}/artifacts?kind={kind}&rev={n}` | `kind` is required and nonempty; optional `rev` is parsed below | `200` + artifact read; an absent `rev` parameter selects the latest revision; a missing kind/revision is `200 null` |
302
304
  | `listArtifacts` | `GET /builds/{slug}/artifact-list?kind={kind}` | optional `kind`; absence means all kinds | `200` + metadata ordered by kind and then increasing revision |
@@ -336,6 +338,32 @@ part of this protocol. Retention is enforced server-side inside the store at
336
338
  deposit time (bounded pruning of dispatcher run/config artifact revisions),
337
339
  not through any endpoint.
338
340
 
341
+ ### Reducer snapshots
342
+
343
+ A reducer snapshot is one registered reducer's serialized state as of a log
344
+ cursor, kept per scope (one build log or one repository journal) and reducer
345
+ name. It is a cache the log regenerates, never an authority: a process with no
346
+ hot memory loads it, reads only the events newer than its cursor, advances the
347
+ reducer, and writes the advanced state back, and deleting every snapshot changes
348
+ no observable output. The routes above and in section 4 are additive and carry
349
+ no protocol-version bump. The store, not the client, enforces the invariants:
350
+
351
+ - the stored cursor never decreases, whatever the versions of the competing
352
+ writers; a write succeeds only when no row exists, when its cursor is above the
353
+ stored one, or when its cursor equals the stored one and its version is higher
354
+ (an upgraded reducer replacing the old shape at an unchanged tail);
355
+ - a read answers `null` for a row whose version differs from the requested one,
356
+ and for one whose cursor is beyond the scope's log tail; a write beyond the
357
+ tail is refused;
358
+ - an unknown build or repository answers `null` (read) or `written: false`
359
+ (write) and never creates the scope.
360
+
361
+ A client treats `404` and `405` from either route as "no snapshot", so a client
362
+ newer than its store replays the log with no error, and a client older than its
363
+ store never calls the routes. Authentication and transport failures still
364
+ surface normally. A build token may touch only its own build's snapshots and a
365
+ repository token only its own repository's, exactly as for events.
366
+
339
367
  ### The event-read bounded wait
340
368
 
341
369
  Both event reads (`getEvents` and, symmetrically, the `getRepoEvents` read of
@@ -382,6 +410,8 @@ without a journal record, or a repository without builds.
382
410
  | `appendRepo` | `POST /repos/{repo}/events` | event write | `201` + repository event envelope |
383
411
  | `getRepoEvents` | `GET /repos/{repo}/events?since={n}&wait={s}` | optional `since` query value, parsed as in section 3; absence defaults to `0`. Optional `wait` in whole seconds, per the event-wait rules of section 3 | `200` + envelopes with `seq >` parsed `since`, in increasing sequence order |
384
412
  | `getRepoStateEvents` | `GET /repos/{repo}/state-events` | none | `200` + the bounded repository-journal subset (AUT-489): every durable event type (`harvest.*`, `orchestrator.sandbox.*`, the three dispatcher setting types) plus, when the journal has one, the tail from the latest `dispatcher.run-started`, in increasing sequence order; served inside the repository-existence gate like `GET events`, so an unknown repository is `404` |
413
+ | `getReducerSnapshot` (repo scope) | `GET /repos/{repo}/snapshots/{reducer}?version={n}` | required `version` query value, a positive integer; `reducer` is a registry name (`^[A-Za-z][A-Za-z0-9._-]{0,63}$`) | `200` + `{"version", "cursor", "state"}` or `200 null` when absent, written by another reducer version, ahead of the journal's tail, or the repository has no journal record; an unknown repository is `404`, which the shipped client also maps to `null` |
414
+ | `putReducerSnapshot` (repo scope) | `PUT /repos/{repo}/snapshots/{reducer}` | `{"version": int >= 1, "cursor": int >= 0, "state": JSON}`, at most 16 MiB (`413` above) | `200 {"written": boolean}`; the stored cursor never decreases, so a write at an equal or lower cursor (or an equal cursor at an equal or lower version) and one past the journal's tail answer `written: false` |
385
415
  | `appendRepoWithArtifacts` | `POST /repos/{repo}/deposits` | atomic deposit request | `201` + `{event, artifacts}` using repository shapes |
386
416
  | `putRepoArtifact` | `POST /repos/{repo}/artifacts` | artifact input | `201` + repository artifact metadata |
387
417
  | `getRepoArtifact` | `GET /repos/{repo}/artifacts?kind={kind}&rev={n}` | required nonempty `kind`; optional `rev` is parsed as in section 3 | `200` + artifact read; latest only when the `rev` parameter is absent; missing kind/revision is `200 null` |
@@ -834,6 +864,11 @@ resource access; an explicit resource whose id is `"*"` is not admin.
834
864
  | repo | no | no | no | exact id only | yes | exact id only | exact id only | exact repo only | no |
835
865
  | session | no | no | no | no | no | no | no | no | exact id only |
836
866
 
867
+ The snapshot routes (`/builds/{slug}/snapshots/{reducer}` and
868
+ `/repos/{repo}/snapshots/{reducer}`) follow the one-matching-build and
869
+ one-matching-repo columns: a build token reads and writes only its own build's
870
+ snapshots, a repo token only its own repository's.
871
+
837
872
  A valid token used for the wrong resource receives `403 auth`. Resource scope
838
873
  gates all operations, including reads, artifact operations, and leases. A
839
874
  session resource token's authority is exactly its one session — it cannot
package/docs/website.md CHANGED
@@ -5,6 +5,10 @@ The marketing site is one static page in `packages/website`, reproduced from the
5
5
  - `bun run website:dev` serves it on port 3300 (`PORT` overrides), rebuilding on every request.
6
6
  - `bun run website:build` writes `packages/website/dist/` (`index.html`, `site.css`, `site.js`).
7
7
 
8
+ The hero is the real `ab dispatch` dashboard: `packages/website/src/hero-frame.txt` holds the exact colored lines the terminal dashboard painted for the scripted `website-hero` capture, and the page renders them as preformatted HTML with each terminal color mapped to its design token. `bun run capture:website-hero` regenerates the file from the capture, and `bun run check` fails when it is stale.
9
+
10
+ Shared links preview through Open Graph and Twitter card tags in the page head, pointing at `og.png`, a 1200×630 card drawn from the same frame in the site's own styles. `bun run capture:website-og` screenshots it with a local Chromium into `packages/website/src/og.png`; rerun it after changing the headline, the hero frame, or the styles, and look at the result, since font rasterization differs between machines and nothing checks the bytes. The canonical origin every absolute URL is built from is `SITE_URL` in `packages/website/src/constants.ts`.
11
+
8
12
  `dist/` is plain static files: any static host can serve it with no server runtime, credentials, or environment variables. On Vercel, set the root directory to `packages/website`, the build command to `bun run build`, and the output directory to `dist`.
9
13
 
10
14
  The page loads one webfont from Google Fonts and makes no other third-party requests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defrex/autobuild",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Agent-driven software delivery from groomed ticket to merged PR",
5
5
  "repository": {
6
6
  "type": "git",
@@ -117,9 +117,11 @@
117
117
  "website:dev": "bun packages/website/src/dev.ts",
118
118
  "website:build": "bun packages/website/src/build.ts",
119
119
  "capture:readme-headline": "bun tools/readme-headline.ts",
120
+ "capture:website-hero": "bun tools/website-hero.ts",
121
+ "capture:website-og": "bun tools/website-og.ts",
120
122
  "release": "bun tools/release.ts",
121
123
  "postgres:migrate": "bun packages/postgres-store/src/bin.ts migrate",
122
- "check": "bun tools/biome-check.ts && bun tools/product-name-check.ts && bun tools/docs-asset-check.ts && bun tools/skill-docs-asset-check.ts && bun tools/readme-headline.ts --check && bun tools/workspace-manifest-check.ts && bun tools/package-boundary-check.ts && bun tools/bin-mode-check.ts && bun tools/publish-contents-check.ts && bun tools/publish-imports-check.ts",
124
+ "check": "bun tools/biome-check.ts && bun tools/product-name-check.ts && bun tools/docs-asset-check.ts && bun tools/skill-docs-asset-check.ts && bun tools/readme-headline.ts --check && bun tools/website-hero.ts --check && bun tools/workspace-manifest-check.ts && bun tools/package-boundary-check.ts && bun tools/bin-mode-check.ts && bun tools/publish-contents-check.ts && bun tools/publish-imports-check.ts",
123
125
  "format:staged": "git-format-staged --formatter './node_modules/.bin/biome format --stdin-file-path {}' '*.js' '*.jsx' '*.ts' '*.tsx' '*.json' '*.jsonc' '*.css' '*.graphql' '*.gql'",
124
126
  "prepare": "husky",
125
127
  "test": "bun test packages/*/src tools bin packages/hosted-store-service/app",
@@ -137,8 +139,8 @@
137
139
  },
138
140
  "devDependencies": {
139
141
  "@biomejs/biome": "2.5.5",
140
- "@defrex/autobuild-postgres-store": "0.8.0",
141
- "@defrex/autobuild-vercel-sandbox": "0.8.0",
142
+ "@defrex/autobuild-postgres-store": "0.9.0",
143
+ "@defrex/autobuild-vercel-sandbox": "0.9.0",
142
144
  "@resvg/resvg-js": "2.6.2",
143
145
  "@types/bun": "^1.3.14",
144
146
  "@types/node": "^24.0.0",