@staix/agent-hub 0.12.21 → 0.12.22
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 +10 -0
- package/README.md +13 -6
- package/docs/agent-notes/adapters.md +6 -3
- package/docs/agent-notes/benchmarks.md +1 -1
- package/docs/agent-notes/bus.md +2 -1
- package/docs/agent-notes/daemon.md +4 -0
- package/docs/agent-notes/local-worker.md +2 -0
- package/docs/agent-notes/tasks.md +1 -1
- package/docs/agent-notes/tests.md +2 -0
- package/docs/events.md +13 -4
- package/docs/operations.md +253 -22
- package/docs/quickstart.md +6 -3
- package/docs/security.md +9 -6
- package/docs/smoke.md +61 -2
- package/docs/specs/2026-09-19-agent-hub-design.md +38 -5
- package/docs/specs/2026-09-20-upgrade-recovery-design.md +84 -8
- package/docs/verified.json +21 -15
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +1 -1
- package/src/adapters/acp.ts +10 -4
- package/src/adapters/codex-appserver.ts +27 -22
- package/src/adapters/local-worker.ts +44 -10
- package/src/adapters/pi.ts +158 -19
- package/src/cli/help.ts +6 -5
- package/src/cli/identity.ts +2 -2
- package/src/cli/launch.ts +182 -7
- package/src/cli/main.ts +137 -18
- package/src/cli/recovery-package.ts +17 -0
- package/src/cli/upgrade-interactive.ts +297 -0
- package/src/cli/upgrade-runtime.ts +56 -2
- package/src/cli/upgrade.ts +27 -10
- package/src/hub/bus.ts +5 -3
- package/src/hub/daemon.ts +145 -60
- package/src/hub/events.ts +2 -1
- package/src/hub/peers.ts +2 -2
- package/src/hub/recovery-store.ts +38 -0
- package/src/hub/tasks.ts +32 -15
- package/src/local/deny.ts +48 -1
- package/src/local/sandbox.ts +6 -3
- package/src/local/tools.ts +25 -6
- package/src/pi/extension.ts +43 -20
- package/src/pi/tool-receipts.ts +17 -1
- package/src/ui/index.html +15 -11
package/docs/operations.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Operations guide
|
|
2
2
|
|
|
3
|
-
This guide describes ahub 0.12.
|
|
3
|
+
This guide describes ahub 0.12.22 and control protocol 16. Live verification
|
|
4
4
|
results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
|
|
5
5
|
|
|
6
6
|
## Command help
|
|
@@ -213,7 +213,8 @@ final response in the native TUI. Broadcasting implementation instructions can
|
|
|
213
213
|
cause an otherwise unassigned owner to claim duplicate work.
|
|
214
214
|
|
|
215
215
|
The person answers approvals in the console, resolves `needs_review` deliveries
|
|
216
|
-
with `ahub queue resolve`,
|
|
216
|
+
with `ahub queue resolve`, stops a headless peer with `ahub stop`, and handles
|
|
217
|
+
budget overrides and hub lifecycle.
|
|
217
218
|
Running these commands from an agent shell is refused; the CLI retains the
|
|
218
219
|
agent's identity even when invoked through a shell tool.
|
|
219
220
|
|
|
@@ -286,6 +287,19 @@ project and a temp dir of their own (`TMPDIR`, made for each command and
|
|
|
286
287
|
removed when it ends; one left by a hub crash, named `ahub-cmd-*`, goes with
|
|
287
288
|
the OS temp cleanup), and nothing else; the shared temp dirs are closed.
|
|
288
289
|
|
|
290
|
+
Some names stay closed to those commands, and to Pi's, wherever they may
|
|
291
|
+
otherwise write: anything named `.git` at any depth, the `config` and `hooks`
|
|
292
|
+
directly under a `.git`, `commondir` below it, and `.agenthub` anywhere in the
|
|
293
|
+
project. A submodule's git directory under `.git/modules/` is not covered yet
|
|
294
|
+
(#281). A command can therefore not create, rename or remove a `.git`:
|
|
295
|
+
`git init`, `git clone` and `git worktree add` do not run inside the sandbox,
|
|
296
|
+
and neither do `git worktree remove`, `git worktree prune` of a stale record,
|
|
297
|
+
`git clean -ffd` over a nested repository or `rm -rf` of a directory that
|
|
298
|
+
holds a `.git`. `git add`, `commit`, `stash`, `checkout` and `gc` work, and
|
|
299
|
+
`.gitignore` and `.github/` stay writable; a commit that your git configuration
|
|
300
|
+
signs with a key under `~/.ssh` or `~/.gnupg` fails, because the sandbox cannot
|
|
301
|
+
read those directories. Run a refused command yourself, in a terminal.
|
|
302
|
+
|
|
289
303
|
Network is off unless `local.bash_network` says otherwise. With `true` it goes
|
|
290
304
|
only through the hub's egress proxy on a loopback port (0.11): commands get
|
|
291
305
|
`HTTPS_PROXY` and the other proxy variables, the proxy opens HTTPS (`CONNECT`,
|
|
@@ -881,12 +895,36 @@ Other tools retain native decisions, which may already allow a harmless shell.
|
|
|
881
895
|
Permission-only PreToolUse never marks Claude busy; former observation hooks
|
|
882
896
|
retain their native-turn role. Settings are passed as a 0600 state file retained
|
|
883
897
|
until that native launch exits. Live/unknown previous launchers keep their files;
|
|
884
|
-
verified dead wrapper and native identities permit crash cleanup.
|
|
898
|
+
verified dead wrapper and native identities permit crash cleanup. Managed direct
|
|
899
|
+
binary launches record their native PID while running; crash cleanup also requires
|
|
900
|
+
that PID to be absent from the process table. Scripts/interpreters and unavailable
|
|
901
|
+
process evidence retain the settings file. Every launch atomically replaces
|
|
902
|
+
`claude-launch.json` with its current managed hook/session identity. Native PID
|
|
903
|
+
proof is separate in a private `claude-native-<binding-hash>.json` file, so an old
|
|
904
|
+
callback cannot overwrite a newer launch record. Only that native proof uses
|
|
905
|
+
`claude-launch.lock`, published with its signed owner already written. It waits
|
|
906
|
+
up to 250 ms for a verified live owner. An empty, unsigned or unverifiable lock
|
|
907
|
+
is retained and disables native identity/crash cleanup for this launch; the
|
|
908
|
+
warning names the path and what is off. Managed hooks and session observations
|
|
909
|
+
still use the newly published launch record. Inspect the named lock and verify
|
|
910
|
+
its owner has ended before removing it; a signed dead lock is recovered. Without
|
|
885
911
|
separate native identity proof, old settings remain for manual inspection. Settings carry
|
|
886
912
|
no inline caller values in argv. Status reports an unverified Claude hook as
|
|
887
913
|
unverified. Unattended native sessions cannot take a runtime mode; restart
|
|
888
914
|
without that native flag. Codex must be behind the hub proxy, and returning to
|
|
889
|
-
ask requires a known native approval baseline.
|
|
915
|
+
ask while attached requires a known native approval baseline. If unavailable,
|
|
916
|
+
close the TUI, then run `ahub permission codex ask` before starting `ahub codex`.
|
|
917
|
+
For a known baseline, a detached proxy keeps restoration owed per thread and sends
|
|
918
|
+
that policy once on the resumed thread's next accepted turn. Visiting another thread
|
|
919
|
+
does not clear the debt. The restoring turn uses that kept baseline even if the
|
|
920
|
+
TUI echoes the resume response's sticky override, whether ask was selected before
|
|
921
|
+
or after that resume. Rejected turns retain it. The protection covers that one
|
|
922
|
+
restoring turn. A later TUI turn that repeats the policy looks exactly like a
|
|
923
|
+
person's explicit native policy choice and is passed through; the proxy cannot
|
|
924
|
+
resolve that ambiguity. An explicit choice on the restoring turn is delayed
|
|
925
|
+
until the following turn. A baseline that was never reported
|
|
926
|
+
is explained in the CLI reply, and that old thread keeps its last policy.
|
|
927
|
+
An offline or absent peer accepts
|
|
890
928
|
`ahub permission <peer> ask` to clear its hub choice. Codex --unattended launch
|
|
891
929
|
is refused under a non-ask choice; clear it with `ahub permission codex ask`
|
|
892
930
|
first. A pending never-ask default cannot be confirmed for an unattended launch.
|
|
@@ -936,9 +974,11 @@ ahub permit <request-id> allow
|
|
|
936
974
|
|
|
937
975
|
Use the exact option shown by `ahub tail`; do not approve an unresolved or
|
|
938
976
|
unexpected request. On macOS a waiting request also raises a desktop
|
|
939
|
-
notification that names the peer and, for Kimi and the local worker, the tool,
|
|
977
|
+
notification that names the peer and, for Kimi, Pi and the local worker, the tool,
|
|
940
978
|
never what it would run. An unanswered request is cancelled after `approvals.timeout_s` (default
|
|
941
979
|
120, 30 to 3600) in `.agenthub/config.json`, and the console and log say so.
|
|
980
|
+
Pi and the local worker are told that nobody answered, and two such requests in
|
|
981
|
+
a row end their turn ([Pi/local unanswered approvals](#pilocal-unanswered-approvals)).
|
|
942
982
|
Set `approvals.notify` to `false` to turn notifications off; a hub started
|
|
943
983
|
without a project config file raises none. While Kimi waits for an answer its
|
|
944
984
|
turn is kept alive, so a timeout longer than the inactivity watchdog does not
|
|
@@ -1044,11 +1084,15 @@ The daemon waits for shutdown work that must survive termination. Afterward,
|
|
|
1044
1084
|
run `ahub status` or `ahub up` and read the project state before restarting
|
|
1045
1085
|
peers. A stopped daemon does not delete task records or the durable journal.
|
|
1046
1086
|
Do not kill a native terminal by PID or start a replacement while ownership is
|
|
1047
|
-
uncertain.
|
|
1087
|
+
uncertain. To stop one headless peer and keep the hub running, use
|
|
1088
|
+
`ahub stop <peer>` ([Stop one headless peer](#stop-one-headless-peer)).
|
|
1048
1089
|
|
|
1049
1090
|
Shutdown is bounded: once it begins, a daemon that cannot finish within
|
|
1050
1091
|
15 seconds exits anyway, and a peer that refuses to stop is logged rather than
|
|
1051
|
-
allowed to block state cleanup. A
|
|
1092
|
+
allowed to block state cleanup. A managed Pi tool that does not settle within
|
|
1093
|
+
the stop grace does not block the teardown either: the stop goes on
|
|
1094
|
+
(shutdown, owner teardown, process stop) and reports the failure at the end.
|
|
1095
|
+
A native Pi TUI owner that does not exit
|
|
1052
1096
|
gracefully is terminated by its verified process identity (never a bare PID),
|
|
1053
1097
|
so a restarted hub never launches beside a survivor. A hub whose project root
|
|
1054
1098
|
or state directory was deleted stops itself within about 10 seconds; the
|
|
@@ -1169,23 +1213,38 @@ Rows without a live process are stale registrations; forget them with
|
|
|
1169
1213
|
|
|
1170
1214
|
## Upgrade and crash recovery
|
|
1171
1215
|
|
|
1216
|
+
On a terminal, one command reviews, applies and follows an upgrade:
|
|
1217
|
+
|
|
1218
|
+
```bash
|
|
1219
|
+
ahub upgrade
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
Without `--to` the target is the registry's latest release. When that release is
|
|
1223
|
+
newer than the installed CLI, the CLI names the release's own coordinator
|
|
1224
|
+
(`bun x --package @staix/agent-hub@<version> ahub upgrade --to <version>`), asks,
|
|
1225
|
+
and hands over to it, so the version is never typed twice and an older CLI never
|
|
1226
|
+
coordinates. From an installed CLI older than 0.12.22, start it once by hand:
|
|
1227
|
+
`bunx --package @staix/agent-hub@<version> ahub upgrade`. The screens are
|
|
1228
|
+
described under [Interactive upgrade](#interactive-upgrade); the rest of this
|
|
1229
|
+
section is what they run.
|
|
1230
|
+
|
|
1172
1231
|
Upgrade running projects with the target release's own coordinator. It accepts
|
|
1173
1232
|
a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
|
|
1174
|
-
11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20
|
|
1233
|
+
11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.22), and only
|
|
1175
1234
|
a target on its own protocol, so the target's coordinator fits every supported
|
|
1176
1235
|
source and carries every recovery fix released up to it. Protocol 8 and older
|
|
1177
1236
|
(0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
|
|
1178
1237
|
project directory, without replacing the global CLI first:
|
|
1179
1238
|
|
|
1180
1239
|
```bash
|
|
1181
|
-
bunx --package @staix/agent-hub@0.12.
|
|
1182
|
-
bunx --package @staix/agent-hub@0.12.
|
|
1240
|
+
bunx --package @staix/agent-hub@0.12.22 ahub upgrade --to 0.12.22 --dry-run
|
|
1241
|
+
bunx --package @staix/agent-hub@0.12.22 ahub upgrade --to 0.12.22 --yes
|
|
1183
1242
|
```
|
|
1184
1243
|
|
|
1185
1244
|
| Running now | Coordinator to use |
|
|
1186
1245
|
| --- | --- |
|
|
1187
1246
|
| 0.6.x (protocol 9) | the target's, through `bunx` as above |
|
|
1188
|
-
| 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20
|
|
1247
|
+
| 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.22 (protocol 16) | the target's, through `bunx` as above |
|
|
1189
1248
|
| any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
|
|
1190
1249
|
| 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
|
|
1191
1250
|
|
|
@@ -1217,21 +1276,27 @@ projects first:
|
|
|
1217
1276
|
|
|
1218
1277
|
```bash
|
|
1219
1278
|
ahub restart --dry-run
|
|
1220
|
-
ahub upgrade --to 0.12.
|
|
1279
|
+
ahub upgrade --to 0.12.22 --dry-run
|
|
1221
1280
|
```
|
|
1222
1281
|
|
|
1223
1282
|
Apply only after reviewing the plan:
|
|
1224
1283
|
|
|
1225
1284
|
```bash
|
|
1226
1285
|
ahub restart --yes
|
|
1227
|
-
ahub upgrade --to 0.12.
|
|
1228
|
-
ahub recovery status <operation-id>
|
|
1229
|
-
ahub recovery resume <operation-id>
|
|
1230
|
-
ahub recovery abort <operation-id>
|
|
1231
|
-
ahub recovery dispose <operation-id> --fresh-session <peer> --reason <text>
|
|
1232
|
-
ahub recovery dispose <operation-id> --stop-and-archive --reason <text>
|
|
1286
|
+
ahub upgrade --to 0.12.22 --yes
|
|
1287
|
+
ahub recovery status [<operation-id>]
|
|
1288
|
+
ahub recovery resume [<operation-id>]
|
|
1289
|
+
ahub recovery abort [<operation-id>]
|
|
1290
|
+
ahub recovery dispose [<operation-id>] --fresh-session <peer> --reason <text>
|
|
1291
|
+
ahub recovery dispose [<operation-id>] --stop-and-archive --reason <text>
|
|
1233
1292
|
```
|
|
1234
1293
|
|
|
1294
|
+
`--yes` needs `--to`; a dry run without `--to` plans the latest release. A target
|
|
1295
|
+
newer than the CLI is handed to that release's coordinator with the same flags,
|
|
1296
|
+
and the command is printed on stderr first. The `recovery` commands take the
|
|
1297
|
+
operation that holds the machine's lock when the id is left out, and say `no
|
|
1298
|
+
recovery operation holds the lock` when none does.
|
|
1299
|
+
|
|
1235
1300
|
The coordinator commits only once the source is quiet: no turn running, no
|
|
1236
1301
|
approval pending, no completion check queued or running. It waits up to 10
|
|
1237
1302
|
minutes and then aborts, leaving the source running; upgrade between long
|
|
@@ -1271,6 +1336,10 @@ runs under the deny-default sandbox, and `hub.log` and `ahub doctor` say so.
|
|
|
1271
1336
|
`local.bash_network: "direct"` still works until 0.13.0, and both name it while
|
|
1272
1337
|
it is set.
|
|
1273
1338
|
|
|
1339
|
+
0.12.22 closes the name `.git` to the local worker's and Pi's commands at any
|
|
1340
|
+
depth: `git init`, `git clone` and `git worktree add` no longer run inside the
|
|
1341
|
+
sandbox (the full list is under Install and start).
|
|
1342
|
+
|
|
1274
1343
|
The 0.7.0 transition stages the verified package and runs a retained
|
|
1275
1344
|
coordinator from the source tree. It accepts a verified protocol-9 source and
|
|
1276
1345
|
moves to a protocol-10 target. The source journal, queued envelopes, tasks,
|
|
@@ -1306,6 +1375,93 @@ Do not run an upgrade with an incompatible active protocol, an unverified
|
|
|
1306
1375
|
terminal binding, or an unresolved operation lock. Dry-run performs no package,
|
|
1307
1376
|
plugin, daemon, or terminal mutation.
|
|
1308
1377
|
|
|
1378
|
+
### Interactive upgrade
|
|
1379
|
+
|
|
1380
|
+
`ahub upgrade` and `ahub restart` are interactive when standard input and output
|
|
1381
|
+
are a terminal and neither `--dry-run` nor `--yes` is given. With either flag, or
|
|
1382
|
+
through a pipe, the JSON plan, the `ahub: blocker:` lines and the exit codes are
|
|
1383
|
+
unchanged. The screens add no way to change an operation: every choice runs one
|
|
1384
|
+
of the commands in this section for you, so that command's refusals decide.
|
|
1385
|
+
|
|
1386
|
+
The plan screen lists each project with its hub version and, per attached peer,
|
|
1387
|
+
its state, how it runs (`TUI` in a terminal the plan bound to its session,
|
|
1388
|
+
`headless` as the hub's own process, `own` for a session the hub did not
|
|
1389
|
+
launch or whose terminal the plan could not bind) and what happens to it: resumed in a new terminal (the old one is
|
|
1390
|
+
named), reconnecting by itself, restarted as a new session, restarted headless,
|
|
1391
|
+
or left offline. `in progress:` repeats what the running hub says keeps it from
|
|
1392
|
+
being quiet (busy peers, pending approvals, a peer starting, a budget
|
|
1393
|
+
transition; a hub of 0.12.22 or newer also names a task command, completion
|
|
1394
|
+
checks or Pi tool calls in flight, ready tasks being released and a Pi that has
|
|
1395
|
+
not settled); apply waits up to 10 minutes for it. Blockers follow, each with its
|
|
1396
|
+
next action. The choices are `[a] apply` (absent while a blocker stands), `[r]
|
|
1397
|
+
refresh`, `[k] end agents` (shown when an attached agent can be ended), `[j]`
|
|
1398
|
+
the plan as JSON, `[x] reset a project's hub` (shown when the plan has a running
|
|
1399
|
+
project) and `[q] quit`.
|
|
1400
|
+
|
|
1401
|
+
`[k] end agents` ends attached agents before the upgrade, so they are neither
|
|
1402
|
+
waited for nor restored: `t` for the TUI agents, `h` for the headless ones
|
|
1403
|
+
(Kimi, Pi in headless mode, the local worker), or their names, written
|
|
1404
|
+
`<project id>/<name>` when the plan covers more than one project. After a
|
|
1405
|
+
confirmation a TUI agent's terminal is closed, which cuts a turn in progress,
|
|
1406
|
+
and a headless agent is stopped by its hub through the `peer_stop` request
|
|
1407
|
+
(#278). A delivery that a busy agent had not settled is then held as
|
|
1408
|
+
`needs_review`, and its queue stays held until `ahub queue resolve` settles it. Each agent is ended only
|
|
1409
|
+
if the lock is free and its hub, its session and its terminal are still the
|
|
1410
|
+
ones the plan read; otherwise the screen says why it was left. A hub that does
|
|
1411
|
+
not know `peer_stop` (0.12.21 and older) cannot end a headless agent: the
|
|
1412
|
+
screen says so, the agent stops with the old hub at the upgrade, and the new
|
|
1413
|
+
hub starts it again. A session marked `own` is ended where it runs. The next
|
|
1414
|
+
plan shows the ended agents as offline.
|
|
1415
|
+
|
|
1416
|
+
After apply the command stays attached and prints each step as the receipt
|
|
1417
|
+
records it, and while a source is being prepared, what it still waits for.
|
|
1418
|
+
Enter opens the operation's screen while the runner works on. Ctrl+C leaves the
|
|
1419
|
+
command, at any prompt and while following, and never stops the runner: the
|
|
1420
|
+
`left:` line names the command that comes back to the screen, with the
|
|
1421
|
+
operation's own coordinator, because the installed `ahub` is the older release
|
|
1422
|
+
until the upgrade ends. A Ctrl+C between `a` and the start of the operation
|
|
1423
|
+
starts nothing. A prompt for a reason that is answered with
|
|
1424
|
+
Ctrl+C or the end of input ends nothing.
|
|
1425
|
+
|
|
1426
|
+
The operation's screen also opens first whenever an operation holds the lock,
|
|
1427
|
+
and for bare `ahub recovery`. It shows the phase and step, each project's phase
|
|
1428
|
+
and effect receipts and the error, then the choices that `status` lists under
|
|
1429
|
+
`next` for the same receipt and live state:
|
|
1430
|
+
|
|
1431
|
+
| Key | Runs | Offered when |
|
|
1432
|
+
| --- | --- | --- |
|
|
1433
|
+
| `r` | `recovery resume`, then follows | resume can get past what is live |
|
|
1434
|
+
| `c` | `recovery abort` | no runtime was stopped and no terminal effect or commit request is recorded |
|
|
1435
|
+
| `f` | `recovery dispose --fresh-session <peer>`, after a reason | a Codex or Claude restoration failed |
|
|
1436
|
+
| `e` | `recovery dispose --stop-and-archive`, after a confirmation and a reason | every unverified project reads as running, stopped or missing |
|
|
1437
|
+
| `x` | `e`, then the reset below | as `e` |
|
|
1438
|
+
| `w` | follows the runner | a runner holds the operation |
|
|
1439
|
+
|
|
1440
|
+
When more than one restoration failed, the second and later peers get
|
|
1441
|
+
`f-<peer>`. `s` refreshes the screen, `j` prints the receipt as JSON and `q`
|
|
1442
|
+
leaves the operation as it is.
|
|
1443
|
+
|
|
1444
|
+
While a runner is only waiting for its source to get quiet (step `prepare`, no
|
|
1445
|
+
effect recorded), `c` stops that runner and then aborts. It is offered only for
|
|
1446
|
+
a runner whose claim carries a process signature that still matches; a claim
|
|
1447
|
+
from a coordinator of 0.12.20 or older has none, so its pid is never signalled,
|
|
1448
|
+
and such a runner gives up its wait after 10 minutes. The receipt is read again
|
|
1449
|
+
when the key is pressed, and a runner that has moved on is left alone. If it
|
|
1450
|
+
records an effect in the instant between that read and the signal, the abort is
|
|
1451
|
+
refused and resume is offered instead. Past that step a running operation
|
|
1452
|
+
offers only `w`.
|
|
1453
|
+
|
|
1454
|
+
A reset is offered from the plan screen for a project whose hub is running, and
|
|
1455
|
+
with one question right after an operation was cancelled or ended from its
|
|
1456
|
+
screen: it asks for the project (when there are several) and the scope, prints
|
|
1457
|
+
the dry run of `ahub reset` or `ahub reset --all`, and applies it with `--yes`
|
|
1458
|
+
only after a second confirmation. While an operation holds the lock it refuses,
|
|
1459
|
+
as `ahub reset` does.
|
|
1460
|
+
|
|
1461
|
+
Ending a peer's running turn is not part of these screens: finish or interrupt
|
|
1462
|
+
it in that peer's terminal, answer approvals with `ahub permit` or the console,
|
|
1463
|
+
or cancel and upgrade later.
|
|
1464
|
+
|
|
1309
1465
|
### A partial operation
|
|
1310
1466
|
|
|
1311
1467
|
`ahub recovery status <operation-id>` reads the receipt without changing it:
|
|
@@ -1849,13 +2005,88 @@ Pi exit records show code or signal, turn/tool activity, whether teardown was
|
|
|
1849
2005
|
expected, startup status, and the last tool's name without arguments. An
|
|
1850
2006
|
unobserved native-terminal exit records unknown OS status. The console notice
|
|
1851
2007
|
gives the next action, usually `ahub pi`, or says why automatic start is held.
|
|
1852
|
-
A
|
|
1853
|
-
|
|
2008
|
+
A replacement says the owner stopped for a new Pi owner, including when the
|
|
2009
|
+
replacement is refused. Internal failure teardown directs session inspection
|
|
2010
|
+
rather than claiming a person requested it. Replacement, failed-start and
|
|
2011
|
+
active-turn guidance appears before automatic-start-disabled guidance.
|
|
1854
2012
|
|
|
1855
2013
|
With `pi.auto_start`, an unexpectedly idle headless Pi is resumed on its
|
|
1856
2014
|
verified persisted session once. The 60-second retry window rearms when the
|
|
1857
2015
|
attempt settles; a second exit inside that window stays offline. Missing or
|
|
1858
2016
|
invalid session history has no fresh fallback; inspect it before running
|
|
1859
2017
|
`ahub pi`. Active turns/tools, failed startup, a superseded owner, native
|
|
1860
|
-
terminal exits, requested
|
|
1861
|
-
restart. Crash recovery's #66
|
|
2018
|
+
terminal exits, a requested stop (`ahub stop pi`), requested shutdown and
|
|
2019
|
+
recovery operations suppress this restart. Crash recovery's #66
|
|
2020
|
+
recorded-session/fresh-start choices are unchanged.
|
|
2021
|
+
|
|
2022
|
+
### Pi/local unanswered approvals
|
|
2023
|
+
|
|
2024
|
+
An expired Pi/local request says "approval expired: no person answered". The
|
|
2025
|
+
worker must not retry that call; hand the task off or stop. Two consecutive
|
|
2026
|
+
unanswered requests end the current turn and produce a console notice. An
|
|
2027
|
+
expiry counts only when no person answered a request of that peer while it
|
|
2028
|
+
was pending, so a batch of parallel requests does not stop a worker whose
|
|
2029
|
+
person is answering; a grant served from the always-allow cache or a permission
|
|
2030
|
+
mode is not an answer and does not reset the count. A new turn also resets it. The turn ends
|
|
2031
|
+
through the same abort path an execution-budget stop uses: Pi stays attached
|
|
2032
|
+
and the sender is told the approval reason. Supported Pi and local settle that
|
|
2033
|
+
delivery after reporting the reason, so the next queued message runs without
|
|
2034
|
+
an operator release. Pi clears turn-only approval state at settlement; the
|
|
2035
|
+
person's idle `!command` remains usable and a later failure keeps its own reason.
|
|
2036
|
+
A Pi whose extension cannot abort
|
|
2037
|
+
a turn (an older hub extension or a runtime without native abort support) goes
|
|
2038
|
+
offline instead. Inspect prior work, restart with `ahub pi`, find the held
|
|
2039
|
+
delivery with `ahub queue list --peer pi`, inspect it with `ahub queue show <id>`,
|
|
2040
|
+
then explicitly settle it with `ahub queue resolve <id> --action completed|retry|discard --reason "<text>"`.
|
|
2041
|
+
A cancelled tool withdraws
|
|
2042
|
+
its request immediately; a later `ahub permit` answer is refused and cannot
|
|
2043
|
+
create an always-allow grant. Tool requests from a previous turn or an older
|
|
2044
|
+
extension missing session/turn identity are refused before execution; restart
|
|
2045
|
+
Pi with the current hub extension rather than retrying that old call.
|
|
2046
|
+
|
|
2047
|
+
The dashboard shows the number of waiting Pi/local requests and where to
|
|
2048
|
+
answer them. It remains deny-only for those private rows. Use the console or
|
|
2049
|
+
`ahub tail` to inspect the request, then `ahub permit` to answer it. Recovery
|
|
2050
|
+
stops waiting for an approval when that request is withdrawn.
|
|
2051
|
+
|
|
2052
|
+
A person's `!command` in the managed Pi TUI runs through the managed sandbox
|
|
2053
|
+
without asking for another hub approval. Its reported exit code comes from
|
|
2054
|
+
the process, including a nonzero exit; text printed by the command does not
|
|
2055
|
+
set its status. This exception requires the current idle TUI owner's reserved
|
|
2056
|
+
request and does not apply to model-origin bash calls.
|
|
2057
|
+
|
|
2058
|
+
### Stop one headless peer
|
|
2059
|
+
|
|
2060
|
+
`ahub stop kimi`, `ahub stop pi` (headless mode), or `ahub stop local` stops
|
|
2061
|
+
that hub-owned runtime through its adapter and prints `<peer>: offline`.
|
|
2062
|
+
The hub and its other peers keep running. The command is human-only;
|
|
2063
|
+
agent shells and tools-role connections cannot use the `peer_stop` request.
|
|
2064
|
+
An older hub that does not know the request needs an upgrade before this
|
|
2065
|
+
command is available.
|
|
2066
|
+
|
|
2067
|
+
Claude, Codex and Pi in TUI mode belong to their native terminals: end them
|
|
2068
|
+
in those terminals. The upgrade plan's end-agents action closes a TUI through
|
|
2069
|
+
its verified terminal binding and uses `peer_stop` for a headless runtime.
|
|
2070
|
+
Unknown and already-offline peers are refused with a next action. A recovery
|
|
2071
|
+
operation or shutdown holds stop mutations; finish that operation first.
|
|
2072
|
+
A peer's concurrent start, stop or permission-mode change must also finish
|
|
2073
|
+
before another lifecycle change is admitted, and an upgrade or restart does not
|
|
2074
|
+
prepare the hub while a stop is in progress.
|
|
2075
|
+
|
|
2076
|
+
A requested stop does not automatically restart a peer. A person or an authorized
|
|
2077
|
+
conductor can explicitly start it again; conductor starts remain permitted. Start it with
|
|
2078
|
+
`ahub kimi`, `ahub pi` or `ahub local`. Pending approvals are withdrawn before
|
|
2079
|
+
teardown. An in-flight delivery becomes `needs_review`, with a reason naming
|
|
2080
|
+
the requested stop; it is neither completed nor automatically replayed.
|
|
2081
|
+
Inspect partial effects with `ahub queue list --peer <peer>` and
|
|
2082
|
+
`ahub queue show <delivery-id>`, then explicitly choose `completed`, `retry`
|
|
2083
|
+
or `discard` with `ahub queue resolve`. Restarting alone does not release
|
|
2084
|
+
that held delivery. Work queued behind an idle stopped peer is retained and
|
|
2085
|
+
runs when it is started again, subject to its existing pauses and holds. New work
|
|
2086
|
+
arriving during teardown stays queued too, even if a console resume runs meanwhile.
|
|
2087
|
+
An idle Pi with no message/file yet records its live empty-session proof before
|
|
2088
|
+
stopping, so its next start resumes that identity. A Pi stopped after its first
|
|
2089
|
+
prompt was accepted but before history was persisted is not treated as empty:
|
|
2090
|
+
its delivery is held, and another start refuses until resume history is verified.
|
|
2091
|
+
Inspect and settle the held delivery before deliberately replacing that unpersisted
|
|
2092
|
+
session by restarting the hub; the stop never silently chooses a fresh session.
|
package/docs/quickstart.md
CHANGED
|
@@ -17,7 +17,7 @@ ahub setup # installs the Claude Code channel plu
|
|
|
17
17
|
Or use the matching GitHub release:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
bun add -g github:STAIxBWLB/agent-hub#v0.12.
|
|
20
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.12.22
|
|
21
21
|
ahub setup
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -26,7 +26,9 @@ From a clone instead: `git clone`, `bun install`, `bun link`.
|
|
|
26
26
|
`ahub doctor` tells you what is installed, running and configured. Rows you do not need can stay red: the hub works with any subset of the peers.
|
|
27
27
|
|
|
28
28
|
For managed launchers, two-agent task/review work, approvals, graceful shutdown,
|
|
29
|
-
and upgrade or crash recovery, see the [operations guide](operations.md).
|
|
29
|
+
and upgrade or crash recovery, see the [operations guide](operations.md). To
|
|
30
|
+
move running projects to a newer release later, run `ahub upgrade` in a
|
|
31
|
+
terminal: it reviews the plan with you, applies it and follows it.
|
|
30
32
|
|
|
31
33
|
## First session
|
|
32
34
|
|
|
@@ -57,7 +59,7 @@ ahub say @kimi "run the tests and report" # one peer
|
|
|
57
59
|
{ "omniroute": { "urls": ["http://your-gateway:20128/v1"], "api_key_file": "/path/to/key" } }
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local] fixed_model`). Then `ahub local`. It works only inside the project, asks before it writes or runs anything (`ahub permit <id> allow`) unless you chose a permission mode for it (`ahub permission`), and everything it executes is sandboxed.
|
|
62
|
+
or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local] fixed_model`). Then `ahub local`. It works only inside the project, asks before it writes or runs anything (`ahub permit <id> allow`) unless you chose a permission mode for it (`ahub permission`), and everything it executes is sandboxed. Keep `ahub console` open while it works: a request nobody answers expires, and after two in a row its turn ends.
|
|
61
63
|
|
|
62
64
|
## Commands of a working day
|
|
63
65
|
|
|
@@ -68,6 +70,7 @@ or set `OMNIROUTE_API_KEY`. Name the model in `.agenthub/routing.toml` (`[local]
|
|
|
68
70
|
| `ahub board` | who has what; `ahub task show <id>` for one task |
|
|
69
71
|
| `ahub status` | peers, queues, pauses; `ahub budget` for quota windows |
|
|
70
72
|
| `ahub ask <question>` | what the board, shared memory and the log say, with the ids the answer rests on |
|
|
73
|
+
| `ahub stop <peer>` | stop one headless peer (Kimi, headless Pi, `local`); the hub and the other peers keep running |
|
|
71
74
|
| `ahub kill` | stop the daemon and everything it started |
|
|
72
75
|
|
|
73
76
|
More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-design.md`](specs/2026-09-19-agent-hub-design.md). What it protects and what it does not: [`docs/security.md`](security.md).
|
package/docs/security.md
CHANGED
|
@@ -6,14 +6,14 @@ agent-hub connects agents that can each run commands. This page says what the hu
|
|
|
6
6
|
|
|
7
7
|
- **Other agents' text is untrusted.** Every message that crosses from one peer to another is framed as untrusted input: a channel tag with `meta.source` for Claude started with `ahub claude`; otherwise a fixed header line (`frame()`, `[agent-hub message from ...]`) with an instruction that says it is untrusted, the plugin's server instructions for a tools-only Claude reading `hub_inbox` and a standing instruction for the others. A message body cannot forge the hub's own headers: such lines are quoted (`sanitize`). Replies inherit a hop count capped at 3, so agents cannot ping-pong forever; neither a digest nor a steer can reset it.
|
|
8
8
|
- **The control link is loopback plus a secret.** The daemon and the Codex proxy bind 127.0.0.1 only. The control WebSocket requires a per-run token (`.agenthub/state/control-token`, mode 600), and both servers refuse any request that carries an `Origin` header: any web page can open a WebSocket to localhost, and browsers always send `Origin`. External clients cannot claim the console user's id or a hub-managed peer's id.
|
|
9
|
-
- **Permission prompts stay on unless an operator selects a permission mode.** `ask` leaves native decisions unchanged. `ask-when-needed` grants scoped Claude file tools or Pi/local writes and edits; Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation or a person's console `y` for a config default at hub start. Until that config confirmation the peer runs in `ask`. Kimi's and `local`'s permission requests are relayed to the console and cancelled after `approvals.timeout_s` (default 120 s) of silence; the macOS notification for a waiting request carries the peer and the tool name only. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They invoke hub-owned operations rather than arbitrary file or shell tools, and every call passes the hub's own checks. Identity comes from the exact permission title, or from the earlier tool-call title bound to the same call id and resolved against the configured MCP servers when the permission title contains argument JSON (Qwen). Payload text and unrelated display titles never establish identity. For ACP agents, a payload longer than the console shows is marked as cut and never offers a session-wide grant. Pi's write, edit, shell and git write requests are relayed the same way and can be answered `Always allow <tool> until Pi restarts`: later calls of that tool by the same Pi start skip the prompt but stay inside the path guard and sandbox. The grant covers every later call of that tool whatever its payload, is kept in memory only, cannot be given from the dashboard, and each call it allows is logged by tool name. `--unattended` turns prompts off, says so loudly, and is never the default.
|
|
9
|
+
- **Permission prompts stay on unless an operator selects a permission mode.** `ask` leaves native decisions unchanged. `ask-when-needed` grants scoped Claude file tools or Pi/local writes and edits; Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation or a person's console `y` for a config default at hub start. Until that config confirmation the peer runs in `ask`. Kimi's and `local`'s permission requests are relayed to the console and cancelled after `approvals.timeout_s` (default 120 s) of silence; the macOS notification for a waiting request carries the peer and the tool name only. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They invoke hub-owned operations rather than arbitrary file or shell tools, and every call passes the hub's own checks. Identity comes from the exact permission title, or from the earlier tool-call title bound to the same call id and resolved against the configured MCP servers when the permission title contains argument JSON (Qwen). Payload text and unrelated display titles never establish identity. For ACP agents, a payload longer than the console shows is marked as cut and never offers a session-wide grant. Pi's write, edit, shell and git write requests are relayed the same way and can be answered `Always allow <tool> until Pi restarts`: later calls of that tool by the same Pi start skip the prompt but stay inside the path guard and sandbox. The grant covers every later call of that tool whatever its payload, is kept in memory only, cannot be given from the dashboard, and each call it allows is logged by tool name. A Pi or `local` request whose call was cancelled or whose turn ended is withdrawn at once: a later answer is refused and grants nothing, an `Always allow` included. One shell command runs without a hub approval: the `!command` a person types in the managed Pi TUI. It stays inside the same sandbox and denylist, and the hub takes it only from the verified owner process of a Pi in TUI mode, while Pi is idle, on a reservation that is used once; a model's own bash call never takes that path (issue #253). `--unattended` turns prompts off, says so loudly, and is never the default.
|
|
10
10
|
- **A committed config cannot choose launch commands, credential files, data endpoints, permission modes or a wider sandbox.** The machine-local fields (`kimi_cmd`, `permission_modes`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url`, `local.read_allow`, `local.bash_network`, `local.network_allow`) apply only from a config file git confirms nobody committed: `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, a permission mode, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust.
|
|
11
11
|
- **Telemetry holds no bodies.** `.agenthub/state/events.jsonl` (issue #40) records envelope ids, routing and sizes, task ids and states, overlapping paths and token counts, and the task id (with a PII flag) that each usage and token record is attributed to. It never records a message body, a task title or detail, and marks private (PII) envelopes and tasks as such. It stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved task the ids, states, counts, tokens, times and PII flag the events hold, a person's label from a closed list, and a hash in place of the project id; benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
|
|
12
12
|
- **Snapshots stay in your repository.** Per-turn snapshots (issue #33) are git objects in the project's own object store, written through a temporary index; nothing is referenced, pushed or copied elsewhere, and `git gc` prunes them. They hold what the work tree held, including untracked files that are not ignored, so keep secrets in ignored files. They carry the repository's own permissions, and nothing caps their disk use but `git gc`. A turn of a peer holding an open PII task is not snapshotted; a PII file left in the project is snapshotted by later turns like any other file. `ahub undo` restores only files whose current content is exactly what the turn left.
|
|
13
13
|
- **The edit hook reads, never decides.** `ahub check-path --hook` (issue #32) reads hub.db and returns context for Claude and a line for you; it sets no permission decision, so your permission rules stay in charge. It names other owners' task ids, titles and states, which then reach Claude's model; PII tasks are left out.
|
|
14
14
|
- **The session record holds identities only.** `.agenthub/state/sessions.json` (issue #37, mode 600) keeps each attached peer's recovery metadata: launch options, session and thread ids, Pi's session file path. No message or task text; loss notices name deliveries by id, sender and public task title.
|
|
15
15
|
- **Context checkpoints have a separate text record.** Native occupancy readings carry numeric metadata only. An optional context checkpoint persists its summary in `.agenthub/state/context-checkpoint-<peer>.json` with mode 0600 and attempts shared-memory storage when enabled. Completion requires the outstanding request id, current peer/native session and transport generation; new connection claims invalidate old requests. Private turns, every open PII task associated with the peer as owner or reviewer (including pending review), and PII-pattern summaries are refused before persistence or cloud memory. Its text is never broadcast, and context pressure does not pause, reassign or replace the session.
|
|
16
|
-
- **The local worker is boxed in.** Paths are resolved through symlinks and must stay inside the project; a secrets denylist (`.env*`, keys, credential files, the hub's own state and the state archives `ahub reset --all` leaves) applies to its file tools, its git arguments and its memory capture alike; `.git` and `.agenthub` are not writable. Writes, edits, shell commands and mutating git wait for approval unless the operator's permission mode grants them (writes and edits inside the grant filter in `ask-when-needed`, all four in `never-ask`; see below), and the approver sees what will be written or run, with control characters escaped. Everything it executes runs under the macOS sandbox, attended or not. Since 0.10 the profile starts from deny default (issue #39): commands run and read only the system, toolchain and project directories (and the project's git dir and the selected developer dir; with network on, the public CA bundles); no writes outside the project, its git dir and a temp dir of its own (`TMPDIR`, made for each command and removed when it ends; issue #63); no `.git
|
|
16
|
+
- **The local worker is boxed in.** Paths are resolved through symlinks and must stay inside the project; a secrets denylist (`.env*`, keys, credential files, the hub's own state and the state archives `ahub reset --all` leaves) applies to its file tools, its git arguments and its memory capture alike; `.git` and `.agenthub` are not writable. Writes, edits, shell commands and mutating git wait for approval unless the operator's permission mode grants them (writes and edits inside the grant filter in `ask-when-needed`, all four in `never-ask`; see below), and the approver sees what will be written or run, with control characters escaped. Everything it executes runs under the macOS sandbox, attended or not. Since 0.10 the profile starts from deny default (issue #39): commands run and read only the system, toolchain and project directories (and the project's git dir and the selected developer dir; with network on, the public CA bundles); no writes outside the project, its git dir and a temp dir of its own (`TMPDIR`, made for each command and removed when it ends; issue #63); no `.git` name at any depth, closing the creation, rename, symlink and gitfile routes to a planted repository (others stay open; issue #281), no `config` or `hooks` under one (in an external git dir, only its own children and the hooks directory's contents, so refs named `config` or `hooks` still work), no `commondir` at any depth below one, with every such name matched folded against the code points HFS+ ignores, and no `.agenthub` anywhere in the project; the cost is that `git init`, `git clone` and `git worktree add` do not run inside the sandbox (add, commit, stash, checkout and gc still do), and neither do `git worktree remove`, `git worktree prune` of a stale record, `git clean -ffd` in a project with a nested repository, or `rm -rf` of a directory that holds a `.git` (refusing the unlink is what stops the rename-out route); no network, loopback included, unless `local.bash_network`; with it, only through the hub's egress proxy to the hosts in `local.network_allow` (issue #65), which refuses names that resolve to internal addresses (behind NAT64 with the well-known prefix, by the IPv4 address the answer carries; a network-specific prefix is not recognised), so claude-mem and the Codex app-server stay out of reach (`"direct"` opens everything, until 0.13.0). One channel stays open: `trustd`, which TLS clients need, can fetch a certificate's AIA or OCSP URL on a command's behalf, outside the proxy. The allow-default profile of 0.9 and earlier was removed in 0.12.0 (issue #83); a `local.sandbox` setting is ignored with a note. The shared temp dirs (the user's and `/private/tmp`) are closed. Without the sandbox there is no `bash` tool.
|
|
17
17
|
- **Capabilities are enforced, not suggested.** `capabilities` (issue #39) is checked by the daemon where task operations and messages arrive, so a peer cannot get round it by phrasing. A peer can never answer a permission request: the control link takes `permit` from the console role only, and a message that quotes a permit command is just text. Both bind the hub's own tool paths: a vendor agent with its own shell in the project (Codex, Claude, Kimi) can read `.agenthub/state/control-token` and connect as the console, which only the local worker's and Pi's sandbox prevents.
|
|
18
18
|
- **PII has an enforced path.** A task matching `signals.pii_patterns` goes to `local` or to nobody; its text is absent from other peers' envelopes, the console stream, the log and the board listing; the console user reviews it; `local` answers such a turn to the console only, keeps it out of its history, refuses it when the only gateway is off campus, and may not save notes or spin off tasks during it. Nothing about it is sent to claude-mem, whose observer is a cloud model.
|
|
19
19
|
- **A model screen can add to the patterns (issue #198).** With `signals.pii_screen = "local"` in `routing.toml` (default `off`; it applies while `constraints.pii = "local_only"`), a newly proposed task is read by a model before the hub does anything else with it: until the verdict it is on no board, in no envelope, before no triage model, and the hub makes no brief or note call for it. The local worker's claude-mem capture leaves its hub tool calls out (issue #230), so a proposal it makes in an ordinary turn cannot reach claude-mem ahead of the verdict that way. The screen runs on the model on this machine (MLX or Ollama, loopback only) or, without one, on the gateway only while `onCampus()` holds, and the gateway call is refused once more right before transport if the gateway is behind Access; task text never goes off campus to be screened. When the on-device generation slot is taken (Pi generating, another screen), the screen goes to the campus gateway instead of waiting, and on campus a device that fails or is still loading after 60% of the deadline hands over to the gateway too; off campus it waits for the slot and the device within the deadline. With the legacy MLX provider the screen may start an `mlx_lm.server`, as Pi's relay does, and it stays running until `ahub models stop`. Its answer is `pii` or `clear` plus a category from a closed list (name, student id, phone, address, grade, health, other). Anything else, a timeout (8 s, one call and one generation slot per item), a missing or off-campus model, or text longer than 6000 UTF-8 bytes (about 2000 Hangul syllables) is `unknown`, and an unknown task is a PII task: this is the hub's one model call that fails closed, so with the screener down every new task waits for `local`. An unknown verdict has the costs of any PII task, even when a second look clears it later: opening it lifts every silent turn-free cohort for good and turns facts off while it is open, and off campus a proposal without a class is refused (triage is not asked for a PII task there), with an error that says so. Off campus with the on-device slot taken, a task stays `unknown` until it is screened again: while it is still proposed and no peer ever owned it, the hub's release timer (every 60 s, one task at a time, the least tried first, at most 10 times per task in a hub run; never text too long to read) asks again, and `clear` lifts its `pii` signal and routes it through the class peers (a named owner is not remembered; a reserved one is offered first, as always). A task `local` took keeps its PII path, also once it is declined back to nobody. A pattern match is PII without a call. The task's history records the source of its `pii` signal (`regex`, `screen` with the category, or `unknown` with the reason), an unknown verdict is a console line naming the task id only, and `events.jsonl` gets a `pii_screen` record with the item kind, label, source, category, reason and latency, never the text. A routing explanation of a draft does not call the screen and says so.
|
|
@@ -57,8 +57,9 @@ are passed by path, keeping caller settings out of process argv.
|
|
|
57
57
|
Claude ask-when-needed grants only resolving project file targets outside
|
|
58
58
|
.agenthub, .git, .claude, .codex, .qwen, .kimi, .pi and .mcp.json. Symlink escapes, protected aliases, traversal and
|
|
59
59
|
unresolved wildcard targets receive no decision. Names are compared folded (letter case, compatibility forms and
|
|
60
|
-
the code points
|
|
61
|
-
is the same name), the path is judged inside the project, and
|
|
60
|
+
the code points HFS+ ignores; case-insensitive APFS folds case but ignored none in the probe; so a look-alike such
|
|
61
|
+
as a long s or a name with a zero-width joiner is the same name), the path is judged inside the project, and
|
|
62
|
+
a file with a second hard link receives no decision,
|
|
62
63
|
for Claude and for the Pi/local write and edit grants alike. The local worker's and Pi's write guard refuses `.git`
|
|
63
64
|
and `.agenthub` by the same folded comparison in every mode, so a new `.GIT/config` cannot be planted for git to run. Native rules then apply;
|
|
64
65
|
this is a grant filter, not a filesystem sandbox.
|
|
@@ -72,7 +73,8 @@ as that peer in tools mode. Messages and task changes retain that actor;
|
|
|
72
73
|
capability. Conflicting or malformed markers fail closed. There is no `--as-user`.
|
|
73
74
|
Human-only operations are refused before connecting, including permission answers
|
|
74
75
|
and permission modes, queue resolution, budget overrides, benchmark runs, research
|
|
75
|
-
backfill and task labels,
|
|
76
|
+
backfill and task labels, stopping a peer (`ahub stop`), lifecycle and recovery
|
|
77
|
+
operations, and `ask`.
|
|
76
78
|
The operator uses a plain terminal or `ahub console`. A Claude `!` command that
|
|
77
79
|
inherits the agent markers follows the same rule. The console strips control
|
|
78
80
|
sequences from agent and daemon text before it quotes forged headers, and its
|
|
@@ -92,7 +94,8 @@ move other than the hub's own was not made from the console (to another peer
|
|
|
92
94
|
only with `assign` when its capabilities are listed).
|
|
93
95
|
A conductor may inspect public state, assign or escalate work, start supported
|
|
94
96
|
headless peers and place its own delivery holds. It cannot answer approvals,
|
|
95
|
-
resolve durable deliveries, override budget pauses
|
|
97
|
+
resolve durable deliveries, override budget pauses, release a human hold or stop
|
|
98
|
+
a peer: `peer_stop` is taken from the console role only, and no hub tool sends it.
|
|
96
99
|
Pending approval summaries exclude titles; PII tasks remain public stubs.
|
|
97
100
|
Conduct events contain ids only. Supervision feeds use structured reasons and
|
|
98
101
|
never carry check output or approval bodies.
|
package/docs/smoke.md
CHANGED
|
@@ -2,6 +2,62 @@
|
|
|
2
2
|
|
|
3
3
|
`scripts/check.sh` covers everything against fakes. The legs below need real accounts and an interactive terminal, so they are run by hand and recorded here.
|
|
4
4
|
|
|
5
|
+
## Codex resumed approval policy (#270)
|
|
6
|
+
|
|
7
|
+
2026-10-10: installed version read back as `codex-cli 0.162.1`. The live
|
|
8
|
+
stickiness leg is unverified: a hand-run `codex --remote` resume and a human-only
|
|
9
|
+
`ahub permission codex ask` require a person's interactive terminal. The approved
|
|
10
|
+
fallback is implemented: keep restoration owed per thread and send the captured
|
|
11
|
+
native policy once on that thread's next accepted turn, whether or not the native
|
|
12
|
+
override is sticky across resume. Fake app-server coverage verifies both overlays,
|
|
13
|
+
other-thread visits, either resume/ask order, rejected restoration and restoration
|
|
14
|
+
only once. The proxy protects the one restoring turn; a repeated policy on the
|
|
15
|
+
next TUI turn is indistinguishable from a person's explicit native choice.
|
|
16
|
+
|
|
17
|
+
Manual leg in a disposable project:
|
|
18
|
+
|
|
19
|
+
1. Start `ahub codex`, record its thread ID and native approval policy, then run
|
|
20
|
+
`ahub permission codex never-ask --yes` from a human shell and complete a benign turn.
|
|
21
|
+
2. Close the TUI without stopping its hub proxy. Run `ahub permission codex ask`.
|
|
22
|
+
3. Hand-run `codex --remote <the existing proxy URL>` and resume that same thread.
|
|
23
|
+
Record what the TUI sends as `approvalPolicy` on its first turn after a resume,
|
|
24
|
+
including any echo of the resume response. Observe the policy before and after
|
|
25
|
+
that benign turn; record whether the override persisted across resume and
|
|
26
|
+
whether the original policy was restored.
|
|
27
|
+
4. Send the TUI's second turn after that resume; record its outgoing policy and
|
|
28
|
+
the effective native policy. A repeat of the reported override is passed through
|
|
29
|
+
after the one restoring turn, just like an explicit choice.
|
|
30
|
+
5. Repeat with `ask-when-needed` and with resume before switching to ask. Record
|
|
31
|
+
the Codex version, thread ID, policies and outcomes. This checklist is not
|
|
32
|
+
evidence that any live leg has passed.
|
|
33
|
+
|
|
34
|
+
## Interactive upgrade (#272)
|
|
35
|
+
|
|
36
|
+
Not run live yet. Covered by tests with a scripted terminal and, for `restart`,
|
|
37
|
+
by a real detached operation driven through the plan screen
|
|
38
|
+
(`test/upgrade-cli.test.ts`, with standard input and output marked as a terminal).
|
|
39
|
+
The live leg, at a real terminal with Orca-launched peers:
|
|
40
|
+
|
|
41
|
+
1. With a hub on the previous release, run
|
|
42
|
+
`bunx --package @staix/agent-hub@<version> ahub upgrade`. Expect the plan
|
|
43
|
+
screen: the project, each peer's fate, `in progress:` when a peer is busy, no
|
|
44
|
+
blocker.
|
|
45
|
+
2. Press `a`. Expect the steps in order, `waiting for:` while a peer is busy, and
|
|
46
|
+
`upgrade to <version> completed`; `ahub --version` and `ahub status` report it.
|
|
47
|
+
3. On a scratch project, start `ahub restart` while a peer is busy, press `a`,
|
|
48
|
+
then Enter during the wait and `c` on the operation screen. Expect `operation
|
|
49
|
+
<id> is cancelled; the recovery lock is free`, the reset question, and the hub
|
|
50
|
+
still running.
|
|
51
|
+
4. With an installed CLI one release behind, run `ahub upgrade`, confirm the
|
|
52
|
+
hand-over, press `a`, then Ctrl+C during the wait. Expect the `left:` line, a
|
|
53
|
+
clean shell prompt, and `ahub recovery` showing the runner still working.
|
|
54
|
+
|
|
55
|
+
5. On the plan screen press `k`, then `t`, and confirm. Expect each TUI agent's
|
|
56
|
+
terminal closed and the next plan showing them offline; with a hub of 0.12.22
|
|
57
|
+
or newer, `h` stops the headless agents the same way.
|
|
58
|
+
|
|
59
|
+
Record the versions, the screens' text and what differed.
|
|
60
|
+
|
|
5
61
|
## ahub reset on a scratch project (#214)
|
|
6
62
|
|
|
7
63
|
Not yet run. A person runs it in a terminal, on a scratch project only, never
|
|
@@ -1405,8 +1461,11 @@ The historical Claude benign checks above used the earlier hook policy. The curr
|
|
|
1405
1461
|
|
|
1406
1462
|
Unverified natively (Codex 0.162.0): whether a thread that got a `never` or `on-request` override keeps it when the
|
|
1407
1463
|
same thread is resumed through the hub's proxy by a hand-run `codex --remote` after `ahub permission codex ask` was
|
|
1408
|
-
given while no TUI was attached. The
|
|
1409
|
-
|
|
1464
|
+
given while no TUI was attached. The status says ask; the current proxy keeps restoration owed per thread and
|
|
1465
|
+
sends the captured native policy once on the resumed thread's next accepted turn, including when the TUI echoes
|
|
1466
|
+
the resume response's sticky override. This protects only the restoring turn; a repeat on the second TUI turn
|
|
1467
|
+
cannot be distinguished from an explicit choice and passes through. Both turns' TUI traffic remain natively unverified. `ahub codex` always starts
|
|
1468
|
+
a new proxy, which is not affected. See the current #270 manual checklist above.
|
|
1410
1469
|
|
|
1411
1470
|
## Whole-board task progress and dashboard themes (#246)
|
|
1412
1471
|
|