@staix/agent-hub 0.12.19 → 0.12.20
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/CHANGELOG.md +16 -0
- package/README.md +3 -3
- package/docs/agent-notes/adapters.md +1 -0
- package/docs/agent-notes/bus.md +1 -0
- package/docs/agent-notes/daemon.md +2 -1
- package/docs/agent-notes/models.md +3 -0
- package/docs/agent-notes/tasks.md +3 -2
- package/docs/events.md +4 -1
- package/docs/operations.md +413 -17
- package/docs/quickstart.md +1 -1
- package/docs/security.md +17 -10
- package/docs/smoke.md +58 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +399 -3
- package/docs/specs/2026-09-20-pi-local-models-design.md +2 -0
- package/docs/specs/2026-09-20-upgrade-recovery-design.md +287 -0
- package/docs/specs/2026-10-04-switchyard-source-port-design.md +10 -0
- package/docs/verified.json +13 -13
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +63 -25
- package/src/adapters/claude-channel.ts +61 -20
- package/src/adapters/codex-appserver.ts +3 -0
- package/src/adapters/local-worker.ts +4 -0
- package/src/cli/console-state.ts +325 -102
- package/src/cli/console.ts +44 -23
- package/src/cli/help.ts +120 -0
- package/src/cli/identity.ts +3 -3
- package/src/cli/launch.ts +2 -0
- package/src/cli/main.ts +171 -75
- package/src/cli/preview.ts +1 -0
- package/src/cli/reset.ts +194 -0
- package/src/cli/status-lines.ts +9 -5
- package/src/cli/terminal-recovery.ts +140 -36
- package/src/cli/upgrade-runtime.ts +274 -56
- package/src/cli/upgrade.ts +366 -41
- package/src/hub/ask.ts +1 -1
- package/src/hub/board.ts +15 -7
- package/src/hub/bus.ts +60 -4
- package/src/hub/child-process.ts +1 -0
- package/src/hub/conductor.ts +38 -6
- package/src/hub/control-client.ts +5 -3
- package/src/hub/daemon.ts +56 -9
- package/src/hub/events.ts +7 -1
- package/src/hub/execution-budget.ts +15 -6
- package/src/hub/hub-tools.ts +4 -4
- package/src/hub/inference.ts +136 -1
- package/src/hub/lifecycle.ts +7 -1
- package/src/hub/peers.ts +2 -0
- package/src/hub/recovery-store.ts +51 -3
- package/src/hub/report.ts +43 -1
- package/src/hub/restart.ts +25 -0
- package/src/hub/routing.ts +48 -16
- package/src/hub/snapshots.ts +3 -2
- package/src/hub/tasks.ts +236 -83
- package/src/local/deny.ts +2 -1
- package/src/models/mlx.ts +11 -8
- package/src/models/ollama.ts +2 -2
- package/src/models/relay.ts +190 -42
- package/src/models/route/relay-selector.ts +43 -31
- package/src/models/route/runtime.ts +17 -7
- package/src/models/route/signals.ts +12 -1
- package/src/models/route/stage.ts +57 -3
- package/src/omniroute/client.ts +1 -1
- package/src/ui/index.html +1 -0
- package/templates/AGENTS.block.md +1 -1
- package/templates/routing.toml +8 -0
|
@@ -163,3 +163,290 @@ The target coordinator accepts authenticated protocol 9, 10 and 11 sources, then
|
|
|
163
163
|
uses protocol 11 after startup. Older supported managers are refreshed onto the
|
|
164
164
|
current implementation. The real 0.6.4/protocol-9 transition and the 0.12.0/protocol-10
|
|
165
165
|
development-hub transition are recorded in `docs/smoke.md`.
|
|
166
|
+
|
|
167
|
+
### Unmanaged Claude sessions (#206)
|
|
168
|
+
|
|
169
|
+
Amended 2026-10-09. An attached Claude session is managed when a live `ahub claude`
|
|
170
|
+
launcher is recorded for the running daemon instance (`terminal-recovery.json`, its
|
|
171
|
+
launcher identity checked with the shared `processSignature`) and the session record
|
|
172
|
+
(`claude-session.json`) was written by that launch. Managed sessions are closed and
|
|
173
|
+
resumed in a new terminal as before.
|
|
174
|
+
|
|
175
|
+
Any other attached Claude session is unmanaged: a plain `claude`, an `ahub claude`
|
|
176
|
+
outside Orca, or one whose launcher has ended. Its session record cannot be bound to
|
|
177
|
+
the attached session, so the plan drops the id (no output names it) and lists the
|
|
178
|
+
peer under `reconnectOnly`: no terminal is inspected, closed or relaunched, and the
|
|
179
|
+
source roster check compares its membership only. Before starting the target the
|
|
180
|
+
coordinator writes an operation-fenced waiver (`recovery-waivers.json`, mode 0600),
|
|
181
|
+
so the restored daemon does not require the saved session id for that peer; release
|
|
182
|
+
still requires it to reattach, which the coordinator waits for up to 90 seconds
|
|
183
|
+
(three times the plugin's longest reconnect backoff). Reconnect-only requires the
|
|
184
|
+
source control protocol to equal the target's; otherwise the plan names a blocker
|
|
185
|
+
whose next action is to end the session or relaunch it with `ahub claude` in Orca.
|
|
186
|
+
A live launcher whose record was written by another launch is a blocker, never a
|
|
187
|
+
target. Refused `upgrade --yes` and `restart --yes`, and every dry-run, print each
|
|
188
|
+
blocker on stderr before the final line.
|
|
189
|
+
|
|
190
|
+
The channel's hello carries no launch id, so a plain `claude` that took the peer id
|
|
191
|
+
from a live managed session is classified as managed. Telling them apart needs the
|
|
192
|
+
launch id at hello, which is a protocol change and is not claimed here. A live
|
|
193
|
+
upgrade with an unmanaged session attached has not been run.
|
|
194
|
+
|
|
195
|
+
### Partial operations (#215)
|
|
196
|
+
|
|
197
|
+
Amended 2026-10-09, before implementation.
|
|
198
|
+
|
|
199
|
+
Expired preparation. The source hold lapses after ten minutes. When a project is
|
|
200
|
+
still `prepared` (terminal effects may already be recorded) and its hold is gone,
|
|
201
|
+
resume re-prepares the same source instead of reporting "source is no longer
|
|
202
|
+
prepared". The running daemon must be the planned source instance: a replaced
|
|
203
|
+
daemon or a source held by another operation is refused, with nothing prepared,
|
|
204
|
+
closed or stopped. The coordinator prepares again under the same operation id,
|
|
205
|
+
waits for readiness within the preparation bound (on timeout it aborts only its
|
|
206
|
+
own hold) and checks the roster again. A peer whose terminal this operation
|
|
207
|
+
already closed must stay detached; every other peer must keep its planned
|
|
208
|
+
conversation. Effect receipts are never reset, so no terminal is closed twice,
|
|
209
|
+
and the exclusive runner claim and instance fencing are unchanged. The
|
|
210
|
+
re-prepared source records a closed peer as offline, so the target daemon does
|
|
211
|
+
not require that peer itself; the coordinator still requires every originally
|
|
212
|
+
attached peer back before release. A roster change while effects are recorded
|
|
213
|
+
names the peer and the choices below instead of asking for a new plan.
|
|
214
|
+
|
|
215
|
+
Native resume viability. A Codex thread counts as resumable only when a rollout
|
|
216
|
+
file naming it exists under `sessions/` of the store its restoration uses (the
|
|
217
|
+
launcher's captured `CODEX_HOME`, else `~/.codex`); an app-server thread id
|
|
218
|
+
alone proves nothing. The plan blocks a Codex peer without one, the coordinator
|
|
219
|
+
checks again before closing its terminal (closing nothing when it fails) and
|
|
220
|
+
before creating the replacement.
|
|
221
|
+
|
|
222
|
+
Launcher failure. Orca's `terminal create --command` types the command into a
|
|
223
|
+
login shell, which outlives it, so the terminal never exits with the launcher
|
|
224
|
+
and its exit is no evidence. The evidence is the hub's own: `ahub codex`,
|
|
225
|
+
`ahub claude` and `ahub pi --mode tui` record their launcher (pid and process
|
|
226
|
+
signature) in `terminal-recovery.json` for the terminal they run in, before
|
|
227
|
+
they start the native agent. A replacement is awaited in 5-second slices of
|
|
228
|
+
Orca's `tui-idle` wait (Orca 1.4.223 answers a timed-out wait with exit 1 and
|
|
229
|
+
`{"ok":false,"error":{"code":"timeout"}}`, which counts as not yet). Between
|
|
230
|
+
slices, and once the terminal reads idle (an idle terminal may be the shell the
|
|
231
|
+
launcher returned to), a recorded launcher that is gone means the launch failed,
|
|
232
|
+
recognized within one slice. A launcher whose identity cannot be read is unknown
|
|
233
|
+
and is waited for, never taken as gone. A handle Orca reports
|
|
234
|
+
`terminal_handle_stale` means the terminal is gone only when no recorded
|
|
235
|
+
launcher may still run and Orca no longer lists the terminal's incarnation;
|
|
236
|
+
otherwise the create stays uncertain. A launcher that died before recording
|
|
237
|
+
itself is not distinguished from a slow one, and whether Orca reports the bare
|
|
238
|
+
login shell as TUI-idle is not verified: if it does, the create stops at once as
|
|
239
|
+
not mappable to the session; if not, after the 10-minute readiness bound. Either
|
|
240
|
+
way it blocks as needing manual verification with the receipt `pending`, and the
|
|
241
|
+
next resume, finding no live launcher and no attached session, records `failed`. Resume settles a `pending` or `failed` receipt by what is live
|
|
242
|
+
(the table below): an attached planned (or accepted) session is the
|
|
243
|
+
restoration, a launcher that may run is waited for, and only nothing live makes
|
|
244
|
+
it `failed` or, for `failed`, launches again. No terminal is created while a
|
|
245
|
+
recorded launcher for the peer may run on the target or its session is attached.
|
|
246
|
+
A launch that failed, or a thread found not resumable before the launch, is
|
|
247
|
+
receipted `restored:<peer>` = `failed` and never counts as restored. A Codex
|
|
248
|
+
store that cannot be read (other than missing) is unknown and blocks the plan,
|
|
249
|
+
the close and the create rather than counting as not resumable. Claude's
|
|
250
|
+
zero-turn rule is unchanged; Pi resume viability is not checked.
|
|
251
|
+
|
|
252
|
+
Failed restoration. The operation blocks naming the peer, its session or thread
|
|
253
|
+
id and the choices. `resume` launches a failed peer again once its cause is
|
|
254
|
+
fixed; a thread that is still not resumable fails again with the same choices.
|
|
255
|
+
|
|
256
|
+
Status. `ahub recovery status <id>` reports the receipt (phase, step, update
|
|
257
|
+
time, error); whether a runner process holds the operation now (`runner.state`
|
|
258
|
+
`running` with its pid, or `none`); `stale` when the receipt says `running` but
|
|
259
|
+
no runner holds it; each project's phase and effect receipts (`closed:<peer>`,
|
|
260
|
+
`restored:<peer>` as `done`, `pending` or `failed`); whether the shared plugin
|
|
261
|
+
and the global CLI were installed; any lost continuity and disposition; and
|
|
262
|
+
`next`, the commands that apply now. It never contains task or message text.
|
|
263
|
+
`resume` does not start a second runner while one is alive.
|
|
264
|
+
|
|
265
|
+
Disposition. Two human-only choices, each with a required `--reason`, run from
|
|
266
|
+
an ordinary terminal (agent shells are refused by the CLI identity gate), claim
|
|
267
|
+
the runner (refusing while one is alive) and append to the receipt's audit:
|
|
268
|
+
|
|
269
|
+
- `ahub recovery dispose <id> --fresh-session <peer> --reason <text>` applies
|
|
270
|
+
only to a Codex or Claude peer whose restoration is receipted `failed`; Pi is
|
|
271
|
+
refused, because a restored hub refills a Pi start from its recorded resume.
|
|
272
|
+
Like reconnect-only (#206), it needs a target that reads
|
|
273
|
+
`recovery-waivers.json`: staging refuses a target without it when a
|
|
274
|
+
reconnect-only session is planned, and the fresh launch is refused on one. Per affected project
|
|
275
|
+
it records the lost session or thread id (`fresh`) and schedules `resume`,
|
|
276
|
+
which writes an operation-fenced waiver so the restored daemon accepts a new
|
|
277
|
+
session for that peer, launches the peer without a resume id in a new
|
|
278
|
+
terminal, records the id the daemon then reports, and verifies everything
|
|
279
|
+
else as before. The operation may then complete; its status keeps the lost
|
|
280
|
+
continuity.
|
|
281
|
+
- `ahub recovery dispose <id> --stop-and-archive --reason <text>` abandons the
|
|
282
|
+
operation. It inspects every project before acting. Every unverified project
|
|
283
|
+
must read as running, stopped or missing (`disposeRefusal`): any other reading
|
|
284
|
+
(unavailable, starting, stopping, incompatible, or an inspection that failed)
|
|
285
|
+
refuses the whole disposition, and a project whose directory is gone is
|
|
286
|
+
recorded as such (nothing to stop or archive). A
|
|
287
|
+
source this operation holds and has not committed gets its hold aborted and
|
|
288
|
+
keeps running. A target this operation started and has not released (a
|
|
289
|
+
running daemon in phase `restored` under this operation id, other than the
|
|
290
|
+
source, whether or not the receipt recorded its instance) is stopped by its
|
|
291
|
+
live instance through the lifecycle stop, which verifies the instance and
|
|
292
|
+
waits until its manifest and registry claim are gone, and is then inspected
|
|
293
|
+
again. A daemon this operation does not own, or one already released, is left
|
|
294
|
+
running. Each project's outcome is recorded as it happens; a disposition that
|
|
295
|
+
stops partway keeps the lock, and resume and `--fresh-session` then refuse
|
|
296
|
+
until `--stop-and-archive` is run again and finishes. A stopped project's committed
|
|
297
|
+
snapshot of this operation moves to `restart.abandoned.<hash>.json`, which is
|
|
298
|
+
never replayed, so an ordinary `up` works again. Queued envelopes are not
|
|
299
|
+
delivered from that file; a hub with a delivery journal (protocol 10 and
|
|
300
|
+
later) reloads the queues it persisted in `hub.db` on its next start.
|
|
301
|
+
Replacement terminals the operation created stay open, attached to nothing
|
|
302
|
+
until a hub runs again. Only then is the operation
|
|
303
|
+
recorded `cancelled` with its disposition (per-project outcome, plugin and CLI
|
|
304
|
+
state) and the lock released. It is never recorded as completed, the global
|
|
305
|
+
CLI is not promoted, and terminals it closed stay closed. A project whose
|
|
306
|
+
target ran is started again with the target's CLI. The archived reset proposed
|
|
307
|
+
in #214 can follow: no lock or replayable snapshot is left in its way.
|
|
308
|
+
- `abort` stays the escape for a preflight without effects; its refusal names
|
|
309
|
+
the disposition. It also cancels a prepared project whose source still runs as
|
|
310
|
+
the same instance after its hold lapsed, which was never committed. A roster
|
|
311
|
+
change found while no effects are recorded offers `abort` exactly when
|
|
312
|
+
`abortRefusal` allows it (this operation's lock refuses a new upgrade until it
|
|
313
|
+
is cancelled).
|
|
314
|
+
|
|
315
|
+
#### Receipts, evidence and next actions
|
|
316
|
+
|
|
317
|
+
Resume, abort, dispose, the `next` list of `status` and every error follow this
|
|
318
|
+
table. A `failed` receipt stays `failed` until the relaunch replaces it with
|
|
319
|
+
`pending`, so a refusal on the way (an unreadable store, a launch found live, a
|
|
320
|
+
target without waivers) keeps its choices. `--fresh-session` is neither offered
|
|
321
|
+
nor accepted when the target cannot read recovery waivers; `status` then says
|
|
322
|
+
why in its own `freshSession` field, since `next` holds commands only. A runner
|
|
323
|
+
record that cannot be read shows as `unknown` in `status`, never as no runner;
|
|
324
|
+
while it reads so, resume, abort and dispose are all refused (the runner claim
|
|
325
|
+
cannot tell its owner), so `next` names only `status` again. Otherwise `status` and every error that lists choices build them with one function
|
|
326
|
+
(`nextActions`), in this order: resume (which also launches a failed peer
|
|
327
|
+
again), unless it can never get past what is live (`resumeBlocked`): a
|
|
328
|
+
recorded final refusal (staging refused the fixed target or preserved source), a
|
|
329
|
+
missing project directory, a hub that speaks another control protocol, a
|
|
330
|
+
runtime other than the one the receipt expects (a replaced source; a target not
|
|
331
|
+
fenced to this operation, another instance or another version), a stopped
|
|
332
|
+
pending source, a prepared source of a #215 coordinator that stopped with no
|
|
333
|
+
commit request, a target that stopped after it started (restarting it is not
|
|
334
|
+
supported in this release: its peers would have to be relaunched against the
|
|
335
|
+
new instance), or any other stopped runtime without this operation's unreleased
|
|
336
|
+
restart snapshot to start from (unavailable, starting or stopping is waited
|
|
337
|
+
for). Then abort where `abortRefusal` allows it; a fresh session for each failed
|
|
338
|
+
Codex or Claude restoration, only where resume is offered (resume launches the
|
|
339
|
+
new session, and dispose refuses `--fresh-session` on the same predicate); and
|
|
340
|
+
last stop-and-archive where `disposeRefusal` allows it, or else `wait until
|
|
341
|
+
<project>'s hub settles, then <c> recovery status <id>`. `test/upgrade.test.ts`
|
|
342
|
+
holds `next` to the commands with a fake driver for one project with no peers:
|
|
343
|
+
ten receipt states (pending, prepared, prepared with a sent commit, prepared by
|
|
344
|
+
an older coordinator, prepared with a closed terminal, stopped, started, started
|
|
345
|
+
with a failed Codex restoration, peers restored, a partway stop-and-archive)
|
|
346
|
+
crossed with ten live states (as planned, replaced, held by another operation,
|
|
347
|
+
crashed, crashed with the snapshot kept, unavailable, starting, stopping,
|
|
348
|
+
incompatible, missing); the older coordinator's operation is run there by the
|
|
349
|
+
current runner, and its fakes never fail start, restore or verify (terminals,
|
|
350
|
+
Orca and peers are covered by their own tests, not by this one). Abort, stop-and-archive and `--fresh-session codex` are offered
|
|
351
|
+
exactly when the command accepts them on a twin fixture, and where resume is
|
|
352
|
+
offered it makes progress or its error names a step a person takes first. An error gives only its own step (the
|
|
353
|
+
last column below) and the runner ends it with that list (`next actions: ...`),
|
|
354
|
+
read from the same sources as `status`, so it names abort exactly when
|
|
355
|
+
`abortRefusal` allows it, the same as `status`, and never
|
|
356
|
+
"make a new plan" alone: this operation's lock refuses a new one. `<c>` is the operation's own coordinator, printed in full as
|
|
357
|
+
`bun <preserved source>/src/cli/main.js`: during an upgrade the global `ahub`
|
|
358
|
+
may still be the older release, whose `recovery` lacks these commands.
|
|
359
|
+
"Effects" means any project past `prepared` or any terminal receipt in the whole
|
|
360
|
+
operation. Stop-and-archive (`<c> recovery dispose <id> --stop-and-archive
|
|
361
|
+
--reason <text>`) is offered, as the last `next` entry, exactly when
|
|
362
|
+
`disposeRefusal` allows it; the rows give what else applies.
|
|
363
|
+
|
|
364
|
+
A recorded launcher is the `terminal-recovery.json` row for that peer on the
|
|
365
|
+
target instance. It is live when its process signature matches, gone when the
|
|
366
|
+
pid no longer exists (ESRCH) or now belongs to another process, and unknown when
|
|
367
|
+
the pid exists but its identity cannot be read. Unknown is never treated as gone. At planning, an
|
|
368
|
+
unknown Claude launcher blocks: whether the attached session is managed cannot
|
|
369
|
+
be told.
|
|
370
|
+
Restore reads this evidence in one place: the target's report, which counts only
|
|
371
|
+
when the target runs as the expected instance, then the recorded launcher. A
|
|
372
|
+
target that does not, a launcher that cannot be read, or a launcher record file
|
|
373
|
+
that cannot be read or parsed is unknown and blocks without changing a receipt;
|
|
374
|
+
the record file is written atomically (temp + rename), and a launch is not recorded
|
|
375
|
+
over a record file that cannot be read (that would erase the other launchers'
|
|
376
|
+
records). `ahub codex` and `ahub pi --mode tui` launched by the operation that
|
|
377
|
+
holds the lock record themselves before the hub's start round trip. Whether a
|
|
378
|
+
target reads waivers is one check, used by staging, restore, `next` and dispose. A truncated Orca
|
|
379
|
+
inventory never shows a terminal as gone. While a stop-and-archive is recorded,
|
|
380
|
+
`resume` is refused by the running release, whichever coordinator started the
|
|
381
|
+
operation, and the runner keeps the disposition's own error. Abort and `next` decide with one predicate
|
|
382
|
+
(`abortRefusal`). `status`, abort and the runner's errors read the same live
|
|
383
|
+
state: every project not yet verified, its source or its target. A manifest
|
|
384
|
+
whose pid no longer exists reads as stopped, whatever protocol it names; probing
|
|
385
|
+
it would read unavailable forever. A stopped runtime also reports whose
|
|
386
|
+
unreleased restart snapshot its state directory holds.
|
|
387
|
+
An attached session is the target's report of that peer online with a thread
|
|
388
|
+
(Codex) or session (Claude, Pi) id.
|
|
389
|
+
|
|
390
|
+
| Receipt | Live evidence | Resume | Other actions | Next action text |
|
|
391
|
+
| --- | --- | --- | --- | --- |
|
|
392
|
+
| project `pending` or `prepared`, no effects | roster changed | blocks | abort where `abortRefusal` allows it, dispose | `end that <peer> session` or `restore <peer>'s original session` `before resuming, or end this operation: a new plan can be made once it is cancelled or ended` |
|
|
393
|
+
| project `prepared`, hold ours | roster as planned | roster checked again, then close and commit | abort if no effects | none (it proceeds) |
|
|
394
|
+
| project `prepared`, hold ours | roster changed, effects | blocks, hold kept | dispose | `end that <peer> session` (joined after the plan, or its terminal was closed) or `restore <peer>'s original session`, `then <c> recovery resume <id>` |
|
|
395
|
+
| project `prepared`, hold lapsed (same source instance) | roster as planned | re-prepare, check roster, close, commit | abort if no effects | none |
|
|
396
|
+
| project `prepared`, hold lapsed | roster changed | as the two rows above | as above | as above |
|
|
397
|
+
| project `pending` | source replaced, stopped or missing | blocks, nothing touched; not offered | abort if no effects, dispose | `source runtime changed` |
|
|
398
|
+
| project `pending` or `prepared` | source unavailable, starting or stopping | blocks until it answers | abort as `abortRefusal` decides; stop-and-archive refused until it reads | `wait until it answers` |
|
|
399
|
+
| project `prepared` | source replaced | blocks, nothing touched; not offered | abort if no effects (cancels and leaves that daemon alone), dispose | stop-and-archive |
|
|
400
|
+
| project `pending` or `prepared` | source held by another operation | blocks, nothing touched, until that operation ends | abort if no effects (leaves that hold alone), dispose | `wait until that operation ends or its hold lapses` |
|
|
401
|
+
| any project not verified | project directory missing | blocks; not offered | abort as `abortRefusal` decides, dispose | `the project directory is missing` |
|
|
402
|
+
| any project not verified | hub speaks another control protocol (incompatible) | blocks; not offered | abort as `abortRefusal` decides; stop-and-archive refused | `wait until <project>'s hub settles` (end it by hand: operations guide) |
|
|
403
|
+
| project `prepared` with `commitSent` (a per-project receipt flag written right before the commit request; `step` is rewritten on every resume and is not evidence) | any (the commit may have been sent) | continues from the commit; a stopped source starts the target only from this operation's snapshot, so without it resume is not offered | dispose; abort is neither offered nor accepted | `<c> recovery resume <id>` |
|
|
404
|
+
| project `prepared`, no `commitSent`, operation of a #215 coordinator | source stopped (crashed before any commit request) | blocks; the phase stays `prepared` (nothing was committed, so there is nothing to start from); not offered | abort if no effects, dispose | `<c> recovery abort <id>` without effects, else stop-and-archive (the lock refuses starting that hub by hand) |
|
|
405
|
+
| project `prepared` | source unavailable, starting, stopping or not inspected | as above when it reads again | none: abort (its hold may still stand) and stop-and-archive are refused until it reads | `<c> recovery status <id>` once it answers |
|
|
406
|
+
| project `prepared`, operation of an older coordinator (no `commitSent` written) | source not running, or not inspected | as its own runner does | dispose once it reads running, stopped or missing; abort neither offered nor accepted (it may have committed) | `<c> recovery status <id>` |
|
|
407
|
+
| project `started` or `peers-restored` | target stopped (a crash or reboot after it started), with or without its snapshot | blocks; not offered: restarting it is not supported in this release | dispose; no fresh session | `the target stopped after it started; restarting it is not supported in this release` |
|
|
408
|
+
| project `stopped` | target stopped without this operation's snapshot (never committed) | blocks; not offered | dispose | `the target is stopped and this operation's restart snapshot is gone` |
|
|
409
|
+
| project `stopped`, `started` or `peers-restored` | target running unfenced, as another instance or another version | blocks; not offered | dispose | `daemon is not owned by this recovery operation` (or `daemon instance changed`, `target daemon version mismatch`) |
|
|
410
|
+
| any, at staging | preserved source or staged target changed, or (first staging of these bytes only) target protocol or recovery waivers refused | blocks; recorded as `final`, so resume is never offered again | abort if no effects, dispose | the refusal |
|
|
411
|
+
| any, at staging | the protocol probe did not answer (killed, exit 1) before the first staging | blocks, not final | dispose; abort if no effects | `resume again once it can run`; once staged, the checks are not run again |
|
|
412
|
+
| disposition recorded, first act not finished | any | refused | abort refused; stop-and-archive where `disposeRefusal` allows it | as the disposition row below |
|
|
413
|
+
| `closed:<peer>` `pending` | Orca still lists the terminal | blocks | dispose | `close Orca terminal <handle> (the login shell it runs in) by hand, then <c> recovery resume <id>` |
|
|
414
|
+
| `closed:<peer>` done | peer attached again | blocks | dispose | `end that <peer> session, then <c> recovery resume <id>` |
|
|
415
|
+
| `restored:<peer>` `pending` or `failed` | session attached with the planned id (or an accepted new one: fresh choice, planned fresh start, zero-turn Claude) | recorded as restored | none: `--fresh-session` is neither offered nor accepted while the inspection shows the planned session attached; when it cannot tell (not inspected, target unreadable), the `failed` receipt alone decides, and a chosen fresh session whose original then attaches is cleared on resume while its audit entry stays | none |
|
|
416
|
+
| `restored:<peer>` `pending` or `failed` | another session attached | blocks | dispose | `end that <peer> session and close its terminal, then <c> recovery resume <id>` |
|
|
417
|
+
| `restored:<peer>` `pending` or `failed` | no session, recorded launcher live or unknown | blocks | dispose | `wait until it attaches, or end it and close terminal <handle>, then <c> recovery resume <id>` |
|
|
418
|
+
| `restored:<peer>` `pending` | no session, launcher gone or never recorded | receipted `failed`, blocks | fresh session (Codex, Claude), dispose | the failed-restoration choices below |
|
|
419
|
+
| `restored:<peer>` `failed` | no session, launcher gone or never recorded | launches again (fresh if chosen) | fresh session (Codex, Claude), dispose | the failed-restoration choices below |
|
|
420
|
+
| `restored:codex` absent | rollout missing | receipted `failed`, blocks, no terminal created | fresh session, dispose | the failed-restoration choices below |
|
|
421
|
+
| any, before close or create | Codex store unreadable | blocks, nothing closed or created | dispose | `make <store> readable, then <c> recovery resume <id>` |
|
|
422
|
+
| `restored:<peer>` absent | the planned session attached | recorded as restored | none | none |
|
|
423
|
+
| `restored:<peer>` absent | another session attached, or a recorded launcher live | blocks, no terminal created | dispose | `end that <peer> session and close its terminal` (or `wait until it attaches, or end it and close terminal <handle>`), `then <c> recovery resume <id>` |
|
|
424
|
+
| any receipt being settled or launched | target hub not running as the expected instance (unavailable, starting or stopping; a stopped or other target is settled by the rows above), or a launcher or launcher record file that cannot be read | blocks; no receipt changes, nothing created | dispose once the target reads running or stopped | `wait until the target answers` (or wait for / end the launcher, or inspect the record file and move it aside), `then <c> recovery resume <id>` |
|
|
425
|
+
| `fresh` recorded for a peer | none live | launches it without a resume id, records the new id | dispose | none |
|
|
426
|
+
| disposition recorded, not finished | any | refused | stop-and-archive only | `rerun <c> recovery dispose <id> --stop-and-archive --reason <text>` where `disposeRefusal` allows it, else `wait until <project>'s hub settles, then <c> recovery status <id>` |
|
|
427
|
+
| `completed` or `cancelled` | any | nothing to do | none | none |
|
|
428
|
+
|
|
429
|
+
The failed-restoration choices are `<c> recovery dispose <id> --fresh-session
|
|
430
|
+
<peer> --reason <text>` (Codex or Claude; records the lost session and resumes;
|
|
431
|
+
if the original session attaches after all, the recorded loss is cleared) and
|
|
432
|
+
stop-and-archive. When the operation's own coordinator predates these commands
|
|
433
|
+
(it has no `dispose`, refuses abort and resume on a lapsed hold and prints no
|
|
434
|
+
`next`), `<c>` names the running release for every action: abort, status and
|
|
435
|
+
dispose run in that process, while resume still runs the operation's own runner
|
|
436
|
+
and its `next` entry says what that runner cannot do. Stop-and-archive stops a
|
|
437
|
+
target that speaks an older control protocol at that protocol (a `kill` fenced
|
|
438
|
+
by its instance), then waits for its manifest and registry claim as the
|
|
439
|
+
lifecycle stop does. The disposition and its audit are written before the first
|
|
440
|
+
act. A recovery launch of `ahub codex` records its launcher before the hub's
|
|
441
|
+
`start` round trip; an ordinary launch records it after, so a refused start never
|
|
442
|
+
replaces the record of a Codex already running.
|
|
443
|
+
|
|
444
|
+
A Codex thread with no rollout is not blocked at the plan only when the hub saw
|
|
445
|
+
Codex start it (`native_thread` with `fresh`, logged by the adapter when it
|
|
446
|
+
adopts a thread) and logged no Codex `turn_start` while it was the adopted
|
|
447
|
+
thread (Codex writes the rollout with the first message). The plan then lists
|
|
448
|
+
it under `freshStart`, says so, and it restarts as a new session with nothing to
|
|
449
|
+
lose. Detaching forgets nothing; a thread whose start the log does not show
|
|
450
|
+
(resumed, an older hub, a pruned log) is unsure and stays a plan blocker, as
|
|
451
|
+
does one with turns. A planned fresh start still needs the store to show the
|
|
452
|
+
rollout missing: an unreadable store blocks the close and the create.
|
|
@@ -306,4 +306,14 @@ related: 261004-review-agent-hub-switchyard-comparison.md, 261004-plan-agent-hub
|
|
|
306
306
|
- Existing sidecar routes remain optional and backward compatible; `[hub_routes]` never enters generated sidecar TOML. The sidecar retirement decision remains evidence-driven.
|
|
307
307
|
- Each required phase is a separate reviewable commit and PR. No release or production configuration changes are implied.
|
|
308
308
|
|
|
309
|
+
## Amendment: session-aware stay/switch (#197, 2026-10-09)
|
|
310
|
+
|
|
311
|
+
- The stage tier is no longer re-decided in isolation on every call. `planSwitch(pin, fresh, turn, cost)` sits next to `selectStage` in `src/models/route/stage.ts`; `hub/auto` and local `stage` routes both reach it through `stayOrSwitch`, so they record the same trace.
|
|
312
|
+
- Rules: a new session (the relay's `routeSessionKey`, the local route scope) or a compaction starts a fresh pin; a hard override escalates on any turn; a tool-result turn stays on the pinned tier; other changes wait for a user turn; a de-escalation whose conversation exceeds `max_switch_prefill_tokens` stays; a tier whose backend cannot hold the conversation is never chosen. Nothing summarizes, trims or rewrites the forwarded conversation.
|
|
313
|
+
- `hold_turns` is unchanged: the stage hold still counts calls, and enforced, it can only extend past the next user turn, never end inside a tool loop.
|
|
314
|
+
- Enforced, a clean test pass inside a tool loop does not de-escalate: it ends the stage hold, but the efficient tier waits for the next user turn. A dimension-driven escalation that a tool loop defers is not remembered by the planner: it reaches the next user turn only while `selectStage`'s hold still holds the capable tier, so a loop longer than the hold drops it. Hard overrides are never deferred: the planner reads pickTier's override predicate on its own (`hardOverride`), so a repeated failure or critical severity during a capable hold still escalates at once.
|
|
315
|
+
- `routing.toml` top-level `stay_switch = "off" | "shadow" | "enforce"`, default `shadow`, which changes no routing decision. Local routes see no context limits (OmniRoute publishes none to the hub), so only `hub/auto` filters by fit.
|
|
316
|
+
- Route events gain `turnType` (the issue's `turn`: the local `turn` field already holds the turn id), `prefillTokens` (omitted on PII routes, as a private envelope omits its size; a PII route's planner gets no prefill bound, so no plan or reason depends on the size), `staySwitch`, `plan` and `reason`. `ahub report` counts model changes per session and inside tool loops per peer and route; sessions are counted from pins, so they are unknown while `stay_switch` is `off`.
|
|
317
|
+
- Deferred: joining provider cache counters to switches (the relay has no usage events), and switching the default to `enforce` until the measurement in #197 AC6.
|
|
318
|
+
|
|
309
319
|
Implementation tracking: [#124](https://github.com/STAIxBWLB/agent-hub/issues/124), core [#125](https://github.com/STAIxBWLB/agent-hub/issues/125), local [#126](https://github.com/STAIxBWLB/agent-hub/issues/126), Pi [#127](https://github.com/STAIxBWLB/agent-hub/issues/127), observations [#128](https://github.com/STAIxBWLB/agent-hub/issues/128).
|
package/docs/verified.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"documents": {
|
|
4
4
|
"README.md": {
|
|
5
|
-
"verifiedAgainst": "
|
|
5
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
6
6
|
"paths": [
|
|
7
7
|
"package.json",
|
|
8
8
|
"src/",
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
13
|
"docs/security.md": {
|
|
14
|
-
"verifiedAgainst": "
|
|
14
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
15
15
|
"paths": [
|
|
16
16
|
"src/",
|
|
17
17
|
"templates/"
|
|
18
18
|
]
|
|
19
19
|
},
|
|
20
20
|
"docs/operations.md": {
|
|
21
|
-
"verifiedAgainst": "
|
|
21
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
22
22
|
"paths": [
|
|
23
23
|
"package.json",
|
|
24
24
|
"src/",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"docs/quickstart.md": {
|
|
38
|
-
"verifiedAgainst": "
|
|
38
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
39
39
|
"paths": [
|
|
40
40
|
"package.json",
|
|
41
41
|
"src/",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
45
|
"docs/agent-notes/adapters.md": {
|
|
46
|
-
"verifiedAgainst": "
|
|
46
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
47
47
|
"paths": [
|
|
48
48
|
"src/adapters/",
|
|
49
49
|
"src/pi/",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
]
|
|
57
57
|
},
|
|
58
58
|
"docs/agent-notes/benchmarks.md": {
|
|
59
|
-
"verifiedAgainst": "
|
|
59
|
+
"verifiedAgainst": "84a3c404d3fe3e293495009cffa11b204d0d3523",
|
|
60
60
|
"paths": [
|
|
61
61
|
"scripts/benchmarks/",
|
|
62
62
|
"test/benchmarks/",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
]
|
|
69
69
|
},
|
|
70
70
|
"docs/agent-notes/budget.md": {
|
|
71
|
-
"verifiedAgainst": "
|
|
71
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
72
72
|
"paths": [
|
|
73
73
|
"src/hub/budget.ts",
|
|
74
74
|
"src/cli/statusline-tee.ts",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
]
|
|
81
81
|
},
|
|
82
82
|
"docs/agent-notes/bus.md": {
|
|
83
|
-
"verifiedAgainst": "
|
|
83
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
84
84
|
"paths": [
|
|
85
85
|
"src/hub/bus.ts",
|
|
86
86
|
"src/hub/envelope.ts",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
]
|
|
93
93
|
},
|
|
94
94
|
"docs/agent-notes/daemon.md": {
|
|
95
|
-
"verifiedAgainst": "
|
|
95
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
96
96
|
"paths": [
|
|
97
97
|
"src/hub/daemon.ts",
|
|
98
98
|
"src/hub/control-client.ts",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
]
|
|
119
119
|
},
|
|
120
120
|
"docs/agent-notes/local-worker.md": {
|
|
121
|
-
"verifiedAgainst": "
|
|
121
|
+
"verifiedAgainst": "84a3c404d3fe3e293495009cffa11b204d0d3523",
|
|
122
122
|
"paths": [
|
|
123
123
|
"src/adapters/local-worker.ts",
|
|
124
124
|
"src/local/",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
]
|
|
128
128
|
},
|
|
129
129
|
"docs/agent-notes/models.md": {
|
|
130
|
-
"verifiedAgainst": "
|
|
130
|
+
"verifiedAgainst": "ab2925f7848a2eb848e9f0ab664df297c2c0f5fb",
|
|
131
131
|
"paths": [
|
|
132
132
|
"src/models/",
|
|
133
133
|
"src/hub/inference.ts",
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
]
|
|
137
137
|
},
|
|
138
138
|
"docs/agent-notes/tasks.md": {
|
|
139
|
-
"verifiedAgainst": "
|
|
139
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
140
140
|
"paths": [
|
|
141
141
|
"src/hub/tasks.ts",
|
|
142
142
|
"src/hub/board.ts",
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
]
|
|
152
152
|
},
|
|
153
153
|
"docs/agent-notes/tests.md": {
|
|
154
|
-
"verifiedAgainst": "
|
|
154
|
+
"verifiedAgainst": "f18ba89fe7f41d3ff11c87ecdd02886386787227",
|
|
155
155
|
"paths": [
|
|
156
156
|
"test/",
|
|
157
157
|
"scripts/check.sh",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-hub",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.20",
|
|
4
4
|
"description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Young Joon Lee",
|