@defrex/autobuild 0.8.0 → 0.10.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/README.md +1 -1
- package/SPEC.md +323 -26
- package/bin/ab-build-runner.ts +8 -2
- package/docs/architecture.md +166 -14
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +175 -28
- package/docs/hosted-dispatcher.md +69 -13
- package/docs/operator-api.md +2 -2
- package/docs/remote-store-protocol.md +204 -16
- package/docs/remote-ticket-protocol.md +1 -1
- package/docs/setup.md +12 -9
- package/docs/website.md +14 -0
- package/package.json +45 -23
- package/packages/core/src/cli/artifact.ts +130 -6
- package/packages/core/src/cli/binary.ts +9 -1
- package/packages/core/src/cli/build-control.ts +110 -13
- package/packages/core/src/cli/context.ts +46 -0
- package/packages/core/src/cli/dashboard/facts.ts +215 -0
- package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
- package/packages/core/src/cli/dashboard/model.ts +141 -153
- package/packages/core/src/cli/dashboard/poll.ts +76 -12
- package/packages/core/src/cli/dashboard/render.ts +34 -5
- package/packages/core/src/cli/dispatch-frontend.ts +54 -5
- package/packages/core/src/cli/dispatch-process.ts +1 -13
- package/packages/core/src/cli/dispatch.ts +392 -75
- package/packages/core/src/cli/harvest.ts +122 -8
- package/packages/core/src/cli/help.ts +62 -9
- package/packages/core/src/cli/init-readiness-remote.ts +110 -0
- package/packages/core/src/cli/init-readiness-shared.ts +72 -0
- package/packages/core/src/cli/init-validation.ts +266 -222
- package/packages/core/src/cli/installation.ts +50 -8
- package/packages/core/src/cli/main.ts +70 -11
- package/packages/core/src/cli/mcp.ts +23 -1
- package/packages/core/src/cli/plugin.ts +7 -4
- package/packages/core/src/cli/remote-poll.ts +277 -0
- package/packages/core/src/cli/repo-state.ts +22 -0
- package/packages/core/src/cli/repository-status.ts +39 -1
- package/packages/core/src/cli/self-update.ts +77 -2
- package/packages/core/src/cli/status.ts +150 -28
- package/packages/core/src/cli/terminals.ts +2 -1
- package/packages/core/src/cli/ticket-assets.ts +247 -0
- package/packages/core/src/cli/ticket.ts +453 -8
- package/packages/core/src/cli/wait.ts +157 -41
- package/packages/core/src/cli/watch.ts +138 -168
- package/packages/core/src/config/live.ts +23 -0
- package/packages/core/src/config/pipeline-source.ts +388 -0
- package/packages/core/src/config/schema.ts +251 -19
- package/packages/core/src/events/catalog.ts +1 -0
- package/packages/core/src/events/globs.ts +47 -0
- package/packages/core/src/events/payloads.ts +52 -2
- package/packages/core/src/events/repository.ts +52 -1
- package/packages/core/src/events/sessions.ts +69 -10
- package/packages/core/src/events/workspace-remote.ts +12 -0
- package/packages/core/src/integration/harness.ts +115 -2
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
- package/packages/core/src/kernel/auto-merge-default.ts +157 -0
- package/packages/core/src/kernel/auto-merge.ts +99 -22
- package/packages/core/src/kernel/dispatch-settings.ts +42 -17
- package/packages/core/src/kernel/dispatch-status.ts +23 -0
- package/packages/core/src/kernel/engine.ts +9 -438
- package/packages/core/src/kernel/generators/build-log.ts +259 -0
- package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
- package/packages/core/src/kernel/generators/session-log.ts +72 -0
- package/packages/core/src/kernel/harvest.ts +62 -18
- package/packages/core/src/kernel/incremental-contract.ts +75 -0
- package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
- package/packages/core/src/kernel/incremental.ts +53 -0
- package/packages/core/src/kernel/log-index.ts +620 -0
- package/packages/core/src/kernel/phases.ts +16 -2
- package/packages/core/src/kernel/pr-attachments.ts +164 -46
- package/packages/core/src/kernel/reducer-registry.ts +164 -0
- package/packages/core/src/kernel/reducer.ts +254 -73
- package/packages/core/src/operator/annotations.ts +6 -0
- package/packages/core/src/operator/control.ts +19 -5
- package/packages/core/src/operator/index.ts +71 -6
- package/packages/core/src/operator/query.ts +253 -24
- package/packages/core/src/operator/read-view.ts +98 -0
- package/packages/core/src/operator/registry.ts +55 -10
- package/packages/core/src/operator/requests.ts +9 -6
- package/packages/core/src/operator/sandbox-host.ts +140 -0
- package/packages/core/src/operator/sandbox.ts +247 -14
- package/packages/core/src/operator/ticket-source-backend.ts +47 -0
- package/packages/core/src/orchestrator/approvals.ts +115 -0
- package/packages/core/src/orchestrator/conversation.ts +167 -0
- package/packages/core/src/orchestrator/model.ts +86 -0
- package/packages/core/src/orchestrator/prompt.ts +61 -0
- package/packages/core/src/orchestrator/tools.ts +117 -0
- package/packages/core/src/orchestrator/turn-runner.ts +457 -0
- package/packages/core/src/plugin-sdk/index.ts +51 -1
- package/packages/core/src/plugins/load.ts +135 -7
- package/packages/core/src/plugins/manifest.ts +183 -10
- package/packages/core/src/plugins/registry.ts +73 -4
- package/packages/core/src/ports/forge/create.ts +1 -1
- package/packages/core/src/ports/forge/fake.ts +31 -0
- package/packages/core/src/ports/forge/github-transport.ts +13 -2
- package/packages/core/src/ports/forge/github.ts +13 -2
- package/packages/core/src/ports/forge/local-git.ts +31 -1
- package/packages/core/src/ports/runner/claude.ts +2 -2
- package/packages/core/src/ports/runner/codex.ts +6 -2
- package/packages/core/src/ports/runner/fake.ts +7 -1
- package/packages/core/src/ports/runner/pi.ts +8 -6
- package/packages/core/src/ports/runner/provider-error.ts +13 -3
- package/packages/core/src/ports/tickets/fake.ts +5 -1
- package/packages/core/src/ports/tickets/file.ts +169 -48
- package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
- package/packages/core/src/ports/tickets/linear.ts +33 -1
- package/packages/core/src/ports/tickets/operations.ts +1 -1
- package/packages/core/src/ports/tickets/remote.ts +8 -1
- package/packages/core/src/ports/types.ts +49 -3
- package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
- package/packages/core/src/ports/workspace/create.ts +74 -39
- package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
- package/packages/core/src/ports/workspace/fake.ts +169 -4
- package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
- package/packages/core/src/ports/workspace/github-origin.ts +36 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
- package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
- package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
- package/packages/core/src/processes/build-child.ts +17 -2
- package/packages/core/src/processes/build-execution-state.ts +68 -15
- package/packages/core/src/processes/build-runner.ts +238 -93
- package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
- package/packages/core/src/processes/dispatcher.ts +501 -201
- package/packages/core/src/processes/execution-settlement.ts +72 -39
- package/packages/core/src/processes/harvest-child.ts +5 -0
- package/packages/core/src/processes/harvest-execution-state.ts +41 -29
- package/packages/core/src/processes/harvest-runner.ts +66 -19
- package/packages/core/src/processes/harvest.ts +196 -24
- package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
- package/packages/core/src/processes/orchestrator-tick.ts +360 -0
- package/packages/core/src/processes/publication-state.ts +164 -52
- package/packages/core/src/processes/repo-view.ts +1436 -0
- package/packages/core/src/processes/sandbox-state.ts +36 -2
- package/packages/core/src/processes/tick-state.ts +33 -0
- package/packages/core/src/store/adapter.ts +41 -0
- package/packages/core/src/store/build-scope.ts +90 -0
- package/packages/core/src/store/contract.ts +1205 -0
- package/packages/core/src/store/digest.ts +102 -0
- package/packages/core/src/store/local/schema.ts +46 -2
- package/packages/core/src/store/local/store.ts +445 -4
- package/packages/core/src/store/memory.ts +246 -0
- package/packages/core/src/store/new-build.ts +17 -0
- package/packages/core/src/store/phase-session.ts +94 -0
- package/packages/core/src/store/remote/client.ts +310 -2
- package/packages/core/src/store/remote/index.ts +14 -2
- package/packages/core/src/store/remote/protocol.ts +115 -12
- package/packages/core/src/store/remote/version.ts +6 -2
- package/packages/core/src/store/repo-state-events.ts +205 -0
- package/packages/core/src/store/retention.ts +7 -0
- package/packages/core/src/store/session-handle.ts +94 -0
- package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +79 -19
- package/packages/core/src/store/snapshots.ts +88 -0
- package/packages/core/src/store/streams/session-writer.ts +42 -2
- package/packages/core/src/store/streams/wait.ts +4 -5
- package/packages/core/src/store/ticket-assets.ts +458 -0
- package/packages/core/src/store/types.ts +160 -1
- package/packages/core/src/testing/index.ts +59 -0
- package/skills/code-review/SKILL.md +5 -1
- package/skills/guide/SKILL.md +152 -40
- package/skills/guide/references/operator-api.md +2 -2
- package/skills/guide/references/plugin-authoring.md +17 -0
- package/skills/guide/references/remote-store-protocol.md +204 -16
- package/skills/guide/references/remote-ticket-protocol.md +1 -1
- package/skills/guide/references/setup.md +12 -9
- package/skills/implement/SKILL.md +3 -0
- package/skills/operate/SKILL.md +126 -0
- package/skills/plan/SKILL.md +4 -0
- package/skills/plan-review/SKILL.md +5 -1
- package/skills/spec/SKILL.md +46 -3
- package/skills/tickets/SKILL.md +57 -5
- package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
- package/packages/core/src/cli/store-opening.contract.ts +0 -289
- package/packages/core/src/cli/testkit.ts +0 -194
- package/packages/core/src/markdown.ts +0 -67
- package/packages/core/src/operator/client.ts +0 -311
- package/packages/core/src/operator/server.ts +0 -616
- package/packages/core/src/operator/sessions.ts +0 -223
- package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
- package/packages/core/src/ports/tickets/remote-server.ts +0 -185
- package/packages/core/src/store/remote/server.ts +0 -923
- package/packages/core/src/testing/store-failures.ts +0 -32
|
@@ -38,6 +38,17 @@ the durable provider locator as `BuildExecutionStart.workspaceRef`; local PID
|
|
|
38
38
|
supervision remains host-owned. A workspace plugin relying on these fields
|
|
39
39
|
should declare an API range containing `^1.5.0`.
|
|
40
40
|
|
|
41
|
+
Plugin API 1.7 extends the workspace-provider factory context with the
|
|
42
|
+
host-derived seams a store-requiring remote provider needs. Workspace-provider
|
|
43
|
+
factories additionally receive `storeRef` and `storeToken` (guaranteed present
|
|
44
|
+
when the registration declares `capabilities.storeRequirements`),
|
|
45
|
+
`runtimeReferences` (the host's effective runtime routes; absent at call sites
|
|
46
|
+
that reference no runtimes), and the checkout-less `origin` and
|
|
47
|
+
`remoteBranchHead` readers (absent means fall back to host `git` from
|
|
48
|
+
`repoRoot`). All five are optional, and a factory that ignores them changes
|
|
49
|
+
nothing. A plugin reading the new seams should declare an API range containing
|
|
50
|
+
`^1.7.0`.
|
|
51
|
+
|
|
41
52
|
`BuildStore` and `BlobStore` contract types are exported for remote-server
|
|
42
53
|
authors, but BuildStore is **not** an in-process manifest map. Implement the
|
|
43
54
|
colocated language-neutral
|
|
@@ -113,6 +124,12 @@ Every module must default-export one strict `AutobuildPluginManifest`:
|
|
|
113
124
|
absolute consuming-repository root. Ticket sources receive the existing
|
|
114
125
|
`[tickets]` fields, workspace providers receive `[workspace.config]`, and
|
|
115
126
|
runtime and forge factories currently receive an empty `config` object.
|
|
127
|
+
Workspace providers additionally receive the host-derived seams (API 1.7):
|
|
128
|
+
`storeRef` and `storeToken` — guaranteed strings when the registration
|
|
129
|
+
declares `capabilities.storeRequirements` — plus `runtimeReferences`,
|
|
130
|
+
`origin`, and `remoteBranchHead`. Each seam is optional and absent when the
|
|
131
|
+
host call site supplies no such value; absent `origin`/`remoteBranchHead`
|
|
132
|
+
fall back to host `git` from `repoRoot`.
|
|
116
133
|
|
|
117
134
|
Add the module specifier at the TOML root, before any table. Repository-path
|
|
118
135
|
specifiers resolve from the config-bearing root, which is the immutable build
|
|
@@ -20,7 +20,7 @@ document and the package's executable contracts together.
|
|
|
20
20
|
`AB_STORE`; the shipped client appends the routes below.
|
|
21
21
|
- Every `/builds`, `/repos`, and `/sessions` request sends
|
|
22
22
|
`X-Autobuild-Version` with the exact client package version and
|
|
23
|
-
`X-Autobuild-Protocol-Version` with the remote protocol version (`
|
|
23
|
+
`X-Autobuild-Protocol-Version` with the remote protocol version (`3` in this
|
|
24
24
|
distribution). Before authentication,
|
|
25
25
|
body parsing, or resource lookup, the server compares both exact strings.
|
|
26
26
|
Missing or different values receive `409 {"kind":"conflict","error":"…"}`;
|
|
@@ -290,13 +290,15 @@ it to exist. An unknown build returns `404 not-found`.
|
|
|
290
290
|
|
|
291
291
|
| BuildStore operation | HTTP route | Request | Success |
|
|
292
292
|
|---|---|---|---|
|
|
293
|
-
| `createBuild` | `POST /builds` | `{slug: string, repo: string, repoOrigin?: string, ticket?: TicketRef, branch?: string}`; `slug`, `repo`, and a supplied `branch` are nonempty | `201` + `BuildRecord`; duplicate slug is `409 conflict` |
|
|
293
|
+
| `createBuild` | `POST /builds` | `{slug: string, repo: string, repoOrigin?: string, ticket?: TicketRef, branch?: string, created?: event write}`; `slug`, `repo`, and a supplied `branch` are nonempty. `created`, when present, must be a valid `build.created` write; the server appends it as seq 1 atomically with the record (both exist or neither), and an invalid one is `422` with nothing created | `201` + `BuildRecord`; duplicate slug is `409 conflict` |
|
|
294
294
|
| `listBuilds` | `GET /builds` | none | `200` + `BuildRecord[]`; list order is unspecified |
|
|
295
295
|
| `getBuild` | `GET /builds/{slug}` | none | `200` + `BuildRecord`; absent is `404` (the shipped client maps this to `null`) |
|
|
296
296
|
| `append` | `POST /builds/{slug}/events` | event write | `201` + build event envelope |
|
|
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
|
|
@@ -357,7 +385,8 @@ requires. A held request's authentication, token scope, and version-skew rules
|
|
|
357
385
|
are identical to the immediate form: a `401`, `403`, or `409` identity failure
|
|
358
386
|
is answered immediately, never held.
|
|
359
387
|
|
|
360
|
-
The parameter is purely additive
|
|
388
|
+
The parameter is purely additive in the version it landed in (protocol `2`,
|
|
389
|
+
where the event-wait rules were introduced): a server
|
|
361
390
|
that ignores `wait` remains conforming — its immediate empty answers only raise
|
|
362
391
|
the client's request rate — and a client that sends `wait` to such a server
|
|
363
392
|
observes no other difference. This is the same reasoning the stream and session
|
|
@@ -368,7 +397,11 @@ waits apply. There is no push transport; a held request is the whole mechanism.
|
|
|
368
397
|
Repository journals are separate resources with independent event sequences,
|
|
369
398
|
artifacts, and leases. `{repo}` is one percent-encoded repository id; encode
|
|
370
399
|
embedded `/` characters. All resource routes authorize and require the
|
|
371
|
-
repository to exist. An unknown repository returns `404 not-found
|
|
400
|
+
repository to exist. An unknown repository returns `404 not-found` — with one
|
|
401
|
+
carve-out: the build-digest batch read of this section is keyed to build
|
|
402
|
+
records, not the repository journal record, so it authorizes the repo token
|
|
403
|
+
but answers `200` (possibly empty) even for an unknown repository, a repository
|
|
404
|
+
without a journal record, or a repository without builds.
|
|
372
405
|
|
|
373
406
|
| BuildStore operation | HTTP route | Request | Success |
|
|
374
407
|
|---|---|---|---|
|
|
@@ -376,10 +409,14 @@ repository to exist. An unknown repository returns `404 not-found`.
|
|
|
376
409
|
| `getRepo` | `GET /repos/{repo}` | none | `200` + `RepositoryRecord`; absent is `404` (the shipped client maps this to `null`) |
|
|
377
410
|
| `appendRepo` | `POST /repos/{repo}/events` | event write | `201` + repository event envelope |
|
|
378
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 |
|
|
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` |
|
|
379
415
|
| `appendRepoWithArtifacts` | `POST /repos/{repo}/deposits` | atomic deposit request | `201` + `{event, artifacts}` using repository shapes |
|
|
380
416
|
| `putRepoArtifact` | `POST /repos/{repo}/artifacts` | artifact input | `201` + repository artifact metadata |
|
|
381
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` |
|
|
382
418
|
| `listRepoArtifacts` | `GET /repos/{repo}/artifact-list?kind={kind}` | optional `kind`; absence means all kinds | `200` + metadata ordered by kind and then increasing revision |
|
|
419
|
+
| `getRepoBuildDigests` | `GET /repos/{repo}/build-digests` | none | `200` + `BuildDigest[]`, one entry per build record of the repository (possibly empty), ordered by slug; handled before the repository-existence gate, so an unknown or journal-less repository answers `200 []` |
|
|
383
420
|
| `claimRepoLease` | `POST /repos/{repo}/lease/claim` | claim request | `200 {"ok": boolean}` |
|
|
384
421
|
| `heartbeatRepo` | `POST /repos/{repo}/lease/heartbeat` | holder request | `200 {"ok": boolean}` |
|
|
385
422
|
| `releaseRepoLease` | `POST /repos/{repo}/lease/release` | holder request | `200 {"ok": true}`; a wrong holder is a successful no-op |
|
|
@@ -389,10 +426,153 @@ creation operation and must not reset timestamps, events, artifacts, or lease
|
|
|
389
426
|
state when the repository already exists. Event sequence numbering starts at 1
|
|
390
427
|
for each repository independently of every build and other repository.
|
|
391
428
|
|
|
429
|
+
A `BuildDigest` is the projection of one build's log onto its terminal fact
|
|
430
|
+
and its `observation.recorded` occurrences:
|
|
431
|
+
|
|
432
|
+
```jsonc
|
|
433
|
+
// BuildDigest
|
|
434
|
+
{
|
|
435
|
+
"slug": "ab-fix-login-3",
|
|
436
|
+
"terminal": "done", // optional: "done" | "aborted", omitted while the log has neither
|
|
437
|
+
"observations": [ // ascending-seq pairs for the log's observation.recorded events
|
|
438
|
+
{ "seq": 1, "ts": "2026-07-15T12:00:01.000Z" },
|
|
439
|
+
{ "seq": 4, "ts": "2026-07-15T12:00:04.000Z" }
|
|
440
|
+
],
|
|
441
|
+
"merged": "2026-07-15T12:00:06.000Z" // optional: ts of the log's latest pr.merged, omitted when none
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
The digest is derived from the event log on every call — the server persists
|
|
446
|
+
nothing and there is no refresh step. Protocol version `3` reshaped the
|
|
447
|
+
digest's `observations` from bare sequence numbers to `{seq, ts}` pairs and
|
|
448
|
+
added the optional `merged` fact, so the harvest pressure gate can evaluate
|
|
449
|
+
launch decisions from digests and the repository journal alone instead of
|
|
450
|
+
reading every build's full history per evaluation. That is a breaking wire
|
|
451
|
+
change, not an additive one: a server that predates it returns the old shape
|
|
452
|
+
and would fail the client's schema parse mid-request, which is why the version
|
|
453
|
+
moved — a stale peer is now rejected at the handshake (`409 conflict`) instead.
|
|
454
|
+
This is the same discipline the transport's exact-match version rules promise:
|
|
455
|
+
wire shapes that independently implemented servers must reproduce update this
|
|
456
|
+
document and the protocol version together.
|
|
457
|
+
|
|
392
458
|
The build-stream query, artifact, ordering, timestamp, and validation rules
|
|
393
459
|
apply symmetrically to repository journals, including the event-read bounded
|
|
394
460
|
wait of section 3.
|
|
395
461
|
|
|
462
|
+
### Ticket assets
|
|
463
|
+
|
|
464
|
+
A ticket asset is a named, versioned bundle of files attached to
|
|
465
|
+
a ticket, keyed by `(repo, ticketId, kind, name)`. These routes live under the
|
|
466
|
+
repository but, unlike the journal routes, **do not require a repository
|
|
467
|
+
record**: assets can precede any journal row, so the repository-existence gate
|
|
468
|
+
does not apply. `{repo}` and `{id}` are percent-encoded path segments. Token
|
|
469
|
+
authorization follows the one-matching-repo column of the matrix (a repo token
|
|
470
|
+
for its own repository, or admin); build and session tokens receive `403 auth`.
|
|
471
|
+
The family is additive, so `REMOTE_STORE_PROTOCOL_VERSION` did not change.
|
|
472
|
+
|
|
473
|
+
A build also reads the assets it froze at its claim through its own build-scoped
|
|
474
|
+
routes, which a build token may use (no ticket-asset authority needed). The
|
|
475
|
+
server reduces the build's events to find the pinned revision, then returns that
|
|
476
|
+
exact revision, so a later replace or removal never changes the answer and a
|
|
477
|
+
finished build still answers:
|
|
478
|
+
|
|
479
|
+
| BuildStore operation | HTTP route | Request | Success |
|
|
480
|
+
|---|---|---|---|
|
|
481
|
+
| `getPinnedTicketAsset` (manifest) | `GET /builds/{slug}/ticket-assets?kind=&name=[&rev=]` | required `kind` and `name`; optional `rev` | `200` + `TicketAssetMeta` for the latest pinned revision (or the named pinned `rev`), or `200 null` when the build never pinned it |
|
|
482
|
+
| `getPinnedTicketAsset` (one file) | `GET /builds/{slug}/ticket-assets/file?kind=&name=&rev=&path=` | required `kind`, `name`, `path`; optional `rev` | `200` + `{"contentBase64": string}`, or `200 null` when not pinned; `404` when the revision has no such file |
|
|
483
|
+
|
|
484
|
+
| BuildStore operation | HTTP route | Request | Success |
|
|
485
|
+
|---|---|---|---|
|
|
486
|
+
| `ticketAssetLimits` | `GET /repos/{repo}/ticket-asset-limits` | none | `200` + `{maxBytes, maxEntries, maxRequestBytes?}` — the effective per-revision ceilings and, when the deployment has one, the largest request or response body it can carry. A server without this route answers `404`; clients treat that as the defaults |
|
|
487
|
+
| `putTicketAsset` | `POST /repos/{repo}/tickets/{id}/assets` | `{kind, name, layout, entries}` | `201` + `TicketAssetMeta` (with its manifest) |
|
|
488
|
+
| `getTicketAsset` (manifest) | `GET /repos/{repo}/tickets/{id}/assets?kind=&name=[&rev=]` | required `kind` and `name`; optional `rev` | `200` + `TicketAssetMeta` — **the manifest only, no bytes** — or `200 null` when the asset is absent or removed (latest) or the revision does not exist |
|
|
489
|
+
| `getTicketAsset` (one file) | `GET /repos/{repo}/tickets/{id}/assets/file?kind=&name=&rev=&path=` | required `kind`, `name`, `path`; optional `rev` | `200` + `{"contentBase64": string}`, or `200 null` when the asset is absent; `404` when the revision has no such file |
|
|
490
|
+
| `listTicketAssets` | `GET /repos/{repo}/tickets/{id}/assets/list[?revisions=1]` | optional `revisions=1` | `200` + `TicketAssetSummary[]` ordered by kind, name, revision: the latest live revision per kind and name, or with `revisions=1` every real revision (no tombstones). Summaries carry no manifest |
|
|
491
|
+
| `removeTicketAsset` | `POST /repos/{repo}/tickets/{id}/assets/remove` | `{kind, name}` | `200` + the removed `TicketAssetMeta`, or `200 null` when no live asset exists |
|
|
492
|
+
|
|
493
|
+
Wire types:
|
|
494
|
+
|
|
495
|
+
```jsonc
|
|
496
|
+
// put request: entries are files (content as base64) or explicit directories
|
|
497
|
+
{ "kind": "design", "name": "home", "layout": "tree", // layout: "file" | "tree"
|
|
498
|
+
"entries": [
|
|
499
|
+
{ "type": "file", "path": "index.html", "contentBase64": "PGgxPg==" },
|
|
500
|
+
{ "type": "dir", "path": "img/empty" }
|
|
501
|
+
] }
|
|
502
|
+
|
|
503
|
+
// TicketAssetMeta: dir entries carry no size or blobRef
|
|
504
|
+
{ "repo": "…", "ticketId": "AUT-1", "kind": "design", "name": "home",
|
|
505
|
+
"revision": 0, "layout": "tree", "size": 1234, // size = total file bytes
|
|
506
|
+
"entries": [
|
|
507
|
+
{ "type": "file", "path": "index.html", "size": 1234, "blobRef": "<sha256 hex>" },
|
|
508
|
+
{ "type": "dir", "path": "img/empty" }
|
|
509
|
+
],
|
|
510
|
+
"createdAt": "2026-10-01T12:00:00.000Z" }
|
|
511
|
+
|
|
512
|
+
// TicketAssetSummary (list): the same without entries
|
|
513
|
+
{ "repo": "…", "ticketId": "AUT-1", "kind": "design", "name": "home",
|
|
514
|
+
"revision": 0, "layout": "tree", "size": 1234,
|
|
515
|
+
"fileCount": 1, "dirCount": 1, "createdAt": "…" }
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Semantics every server must reproduce:
|
|
519
|
+
|
|
520
|
+
- **Revisions** are 0-based per `(repo, ticketId, kind, name)`. Putting the same
|
|
521
|
+
kind and name again creates the next revision; earlier revisions stay readable
|
|
522
|
+
by explicit `rev`.
|
|
523
|
+
- **Removal is a tombstone** that takes the next revision number with an empty
|
|
524
|
+
manifest. The latest row decides liveness: a read without `rev` and the
|
|
525
|
+
default list skip a removed asset, while an explicit earlier `rev` keeps
|
|
526
|
+
working (a build that pinned that revision still retrieves it). Re-attaching
|
|
527
|
+
takes the next revision, so numbers may skip the tombstone. `revisions=1`
|
|
528
|
+
lists real revisions only.
|
|
529
|
+
- **Validation** (`422 validation`, nothing stored). `kind` and `name` match
|
|
530
|
+
`[A-Za-z0-9._-]+`, at most 64 characters. Paths are relative and
|
|
531
|
+
`/`-separated, with no empty, `.`, or `..` segment and no NUL, at most 256
|
|
532
|
+
UTF-8 bytes (128 per segment); paths are unique across files and directories
|
|
533
|
+
and no file is the ancestor of another entry. `layout: "file"` has exactly one
|
|
534
|
+
top-level `file` entry and no directory; `layout: "tree"` may hold any number
|
|
535
|
+
of entries, including zero (an empty folder; its root is implicit). A revision
|
|
536
|
+
holds at most 1000 entries (files plus directories) and **26214400 bytes (25
|
|
537
|
+
MiB)** of file bytes; the error names the limit, for example `ticket asset
|
|
538
|
+
exceeds the 26214400-byte (25 MiB) limit`.
|
|
539
|
+
- File bytes are content-addressed (`blobRef` is the sha256 hex) in the same
|
|
540
|
+
blob store as artifacts; the database holds only the manifest.
|
|
541
|
+
|
|
542
|
+
**Deployment ceilings.** A platform may reject oversized request or response
|
|
543
|
+
bodies before any application code runs (Vercel's functions cap them at 4.5
|
|
544
|
+
MB), so validating on the server alone cannot enforce a limit. A server that
|
|
545
|
+
has such a ceiling advertises it as `maxRequestBytes` on the limits route
|
|
546
|
+
together with the effective `maxBytes`, computed from it as follows. Paths and
|
|
547
|
+
entry counts are bounded, so the worst-case wire overhead is a constant:
|
|
548
|
+
`overhead = 1024 + 1000 × 320 = 321024` bytes, and
|
|
549
|
+
`maxBytes = min(26214400, max(0, floor((maxRequestBytes − overhead) × 3 / 4)))`
|
|
550
|
+
(base64 inflates content by 4/3); `maxEntries` stays 1000. The hosted service
|
|
551
|
+
sets `maxRequestBytes` to 4 MiB, giving an effective per-asset limit of 2904960
|
|
552
|
+
bytes (about 2.8 MiB). The formula is a safe lower bound, not the final
|
|
553
|
+
authority:
|
|
554
|
+
|
|
555
|
+
- The shipped **client** reads the limits once per store handle and, before any
|
|
556
|
+
upload, validates against `maxBytes` and `maxEntries` and then compares the
|
|
557
|
+
exact UTF-8 length of the serialized request with `maxRequestBytes`. A bundle
|
|
558
|
+
that does not fit is refused locally with `ticket asset exceeds this store's
|
|
559
|
+
N-byte limit (deployment request ceiling M bytes)` and **no upload request is
|
|
560
|
+
made**.
|
|
561
|
+
- The **server** re-validates every put against its effective limits and checks
|
|
562
|
+
every ticket-asset body against `maxRequestBytes` itself, answering `413` (a
|
|
563
|
+
request) or `422` with a limit-naming message where the platform lets the
|
|
564
|
+
request through.
|
|
565
|
+
- **Downloads never need a bundle-sized response**: the client reads the
|
|
566
|
+
manifest and then each file by its own request. The manifest, file, and list
|
|
567
|
+
routes check the exact serialized length of their response against
|
|
568
|
+
`maxRequestBytes` and fail with a `413 validation` error naming the asset (or
|
|
569
|
+
the file, its size, and the ceiling) rather than letting the platform reject
|
|
570
|
+
it. This matters for a revision stored under an older or higher ceiling:
|
|
571
|
+
its manifest and smaller files remain retrievable; a file that cannot fit can
|
|
572
|
+
only be fetched from a store with a higher ceiling. Chunked or ranged transfer
|
|
573
|
+
is not part of the protocol, so a deployment's effective per-asset limit is
|
|
574
|
+
its advertised `maxBytes`.
|
|
575
|
+
|
|
396
576
|
## 5. Operator-session operations
|
|
397
577
|
|
|
398
578
|
Operator sessions are a third resource kind: durable orchestrator-conversation
|
|
@@ -430,6 +610,7 @@ identity is already the actor.
|
|
|
430
610
|
| `listSessions` | `GET /repos/{repo}/sessions` | none | `200` + `SessionRecord[]`, creation order (`createdAt`, then the store's creation counter for same-millisecond ties) |
|
|
431
611
|
| `getSession` | `GET /sessions/{id}` | none | `200` + `SessionRecord`; absent is `404` (the shipped client maps this to `null`) |
|
|
432
612
|
| `appendSessionEvent` | `POST /sessions/{id}/events` | event write | `201` + session event envelope (same envelope shape with `"session"` in place of `"build"`) |
|
|
613
|
+
| `appendSessionEventIfCurrent` | `POST /sessions/{id}/events/conditional` | `{"expectedSeq": nonnegative integer, "event": event write}` | appended: `201` + session event envelope; session advanced: `200 null` (mirroring section 3's build compare-and-append) |
|
|
433
614
|
| `getSessionEvents` | `GET /sessions/{id}/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` + session event envelopes with `seq >` parsed `since`, in increasing sequence order |
|
|
434
615
|
| `appendSessionWithArtifacts` | `POST /sessions/{id}/deposits` | atomic deposit request | `201` + `{event, artifacts}` using session shapes; the substitution algorithm of section 8 applies unchanged |
|
|
435
616
|
| `putSessionArtifact` | `POST /sessions/{id}/artifacts` | artifact input | `201` + session artifact metadata |
|
|
@@ -599,7 +780,8 @@ by this rule (their retention belongs to the open archival thread), and open
|
|
|
599
780
|
streams are never pruned. A read of a stream whose chunks were pruned returns
|
|
600
781
|
no chunks, `status: "closed"`, and the artifact reference.
|
|
601
782
|
|
|
602
|
-
These additions
|
|
783
|
+
These additions were purely additive in the version they landed in (protocol
|
|
784
|
+
`2`, where the stream routes were introduced), and a
|
|
603
785
|
conforming server implements the stream routes together with this document's
|
|
604
786
|
rules exactly as it does for the event and artifact routes.
|
|
605
787
|
|
|
@@ -675,12 +857,17 @@ resource access; an explicit resource whose id is `"*"` is not admin.
|
|
|
675
857
|
|
|
676
858
|
### Resource authorization matrix
|
|
677
859
|
|
|
678
|
-
| Token resource | `/builds` create/list | one matching build | `/repos` ensure | one matching repo | `/repos/{repo}/sessions` create/list | one matching session |
|
|
679
|
-
|
|
680
|
-
| admin (`build: "*"`) | yes | any | yes | any | yes | any |
|
|
681
|
-
| build | no | exact id only | no | no | no | no |
|
|
682
|
-
| repo | no | no | no | exact id only | yes | no |
|
|
683
|
-
| session | no | no | no | no | no | exact id only |
|
|
860
|
+
| Token resource | `/builds` create/list | one matching build | `/repos` ensure | one matching repo | `/repos/{repo}/sessions` create/list | `/repos/{repo}/build-digests` | `/repos/{repo}/state-events` | `/repos/{repo}/tickets/{id}/assets` and `/repos/{repo}/ticket-asset-limits` | one matching session |
|
|
861
|
+
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
862
|
+
| admin (`build: "*"`) | yes | any | yes | any | yes | any | any | any | any |
|
|
863
|
+
| build | no | exact id only | no | no | no | no | no | no | no |
|
|
864
|
+
| repo | no | no | no | exact id only | yes | exact id only | exact id only | exact repo only | no |
|
|
865
|
+
| session | no | no | no | no | no | no | no | no | exact id only |
|
|
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.
|
|
684
871
|
|
|
685
872
|
A valid token used for the wrong resource receives `403 auth`. Resource scope
|
|
686
873
|
gates all operations, including reads, artifact operations, and leases. A
|
|
@@ -793,8 +980,8 @@ The shipped server maps failures as follows:
|
|
|
793
980
|
| `403` | `auth` | Valid token with the wrong resource scope or event-session attribution |
|
|
794
981
|
| `404` | `not-found` | Unknown route, unsupported method, unknown build, unknown repository, unknown session, or an unknown or foreign-scoped stream |
|
|
795
982
|
| `409` | `conflict` | Missing/mismatched package or protocol identity, duplicate build creation, appending to a closed stream (`stream "…" is closed`), or a backing conflict reported as already existing |
|
|
796
|
-
| `413` | `validation` | Decoded artifact or serialized stream batch exceeds the configured ceiling; the message names the ceiling |
|
|
797
|
-
| `422` | `validation` | `EventValidationError` from build, repository, or session catalog validation; its message is preserved verbatim |
|
|
983
|
+
| `413` | `validation` | Decoded artifact or serialized stream batch exceeds the configured ceiling; a ticket-asset request or response exceeds the deployment's request ceiling; the message names the ceiling |
|
|
984
|
+
| `422` | `validation` | `EventValidationError` from build, repository, or session catalog validation, or a ticket-asset validation failure (including the 25 MiB limit); its message is preserved verbatim |
|
|
798
985
|
| `500` | `internal` | Any other unexpected backing or server failure; the thrown error message is returned |
|
|
799
986
|
|
|
800
987
|
Authentication runs before resource lookup, and session authorization runs
|
|
@@ -916,11 +1103,12 @@ Four shipped behaviors do not add `BuildStore` routes:
|
|
|
916
1103
|
store lifecycle; there is no close endpoint.
|
|
917
1104
|
- `GET /health` is outside the `BuildStore` interface. It is always open and
|
|
918
1105
|
returns exactly `200 {"ok":true,"autobuildVersion":"<package>",
|
|
919
|
-
"protocolVersion":"
|
|
1106
|
+
"protocolVersion":"3"}`. It reports HTTP-process availability and identity,
|
|
920
1107
|
not a deeper backing-store transaction or migration check.
|
|
921
1108
|
|
|
922
|
-
There is no push/WebSocket subscription protocol,
|
|
923
|
-
|
|
1109
|
+
There is no push/WebSocket subscription protocol, repository listing, artifact
|
|
1110
|
+
deletion, or server deployment API. The one repo-scoped batch read is the
|
|
1111
|
+
build-digest route of section 4; no other batch-read route exists.
|
|
924
1112
|
|
|
925
1113
|
## 12. Conformance
|
|
926
1114
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The hosted ticket protocol exposes the complete `TicketSource` port without
|
|
4
4
|
exposing backend credentials. It shares the Autobuild and remote protocol
|
|
5
5
|
version headers with the remote BuildStore. Both must exactly match the service;
|
|
6
|
-
this release uses remote protocol version `
|
|
6
|
+
this release uses remote protocol version `3`.
|
|
7
7
|
|
|
8
8
|
## Authentication and context
|
|
9
9
|
|
|
@@ -59,7 +59,7 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y chromium""" },
|
|
|
59
59
|
|
|
60
60
|
[workspace.config.runtimeProvisioning.pi]
|
|
61
61
|
install = "npm install --global --ignore-scripts @earendil-works/pi-coding-agent@0.84.4"
|
|
62
|
-
preflight = "test \"$(pi --version)\" = \"0.84.4\""
|
|
62
|
+
preflight = "test \"$(pi --version)\" = \"0.84.4\" && pi update --models"
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
Use a `vercel-ai-gateway/...` Pi model with `AI_GATEWAY_API_KEY` in the
|
|
@@ -220,14 +220,17 @@ same lockstep window. If the release is cut but the store upgrade lags, guests
|
|
|
220
220
|
carrying the new distribution fail loudly against the old store (409) rather
|
|
221
221
|
than running silently stale.
|
|
222
222
|
|
|
223
|
-
Provisioning records the installed distribution's
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
Provisioning records the installed distribution's identity — package version
|
|
224
|
+
and remote-store protocol version — in the guest's
|
|
225
|
+
`/opt/autobuild/.distribution-version` marker. Every path that launches guest
|
|
226
|
+
code in an existing sandbox — provisioning reuse, harvest reuse, the operator
|
|
227
|
+
sandbox, and build execution restart after a lease sweep — compares the marker
|
|
228
|
+
against the identity the current system would deliver and, when they disagree
|
|
229
|
+
(which includes every guest provisioned before the marker existed), refreshes
|
|
230
|
+
it in place (archive re-fetched, extracted, and production-installed) first,
|
|
231
|
+
so an upgraded dispatcher retrofits its existing guests instead of resuming a
|
|
232
|
+
stale distribution the hosted store would reject. A refresh failure deletes
|
|
233
|
+
the sandbox so the next provisioning pass rematerializes it from scratch.
|
|
231
234
|
|
|
232
235
|
After a lockstep upgrade, confirm a fix-bearing guest from the guest
|
|
233
236
|
perspective:
|
|
@@ -21,6 +21,9 @@ You never push — the push is plumbing that happens when you finish.
|
|
|
21
21
|
operator's answer to the escalation that blocked this build). The manifest's
|
|
22
22
|
`feedback` field names which one this round is, and is absent when the round
|
|
23
23
|
carries none.
|
|
24
|
+
When the manifest lists `assets`, they are the build's pinned ticket assets
|
|
25
|
+
under `.ab/assets/<kind>/<name>/`; read them with the spec. A `design` asset
|
|
26
|
+
is a contract on observable visual and reference outcomes, not on implementation — what you build must satisfy it, while how stays yours.
|
|
24
27
|
2. Execute the plan. Commit in coherent increments with real messages —
|
|
25
28
|
the commit history is part of the paper trail.
|
|
26
29
|
3. Run the repo's checks yourself before finishing (the config's typecheck /
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: operate
|
|
3
|
+
description: You are the embedded orchestrator for one repository - the agent that answers its operator sessions, watches its builds, unblocks them, and discusses direction. You run inside the hosted service with the operator tool registry bound in process. Use this skill as your operating manual — it defines your role, the complete tool surface, the approval rules, and when to stop and ask the operator instead of acting.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Operating the repository
|
|
7
|
+
|
|
8
|
+
You are the orchestrator session for one repository. You file tickets, watch
|
|
9
|
+
builds, unblock them, answer operator questions, and discuss direction. You
|
|
10
|
+
are not a build phase: you never run pipeline steps, never edit the
|
|
11
|
+
repository, and never touch a checkout. Your entire reach is the operator
|
|
12
|
+
tool registry bound into your turn.
|
|
13
|
+
|
|
14
|
+
## How you run
|
|
15
|
+
|
|
16
|
+
Each of your turns is one durable agent loop:
|
|
17
|
+
|
|
18
|
+
- The conversation is reconstructed from durable state on every invocation.
|
|
19
|
+
Nothing is held in memory between invocations; if your turn suspends and
|
|
20
|
+
resumes in a fresh invocation, the model input is identical.
|
|
21
|
+
- Your output streams as protocol parts onto the turn's stream. The operator
|
|
22
|
+
sees it live in the session.
|
|
23
|
+
- Every tool call is attributed to the session's operator — the human actor
|
|
24
|
+
named on every write is that operator, with your session as the delegate.
|
|
25
|
+
Act accordingly: you are spending their authority.
|
|
26
|
+
- Your tools target exactly one repository, the session's own. You never
|
|
27
|
+
supply a repository argument; it is bound for you.
|
|
28
|
+
|
|
29
|
+
## Suspension
|
|
30
|
+
|
|
31
|
+
Your turn can stop in two ways, and both are normal:
|
|
32
|
+
|
|
33
|
+
- **Budget.** Each invocation has a wall-clock budget. When it runs out, the
|
|
34
|
+
turn suspends (`cause: budget`) and resumes in a fresh invocation — a new
|
|
35
|
+
provider call — with the identical conversation. Never assume a turn
|
|
36
|
+
died; never repeat work a checkpoint already covers.
|
|
37
|
+
- **Approval.** Some tools are on the repository's approval list. Calling
|
|
38
|
+
one suspends your turn (`cause: approval`) until the operator answers.
|
|
39
|
+
An `approve` answer executes the call; a `deny` answer hands you a denied
|
|
40
|
+
tool output — treat it as a firm no, say so briefly, and move on. Tools
|
|
41
|
+
not on the list run without pause.
|
|
42
|
+
|
|
43
|
+
When suspended, just stop cleanly. Anything important you want to say should
|
|
44
|
+
already be in your streamed output before the suspension point.
|
|
45
|
+
|
|
46
|
+
## The tool surface
|
|
47
|
+
|
|
48
|
+
Your tools are the operator registry. The read-only ones are always safe:
|
|
49
|
+
|
|
50
|
+
- `builds.list` — active, queued, or all builds, with effective status.
|
|
51
|
+
- `builds.get` — one build's detail.
|
|
52
|
+
- `builds.events` — one build's event log from a cursor (use a cursor; logs
|
|
53
|
+
are long).
|
|
54
|
+
- `builds.artifact` — one artifact's bytes, base64-encoded.
|
|
55
|
+
- `repository.status` — the repository's derived status.
|
|
56
|
+
- `harvest.status` — the observation workflow's current state.
|
|
57
|
+
|
|
58
|
+
The mutating ones carry the operator's authority — prefer the narrowest
|
|
59
|
+
that does the job, and say what you are doing before you do it:
|
|
60
|
+
|
|
61
|
+
- `builds.control` — pause, resume, abort, or discard a build. `abort` and
|
|
62
|
+
`discard` are destructive to in-flight work.
|
|
63
|
+
- `builds.answer` — answer an open escalation: guidance text, a retry, a
|
|
64
|
+
review-round ceiling, or a revise-spec resolution.
|
|
65
|
+
- `tickets.list`, `tickets.get` — read the ticket queue and one ticket.
|
|
66
|
+
- `tickets.create` — file a new ticket from a conversation.
|
|
67
|
+
- `tickets.update`, `tickets.block`, `tickets.unblock` — edit fields and
|
|
68
|
+
blocker relations.
|
|
69
|
+
- `tickets.move` — move a ticket between workflow states. Moving a ticket
|
|
70
|
+
into the ready state sends it to dispatch — do that only when the ticket
|
|
71
|
+
genuinely conforms and the operator's intent is clear.
|
|
72
|
+
- `notes.read`, `notes.write` — the operator-notes artifact: durable,
|
|
73
|
+
human-readable notes about this repository's operation. Write what the
|
|
74
|
+
next turn (or the next human) will need; read it before acting on
|
|
75
|
+
anything you'd otherwise guess.
|
|
76
|
+
- `repository.settings` and `repository.bulk_control` — repository-wide
|
|
77
|
+
switches; use sparingly and only on clear instruction.
|
|
78
|
+
- `harvest.control` — start or control an observation run.
|
|
79
|
+
|
|
80
|
+
Every tool returns the operator API's shapes: results, or a typed failure
|
|
81
|
+
(`kind`, `error`, `code`). A refusal is information, not an exception:
|
|
82
|
+
report it, adapt, and carry on.
|
|
83
|
+
|
|
84
|
+
## The attention set
|
|
85
|
+
|
|
86
|
+
When the session has wake settings, the dispatcher wakes you when a build
|
|
87
|
+
event matching them lands. A wake turn's first message carries the event
|
|
88
|
+
record and the build's reduced state as JSON. Typical wakes and what to do:
|
|
89
|
+
|
|
90
|
+
- `escalation.raised` / `phase.failed` / `infrastructure.failed` — read the
|
|
91
|
+
escalation and the build's recent events; if the operator notes or the
|
|
92
|
+
conversation already answer it, say so; otherwise summarize and decide
|
|
93
|
+
whether it needs the operator.
|
|
94
|
+
- `finalize.completed` / `build.completed` / `pr.merged` / `pr.closed` /
|
|
95
|
+
`pr.conflicted` / `build.aborted` — acknowledge outcome changes that the
|
|
96
|
+
operator should know about, and file or move tickets when the outcome
|
|
97
|
+
makes that obviously right.
|
|
98
|
+
- Anything else matching the wake settings — read the event, then decide.
|
|
99
|
+
|
|
100
|
+
Do not re-derive history the build's events already tell you; read events
|
|
101
|
+
with a cursor and keep it short.
|
|
102
|
+
|
|
103
|
+
## When to escalate instead of act
|
|
104
|
+
|
|
105
|
+
Stop and ask the operator — in plain streamed text — whenever:
|
|
106
|
+
|
|
107
|
+
- The action is destructive and not clearly instructed (`abort`, `discard`,
|
|
108
|
+
a bulk control, a repository-wide setting).
|
|
109
|
+
- Two readings of the operator's intent diverge in cost or risk.
|
|
110
|
+
- You would be guessing about product direction, priorities, or scope.
|
|
111
|
+
- A tool refusal repeats and you cannot resolve it with the tools you have.
|
|
112
|
+
- The attention event is one you cannot act on with the registry at all.
|
|
113
|
+
|
|
114
|
+
Filing a ticket IS acting — do it when the request is concrete. Asking is
|
|
115
|
+
not a failure; an unasked destructive action is.
|
|
116
|
+
|
|
117
|
+
## Boundaries
|
|
118
|
+
|
|
119
|
+
- You never claim to have done anything outside the registry. If you cannot
|
|
120
|
+
observe it with a tool, say you cannot observe it.
|
|
121
|
+
- You never invent repository identity, ticket ids, or build slugs; every
|
|
122
|
+
id you act on came from a tool result or the conversation.
|
|
123
|
+
- You do not run concurrent work: one turn at a time, and a message that
|
|
124
|
+
arrives mid-turn is answered after the current turn ends.
|
|
125
|
+
- The operator's notes artifact is shared ground truth; keep it current
|
|
126
|
+
when you learn something durable.
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -19,6 +19,10 @@ write product code in this phase.
|
|
|
19
19
|
reviewer's findings) or `.ab/guidance.json` (a human operator's answer to
|
|
20
20
|
the escalation that blocked this build). A round never carries both, and a
|
|
21
21
|
round may carry neither.
|
|
22
|
+
When the manifest lists `assets`, they are the build's pinned ticket assets
|
|
23
|
+
under `.ab/assets/<kind>/<name>/`; read them with the spec. A `design` asset
|
|
24
|
+
is a contract on observable visual and reference outcomes, not on implementation — the plan must satisfy it, while how stays yours. The spec cites
|
|
25
|
+
assets by kind and name.
|
|
22
26
|
2. Read the spec, then the codebase. The spec says what and why; you decide
|
|
23
27
|
how. Explore enough of the code to name real files and real seams. When an
|
|
24
28
|
approach touches a third-party service, ground it in the provider's own
|
|
@@ -17,7 +17,11 @@ verdict travels the typed channel; nothing you print matters to the pipeline.
|
|
|
17
17
|
lists finding ids a human explicitly dismissed.
|
|
18
18
|
2. Judge the plan on exactly these axes:
|
|
19
19
|
- **Spec conformance** — does executing this plan satisfy every
|
|
20
|
-
acceptance criterion? Does anything in it exceed the spec's scope?
|
|
20
|
+
acceptance criterion? Does anything in it exceed the spec's scope? A
|
|
21
|
+
pinned `design` asset (listed under `assets` in `.ab/context.json`, files
|
|
22
|
+
under `.ab/assets/<kind>/<name>/`) is a contract on observable visual and reference outcomes, not on implementation: open it and flag a plan that
|
|
23
|
+
contradicts or ignores it, as you would an unmet criterion. A gap in the
|
|
24
|
+
asset is not a reason to invent requirements.
|
|
21
25
|
- **Groundedness** — do the files and seams it names actually exist and
|
|
22
26
|
work the way the plan assumes? Spot-check the code; a plan built on a
|
|
23
27
|
wrong assumption fails in `implement` at ten times the cost. For every
|
package/skills/spec/SKILL.md
CHANGED
|
@@ -37,7 +37,9 @@ in order, but as a conversation, not a form:
|
|
|
37
37
|
3. **Out of scope.** Ask what adjacent work is explicitly excluded. Propose
|
|
38
38
|
candidates — the tempting expansions you noticed while reading the code.
|
|
39
39
|
4. **Evidence.** Link what exists: the error rates, the user reports, the
|
|
40
|
-
prior discussion.
|
|
40
|
+
prior discussion. Designs and other reference material are attached as
|
|
41
|
+
ticket assets and cited by kind and name — see
|
|
42
|
+
[Designs and reference material](#designs-and-reference-material).
|
|
41
43
|
|
|
42
44
|
Draft the spec in full, show it, iterate until the user accepts. Then create
|
|
43
45
|
the ticket with the spec as its body using `ab ticket create`. Honor a
|
|
@@ -102,6 +104,9 @@ spec to a file, then sync only the body:
|
|
|
102
104
|
ab ticket update <ticket> --body spec.md
|
|
103
105
|
```
|
|
104
106
|
|
|
107
|
+
If the human points at a design or other reference material while fleshing out,
|
|
108
|
+
follow [Designs and reference material](#designs-and-reference-material).
|
|
109
|
+
|
|
105
110
|
Omitted metadata is preserved, including title, labels, assignee, state, and
|
|
106
111
|
provider-specific fields. If grooming changes dependencies, use the same
|
|
107
112
|
configured-source surface rather than a provider API or MCP call:
|
|
@@ -114,10 +119,48 @@ ab ticket unblock <ticket> <blocker-id>
|
|
|
114
119
|
The first id is the ticket being amended. Both relationship operations are
|
|
115
120
|
idempotent; adding validates the blocker exists and rejects a self-block.
|
|
116
121
|
|
|
122
|
+
## Designs and reference material
|
|
123
|
+
|
|
124
|
+
When the user points at a design, mock, prototype, screenshot set, sample
|
|
125
|
+
payload, or other reference material, export it during this conversation, so
|
|
126
|
+
no build worker ever has to convert a link. Use whatever tools this session
|
|
127
|
+
has (a browser, a connector, a screenshot tool, fetch, a CLI) and do not tie
|
|
128
|
+
the result to any one design tool. Produce portable static files in one
|
|
129
|
+
folder: a static reference page (`index.html`, with its assets inline or
|
|
130
|
+
alongside) plus screenshots of the key states and widths.
|
|
131
|
+
|
|
132
|
+
Attach the folder (or a single file) to the ticket as a `design` asset:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
ab ticket attach <id> design <path> [--name <name>]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The ticket must exist first. For a new ticket, create it in a non-ready
|
|
139
|
+
staging state, attach, and check with `ab ticket show <id>`. Then place it
|
|
140
|
+
where the human asked: if the intended destination is ready, move it there
|
|
141
|
+
last, so a build cannot claim it before its assets exist; otherwise leave it
|
|
142
|
+
in its destination (or the configured create state) and do not promote it. Attaching the same
|
|
143
|
+
kind and name again makes a new revision. Assets are frozen into a build when
|
|
144
|
+
it claims the ticket; attaching afterward does not reach a build already
|
|
145
|
+
running.
|
|
146
|
+
|
|
147
|
+
Cite the asset in the spec's Evidence by kind and name (``design `home-page` ``),
|
|
148
|
+
never by external link. The spec still states the underlying need; the asset is
|
|
149
|
+
the visual contract.
|
|
150
|
+
|
|
151
|
+
If this session cannot export the design (no tool for it, an auth wall, or
|
|
152
|
+
files over the store's size limit), keep the external link in Evidence and say
|
|
153
|
+
plainly, in the spec and to the human, that builds cannot read it, so the
|
|
154
|
+
planner does not treat it as a contract.
|
|
155
|
+
|
|
117
156
|
## Rules
|
|
118
157
|
|
|
119
|
-
- The spec says **what and why, never how
|
|
120
|
-
|
|
158
|
+
- The spec says **what and why, never how.** An exported, attached `design`
|
|
159
|
+
asset is a contract on observable visual and reference outcomes — layout,
|
|
160
|
+
content, states, copy, look — that plan, review, and verify judge against;
|
|
161
|
+
it is not a contract on implementation, and component structure, libraries,
|
|
162
|
+
and file layout stay the planner's. A technical design or proposed approach
|
|
163
|
+
the user hands you, and any link you could not export, goes in Evidence as
|
|
121
164
|
"proposed approach", clearly non-binding on the planner.
|
|
122
165
|
- Don't gold-plate: a spec is buildable when a planner could start, not when
|
|
123
166
|
every question is answered. Thin-but-groomed is a valid state — dispatch
|