@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.
- package/README.md +97 -113
- package/SPEC.md +17 -4
- package/docs/architecture.md +109 -0
- package/docs/configuration.md +26 -7
- package/docs/remote-store-protocol.md +35 -0
- package/docs/website.md +4 -0
- package/package.json +6 -4
- 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 +85 -147
- package/packages/core/src/cli/dashboard/poll.ts +7 -2
- package/packages/core/src/cli/dispatch-process.ts +1 -13
- package/packages/core/src/cli/dispatch.ts +80 -37
- package/packages/core/src/cli/help.ts +15 -1
- package/packages/core/src/cli/init-validation.ts +18 -0
- package/packages/core/src/cli/init.ts +3 -2
- package/packages/core/src/cli/installation.ts +50 -8
- package/packages/core/src/cli/self-update.ts +77 -2
- package/packages/core/src/cli/status.ts +1 -1
- package/packages/core/src/cli/ticket.ts +146 -1
- package/packages/core/src/cli/wait.ts +10 -2
- package/packages/core/src/cli/watch.ts +10 -2
- package/packages/core/src/config/live.ts +1 -0
- package/packages/core/src/config/pipeline-source.ts +17 -5
- package/packages/core/src/config/schema.ts +21 -0
- package/packages/core/src/integration/harness.ts +113 -1
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
- package/packages/core/src/kernel/auto-merge-default.ts +43 -9
- 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/pr-attachments.ts +164 -46
- package/packages/core/src/kernel/reducer-registry.ts +164 -0
- package/packages/core/src/kernel/reducer.ts +216 -74
- package/packages/core/src/operator/query.ts +68 -28
- package/packages/core/src/operator/read-view.ts +98 -0
- package/packages/core/src/orchestrator/prompt.ts +3 -1
- 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/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 +32 -1
- package/packages/core/src/processes/build-execution-state.ts +31 -15
- package/packages/core/src/processes/build-runner.ts +184 -90
- package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
- package/packages/core/src/processes/dispatcher.ts +270 -176
- package/packages/core/src/processes/execution-settlement.ts +72 -39
- package/packages/core/src/processes/harvest-execution-state.ts +41 -29
- package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
- package/packages/core/src/processes/orchestrator-tick.ts +44 -3
- 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 +26 -2
- package/packages/core/src/processes/tick-state.ts +33 -0
- package/packages/core/src/store/adapter.ts +10 -1
- package/packages/core/src/store/build-scope.ts +26 -0
- package/packages/core/src/store/contract.ts +337 -0
- package/packages/core/src/store/digest.ts +71 -18
- package/packages/core/src/store/local/schema.ts +17 -0
- package/packages/core/src/store/local/store.ts +111 -4
- package/packages/core/src/store/memory.ts +68 -1
- package/packages/core/src/store/phase-session.ts +23 -0
- package/packages/core/src/store/remote/client.ts +50 -1
- package/packages/core/src/store/remote/index.ts +2 -0
- package/packages/core/src/store/remote/protocol.ts +15 -0
- package/packages/core/src/store/repo-state-events.ts +72 -7
- package/packages/core/src/store/session-handle.ts +30 -0
- package/packages/core/src/store/session-reducer.ts +52 -11
- package/packages/core/src/store/snapshots.ts +88 -0
- package/packages/core/src/store/ticket-assets.ts +41 -8
- package/packages/core/src/store/types.ts +38 -0
- package/skills/guide/SKILL.md +38 -21
- package/skills/guide/references/remote-store-protocol.md +35 -0
- package/skills/operate/SKILL.md +146 -19
- package/skills/tickets/SKILL.md +57 -5
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reducer snapshots: a cache beside the event log, never an authority.
|
|
3
|
+
*
|
|
4
|
+
* For one scope (a build log or a repository journal) and one registered
|
|
5
|
+
* reducer, the store may keep that reducer's serialized accumulator as of a
|
|
6
|
+
* cursor, so a process with no hot memory loads it, reads only the events
|
|
7
|
+
* newer than the cursor, and advances — instead of replaying the whole log.
|
|
8
|
+
*
|
|
9
|
+
* The log always regenerates a snapshot. Every consumer treats a missing,
|
|
10
|
+
* version-mismatched, ahead-of-log, malformed or unreadable snapshot as a miss
|
|
11
|
+
* and replays; deleting every snapshot changes no observable output.
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod'
|
|
14
|
+
import type { IncrementalReducer } from '../kernel/incremental'
|
|
15
|
+
import type { BuildStore } from './types'
|
|
16
|
+
|
|
17
|
+
export type SnapshotScope = { kind: 'build'; slug: string } | { kind: 'repo'; repo: string }
|
|
18
|
+
|
|
19
|
+
export interface ReducerSnapshot {
|
|
20
|
+
/** The reducer version that wrote `state`; a reader on another version
|
|
21
|
+
* ignores the snapshot. */
|
|
22
|
+
version: number
|
|
23
|
+
/** The greatest event seq folded into `state`. */
|
|
24
|
+
cursor: number
|
|
25
|
+
/** The reducer's JSON accumulator as of `cursor`. */
|
|
26
|
+
state: unknown
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const reducerSnapshotWireSchema = z.object({
|
|
30
|
+
version: z.number().int().min(1),
|
|
31
|
+
cursor: z.number().int().min(0),
|
|
32
|
+
state: z.unknown(),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
/** The registry name a snapshot is keyed by: a short identifier, safe in a URL
|
|
36
|
+
* path segment and a SQL key. */
|
|
37
|
+
export const SNAPSHOT_REDUCER_NAME = /^[A-Za-z][A-Za-z0-9._-]{0,63}$/
|
|
38
|
+
|
|
39
|
+
export function snapshotScopeKey(scope: SnapshotScope): string {
|
|
40
|
+
return scope.kind === 'build' ? `build:${scope.slug}` : `repo:${scope.repo}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Whether a put replaces the stored row. The stored cursor never decreases:
|
|
44
|
+
* a higher cursor wins whatever the versions, and an upgraded reducer replaces
|
|
45
|
+
* an older shape at an unchanged cursor. */
|
|
46
|
+
export function snapshotSupersedes(
|
|
47
|
+
next: Pick<ReducerSnapshot, 'version' | 'cursor'>,
|
|
48
|
+
stored: Pick<ReducerSnapshot, 'version' | 'cursor'> | undefined,
|
|
49
|
+
): boolean {
|
|
50
|
+
if (stored === undefined) return true
|
|
51
|
+
if (next.cursor !== stored.cursor) return next.cursor > stored.cursor
|
|
52
|
+
return next.version > stored.version
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type SnapshotStore = Pick<BuildStore, 'getReducerSnapshot' | 'putReducerSnapshot'>
|
|
56
|
+
|
|
57
|
+
/** Read a snapshot, degrading every failure to a miss. */
|
|
58
|
+
export async function loadReducerSnapshot(
|
|
59
|
+
store: Partial<SnapshotStore>,
|
|
60
|
+
scope: SnapshotScope,
|
|
61
|
+
name: string,
|
|
62
|
+
reducer: Pick<IncrementalReducer<unknown, unknown, unknown>, 'version'>,
|
|
63
|
+
): Promise<ReducerSnapshot | null> {
|
|
64
|
+
if (store.getReducerSnapshot === undefined) return null
|
|
65
|
+
try {
|
|
66
|
+
const found = await store.getReducerSnapshot(scope, name, reducer.version)
|
|
67
|
+
if (found === null || found.version !== reducer.version) return null
|
|
68
|
+
if (!Number.isInteger(found.cursor) || found.cursor < 0) return null
|
|
69
|
+
return found
|
|
70
|
+
} catch {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Write a snapshot best-effort; a failing store never reaches the caller. */
|
|
76
|
+
export async function persistReducerSnapshot(
|
|
77
|
+
store: Partial<SnapshotStore>,
|
|
78
|
+
scope: SnapshotScope,
|
|
79
|
+
name: string,
|
|
80
|
+
snapshot: ReducerSnapshot,
|
|
81
|
+
): Promise<boolean> {
|
|
82
|
+
if (store.putReducerSnapshot === undefined) return false
|
|
83
|
+
try {
|
|
84
|
+
return await store.putReducerSnapshot(scope, name, snapshot)
|
|
85
|
+
} catch {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { AbEvent } from '../events/catalog'
|
|
12
12
|
import type { PinnedAsset } from '../events/payloads'
|
|
13
|
+
import { defineReducer } from '../kernel/incremental'
|
|
13
14
|
import { contentHash, type BlobStore, type BuildStore } from './types'
|
|
14
15
|
|
|
15
16
|
export type { PinnedAsset }
|
|
@@ -379,19 +380,40 @@ export async function samplePinnedAssets(
|
|
|
379
380
|
return (await store.listTicketAssets(repo, ticketId)).map(pinnedAssetOf)
|
|
380
381
|
}
|
|
381
382
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
383
|
+
export const PINNED_ASSETS_REDUCER_VERSION = 1
|
|
384
|
+
|
|
385
|
+
/** The asset sets recorded by every `build.created`/`spec.revised` pin event
|
|
386
|
+
* that carries one, in array order. Each pin event replaces the set, so the
|
|
387
|
+
* newest decides the live pin; all of them answer "was this revision ever
|
|
388
|
+
* named". `pinnedRevision` is the query over the finished state. */
|
|
389
|
+
export const pinnedAssetsReducer = defineReducer<
|
|
390
|
+
{ pins: PinnedAsset[][] },
|
|
391
|
+
AbEvent,
|
|
392
|
+
{ pins: PinnedAsset[][] }
|
|
393
|
+
>({
|
|
394
|
+
version: PINNED_ASSETS_REDUCER_VERSION,
|
|
395
|
+
initial: () => ({ pins: [] }),
|
|
396
|
+
fold(acc, events) {
|
|
397
|
+
for (const event of events) {
|
|
398
|
+
if (event.type !== 'build.created' && event.type !== 'spec.revised') continue
|
|
399
|
+
const assets = event.payload.assets
|
|
400
|
+
if (assets === undefined) continue
|
|
401
|
+
acc.pins.push(assets)
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
finish: (acc) => ({ pins: acc.pins.slice() }),
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
/** Latest-pinned revision of `kind/name` over a reduced pin state, or (with
|
|
408
|
+
* `rev`) the revision when any pin named it. */
|
|
409
|
+
export function pinnedRevision(
|
|
410
|
+
state: { pins: readonly (readonly PinnedAsset[])[] },
|
|
386
411
|
kind: string,
|
|
387
412
|
name: string,
|
|
388
413
|
rev?: number,
|
|
389
414
|
): number | undefined {
|
|
390
415
|
let latest: number | undefined
|
|
391
|
-
for (const
|
|
392
|
-
if (event.type !== 'build.created' && event.type !== 'spec.revised') continue
|
|
393
|
-
const assets = event.payload.assets
|
|
394
|
-
if (assets === undefined) continue
|
|
416
|
+
for (const assets of state.pins) {
|
|
395
417
|
if (rev !== undefined) {
|
|
396
418
|
if (assets.some((a) => a.kind === kind && a.name === name && a.revision === rev)) return rev
|
|
397
419
|
continue
|
|
@@ -403,6 +425,17 @@ export function findPinnedRevision(
|
|
|
403
425
|
return latest
|
|
404
426
|
}
|
|
405
427
|
|
|
428
|
+
/** Latest-pinned revision of `kind/name` as the build's events record it, or
|
|
429
|
+
* (with `rev`) the revision when any pin in the log named it. */
|
|
430
|
+
export function findPinnedRevision(
|
|
431
|
+
events: AbEvent[],
|
|
432
|
+
kind: string,
|
|
433
|
+
name: string,
|
|
434
|
+
rev?: number,
|
|
435
|
+
): number | undefined {
|
|
436
|
+
return pinnedRevision(pinnedAssetsReducer.reduce(events), kind, name, rev)
|
|
437
|
+
}
|
|
438
|
+
|
|
406
439
|
/** `BuildStore.getPinnedTicketAsset` for any adapter: read the build's own
|
|
407
440
|
* events to find the pinned revision, then fetch exactly that revision from the
|
|
408
441
|
* ticket's assets. Earlier revisions stay retrievable after a replace or a
|
|
@@ -26,6 +26,8 @@ import type {
|
|
|
26
26
|
} from '../events/sessions'
|
|
27
27
|
export type { SessionEventEnvelope }
|
|
28
28
|
import type { TicketRef } from '../ontology'
|
|
29
|
+
import type { ReducerSnapshot, SnapshotScope } from './snapshots'
|
|
30
|
+
export type { ReducerSnapshot, SnapshotScope }
|
|
29
31
|
import type {
|
|
30
32
|
TicketAsset,
|
|
31
33
|
TicketAssetInput,
|
|
@@ -115,6 +117,12 @@ export interface BuildDigest {
|
|
|
115
117
|
* when any of its merges is strictly after the oldest unclaimed
|
|
116
118
|
* observation). */
|
|
117
119
|
merged?: string
|
|
120
|
+
/** A `pr-attachment.hosted` has no later matching `pr-attachment.reclaimed`
|
|
121
|
+
* (the `pendingPrAttachmentReclaims` rule). Absent otherwise. */
|
|
122
|
+
reclaimPending?: true
|
|
123
|
+
/** The latest `execution.started` has no later `execution.ended` with the
|
|
124
|
+
* same `instance` (the `openExecution` rule). Absent otherwise. */
|
|
125
|
+
executionOpen?: true
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
export interface RepositoryRecord {
|
|
@@ -365,6 +373,36 @@ export interface BuildStore {
|
|
|
365
373
|
* replay yields (`projectRepositoryStateEvents` is the normative oracle).
|
|
366
374
|
* Unknown repo rejects like `getRepoEvents`. */
|
|
367
375
|
getRepoStateEvents(repo: string): Promise<RepositoryEvent[]>
|
|
376
|
+
/** The journal's true high-water mark: the greatest assigned seq of any
|
|
377
|
+
* event type, 0 for an empty journal. Returns no event rows — the cursor a
|
|
378
|
+
* long-lived reader (the dispatcher's repository view) needs to resume with
|
|
379
|
+
* `getRepoEvents(repo, cursor)` even when the bounded subset excludes the
|
|
380
|
+
* journal's trailing events. Unknown repo rejects like `getRepoEvents`;
|
|
381
|
+
* never an `ensureRepo` side effect. */
|
|
382
|
+
getRepoHighWater(repo: string): Promise<number>
|
|
383
|
+
/** The stored reducer snapshot for a scope (a build log or a repository
|
|
384
|
+
* journal), a cache beside the log and never an authority. `null` when
|
|
385
|
+
* absent, when the stored version differs from `version`, when the stored
|
|
386
|
+
* cursor is ahead of the scope's log tail, or when the scope is unknown (no
|
|
387
|
+
* `ensureRepo` side effect, no rejection). A store without snapshot storage
|
|
388
|
+
* answers `null`. */
|
|
389
|
+
getReducerSnapshot(
|
|
390
|
+
scope: SnapshotScope,
|
|
391
|
+
reducer: string,
|
|
392
|
+
version: number,
|
|
393
|
+
): Promise<ReducerSnapshot | null>
|
|
394
|
+
/** Upsert a reducer snapshot; the stored cursor never decreases. Writes only
|
|
395
|
+
* when no row exists, when `cursor` is above the stored one (whatever the
|
|
396
|
+
* versions — a mismatched row is replaced as soon as any writer advances
|
|
397
|
+
* past it), or when the cursor is equal and `version` is higher. Everything
|
|
398
|
+
* else, and any cursor beyond the log tail, is a no-op. Atomic, so
|
|
399
|
+
* concurrent writers cannot undo each other's progress. Resolves `true`
|
|
400
|
+
* when the row was written. */
|
|
401
|
+
putReducerSnapshot(
|
|
402
|
+
scope: SnapshotScope,
|
|
403
|
+
reducer: string,
|
|
404
|
+
snapshot: ReducerSnapshot,
|
|
405
|
+
): Promise<boolean>
|
|
368
406
|
putRepoArtifact(repo: string, artifact: ArtifactInput): Promise<RepositoryArtifactMeta>
|
|
369
407
|
getRepoArtifact(repo: string, kind: string, rev?: number): Promise<RepositoryArtifact | null>
|
|
370
408
|
listRepoArtifacts(repo: string, kind?: string): Promise<RepositoryArtifactMeta[]>
|
package/skills/guide/SKILL.md
CHANGED
|
@@ -732,7 +732,8 @@ state eligible.
|
|
|
732
732
|
|---|---|---|---|
|
|
733
733
|
| `source` | — | **required**, nonblank builtin or plugin registration name | Which provider backs all ticket operations. Unknown names report every available builtin and plugin source. |
|
|
734
734
|
| `readyLabels` | — (source-aware) | optional; array of nonempty strings | A ticket must carry **every** listed label to be dispatchable. `[]` = **no label gate**. Absent uses the source default below. |
|
|
735
|
-
| `readyState` | — | **required**, non-blank string | The one workflow state a ticket must sit in to be dispatchable. Linear matches exactly and case-sensitively; file
|
|
735
|
+
| `readyState` | — | **required**, non-blank string | The one workflow state a ticket must sit in to be dispatchable. Linear matches exactly and case-sensitively; file matches it case-insensitively to a state directory (`ready` → `ready/`). There is no any-state mode. |
|
|
736
|
+
| `readyAssignee` | — | optional; `"me"` or an email; `linear` only | Also require the ticket to be assigned to that Linear user (`me` = owner of `LINEAR_API_KEY`); unassigned tickets are never ready. |
|
|
736
737
|
| `teamKey` | — | required by `linear`, forbidden by `file`, allowed for plugins | The Linear team key (e.g. `"ENG"`) or an existing plugin configuration field. |
|
|
737
738
|
| `claimedState` | — | optional nonempty string; forbidden by `file`, allowed for plugins | Workflow state `claim()` moves a ticket to when a build starts. |
|
|
738
739
|
| `createState` | — | optional, nonempty string | Default state for new tickets when a create does not name one. Absent = the provider's default (Linear: the team's default, e.g. Backlog; file: Triage). |
|
|
@@ -772,11 +773,16 @@ Cross-field rules, each an **error**:
|
|
|
772
773
|
janitor completion. Every source-agnostic `ab ticket` command loads and uses
|
|
773
774
|
the same configured registration.
|
|
774
775
|
|
|
775
|
-
The file tracker is **directory-per-state**: `<dir>/<state>/<id>.md
|
|
776
|
-
|
|
776
|
+
The file tracker is **directory-per-state**: `<dir>/<state>/<id>.md`, where
|
|
777
|
+
every directory directly under the root is a state: the four lifecycle
|
|
778
|
+
directories `triage/ ready/ doing/ done/` (always present, created on first use)
|
|
779
|
+
plus any others the repository adds, such as `icebox/`. A state exists when its
|
|
780
|
+
directory exists; state names match directories case-insensitively, and a name
|
|
781
|
+
with no directory is an error listing those that exist. The directory *is* the
|
|
782
|
+
state, so a transition or
|
|
777
783
|
claim is a rename — frontmatter carries no `state`/`claimedBy`, and a ticket
|
|
778
784
|
body survives byte-exactly because a move never rewrites the file. A malformed
|
|
779
|
-
record in any
|
|
785
|
+
record in any state directory is diagnosed with its path and validation
|
|
780
786
|
problem, left untouched, and excluded from listing/claim so unrelated valid
|
|
781
787
|
ready work can continue. `ab ticket list` sends those diagnostics to stderr and
|
|
782
788
|
keeps JSON stdout as one bare valid-ticket array. Tracker-wide safety checks
|
|
@@ -784,8 +790,8 @@ remain fatal: the same id in two state dirs is a loud error naming both paths,
|
|
|
784
790
|
as are root-level ticket files with no state and filesystem failures. When
|
|
785
791
|
`dir` is defaulted, the backlog writes its own `.gitignore` of `*`, so git never sees it; an
|
|
786
792
|
explicit `dir` is the user's and is left alone. Agents and operators drive it
|
|
787
|
-
through the source-agnostic `ab ticket` commands rather than running `mv`
|
|
788
|
-
hand.
|
|
793
|
+
through the source-agnostic `ab ticket` commands rather than running `mv` or
|
|
794
|
+
`mkdir` by hand; `ab ticket state create` makes a new state.
|
|
789
795
|
|
|
790
796
|
**Secrets never live in this file.** `LINEAR_API_KEY` and plugin-declared
|
|
791
797
|
`requiredEnv` credentials are environment variables (a local `.env` works).
|
|
@@ -893,7 +899,7 @@ repo path, needs no `AB_*` environment, and is safe to re-run. It:
|
|
|
893
899
|
base for `ab upgrade`'s three-way merges.
|
|
894
900
|
- Rewrites frontmatter on install: `name` → `ab-<name>`, and
|
|
895
901
|
`disable-model-invocation: true` on every skill outside the model-invocable
|
|
896
|
-
set (`ab-spec`, `ab-tickets`, `ab-guide`).
|
|
902
|
+
set (`ab-spec`, `ab-tickets`, `ab-guide`, `ab-operate`).
|
|
897
903
|
|
|
898
904
|
The final report states `autobuild.toml: written|skipped`, aggregates all skill
|
|
899
905
|
counts, names only attention-worthy `kept` and `overwritten` skills, and reports
|
|
@@ -1063,6 +1069,19 @@ with Linear and the file tracker, without provider-specific API/MCP calls:
|
|
|
1063
1069
|
labeled metadata and the body verbatim, so it can read a stored spec back.
|
|
1064
1070
|
- `ab ticket move <id> <state> [--json]` transitions one ticket and reports its
|
|
1065
1071
|
post-transition value.
|
|
1072
|
+
- `ab ticket states [--json]` lists every state of the configured source:
|
|
1073
|
+
lifecycle states first (triage, ready, doing, done), then custom states
|
|
1074
|
+
alphabetically. Each row has the state name, the roles it plays under the
|
|
1075
|
+
current config (create, ready, claimed, triage, proposal, done, or none), its
|
|
1076
|
+
ticket count, and its purpose when set. `--json` emits one bare array of
|
|
1077
|
+
`{name, roles, tickets, about?}`.
|
|
1078
|
+
- `ab ticket state create <name> [--about <text>] [--json]` makes a state, for
|
|
1079
|
+
example `icebox`. It is idempotent: an existing state is reported as already
|
|
1080
|
+
existing, and `--about` (one line) records or replaces its purpose. A name is
|
|
1081
|
+
one to forty lowercase letters, digits, and hyphens starting with a letter,
|
|
1082
|
+
and may not match an existing state case-insensitively. There is no rename or
|
|
1083
|
+
delete. Only the file source supports `states` and `state create`; other
|
|
1084
|
+
sources exit nonzero naming the source and the unsupported operation.
|
|
1066
1085
|
|
|
1067
1086
|
State names and ids are source-local. For block and unblock, the first id is the
|
|
1068
1087
|
ticket being changed and the comma-separated second operand names its blockers.
|
|
@@ -1849,7 +1868,7 @@ default, when you need to know what this repo's version says).
|
|
|
1849
1868
|
| Skill | Place in the lifecycle | Purpose |
|
|
1850
1869
|
|---|---|---|
|
|
1851
1870
|
| `ab-spec` | Before a build exists | Design a feature spec-first through conversation, or flesh out a ticket to the spec standard. The human-interactive surface; takes a ticket, not a build slug. **Model-invocable.** |
|
|
1852
|
-
| `ab-tickets` | Before a build exists | Drive this repo's local file tracker: create a ticket, report the backlog, groom or move one between `triage/ ready/ doing/ done
|
|
1871
|
+
| `ab-tickets` | Before a build exists | Drive this repo's local file tracker: create a ticket, report the backlog, groom or move one between the lifecycle directories (`triage/ ready/ doing/ done/`) and any repository-defined states, including the icebox. The agent-facing surface on the tracker — use it instead of `mv`. **Model-invocable.** |
|
|
1853
1872
|
| `ab-guide` | Outside the pipeline | This skill: reference for the lifecycle, config surface, setup/upgrade behavior, and the installed skills. Its editable `references/setup.md` guides the init handoff. **Model-invocable.** |
|
|
1854
1873
|
| `ab-harvest` | harvest `synthesize` step | Continue the producer across review rounds: cluster the claimed structured observations and author typed spec-standard create/join/suppress proposals. Runner-only. |
|
|
1855
1874
|
| `ab-harvest-review` | harvest `review` step | Fresh adversarial reviewer for proposal coverage, semantic dedup, spec quality, and evidence; returns `approve`/`revise`/`escalate`. Runner-only. |
|
|
@@ -1859,16 +1878,14 @@ default, when you need to know what this repo's version says).
|
|
|
1859
1878
|
| `ab-code-review` | `code-review` phase | Fresh skeptic: review the implementation diff against spec and plan, same verdict vocabulary. |
|
|
1860
1879
|
| `ab-reconcile` | `reconcile` phase (epilogue) | Resolve a conflicted PR with one merge commit, base merged *into* the build branch. Never rebases. |
|
|
1861
1880
|
| `ab-finalize` | `finalize` phase | Write the PR description for a green build; the kernel opens the PR. |
|
|
1862
|
-
| `ab-operate` |
|
|
1863
|
-
|
|
1864
|
-
Autobuild installs 12 skills. Everything except `ab-spec`, `ab-tickets`,
|
|
1865
|
-
`ab-guide` carries `disable-model-invocation: true` — do not
|
|
1866
|
-
skill yourself, and do not remove that key to make one
|
|
1867
|
-
model starting a pipeline phase by pattern-matching a
|
|
1868
|
-
what the flag prevents. Those
|
|
1869
|
-
the criterion for membership (§16.3): `ab-spec` and
|
|
1870
|
-
human/agent-facing pre-build surfaces,
|
|
1871
|
-
including the setup handoff guidance
|
|
1872
|
-
|
|
1873
|
-
embedded orchestrator's own operating manual, read by the hosted turn runner as
|
|
1874
|
-
its system prompt and never invoked by a model in a build session.
|
|
1881
|
+
| `ab-operate` | outside the pipeline; model-invocable | The orchestrator role's operating manual, for any agent: file and groom tickets, watch builds with a resumable `ab watch` / `ab wait` loop, answer escalations, pause, resume, abort, control auto-merge, and keep going until every shepherded build is merged, closed, or aborted. Also read by the hosted turn runner as its system prompt (registry tools, approval and budget suspension, wake turns). |
|
|
1882
|
+
|
|
1883
|
+
Autobuild installs 12 skills. Everything except `ab-spec`, `ab-tickets`,
|
|
1884
|
+
`ab-guide`, and `ab-operate` carries `disable-model-invocation: true` — do not
|
|
1885
|
+
invoke a phase skill yourself, and do not remove that key to make one
|
|
1886
|
+
convenient to call. A model starting a pipeline phase by pattern-matching a
|
|
1887
|
+
description is exactly what the flag prevents. Those four exceptions drive no
|
|
1888
|
+
build phase, which is the criterion for membership (§16.3): `ab-spec` and
|
|
1889
|
+
`ab-tickets` are human/agent-facing pre-build surfaces, `ab-guide` is
|
|
1890
|
+
read-only reference including the setup handoff guidance, and `ab-operate` is
|
|
1891
|
+
the manual for the orchestrator role, which any agent may take on.
|
|
@@ -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/skills/operate/SKILL.md
CHANGED
|
@@ -1,17 +1,140 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: operate
|
|
3
|
-
description:
|
|
3
|
+
description: Orchestrate a repository's builds - file and groom tickets, watch builds, answer escalations, pause, resume, or abort builds, control auto-merge, and drive work to merge. Use when asked to watch, shepherd, or orchestrate builds, whether from a coding-agent session at the repository root using the ab CLI or inside the hosted service with the operator tool registry.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Operating the repository
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
The orchestrator is a role, not a deployment. It is whichever agent is
|
|
9
|
+
pointed at a repository to file tickets, watch builds, unblock them, answer
|
|
10
|
+
operator questions, and drive work to merge. Any agent can take it on: a
|
|
11
|
+
coding-agent session at the repository root using the `ab` CLI (or `ab mcp`),
|
|
12
|
+
or the hosted service's embedded turn runner. There is no initialization
|
|
13
|
+
step — deciding to orchestrate is enough.
|
|
14
|
+
|
|
15
|
+
The orchestrator is not a build phase: it never runs pipeline steps, never
|
|
16
|
+
edits the repository, and never touches a build's checkout. Its reach is the
|
|
17
|
+
operator surface — the `ab` CLI, or the operator tool registry bound into a
|
|
18
|
+
hosted turn.
|
|
19
|
+
|
|
20
|
+
Find your setting and read that section: **Running locally with the `ab`
|
|
21
|
+
CLI** if you are a session with a shell, **Running in the hosted service** if
|
|
22
|
+
your tools are the registry's `builds.*` / `tickets.*` family. The sections
|
|
23
|
+
after them apply to both.
|
|
24
|
+
|
|
25
|
+
## Running locally with the `ab` CLI
|
|
26
|
+
|
|
27
|
+
Run the commands below from the repository root. Each acts with the operator's
|
|
28
|
+
authority, so say what you are doing before you do anything destructive.
|
|
29
|
+
|
|
30
|
+
### Watch for attention events
|
|
31
|
+
|
|
32
|
+
`ab watch --json` streams the attention set (escalations, failures, finalize,
|
|
33
|
+
PR conflicted/merged/closed, build completed/aborted) as newline-delimited
|
|
34
|
+
records. Every record carries a `cursor`; a bounded watch ends with a trailing
|
|
35
|
+
`{"cursor": …}` line. Resume with `--since <cursor>` and every event after it
|
|
36
|
+
is delivered exactly once, including events that landed while nothing was
|
|
37
|
+
watching. Name slugs to watch only those builds; with none, every active build
|
|
38
|
+
(including ones created later) is watched. `--event <glob>` replaces the
|
|
39
|
+
default filter, `--timeout <dur>` bounds it (default 30 minutes, `0` is
|
|
40
|
+
unbounded), `--count <n>` ends it after n records.
|
|
41
|
+
|
|
42
|
+
`ab wait [<slug>...] (--for <condition> | --event <glob>) [--since <cursor>] [--json]`
|
|
43
|
+
blocks until a condition holds and prints one record. `--for` state conditions
|
|
44
|
+
are also satisfied immediately by a build already in that state; `--event` is
|
|
45
|
+
satisfied only by new events. Conditions include `attention`,
|
|
46
|
+
`blocked`, `paused`, `running`, `done`, `aborted`, `terminal`, `pr=merged`,
|
|
47
|
+
`pr=closed`, `pr=conflicted`, and `phase=<phase>`. Exit codes: 0 satisfied,
|
|
48
|
+
1 usage or store error, 2 every named build is terminal without satisfying a
|
|
49
|
+
condition, 3 timeout, 4 interrupted. On 3 and 4 with `--json`, the output is a
|
|
50
|
+
bare `{"cursor": …}` to resume from.
|
|
51
|
+
|
|
52
|
+
Run long watches and waits in whatever background or monitor facility your
|
|
53
|
+
harness provides, so the session stays responsive and you are woken when they
|
|
54
|
+
emit. Never block a foreground command for their whole duration.
|
|
55
|
+
|
|
56
|
+
### Keep watching until the work is done
|
|
57
|
+
|
|
58
|
+
Checking once is not shepherding. When you take responsibility for builds:
|
|
59
|
+
|
|
60
|
+
1. Record the set you are shepherding (`ab builds --all --json`, or the slugs
|
|
61
|
+
you were given).
|
|
62
|
+
2. Arm an event-only watch and keep its cursor:
|
|
63
|
+
`ab watch <slugs…> --json --since <cursor>` (omit `--since` the first
|
|
64
|
+
time). Events are delivered exactly once, so a build that already finished
|
|
65
|
+
is never replayed. Do not loop on `ab wait --for terminal|attention|…`:
|
|
66
|
+
state conditions are satisfied at once by any build already in that state,
|
|
67
|
+
even with `--since`, so a finished build would be returned again and the
|
|
68
|
+
loop would spin on it. If you use `ab wait`, use only `--event <glob>`
|
|
69
|
+
conditions (satisfied by new events only), never `--for`.
|
|
70
|
+
3. Reconcile current state right after the first watch is armed, and again
|
|
71
|
+
whenever a watch ends without records: a watch baselines each build at its
|
|
72
|
+
current position and does not emit what already happened, so a build that
|
|
73
|
+
merged, or an escalation already open, before registration would otherwise
|
|
74
|
+
never reach you. Run `ab build status <slug> --json` for every slug in the
|
|
75
|
+
set. Reading status after arming leaves no gap — anything earlier shows in
|
|
76
|
+
the status, anything later arrives as an event. Treat a terminal status
|
|
77
|
+
(merged, closed, aborted) as that build's outcome, and an open escalation
|
|
78
|
+
or failed phase as attention to handle now.
|
|
79
|
+
4. When a watch returns records, react: read the record, inspect the build,
|
|
80
|
+
answer or escalate, then re-arm from the last cursor. Keep your own
|
|
81
|
+
outcome table: mark a build done when you see `pr.merged`, `pr.closed`, or
|
|
82
|
+
`build.aborted`, or when status shows it. A timeout (exit 3, or a watch
|
|
83
|
+
that ends with only a `{"cursor": …}` line) is not completion — reconcile
|
|
84
|
+
as in step 3, then re-arm from that cursor.
|
|
85
|
+
5. Stop only when every build in the set has an outcome in your table.
|
|
86
|
+
`pr.conflicted` is not terminal — reconcile follows. An open escalation
|
|
87
|
+
(`blocked`) needs a response, not patience. A watch that exits because
|
|
88
|
+
every named build is terminal still needs the step 3 status read to learn
|
|
89
|
+
how each ended.
|
|
90
|
+
6. Report each outcome to the operator, and file or move tickets when the
|
|
91
|
+
outcome makes that obviously right.
|
|
92
|
+
|
|
93
|
+
### Inspect
|
|
94
|
+
|
|
95
|
+
- `ab builds [--queued] [--all] [--json]` — builds and their effective status.
|
|
96
|
+
- `ab build status <slug> [--events N]` — one build's detail and recent events.
|
|
97
|
+
- `ab repository status` — the repository's derived status.
|
|
98
|
+
- `ab artifact` — fetch one artifact's bytes.
|
|
99
|
+
|
|
100
|
+
Read events with a cursor or a small `--events` count; logs are long.
|
|
101
|
+
|
|
102
|
+
### Act
|
|
103
|
+
|
|
104
|
+
- `ab answer <slug> [text]` — answer an open escalation with guidance; omit the
|
|
105
|
+
text for a bare retry. `--dismiss` retires cited review findings,
|
|
106
|
+
`--review-round-ceiling <n>` raises the round limit, `--revise-spec <file>`
|
|
107
|
+
resolves with a revised spec. Run `ab help answer` for the full menu.
|
|
108
|
+
- `ab pause <slug>` / `ab resume <slug>`; `ab pause --all` / `ab resume --all`
|
|
109
|
+
hold and release the whole repository.
|
|
110
|
+
- `ab abort <slug>` — destructive: closes the PR, removes branches, and
|
|
111
|
+
returns the ticket to triage.
|
|
112
|
+
- `ab auto-merge <slug> on|off` — request or cancel native auto-merge.
|
|
113
|
+
|
|
114
|
+
### Tickets
|
|
115
|
+
|
|
116
|
+
`ab ticket create|update|block|unblock|list|show|move|states` file, edit, and
|
|
117
|
+
move tickets; the `ab-tickets` skill covers the tracker's details. Moving a
|
|
118
|
+
ticket into the ready state sends it to dispatch — do that only when the
|
|
119
|
+
ticket genuinely conforms and the operator's intent is clear.
|
|
120
|
+
|
|
121
|
+
### What the CLI does not offer
|
|
122
|
+
|
|
123
|
+
The registry has operator notes, harvest control, and `discard`, which have no
|
|
124
|
+
CLI equivalent, and no bulk control beyond `pause --all` / `resume --all`. Say
|
|
125
|
+
so rather than improvising; tell the operator, or record it with `ab observe`
|
|
126
|
+
when you are inside a build. `ab mcp` mounts the same registry over MCP for
|
|
127
|
+
harnesses that support it — if you have those tools, the hosted section's tool
|
|
128
|
+
descriptions apply.
|
|
129
|
+
|
|
130
|
+
## Running in the hosted service
|
|
131
|
+
|
|
132
|
+
This section applies when your tools are the operator registry bound into
|
|
133
|
+
your turn. If you are a session with a shell and the `ab` CLI, use the
|
|
134
|
+
section above and ignore this one. In the hosted service you never supply a
|
|
135
|
+
repository argument; it is bound for you.
|
|
136
|
+
|
|
137
|
+
### How you run
|
|
15
138
|
|
|
16
139
|
Each of your turns is one durable agent loop:
|
|
17
140
|
|
|
@@ -26,7 +149,7 @@ Each of your turns is one durable agent loop:
|
|
|
26
149
|
- Your tools target exactly one repository, the session's own. You never
|
|
27
150
|
supply a repository argument; it is bound for you.
|
|
28
151
|
|
|
29
|
-
|
|
152
|
+
### Suspension
|
|
30
153
|
|
|
31
154
|
Your turn can stop in two ways, and both are normal:
|
|
32
155
|
|
|
@@ -43,7 +166,7 @@ Your turn can stop in two ways, and both are normal:
|
|
|
43
166
|
When suspended, just stop cleanly. Anything important you want to say should
|
|
44
167
|
already be in your streamed output before the suspension point.
|
|
45
168
|
|
|
46
|
-
|
|
169
|
+
### The tool surface
|
|
47
170
|
|
|
48
171
|
Your tools are the operator registry. The read-only ones are always safe:
|
|
49
172
|
|
|
@@ -81,7 +204,7 @@ Every tool returns the operator API's shapes: results, or a typed failure
|
|
|
81
204
|
(`kind`, `error`, `code`). A refusal is information, not an exception:
|
|
82
205
|
report it, adapt, and carry on.
|
|
83
206
|
|
|
84
|
-
|
|
207
|
+
### The attention set
|
|
85
208
|
|
|
86
209
|
When the session has wake settings, the dispatcher wakes you when a build
|
|
87
210
|
event matching them lands. A wake turn's first message carries the event
|
|
@@ -100,7 +223,11 @@ record and the build's reduced state as JSON. Typical wakes and what to do:
|
|
|
100
223
|
Do not re-derive history the build's events already tell you; read events
|
|
101
224
|
with a cursor and keep it short.
|
|
102
225
|
|
|
103
|
-
##
|
|
226
|
+
## Shared rules
|
|
227
|
+
|
|
228
|
+
These apply in both settings. Where the text below says "plain streamed text", a local session means plain text to the human in the session.
|
|
229
|
+
|
|
230
|
+
### When to escalate instead of act
|
|
104
231
|
|
|
105
232
|
Stop and ask the operator — in plain streamed text — whenever:
|
|
106
233
|
|
|
@@ -109,18 +236,18 @@ Stop and ask the operator — in plain streamed text — whenever:
|
|
|
109
236
|
- Two readings of the operator's intent diverge in cost or risk.
|
|
110
237
|
- You would be guessing about product direction, priorities, or scope.
|
|
111
238
|
- 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
|
|
239
|
+
- The attention event is one you cannot act on with the tools you have at all.
|
|
113
240
|
|
|
114
241
|
Filing a ticket IS acting — do it when the request is concrete. Asking is
|
|
115
242
|
not a failure; an unasked destructive action is.
|
|
116
243
|
|
|
117
|
-
|
|
244
|
+
### Boundaries
|
|
118
245
|
|
|
119
|
-
- You never claim to have done anything outside the
|
|
246
|
+
- You never claim to have done anything outside the tools you have. If you cannot
|
|
120
247
|
observe it with a tool, say you cannot observe it.
|
|
121
248
|
- You never invent repository identity, ticket ids, or build slugs; every
|
|
122
249
|
id you act on came from a tool result or the conversation.
|
|
123
|
-
-
|
|
124
|
-
arrives mid-turn is answered after the current turn ends.
|
|
125
|
-
-
|
|
126
|
-
when you learn something durable.
|
|
250
|
+
- In the hosted service you do not run concurrent work: one turn at a time,
|
|
251
|
+
and a message that arrives mid-turn is answered after the current turn ends.
|
|
252
|
+
- Where operator notes exist (hosted), they are shared ground truth; keep
|
|
253
|
+
them current when you learn something durable.
|