axstack 0.11.7 → 0.12.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/package.json
CHANGED
|
@@ -11,7 +11,7 @@ Pair A/B is retired for this contract; its artefacts remain untouched.
|
|
|
11
11
|
|
|
12
12
|
- **driver** — every 15 minutes, fresh Opus session in the host's `root`
|
|
13
13
|
folder workspace, which is not a git repository and belongs to no project. It discovers GitHub work, binds the persistent Orca
|
|
14
|
-
Run,
|
|
14
|
+
Run, checks each selected PR's head out into a free project-local slot, dispatches the
|
|
15
15
|
matching Axstack agent, reconciles completions and decisions, then exits. The
|
|
16
16
|
driver is the automation session itself, with no `axstack-monitor` or
|
|
17
17
|
`axstack-owner` role row. It never performs review or repair in its own
|
|
@@ -97,8 +97,8 @@ The driver performs this order and exits:
|
|
|
97
97
|
inbox. For a `worker_done` matching a live marker, verify the review id at
|
|
98
98
|
the bound head, push range, or opened token. Release the worker; once its
|
|
99
99
|
release receipt is settled and the process has exited, run the cleanup
|
|
100
|
-
under "Run directory" below, which proves the
|
|
101
|
-
|
|
100
|
+
under "Run directory" below, which proves the slot disposable before
|
|
101
|
+
resetting it; then clear the marker. Unverifiable delivery stays in
|
|
102
102
|
`pending_settlement[]` and blocks only that PR.
|
|
103
103
|
3. Consume decisions as their sole consumer under "Decision tokens" below.
|
|
104
104
|
4. Reconcile every marker older than 3 h. A live worker gets `worker-stop`; an
|
|
@@ -125,47 +125,17 @@ The driver performs this order and exits:
|
|
|
125
125
|
## Dispatch and repair selection
|
|
126
126
|
|
|
127
127
|
Claude Code trusts a folder per git toplevel and stops at its "Quick safety
|
|
128
|
-
check" dialog otherwise
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
be the clone itself, and must sit at exactly the pinned head — anything
|
|
140
|
-
else exits 3 and is never seeded. It is one process on purpose: lock, refresher,
|
|
141
|
-
render and rename all happen in the same process, so nothing can outlive the
|
|
142
|
-
owner and commit after it dies. It writes under Claude Code's own config
|
|
143
|
-
lock — the `mkdir`-based `~/.claude.json.lock` directory its sessions take —
|
|
144
|
-
following Claude's own lease rules, with a bounded retry. Only a successful
|
|
145
|
-
`mkdir` counts as holding it; a fresh foreign lock is never broken, and the only lock it will
|
|
146
|
-
reclaim is one whose mtime is past Claude's 10 s stale threshold, which is
|
|
147
|
-
exactly what Claude itself treats as abandoned. It keeps the lease alive the
|
|
148
|
-
way Claude does: a refresher touches the lock's mtime every second for as
|
|
149
|
-
long as it is held, so the lock cannot age into staleness under it even if a
|
|
150
|
-
rename stalls. The refresher runs with the owner and dies with it, exits the
|
|
151
|
-
moment the lock is no longer ours, and treats a failed refresh as a
|
|
152
|
-
compromised lease by terminating the owner before it can commit. The helper
|
|
153
|
-
re-reads under the lock, refuses a store that does not parse, writes a
|
|
154
|
-
unique temp file, preserves the store's mode, re-verifies at commit that the
|
|
155
|
-
lease is healthy — unchanged inode, refresher alive, refreshed within the
|
|
156
|
-
last few seconds — and otherwise discards the temp and commits nothing,
|
|
157
|
-
treats a failed chmod or rename as failure with the temp removed, and
|
|
158
|
-
releases only a lock it still owns, stopping the refresher first. A busy or unreadable
|
|
159
|
-
store exits 2: the worktree is retained and nothing is dispatched. The
|
|
160
|
-
worktree cleanup removes the entry through the same helper; if that
|
|
161
|
-
removal fails after the worktree is gone, the marker stays in
|
|
162
|
-
`pending_settlement[]` as `untrust-pending` — which keeps the PR
|
|
163
|
-
undispatchable, so the reused path can never inherit a dead trust entry —
|
|
164
|
-
and the removal is retried next tick. A retained worktree keeps its entry
|
|
165
|
-
while retained; it is the same driver-created path. This is scoped
|
|
166
|
-
exactly there — never for any other path, never for a worktree it did not
|
|
167
|
-
create — because that dialog is the last guard between PR content and a
|
|
168
|
-
worker running with permissions bypassed. Trusting a folder activates the
|
|
128
|
+
check" dialog otherwise, and the driver never answers that dialog for a
|
|
129
|
+
worker. So workers run in a fixed pool: every allowlisted project has
|
|
130
|
+
exactly two slot worktrees, `slot-1` and `slot-2`, its Orca child worktrees
|
|
131
|
+
created once, parented to the project's primary worktree, and trusted once
|
|
132
|
+
by the user through that dialog. The driver never creates or removes a
|
|
133
|
+
worktree and never writes `~/.claude.json`. A slot is free when no live
|
|
134
|
+
dispatch marker names it, it is not in `retained_slots[]`, no terminal is
|
|
135
|
+
listed in it, and its tree is clean; no free slot in the project defers the
|
|
136
|
+
PR to `deferred[]` like a budget, not a hold. Taking a slot fetches the head into the project clone,
|
|
137
|
+
checks the slot out detached at the pinned head, and verifies HEAD equals
|
|
138
|
+
it; the marker's worktree is the slot path. Trusting a folder activates the
|
|
169
139
|
full project surface: its `.claude/settings.json` and the hooks it defines,
|
|
170
140
|
its `.mcp.json` servers, marketplace plugin auto-install, and `CLAUDE.md`;
|
|
171
141
|
a hostile branch's hooks or MCP servers would run the moment the folder
|
|
@@ -177,12 +147,12 @@ MCP servers, custom commands or agents load from anywhere, project or user;
|
|
|
177
147
|
built-in tools and authentication are untouched, and the brief loads the
|
|
178
148
|
skill files it needs by path. The driver confirms readiness from the
|
|
179
149
|
rendered frame — `wait.satisfied`, the prompt marker present, the dialog
|
|
180
|
-
absent — before dispatching
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
150
|
+
absent — before dispatching; a dialog on a slot means the slot is not
|
|
151
|
+
trusted: the slot is named in a health line, the PR is deferred, nothing is
|
|
152
|
+
dispatched. What remains live is the repository's files as data the worker
|
|
153
|
+
reads and the commands the worker itself chooses to run, which it already
|
|
154
|
+
runs today; nothing from the branch loads, executes, or is offered for
|
|
155
|
+
invocation on its own.
|
|
186
156
|
|
|
187
157
|
Every selected PR receives one dispatch marker with task id, dispatch id,
|
|
188
158
|
worktree, head, `started_at`, reservation (`verdict` or `repair`), and trigger:
|
|
@@ -207,10 +177,10 @@ An own PR needs repair when either trigger applies:
|
|
|
207
177
|
triggers again subject to the 24 h cap.
|
|
208
178
|
|
|
209
179
|
Repair also requires no deploy-on-push head branch, no live repair cap, and
|
|
210
|
-
selection of the lowest own PR in its stack that needs repair.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
dispatch one `axstack-watch` agent in authored repair mode. Its
|
|
180
|
+
selection of the lowest own PR in its stack that needs repair. Take a free
|
|
181
|
+
slot of that project (its slots are parented to that project's primary
|
|
182
|
+
worktree, so the work appears under the project it serves) at the exact
|
|
183
|
+
head, and dispatch one `axstack-watch` agent in authored repair mode. Its
|
|
214
184
|
brief contains only the triggering checks or review findings. Each open
|
|
215
185
|
descendant records one user-owned `pending restack` hold until it stops needing
|
|
216
186
|
repair. The 24 h cap starts at dispatch and an abandon does not refund it.
|
|
@@ -232,8 +202,8 @@ and head. Budget exhaustion records the count and is not a hold.
|
|
|
232
202
|
|
|
233
203
|
## Agents and verdicts
|
|
234
204
|
|
|
235
|
-
Every agent works in
|
|
236
|
-
head and reports only through the Orca worker protocol.
|
|
205
|
+
Every agent works in a project-local slot worktree checked out detached at
|
|
206
|
+
the exact head and reports only through the Orca worker protocol.
|
|
237
207
|
|
|
238
208
|
Peer review runs the two isolated configured reviewers on the identical brief,
|
|
239
209
|
then the Luna gate. `APPROVE` requires complete exact-head/base reviews, gate
|
|
@@ -372,12 +342,12 @@ is no gate for health findings.
|
|
|
372
342
|
## Run directory
|
|
373
343
|
|
|
374
344
|
The driver and watchdog run from the host's `root` folder workspace, not a
|
|
375
|
-
project worktree: no project owns the automation, and every
|
|
345
|
+
project worktree: no project owns the automation, and every slot worktree
|
|
376
346
|
belongs to the project it serves. That workspace is not a git repository, so
|
|
377
347
|
the run directory is private host state, one
|
|
378
348
|
`~/.local/share/axstack/runs/<run id>/` directory. Settlement leaves nothing
|
|
379
349
|
behind, but never destroys work. Before any destructive step the driver
|
|
380
|
-
proves the
|
|
350
|
+
proves the slot is disposable: the worker is settled — on the release path
|
|
381
351
|
a settled release receipt, on the abandon path an accepted abandon receipt,
|
|
382
352
|
either with proven process exit; pending or unknown stops here — the
|
|
383
353
|
worktree's HEAD is either the pinned head or a candidate that is durably
|
|
@@ -387,22 +357,27 @@ targeted fetch of that exact remote branch into a per-dispatch ref, never
|
|
|
387
357
|
shared clone can fake durability, a failed fetch retaining the worktree — or held by a
|
|
388
358
|
`refs/axstack/decisions/<token>` ref in the project clone; and, on the
|
|
389
359
|
abandon path, the worktree has no uncommitted changes.
|
|
390
|
-
Only then it closes any terminal tab still listed,
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
path, and verifies the directory is gone. On the settled path
|
|
360
|
+
Only then it closes any terminal tab still listed, resets the slot to the
|
|
361
|
+
pinned head, clears untracked artefacts, and verifies the slot is clean, so
|
|
362
|
+
it is back in the pool. On the settled path
|
|
394
363
|
the worker has finished, so untracked files are artefacts by definition and
|
|
395
|
-
are cleared; a candidate there is already pushed or token-held.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
364
|
+
are cleared; a candidate there is already pushed or token-held. A slot
|
|
365
|
+
whose release is settled but whose HEAD cannot be proven disposable, and an
|
|
366
|
+
abandoned slot that is dirty or holds an unproven candidate, are both
|
|
367
|
+
**retained**: named in one `health[]` line with path and SHA, and blocking
|
|
368
|
+
only that PR with the user as owner. Retention is mechanical on both paths:
|
|
369
|
+
the driver appends `retained_slots[]` `{slot, pr, head, reason}`, which the
|
|
370
|
+
free predicate excludes for every PR — a clean, terminal-less slot holding
|
|
371
|
+
an unpushed candidate would otherwise look free — and an entry is cleared
|
|
372
|
+
only by the user after reconciling the candidate. A dirty slot or a terminal that
|
|
373
|
+
outlives its dispatch without such a retention record is a health finding. The run directory contains:
|
|
400
374
|
|
|
401
375
|
- `cursor.json` — driver only, with these exact keys: `fingerprint`,
|
|
402
376
|
`tick_started_at`, `tick_done_at`, `tick_outcome`, `prs{url: {head, base,
|
|
403
377
|
draft, checks, reviews, last_self_review}}`, `dispatch_markers[]` (`pr`,
|
|
404
378
|
`task_id`, `dispatch_id`, `worktree`, `head`, `started_at`, `reservation`,
|
|
405
379
|
`trigger`), `deferred[]`, `pending_settlement[]`,
|
|
380
|
+
`retained_slots[]` (`slot`, `pr`, `head`, `reason`),
|
|
406
381
|
`repair_caps{url: {expires_at}}`, `abandon_count{head: n}`,
|
|
407
382
|
`processed_reviews[]` (`review_id`, `pr`, `head`, `digest`),
|
|
408
383
|
`deploy_on_push{repo: [branches]}`,
|
|
@@ -410,7 +385,6 @@ its dispatch without such a retention record is a health finding. The run direct
|
|
|
410
385
|
`runtime_refusal{code, first_seen, last_seen}` (absent when no runtime
|
|
411
386
|
hold is open);
|
|
412
387
|
- `pending.json`, `precheck.log` — driver precheck only;
|
|
413
|
-
- `trust.js` — the trust transaction helper, run by the driver only;
|
|
414
388
|
- `decisions/<token>.json` — writers assigned by the lifecycle table;
|
|
415
389
|
- `watchdog.log` and `watchdog-state.json` (occurrence `first_observed` values
|
|
416
390
|
and send receipts) — watchdog only;
|
|
@@ -445,12 +419,12 @@ delivery uses [axstack-relay](../../axstack-relay/SKILL.md).
|
|
|
445
419
|
`runtime_refusal {code, first_seen, last_seen}`; the same code keeps the
|
|
446
420
|
hold and updates `last_seen` without a new health line, a different code is
|
|
447
421
|
a new finding. Prose is never the key. When `worker-start` itself is
|
|
448
|
-
refused after the
|
|
422
|
+
refused after the slot was checked out, there is no worker, so the
|
|
449
423
|
settlement proof does not apply; the driver reads the receipt's `failedStage`
|
|
450
424
|
and `residualResources` first. With no Dispatch and no residual resources
|
|
451
425
|
the no-worker branch applies: the worktree's HEAD must equal the pinned
|
|
452
|
-
head and `git status --porcelain` must be empty, and
|
|
453
|
-
|
|
426
|
+
head and `git status --porcelain` must be empty, and then the slot is simply
|
|
427
|
+
free again in the same tick; there is nothing to remove.
|
|
454
428
|
With a Dispatch or any residual resource the failed start owns runtime
|
|
455
429
|
state, and retaining alone is not recovery: the driver follows the
|
|
456
430
|
runtime's recovery guide. With a Dispatch: `worker-list` for that run, and
|