@staix/agent-hub 0.12.39 → 0.12.41

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +16 -10
  3. package/docs/agent-notes/adapters.md +4 -1
  4. package/docs/agent-notes/budget.md +2 -0
  5. package/docs/agent-notes/bus.md +2 -2
  6. package/docs/agent-notes/daemon.md +10 -6
  7. package/docs/agent-notes/models.md +4 -4
  8. package/docs/agent-notes/tasks.md +3 -0
  9. package/docs/agent-notes/tests.md +1 -1
  10. package/docs/memory.md +1 -1
  11. package/docs/operations.md +152 -28
  12. package/docs/quickstart.md +23 -5
  13. package/docs/security.md +20 -8
  14. package/docs/smoke.md +2 -2
  15. package/docs/specs/2026-09-19-agent-hub-design.md +158 -70
  16. package/docs/specs/2026-10-04-switchyard-source-port-design.md +12 -7
  17. package/docs/verification/2026-10-20-0.12.40.md +63 -0
  18. package/docs/verification/2026-10-20-0.12.41.md +44 -0
  19. package/docs/verified.json +38 -19
  20. package/package.json +1 -1
  21. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  22. package/plugins/agent-hub/server.js +415 -151
  23. package/src/adapters/acp.ts +34 -1
  24. package/src/adapters/claude-channel.ts +35 -4
  25. package/src/adapters/codex-appserver.ts +12 -3
  26. package/src/adapters/definitions/builtin-metadata.ts +4 -2
  27. package/src/adapters/definitions/native-cli-codex-launch.ts +2 -2
  28. package/src/adapters/definitions/native-cli-doctor.ts +8 -5
  29. package/src/adapters/definitions/native-cli-entry-claude.ts +7 -2
  30. package/src/adapters/definitions/native-cli-entry-codex.ts +13 -14
  31. package/src/adapters/definitions/native-cli-entry-jcode.ts +13 -3
  32. package/src/adapters/definitions/native-cli-entry-pi.ts +9 -7
  33. package/src/adapters/definitions/native-cli-entry-types.ts +2 -0
  34. package/src/adapters/definitions/native-cli-jcode-mcp.ts +55 -1
  35. package/src/adapters/jcode.ts +110 -4
  36. package/src/adapters/local-worker.ts +21 -37
  37. package/src/adapters/pi.ts +7 -0
  38. package/src/adapters/registry-types.ts +2 -0
  39. package/src/cli/console-state.ts +1 -0
  40. package/src/cli/console.ts +11 -1
  41. package/src/cli/help.ts +13 -8
  42. package/src/cli/identity-audit.ts +33 -7
  43. package/src/cli/jcode-mcp.ts +1 -1
  44. package/src/cli/launch.ts +1 -1
  45. package/src/cli/main.ts +134 -24
  46. package/src/cli/output.ts +5 -2
  47. package/src/cli/status-lines.ts +1 -1
  48. package/src/cli/terminal-recovery.ts +54 -4
  49. package/src/cli/upgrade-interactive.ts +5 -0
  50. package/src/cli/upgrade-runtime.ts +13 -1
  51. package/src/cli/upgrade.ts +67 -1
  52. package/src/hub/board.ts +4 -2
  53. package/src/hub/bus.ts +8 -3
  54. package/src/hub/checks.ts +15 -5
  55. package/src/hub/conductor.ts +2 -1
  56. package/src/hub/config-apply.ts +20 -2
  57. package/src/hub/config-reload.ts +2 -8
  58. package/src/hub/control-client.ts +26 -3
  59. package/src/hub/daemon.ts +155 -68
  60. package/src/hub/delivery-superseded.ts +19 -2
  61. package/src/hub/events.ts +4 -1
  62. package/src/hub/hub-tools.ts +4 -4
  63. package/src/hub/inference.ts +4 -8
  64. package/src/hub/lifecycle.ts +3 -3
  65. package/src/hub/main-checkout.ts +36 -0
  66. package/src/hub/memory-profile-check.ts +6 -2
  67. package/src/hub/memory-profile.ts +18 -1
  68. package/src/hub/peers.ts +2 -0
  69. package/src/hub/ports.ts +1 -0
  70. package/src/hub/routing.ts +24 -14
  71. package/src/hub/settings.ts +1 -1
  72. package/src/hub/task-repository.ts +136 -0
  73. package/src/hub/tasks.ts +101 -21
  74. package/src/hub/terminal-launch.ts +130 -0
  75. package/src/hub/usage.ts +44 -18
  76. package/src/models/route/config.ts +1 -1
  77. package/src/omniroute/client.ts +7 -19
  78. package/templates/AGENTS.block.md +1 -0
  79. package/templates/routing.toml +10 -44
  80. package/src/switchyard/config.ts +0 -45
  81. package/src/switchyard/sidecar.ts +0 -136
@@ -68,6 +68,39 @@ Facts below are tagged **verified** (measured on 2026-09-19 on the owner's Mac)
68
68
  are never pooled through a gateway; local models are for bulk and low-stakes work;
69
69
  PII never leaves the campus network (internal routing criteria).
70
70
 
71
+ ## Amendment: retire the Switchyard sidecar (#553, 2026-10-20)
72
+
73
+ - All model routing runs in process. Remove `src/switchyard/`, the external binary,
74
+ generated config, lifecycle, health/status/doctor entries, `AGENTHUB_SWITCHYARD_BIN`
75
+ and `x-switchyard-session-id`. Port offset `SWITCHYARD = 3` remains reserved and
76
+ unused; other port offsets do not move. Keep `src/models/route/` Apache-2.0
77
+ notices and `scripts/check-switchyard-upstream.ts` as algorithm provenance.
78
+ - Translate `stage_router` to a hub `stage` route: `efficient_target` becomes
79
+ `efficient`, `capable_target` becomes `capable`, and `confidence_threshold` is
80
+ unchanged. Translate passthrough to the class's `fixed_model`, without a route.
81
+ The old unshipped classifier escalation uses the existing hub escalation route.
82
+ - Existing `[targets]` and `[routes]` tables load as retired settings, are ignored
83
+ and produce startup/doctor diagnostics naming their hub-route replacements.
84
+ Every selection without the `hub/` prefix, including legacy named routes and
85
+ `sy/` selections, uses its fixed-model fallback with one warning, not a
86
+ process start. Never rewrite an operator's routing files during load or reload.
87
+ - The shipped template uses `[hub_routes]`, local/implement `hub/stage` and direct
88
+ `fast` fixed models for former `sy/fast` classes. A class with a `fixed_model`
89
+ and no `route` explicitly selects direct inference rather than inheriting the
90
+ global local route. A fixed-model fallback remains required.
91
+ - Internal digest, triage and other hub inference calls use only their configured
92
+ fixed model through OmniRoute, preserving bounded, optional, fail-open behavior.
93
+ - Doctor compares every local/class fixed alias and every hub route's effective
94
+ efficient/capable/judge alias with gateway inventory: one failure per missing
95
+ alias, or one passing row when all are served. An unavailable inventory is not
96
+ proof that an alias is served. PII keeps positive campus admission and
97
+ `onCampusOnly`; no model-route selection may bypass either guard.
98
+
99
+ This decision supersedes earlier active sidecar deployment and fallback wording
100
+ below. Historical binary observations remain evidence of the former integration,
101
+ not instructions to install or run it. Phase 5 of the source-port design records
102
+ the same retirement decision.
103
+
71
104
  ## Decisions
72
105
 
73
106
  1. Greenfield hub, not a fork. Bun + TypeScript, one repo, MIT.
@@ -80,12 +113,10 @@ Facts below are tagged **verified** (measured on 2026-09-19 on the owner's Mac)
80
113
  per-call model selection needs the hub to own the call. OpenCode ACP is kept as an
81
114
  optional fallback runtime behind the same adapter interface.
82
115
  5. Model selection has three layers: L1 hub policy (which peer, which route),
83
- L2 Switchyard (which model per call for hub-owned traffic), L3 OmniRoute (keys,
84
- provider fallback, usage). Switchyard runs as a hub-managed sidecar on the Mac for the
85
- duration of a hub session only; it is not resident. The hub must work with L2 absent
86
- (fixed model ids straight to OmniRoute).
116
+ L2 in-process hub routes (which model per call for hub-owned traffic), L3 OmniRoute
117
+ (keys, provider fallback, usage). Fixed models go straight to OmniRoute (#553).
87
118
  6. All hub-originated model traffic (local worker, internal summaries, triage) goes
88
- Switchyard -> OmniRoute. Subscription CLIs keep their own auth.
119
+ directly to OmniRoute, optionally selected by an in-process route. Subscription CLIs keep their own auth.
89
120
  7. Quota relay hands open tasks to the `local` peer first, then waits for the window
90
121
  reset for judgment-heavy classes. Task-level escalation on verification failure.
91
122
  8. Safety defaults: cross-peer text is framed as untrusted; permission prompts stay on
@@ -116,7 +147,7 @@ kimi acp (child, stdio) ── ACP client adapter ──────────
116
147
  local worker (in-process agent loop) ─────────────────────────────────┤ router L1
117
148
  hub CLI / console ── control WS 127.0.0.1:<ctl> ──────────────────────┘
118
149
  │ model calls
119
- switchyard-server sidecar (127.0.0.1, session-scoped) L2
150
+ in-process model routes L2
120
151
  │ base_url
121
152
  OmniRoute: internal URL (VPN) / Access-protected public URL L3
122
153
  ```
@@ -126,13 +157,16 @@ hub CLI / console ── control WS 127.0.0.1:<ctl> ─────────
126
157
  Claude Code restarts. State in `.agenthub/state/` (pid, status.json, sqlite, logs).
127
158
  - `plugins/agent-hub`: Claude Code plugin. Its MCP server is the channel; it reconnects to
128
159
  the daemon over the control WS with backoff, except after a close no retry can fix: 4401,
129
- 4403, 4409 and 4426. It then stays detached and its tools report why. Close 4000 (another
160
+ 4403, 4409, 4430 (ended launch) and 4426. It then stays detached and its tools report why. Close 4000 (another
130
161
  session attached as the same peer; the newest hello wins, whatever order the session-start
131
162
  recalls finish in) is not one of them (amended, issue #30): that session stands by, and
132
163
  reconnects only once `status.json` reports the peer offline and not `claiming`, so it never
133
164
  evicts the session holding the id - nor one whose hello is still finishing its preface - and
134
165
  the two cannot trade it back and forth. The daemon writes the status file at the claim, before
135
- the preface, so the arriving session is visible for that window. Its tools say it is standing by
166
+ the preface, so the arriving session is visible for that window. A retry after 4000
167
+ carries `standby: true`; the daemon checks its actual attached or claiming sockets
168
+ before allowing a reclaim, even when the status file was missing, stale or unreadable.
169
+ The push-over-pull exception below remains, but never displaces a pending claim. Its tools say it is standing by
136
170
  until it has the peer again. It exits when its host closes stdin. Exposes tools `hub_send`,
137
171
  `hub_inbox` (fallback drain), `hub_task_*`, `hub_review`, `hub_remember`, `hub_checkpoint`.
138
172
  - Codex adapter: spawns `codex app-server --listen ws://127.0.0.1:<port>`, runs a
@@ -170,7 +204,7 @@ hub CLI / console ── control WS 127.0.0.1:<ctl> ─────────
170
204
  answer); no reads of credential stores or denylisted files, no `.git/hooks` or `.git/config` writes, no network, loopback included, unless
171
205
  `local.bash_network`, scrubbed environment; without seatbelt there is no `bash`. 30 steps per
172
206
  turn, `reasoning_content` is never stored or shared. Model comes from `routing.toml`:
173
- a Switchyard route id, or `fixed_model` straight to OmniRoute.
207
+ an in-process hub route, or `fixed_model` straight to OmniRoute.
174
208
  - Console: `ahub tail` (live stream), `ahub say [@peer] <text>`, `ahub permit`, `ahub doctor`, `ahub board`,
175
209
  `ahub route explain <task>`, `ahub budget`, `ahub status`, `ahub logs`, `ahub kill`.
176
210
  - Launchers: `ahub up`, `ahub claude [--safe|--unattended] [--via dgx]`,
@@ -211,7 +245,7 @@ interface Envelope {
211
245
  - One queue rule per peer (amended in M2): the whole queue goes out as one delivery, a single
212
246
  envelope or a digest of up to 10, when the peer is idle and the queue holds an `important`
213
247
  envelope, or `batch_max` (3) envelopes, or its oldest one has waited `batch_ms` (15 s).
214
- `important` to a busy Codex is steered instead. `fyi` is never delivered to a peer: console
248
+ `important` to a busy Codex or a busy owned Jcode is steered instead: Jcode takes it as a soft interrupt through its own private server socket, injected at its next tool boundary, and the turn answers its sender too (#572). The hub never cancels a turn to deliver; a peer with no steer (headless Kimi, Qwen) keeps it queued, and status reports how long it has waited once that passes `budget.stale_min`. `fyi` is never delivered to a peer: console
215
249
  and log now, the task board from M4. A digest is one prompt (Codex, Kimi) or one channel
216
250
  notification (`meta.source = hub-digest`, senders in `meta.sources`); its reply answers the
217
251
  highest-hop item, so neither a digest nor a steer can reset the hop cap. Important items lead
@@ -241,7 +275,7 @@ interface Envelope {
241
275
  | State | Enter | Delivery rule |
242
276
  | --- | --- | --- |
243
277
  | idle | turn completed / prompt returned | inject now |
244
- | busy | turn started / prompt in flight | Claude: push (Claude Code queues); Codex: steer if important else queue; Kimi/local: queue |
278
+ | busy | turn started / prompt in flight | Claude: push (Claude Code queues); Codex, Jcode: steer if important else queue; Kimi/local: queue |
245
279
  | paused | budget gate (M5) or user `ahub pause` / `ahub resume`; a bus-level flag over the adapter state | queue, never steer; reassign open tasks (M5) |
246
280
  | offline | adapter disconnected | queue |
247
281
 
@@ -311,21 +345,21 @@ long_context_tokens = 120000
311
345
 
312
346
  [classes.implement]
313
347
  peers = ["codex", "kimi", "local"] # preference order among idle, unpaused peers
314
- route = "sy/coding" # Switchyard route id for the local worker
348
+ route = "hub/stage" # in-process route for the local worker
315
349
  escalate_to = ["kimi", "claude"] # on verification failure
316
350
 
317
351
  [classes.bulk_edit]
318
352
  peers = ["local", "kimi"]
319
- route = "sy/fast"
353
+ fixed_model = "fast"
320
354
 
321
355
  [classes.review]
322
356
  peers = ["claude", "codex"]
323
- route = "sy/review"
357
+ route = "hub/review"
324
358
  local_allowed = false # never route judgment classes to local
325
359
 
326
360
  [classes.summarize]
327
361
  peers = ["local"]
328
- route = "sy/fast"
362
+ fixed_model = "fast"
329
363
 
330
364
  [constraints]
331
365
  pii = "local_only" # hard: on-prem models only
@@ -338,28 +372,15 @@ transport, budget admission and complete-turn history ownership; algorithms make
338
372
  from normalized chat input and bounded session state. See
339
373
  [the source-port contract](2026-10-04-switchyard-source-port-design.md) for upstream
340
374
  provenance, intentional fail-open escalation and REDO history differences. These tables
341
- are never copied to sidecar configuration. Existing `sy/` routes and fixed-model fallback
342
- remain supported. The Pi `hub/auto` alias uses the same stage signals with session-scoped
375
+ run without a sidecar. Legacy selections without the `hub/` prefix use only their fixed-model fallback. The Pi `hub/auto` alias uses the same stage signals with session-scoped
343
376
  hold and context admission; peer progress observations suggest but never execute reassignment.
344
377
 
345
- L2, Switchyard sidecar (amended in M3, verified against `switchyard-server` 0.2.0 built with
346
- `cargo install --locked switchyard-server`; there is no prebuilt macOS binary).
347
- `routing.toml` carries `[targets.*]` and `[routes."sy/..."]` in Switchyard's own table
348
- shapes; the hub copies them into `.agenthub/state/switchyard.toml` (0600, removed on stop)
349
- and adds `schema_version`, one `[llm_clients.gateway]` (`format = "openai_chat"`, the live
350
- OmniRoute URL, `api_key_env = "OMNIROUTE_API_KEY"`, Access `extra_headers` only for an
351
- Access host), `llm_client` on targets and the public `id` on routes. The key reaches the
352
- sidecar through its environment only. Verified facts: the binary rejects `timeout_ms` on
353
- `llm_clients`; there is no `escalation_router` type, escalation is `llm_classifier` with
354
- `mode = "escalation"` and a mandatory `[routes.<name>.escalation]` table; the default bind is
355
- `0.0.0.0`, so the hub always passes `--host 127.0.0.1`; a route is selected by sending its id
356
- as `model`; the chosen target returns in `x-model-router-selected-model`; tool calls pass
357
- through. The config goes through `--dry-run` first. The sidecar starts on the first
358
- hub-owned model call and stops with the hub. A missing binary, a rejected config, failed
359
- health, an exit or a failed call turns L2 off for the hub run with one log line and the
360
- worker calls `fixed_model` on OmniRoute directly. Until a second model (GLM-5.3-Flash) is served the
361
- shipped routes are `passthrough` to DeepSeek-V4-Flash; the `stage_router` and escalation
362
- blocks ship commented out and validate against the real binary.
378
+ Legacy L2 compatibility (#553): `[targets]` and `[routes]` are retired, ignored
379
+ settings. Every selection without the `hub/` prefix falls back directly to its
380
+ fixed model with one warning, including legacy route ids without a prefix.
381
+ No binary, generated model-router config or model-router listener remains. The
382
+ migration mapping is in the retirement amendment above; historical native binary
383
+ observations remain in `docs/smoke.md`.
363
384
 
364
385
  L3, OmniRoute (amended in M3): candidates from `omniroute.urls` (for the owner: the internal
365
386
  URL over VPN, then a public URL behind Cloudflare Access; the tool ships with none)
@@ -378,7 +399,7 @@ Assignment is a pure function of `routing.toml`, the task's signals and the peer
378
399
  same function and prints its trace: signals, every candidate with the reason it was kept
379
400
  or skipped, owner, reviewer and route. `local` uses the class's `route` / `fixed_model` for a
380
401
  task turn and `[local]` otherwise. Verification failure (tests fail, review `changes_requested` `tasks.escalate_after` times in a row, default 3, #488) escalates
381
- the task to the next peer in `escalate_to`; Switchyard handles per-call escalation
402
+ the task to the next peer in `escalate_to`; hub routes handle per-call escalation
382
403
  inside a peer.
383
404
 
384
405
  ### Budget relay
@@ -501,11 +522,11 @@ inside a peer.
501
522
  bus.
502
523
  - `ahub ask` (added after 0.1.0). Retrieval first, model second, read-only. Evidence is gathered from the task
503
524
  board, claude-mem (index search plus the timeline around the top hit) and this run's hub log, capped at 6000
504
- characters; the hub's model (`sy/fast`) answers from that list only and has to cite its ids, and an answer that
525
+ characters; the hub's configured fixed model (default `fast`) answers from that list only and has to cite its ids, and an answer that
505
526
  cites nothing from the list is dropped. No evidence means no model call; no model means the evidence is the result.
506
527
  Every row is capped and rows that match the question lead, so one long row cannot empty the list. Every cited id
507
528
  has to be in the list: one invented id drops the answer. PII task text is evidence only when an on-campus model is
508
- positively confirmed (a gateway answered, it is not behind Access, and no sidecar points off campus); otherwise
529
+ positively confirmed (a gateway answered and it is not behind Access); otherwise
509
530
  the row stays as a `[pii]` stub so counts are right. A question that itself carries PII skips the memory worker.
510
531
  Such results are marked and never saved. `--remember` saves a real answer as a model-written note attributed to
511
532
  the hub, under a title later asks exclude from their evidence; "nothing found" is never saved. The call is
@@ -534,9 +555,9 @@ inside a peer.
534
555
 
535
556
  - `.agenthub/config.json` (roles, ports, filter tiers, watchdog), `.agenthub/routing.toml`.
536
557
  - `.agenthub/state/` gitignored: `status.json` (with the daemon's pid and process signature, #226; daemons up to 0.12.20 also wrote `hub.pid`), `control-token` (0600, per run),
537
- `hub.db` (tasks, messages, budget; from M4), `hub.log`, `switchyard.toml`, `checkpoint.md`.
558
+ `hub.db` (tasks, messages, budget; from M4), `hub.log`, `checkpoint.md`.
538
559
  - Env: `AGENTHUB_STATE_DIR`, `AGENTHUB_OMNIROUTE_URL`, `OMNIROUTE_API_KEY`,
539
- `AGENTHUB_SWITCHYARD_BIN`, `AGENTHUB_UNATTENDED`.
560
+ `AGENTHUB_UNATTENDED`.
540
561
 
541
562
  ### Testing
542
563
 
@@ -545,7 +566,7 @@ inside a peer.
545
566
  - Fakes: fake ACP server, fake app-server (ws), fake MCP client, fake OpenAI-compatible
546
567
  model server. Integration tests spawn the daemon against fakes.
547
568
  - Live smoke (manual, documented): trio chat, steer during a Codex turn, Kimi
548
- busy-queue drain, local worker completes a bulk edit through Switchyard -> OmniRoute
569
+ busy-queue drain, local worker completes a bulk edit through in-process routes -> OmniRoute
549
570
  with `x-omniroute-provider` visible, budget pause and resume with a mocked probe.
550
571
  - One command gates everything: `scripts/check.sh` (typecheck, unit, integration).
551
572
 
@@ -556,7 +577,6 @@ New repo layout:
556
577
  ```
557
578
  src/hub/ daemon, bus, peer-registry, state-machines, board, budget, router
558
579
  src/adapters/ claude-channel.ts, codex-appserver.ts, acp.ts, local-worker.ts
559
- src/switchyard/ config generator, sidecar lifecycle, health
560
580
  src/memory/ hub memory integration: claude-mem client, capture, briefs and recall; portable core is @staix/agent-mem
561
581
  src/cli/ up, claude, codex, kimi, local, say, tail, board, route, budget, status, logs, kill, init
562
582
  plugins/agent-hub/ .claude-plugin/plugin.json, .mcp.json, server bundle, hooks (SessionStart health, Stop announce)
@@ -580,7 +600,7 @@ AGENTS.md, REVIEW.md
580
600
  5. `ahub local` completes a `bulk_edit` task; OmniRoute log shows the request with the
581
601
  per-peer token and `x-omniroute-provider: vllm`; `ahub route explain` shows the
582
602
  chosen route.
583
- 6. With Switchyard absent, the local worker still works via `fixed_model`.
603
+ 6. All model routes run in process; retired selections without the `hub/` prefix use `fixed_model`.
584
604
  7. A PII-flagged task never leaves the `local` peer (test with a fake pattern).
585
605
  8. Mocked Codex quota at 0.95 pauses Codex, checkpoints, reassigns its open
586
606
  `implement` task to `local`, and resumes Codex after the mocked reset.
@@ -624,8 +644,8 @@ M2 coordination
624
644
  M3 local worker and routing L2/L3
625
645
  - [x] Local worker agent loop with cwd-scoped tools, secrets denylist, approvals and seatbelt sandbox
626
646
  - [x] OmniRoute client with Cloudflare Access headers and the owner-issued inference key; the off-campus live path remains blocked in `docs/smoke.md` (#1)
627
- - [x] Switchyard sidecar: config generation, lifecycle, health, fallback to fixed model
628
- - [x] `ahub local`, smoke through OmniRoute with provider header check, and through the real sidecar
647
+ - [x] Historical M3 Switchyard integration, retired in #553; fixed fallback remains.
648
+ - [x] `ahub local`, smoke through OmniRoute with provider header check; historical sidecar receipts in smoke.md.
629
649
  - [x] Local worker capture into claude-mem (`sessions/init`, `observations`, `summarize`, `session-end`, skip list)
630
650
 
631
651
  M4 task board, roles, routing L1
@@ -645,7 +665,7 @@ M5 budget relay
645
665
  - [ ] Live: a real pause driven by Codex's own numbers with a TUI attached; the natural near-limit prerequisite remains blocked by issue #1
646
666
 
647
667
  M6 internal inference, packaging
648
- - [x] Status digests and triage through `sy/fast` (amended: optional and fail-open with a backoff. Only plain status
668
+ - [x] Status digests and triage through the configured fixed model (default `fast`; amended: optional and fail-open with a backoff. Only plain status
649
669
  chatter over a threshold is condensed, into one item from `digest` that keeps every sender, envelope id and the
650
670
  highest hop; important, task, review, budget, preface and private items are never touched. A task proposed
651
671
  without a class is labelled from the closed class list, a PII task only when the gateway is on campus. The
@@ -696,7 +716,7 @@ M6 internal inference, packaging
696
716
  | Risk | Mitigation |
697
717
  | --- | --- |
698
718
  | Claude channels are research preview; flag or protocol may change | Adapter isolated in one file; plugin version pinned to a Claude Code range; `hub_inbox` fallback |
699
- | `switchyard-server` is Demo grade, pre-1.0 | Session-scoped sidecar, pinned version, L1 `fixed_model` fallback |
719
+ | Model routing algorithm drift | In-process golden tests, Apache-2.0 notices and upstream provenance check |
700
720
  | Anthropic-to-chat translation drops `tool_use` | Claude never runs on local models in this design |
701
721
  | Local worker executes untrusted room text | Untrusted framing, cwd scope, denylist, no `--unattended` by default |
702
722
  | Steering a Codex turn changes its plan mid-flight | Only `important` steers; default tier is `status` |
@@ -947,7 +967,9 @@ session modes (`default`, `plan`, `auto`, `yolo`) but nothing per server or tool
947
967
  killed at the timeout or on shutdown; a check still queued at shutdown never
948
968
  starts. A check ends when its command exits: what it left in its process group
949
969
  is killed then, and a descendant that left the group gets 0.5 s more to flush
950
- output and is not waited for. Exit 0: the task
970
+ output and is not waited for. A check killed at the timeout or on shutdown
971
+ settles 1 s after the kill even if its exit and close events never arrive
972
+ (#558). Exit 0: the task
951
973
  completes as before, the review envelope carrying `Check: <command> -> exit 0`
952
974
  and the output tail; shared memory gets the summary and the outcome line, never
953
975
  the output. Any other outcome: the task stays with its owner, who gets a task
@@ -1055,8 +1077,23 @@ session modes (`default`, `plan`, `auto`, `yolo`) but nothing per server or tool
1055
1077
  - A plan's text counts for PII detection when a task is proposed. On accept the
1056
1078
  task's signals are fixed, so a plan that matches a PII pattern is refused for
1057
1079
  an ordinary task.
1058
- - Overlap: paths from refs and plan (same file or a directory of it), or the
1059
- same symbol named in both plans. An overlap that only the new plan reveals on
1080
+ - Overlap (#476): each task has one repository key, chosen from explicit
1081
+ `refs.repo` (GitHub owner/name slug or checkout path), else the common Git
1082
+ top-level of outside-project absolute refs (bounded hardened Git and realPath),
1083
+ with common-parent fallback, else this project. Confirmed GitHub remote slugs
1084
+ connect path/SSH/HTTPS aliases; without that evidence a path key stays distinct.
1085
+ Relative plan paths belong to that task's key. Paths from refs and plan (same
1086
+ file or directory prefix), symbols, sections and sources are compared only
1087
+ within one key, including contradiction, replay and held-change comparisons.
1088
+ Existing project-only display spelling, notices, cohorts and telemetry stay
1089
+ unchanged. Repository/root and failed Git probes are cached for the daemon's
1090
+ lifetime; new metadata resolves new inputs, and a restart refreshes prior probes. No network
1091
+ lookup, directory scan, execution-root change or filesystem grant is implied.
1092
+ Propose reports one ids-only notice when refs and absolute plan evidence name
1093
+ different repositories. Both propose and accept tools expose `refs.repo`;
1094
+ acceptance may correct that repository metadata only, with ordinary authority,
1095
+ privacy, spec and plan gates retained. A changed repository leaves its previous
1096
+ cohort before joining any new same-repository overlaps. An overlap that only the new plan reveals on
1060
1097
  accept is announced like one found at assignment (console notice and overlap
1061
1098
  event); one already announced is not announced again, but the other owner
1062
1099
  still gets the new plan.
@@ -1290,7 +1327,7 @@ session modes (`default`, `plan`, `auto`, `yolo`) but nothing per server or tool
1290
1327
  ## Reliability follow-ups, 0.12.1 (#89-#95)
1291
1328
 
1292
1329
  - Delivery retries exhausted for an owned open task trigger escalation through `Tasks`, with a redacted reason and a console notice. Delivery health skips peers after three consecutive exhausted deliveries until a completed delivery clears the streak.
1293
- - `needs_review` holds uncertain work that still matters. The hub settles a row `completed` only when every original is a hub presence item or a hub task/review notice provably moot for its recipient: an approved or cancelled task (except its cancellation stop notice), a review no longer in review for that reviewer, or a notice whose authoritative `refs.taskRecipient` audience (`owner` or `reviewer`) is no longer current. Hub producers tag current-owner notices, reviewer reminders and planner coordination separately; former-owner stop notices and former-owner/former-reviewer withdrawal notices use untagged kind `task` and stay held until task approval or cancellation, because their purpose is to tell the former role to stop. Task-kind reviewer reminders remain live while the recipient is that in-review reviewer. Planner notices and untagged legacy task-kind notices stay held until approval or cancellation: without an audience marker, sender/kind/task id cannot prove that an owner assignment moved. This conservative legacy ceiling preserves still-live coordination and guidance. Unknown tasks, live recipient notices and mixed peer/user rows remain held. Rows whose recorded reason starts with `requested stop:` are never completed automatically, even when their tasks become approved. `supersededDeliveryReason` alone authorizes completion; a separate pure `deliveryTaskContext` retains ids-only approved/moved task context for mixed rows without authorizing settlement. Status, console notices and console/peer/tool route explanations list every hold once, including sender, kind and age; public explanations screen appended hold lines through the current privacy policy and keep task context ids-only. Classify on entry, after restore, after the daemon releases its recovery hold and on board changes; use the existing resolve path with a recorded hub actor, preserving pauses and Tasks state. Hub completion restores presence originals through the same preface merge and transaction rollback path as retry, preserving shared notes for the next queued delivery; person completion remains consumed (#434).
1330
+ - `needs_review` holds uncertain work that still matters. The hub settles a row `completed` only when every original is a hub presence item or a hub task/review notice provably moot for its recipient: an approved or cancelled task (except its cancellation stop notice), a review no longer in review for that reviewer, or a notice whose authoritative `refs.taskRecipient` audience (`owner` or `reviewer`) is no longer current. Hub producers tag current-owner notices, reviewer reminders and planner coordination separately; former-owner stop notices and former-owner/former-reviewer withdrawal notices use untagged kind `task` and stay held until task approval or cancellation, because their purpose is to tell the former role to stop. An owner task offer (kind `task`, `refs.taskRecipient: "owner"`, positive safe-integer `refs.taskOffer`) also becomes moot once that recipient still owns its task in `in_progress`, `in_review` or `changes_requested` and its own `accepted` entry follows the latest ownership event. A person's assignment or acceptance does not count for the peer; a replacement-owner offer under review stays live until that peer accepts. The pure check inspects at most 4096 recent history entries, with absent or out-of-bound proof retaining the hold; approval/cancellation keep their existing rules. The ids-only reason is `superseded: task #N accepted`. This applies on hold entry, restored startup, recovery release and board changes, using the same hub-actor resolution path; it never records an offer read or changes the task. A plain owner reminder without the offer marker, an unaccepted offer and any mixed peer/user row stay held. Task-kind reviewer reminders remain live while the recipient is that in-review reviewer. Planner notices and untagged legacy task-kind notices stay held until approval or cancellation: without an audience marker, sender/kind/task id cannot prove that an owner assignment moved. This conservative legacy ceiling preserves still-live coordination and guidance. Unknown tasks, live recipient notices and mixed peer/user rows remain held. Rows whose recorded reason starts with `requested stop:` are never completed automatically, even when their tasks become approved. `supersededDeliveryReason` alone authorizes completion; a separate pure `deliveryTaskContext` retains ids-only approved/moved task context for mixed rows without authorizing settlement. Status, console notices and console/peer/tool route explanations list every hold once, including sender, kind and age; public explanations screen appended hold lines through the current privacy policy and keep task context ids-only. Classify on entry, after restore, after the daemon releases its recovery hold and on board changes; use the existing resolve path with a recorded hub actor, preserving pauses and Tasks state. Hub completion restores presence originals through the same preface merge and transaction rollback path as retry, preserving shared notes for the next queued delivery; person completion remains consumed (#434).
1294
1331
  - A Claude session without channel support returns `failed_safe` before the push reaches the model, preserving originals for pull. If an older daemon rejects that receipt, the plugin falls back once to its prior generation-bound `needs_review` receipt instead of leaving the row dispatching. A thrown `server.notification` remains `needs_review`: the MCP SDK delegates to a transport whose stdout write can already have emitted bytes before an exception is observed, so the exception alone cannot prove no frame left. Existing proven pre-acceptance guards in Codex, ACP, Pi and the local worker remain unchanged. Console-only command refusals name only the inherited identity marker keys actually present (never their values), explain `!` commands, and direct the person to a separate terminal or `ahub console` (#434).
1295
1332
  - `hub_delivery_retry { delivery_id }` lets an authenticated recipient retry only its own current `needs_review` row. This option is offered only to transports with an independent tool turn (Claude channel/pull, Codex and Pi). The delivery-only local worker and headless ACP peers cannot start a tool turn through their own hold, so they do not advertise this tool and the daemon refuses a manual call before mutation. Their held hints ask the person to resolve the row; ordinary messages never bypass the hold. Kimi TUI remains pull-only and keeps the recipient option. An ephemeral delivery-only MCP marker controls exposure and hint copy only; the current adapter class is the authority. There is no action or reason input: `completed` and `discard` remain person-only. Any explicit retry in that recipient's lineage, including a person's retry, consumes this allowance; a redelivery held again stays for the person. The existing resolution transaction records `peer:<id>` and a fixed reason, keeps original envelopes unchanged and requeues them through the existing put-back path. A busy recipient may consent to this queue-only operation, but delivery waits for idle and all pause/recovery and active-delivery fences remain. The rendered clone reserves the existing journal body cap for the warning, clipping only the clone at a Unicode character boundary with an explicit cut marker when needed; originals remain unchanged. Jcode project MCP entries carry the same delivery-only exposure marker as other headless ACP peers, preserving their own configuration and peer identity rules. Final rendered output, after condensation and on pull or steer, starts with the fixed sanitized header `Hub redelivery warning: An earlier attempt of this delivery is uncertain; check for its effects before acting.` Explicit retry history is scoped by recipient and original envelope ids. A separate monotone consumed-proof projection records one proof id and warning provenance per original, leaving resolution audit rows unchanged. Explicit resolution and coalesced handoff inherit that proof atomically for every grouped non-context original; a rejected checkpoint consumes no fresh allowance. Hub presence originals carry mutable recall/shared context whose id survives merging, so their ids never participate in retry proof lookup, inheritance, warnings or unknown marking. A preface shared with earlier work cannot consume an unrelated new message's retry allowance; actual task/chat originals retain their consumed lineage. Safe-failure requeue, withdrawal of an earlier original, terminal-state changes, pruning, restart or older-writer audit replacement cannot renew the allowance. Older attempt-bearing rows backfill this projection on return. Recovery snapshots without history and unmappable pruned pre-feature retry records mark only their affected recipient/original ids unknown: those lineages require a person, while unrelated new messages keep their allowance. Tool results expose held metadata only (total count, ids, senders, kinds and age), at most 32 rows/4 KiB with explicit truncation; no body or task text enters the hint. These hints work independently of channel mode and do not wait behind the held queue (#434).
1296
1333
  - Local attach and idle model/route replacement require a reachable authenticated gateway, served models and successful minimal availability calls. Busy workers refuse replacement; validation failure leaves the current worker intact. Controlled recovery can reconstruct a manually paused local worker without gateway availability, but manual resume validates its restored choice before lifting the pause. Doctor checks the fixed model against the inventory.
@@ -1699,9 +1736,11 @@ distinct from the completed-message baseline recorded at that start. Later tool
1699
1736
  activity does not move this turn-start boundary. Missing start evidence or a
1700
1737
  start from another session, launch or channel claim leaves completion unknown.
1701
1738
  Accepted completion consumes that start before the peer becomes idle, so another
1702
- message cannot reuse it. The completion and the baseline are read from the last
1703
- 4 MiB of the transcript, whatever its size (#517): the 32 MiB cap applies only to
1704
- usage accounting, so a long session still ends its turns. A Stop refused because
1739
+ message cannot reuse it. Completion and baseline lookup scans backwards in fixed
1740
+ 4 MiB chunks, up to 64 MiB plus one boundary byte (#528). Complete UTF-8 JSONL rows
1741
+ are reconstructed across chunks; a row starting outside the bound stays unknown.
1742
+ A near-EOF completion costs one chunk even in a 200 MiB transcript. The separate
1743
+ 32 MiB cap applies only to usage accounting (#517). A Stop refused because
1705
1744
  no completion is found tells the console once per turn start that the peer stays
1706
1745
  busy and its messages wait in its queue. A valid bound Stop request receives one prompt
1707
1746
  acknowledgement with pending=true, within the existing two-second hook deadline.
@@ -1819,10 +1858,12 @@ push `accepted` and show none of them.
1819
1858
  - A readback (a native turn citing the pushed id) would hold every first
1820
1859
  delivery until the model chose to answer, and a session that never answers
1821
1860
  would look the same as one that never rendered.
1822
- - The existing launch identity (`AGENTHUB_INSTANCE_ID`, `AGENTHUB_LAUNCH_ID`)
1823
- is not used: `ahub claude` sets it only when a daemon is already running at
1824
- launch, so an `ahub claude` started before `ahub up` would lose pushes it
1825
- can show.
1861
+ - Launch fencing (#551): peer hello carries `AGENTHUB_LAUNCH_ID` when present.
1862
+ A Claude-channel hello that differs from this daemon instance's recorded
1863
+ per-peer `claude-launch.json` is refused before claiming, with terminal close
1864
+ 4430. The plugin stops reconnecting and its tools explain how to start a new
1865
+ session. An absent launch id or absent current launch record keeps the prior
1866
+ behavior, including `ahub claude` started before `ahub up`.
1826
1867
  - Limits: the marker is inherited by the session's own shell children, so a
1827
1868
  plain `claude` started inside an `ahub claude` session reads as
1828
1869
  channel-capable (it also inherits the peer id and takes the peer over); so
@@ -1833,6 +1874,17 @@ push `accepted` and show none of them.
1833
1874
  installed plugin server in the same process also sees
1834
1875
  `AGENTHUB_CHANNEL=1`. A session started with the flag by hand reads as
1835
1876
  tools-only and is pointed to `ahub claude`.
1877
+ - Channel activation ceiling (#551 D2): the MCP server observes its inherited
1878
+ environment and the host's `initialize` client information/capabilities through
1879
+ the SDK. The current bridge contract has no positive signal that this specific
1880
+ server was selected by the host's channel flag. Advertising the server's own
1881
+ `experimental["claude/channel"]` capability is not host activation evidence;
1882
+ client name/version and an inherited environment marker cannot establish it
1883
+ either. Therefore a nested host inheriting the current launch id and channel
1884
+ marker remains indistinguishable here. Do not invent a client capability or
1885
+ infer activation from successful notification writes. The stale-launch fence
1886
+ blocks obsolete launch identities; no-marker sessions remain pull-only. A
1887
+ positive per-server host activation signal would permit tightening this rule.
1836
1888
  - Tools-only attach: the server declares tools only, sends `channel: false` in
1837
1889
  its `peer` hello and lists `hub_inbox` but not `hub_delivery_done`. It never
1838
1890
  reports `accepted`: a `deliver` it is handed anyway goes back as a
@@ -1950,7 +2002,7 @@ task list polling does not. `hub_status` stays conductor-only.
1950
2002
  peers. PII, capability limits (`local_allowed`, Pi's context limit),
1951
2003
  exclusions, failing peers and offline or paused peers all pass it over, so a
1952
2004
  PII task still goes to `local` or nobody.
1953
- - Lasting exclusions are refusals only: a decline records the owner it was
2005
+ - Lasting exclusions are refusals only: a plain decline records the owner it was
1954
2006
  declined for in its history entry's `from` (also when the console declines
1955
2007
  for that owner), and an escalation with reason `rejections` (a reviewer's
1956
2008
  repeated changes_requested) or `manual` (by hand) records the owner it took
@@ -1966,6 +2018,16 @@ task list polling does not. `hub_status` stays conductor-only.
1966
2018
  clears an agent's reservation, so a later reroute does not hand the task to
1967
2019
  the agent's choice; the conductor's and a proposer's assigns respect the
1968
2020
  exclusions and keep the reservation.
2021
+ - A capacity decline is not a lasting exclusion (#531): `hub_task_decline` with
2022
+ `capacity: true` says the owner has no room right now. Its history entry
2023
+ records `from` and `capacity: true`, `excluded()` skips it, and the owner is
2024
+ left out of that move's own reroute only, like the budget relay and the idle
2025
+ sweep, so a later reroute or planner pull may offer the task to it again from
2026
+ its reservation. Entries without the field, all older rows included, read as
2027
+ refusals. A planner pull that finds no owner names the cause (the peers a
2028
+ lasting exclusion keeps out, a peer at its WIP limit with wip/limit, a held
2029
+ queue, needs_review), by peer id and count only, and `hub_backlog_plan`
2030
+ refuses a reservation for a peer `excluded()` returns for that task.
1969
2031
  - A named candidate list (console or conductor assign, escalation, budget
1970
2032
  relay, the idle sweep's auto-reassignment of its suggestion) replaces the
1971
2033
  reservation; the idle sweep's step-3 suggestion itself is an `assign()`
@@ -2066,6 +2128,7 @@ task list polling does not. `hub_status` stays conductor-only.
2066
2128
  - Decision, 2026-10-16: an agent explicitly authorized by the user to perform a live test may use the global `--live-test` flag, before the command and in either order with `--project`. Duplicate global options refuse. Default command classification and malformed/conflicting identity refusals stay unchanged.
2067
2129
  - Console-class operations and conductor-class peer starts/pause/resume may use console authority after the original caller is validated; allowed-class commands retain peer identity and capability checks. Flagged task assign/escalate refuse rather than writing user-attributed history. A mandatory ids-only audit records its peer, closed command label and `delegated` outcome before execution. The daemon emits an `agent_cli` event with `delegated: true`, `refused: false`, and a console notice naming the peer. User impersonation, person labels and person-authored workflow writes refuse. Delegated dashboard tickets/sessions allow operational controls but refuse user-attributed content and history actions on both project and unified surfaces; request bodies cannot widen the session.
2068
2130
  - This is a user-authorized operating policy, not a sandbox or authenticated grant. It includes console mutations and requires the agent to remain within the authorized targets and operations. Command-specific confirmations, process ownership, provider identity and recovery locks still apply.
2131
+ - Audit compatibility (#410): ordinary delegated execution requires `cli_audit_delegated` in the authenticated running daemon's welcome, not protocol 16 or a disk feature hint. Reuse the authenticated connection for execution, pin helper connections to the welcomed instance and check it before local command dispatch; replacement control connections refuse. Before effects, require console-only `cli_audit_ack` for the exact audit UUID and authenticated instance, positive only after an actual event append. A bounded 4096-id live-instance acknowledgement window fails closed on eviction/restart. This retains attribution across later local waits without relying on another daemon draining the spool. Reserve its bounded private pending audit before connecting; unsupported/unavailable consumers get only a legacy-compatible `refused` ready row and an upgrade remedy, with no command effects. Upgrade/recovery and local bootstrap commands with no daemon manifest use compatible `run` spool rows. Recovery additionally stores bounded ids-only `delegatedAudits` (peer, closed command, timestamp, operation id) in the schema-1 operation receipt before runtime/target effects; append under the runner claim for resume handoff, runner, abort and dispose. Preserve the initiating row through source replacement and old-coordinator whole-object saves; refuse further delegated attempts at 256 entries. Receipt evidence never supplies invocation authority. Source preservation remains preflight, preceded by the compatible spool audit; the operation receipt precedes staging, prepare and commit. Existing target-support and native-restoration fences remain unchanged.
2069
2132
  - No settings/environment opt-in persists. Native argv does not receive the flag. Ahub's own daemon launch, console subprocesses, recovery runner, interactive continuation commands and newer-version coordinator handover carry it explicitly; older CLIs cannot accept it. The recovery driver's target CLI environment preserves the initiating peer for its delegation audit. Staging checks the target's pinned LIVE_TEST_SUPPORTED declaration before its cached-stage return and before prepare/commit; unsupported old targets refuse without source effects. Headless-native restoration uses the ordinary target CLI with the operationId on its delegated console connection; no headless staging ceiling is added. External computer-use policies remain outside this change. No #273 live success is inferred from this implementation.
2070
2133
 
2071
2134
  ## Amendment: ahub reset (issue #214)
@@ -2176,7 +2239,7 @@ acts on the stopped state directory.
2176
2239
  is started the way Pi's relay starts it and keeps running until `ahub models
2177
2240
  stop`), else the gateway's `local.fixed_model` only while the
2178
2241
  daemon's `onCampus()` holds, with `onCampusOnly` so the client refuses an Access
2179
- host once more before transport; never the Switchyard sidecar. One call per item,
2242
+ host once more before transport. One call per item,
2180
2243
  an 8 s deadline over the whole call, and a generation slot of its own: it asks
2181
2244
  for one without waiting (`acquire(abort.signal, 0)`: one look, then
2182
2245
  `MlxBusyError`); with all taken (Pi generating, another screen) it uses the
@@ -2461,10 +2524,10 @@ the queue.
2461
2524
  - A private foreground daemon/socket and ACP client belong to this peer. The global daemon is never adopted or stopped. No automatic update, self-dev or credential copy. The native MCP store must already exist to avoid implicit Codex import.
2462
2525
  - ACP session MCP arrays are ignored natively, so the person launcher manages only agent-hub-jcode, shared:false, in project .jcode/mcp.json. Later override layers, foreign names and links refuse. Daemon starts verify read-only. .jcode is outside scoped grants.
2463
2526
  - Native policy is unmanaged and has no hub sandbox. Hub permission mappings refuse; native quota/consumption are not inferred. Every task mutation stays in Tasks and PII remains at existing redaction/routing boundaries.
2464
- - Jcode's default start mode is `tui`; `--mode headless` and `peers.jcode.start_mode: headless` retain the owned headless driver. Mode precedence is the explicit flag, the running hub's accepted mode, then the file/default. A console/conductor start opens the same waiting launcher through the terminal provider, including when ACP is already running; no provider means an actionable terminal command, never a headless downgrade.
2527
+ - Jcode's default start mode is `tui`; `--mode headless` and `peers.jcode.start_mode: headless` retain the owned headless driver. Mode precedence is the explicit flag, the running hub's accepted mode, then the file/default. A console `peer_start` opens the same waiting launcher through the terminal provider, including when ACP is already running; a conductor `hub_peer_start` or dashboard start of an attached jcode answers `already` and opens no terminal, so a repeated call never adds another TUI writer to the live session (#559). With no provider the answer is an actionable terminal command, never a headless downgrade.
2465
2528
  - J1.4 (#464): the 0.98.1 native receipt disproved persistence after `session/new`. A person launcher opens the top-level `jcode --socket S --resume ID` only after bounded native saved-session evidence for the same launch, socket and session. Until then it prints once: `jcode's TUI opens after its first hub turn; send it work, or Ctrl-C`. Ctrl-C ends that wait without stopping or cancelling ACP. No artificial production prompt or hub-created native session JSON is allowed. The native prerequisite and account limits are recorded in `docs/verification/2026-10-20-jcode-tui-0.98.1.md`.
2466
2529
  - The actual private tuple is available only through the authenticated console-only, feature-negotiated `native_tui_launch` request, never status, tools replies or generic start replies. Pure dry previews remain redacted and start nothing; a person's print-command may resolve an already-saved running session. TUI argv preserves the owned command prefix, model and provider profile; its environment selects the same native home/socket and telemetry opt-out. No `jcode connect` path or external/global daemon is adopted.
2467
- - J3's lack of a single-writer guard is accepted: a person's TUI input may interleave with a hub ACP turn. Closing the TUI keeps the owned ACP runtime alive. Owned ACP recovery metadata remains headless, and the terminal is recorded separately; native session/load identity refusal is unchanged.
2530
+ - J3's lack of a single-writer guard is accepted: a person's TUI input may interleave with a hub ACP turn. Closing the TUI keeps the owned ACP runtime alive. Owned ACP recovery metadata remains headless. The TUI terminal is not recorded as a managed terminal binding (#564): controlled recovery of Jcode is refused at plan time, so, like Kimi's TUI, it has no terminal recovery adapter; native session/load identity refusal is unchanged.
2468
2531
  - The private socket name ends in `.sock`: jcode names its debug socket by replacing that suffix. Every owned jcode process runs with `JCODE_NO_TELEMETRY=1` (#529).
2469
2532
  - Native ACP load (verified on 0.94.0 and 0.98.1) lacks identity proof. Controlled recovery refuses before effects, crash recovery is manual and a load without an exact echoed id refuses before a prompt or restored claim. Real provider/tool/teardown evidence and comparative measurement remain unverified.
2470
2533
 
@@ -3131,7 +3194,7 @@ and reason. The registry exposes the running and file values separately.
3131
3194
  | next use | permission_modes and catalog_roles at the next peer start; peers.*.start_mode and native launch argv; coordination for newly created tasks; task release/review policy; new approval timeouts; bus batch/admission limits; budget/context/inference/limits; peer watchdog and step limits; recovery and experiment decisions; memory brief/injection/suggestion limits |
3132
3195
  | next use | pi.auto_start changes a later startup/recovery auto-start decision; reloading it never starts or replaces a running peer |
3133
3196
  | restart | memory.enabled/native/worker_url/short_ttl_days/sync_interval_min: open consumers, selected profile binding, client and sync timer must be quiesced together |
3134
- | next use | routing targets/routes/local selection at the next local peer or sidecar creation; existing native sessions retain their model contract |
3197
+ | next use | local model selection at the next local peer creation; retired tables never execute; existing native sessions retain their model contract |
3135
3198
  | restart | pi.dgx_coding/dgx_fast and routing.pi: model catalogue and context publication belong to the existing Pi relay; a reload must not replace a live native relay |
3136
3199
  | restart | omniroute: credential and gateway clients are bound to relay ownership |
3137
3200
  | restart | mlx: model/runtime ownership and device resources belong to the running instance |
@@ -3217,7 +3280,7 @@ Inheritance and undo use the actual destination source trust. They may restore a
3217
3280
 
3218
3281
  Reserved prototype path components are refused throughout nested routing extension tables before publication. Configuration path writes never traverse inherited object properties.
3219
3282
 
3220
- Switchyard targets/routes and hub-route dictionaries retain opaque quoted keys, including dots. Each dictionary is one guarded value for reload confirmation and publication; privacy fields and ordinary registry settings retain leaf-level decisions.
3283
+ Hub-route dictionaries retain opaque quoted keys, including dots. Each active dictionary is one guarded value for reload confirmation and publication; retired targets/routes never enter publication. Privacy fields and ordinary registry settings retain leaf-level decisions.
3221
3284
 
3222
3285
  ### Settled authority and reviewable confirmation (#439 round-2 amendment)
3223
3286
 
@@ -3665,12 +3728,37 @@ extra instance. Revoked declarations or changed directories refuse reuse.
3665
3728
  Effective roles resolve from declaration roles, exact-id policy roles, kind policy
3666
3729
  roles, then definition defaults; launch validation uses the same order.
3667
3730
 
3668
- I2 recovery ceiling: a recovering extra Codex launcher records its launch row only
3669
- after the authenticated start response supplies its execution-root binding. Until
3670
- then, the coordinator retains durable pending terminal-creation and attachment-wait
3671
- evidence; it never records an assumed checkout. Base Codex pre-start recording is
3672
- unchanged. Earlier recording for extras requires independently verified exact-instance
3673
- execution-root evidence. Past-commit resumes do not repeat pre-commit instance
3731
+ Terminal ownership (#482): an authenticated start response supplies an extra
3732
+ Codex launcher's execution-root binding, but is not native ownership proof.
3733
+ Every managed terminal launch first captures an Orca candidate without changing
3734
+ the recovery ledger. A daemon advertising `terminal_owner` admits at most 64 live
3735
+ or unknown candidates through the console-only `terminal_launch` request. It
3736
+ publishes only after the current native owner is bound: the adopted Codex link's
3737
+ launch nonce, the attached Claude channel's launch id, or Pi's verified owner
3738
+ process descending from that launcher. Orca root, handle, incarnation, worktree,
3739
+ launcher signature and current native generation are checked before publication;
3740
+ a failed second launch preserves the incumbent row. Claude's hook record remains
3741
+ pre-spawn and does not wait for this terminal proof. The coordinator retains
3742
+ durable pending creation and attachment-wait evidence until ownership is proven;
3743
+ no pre-start candidate is restoration evidence.
3744
+
3745
+ Background Orca queries have a 30-second deadline and 1 MiB output ceiling. Their
3746
+ owned process groups are cancelled and awaited on shutdown before reconciliation
3747
+ settles. A cleanup failure is logged without preventing other peer teardown or
3748
+ daemon finalization; native TUI foreground execution is unchanged.
3749
+
3750
+ Without the advertised feature a current launcher runs unrecorded and names the
3751
+ upgrade remedy. Attached native TUIs without a live launcher or independent exact
3752
+ Orca session proof are shown as `attached, launcher unrecorded`, with person end
3753
+ or native resume guidance, never a fresh headless restart. Plain same-protocol
3754
+ Claude keeps its unmanaged reconnect-only path. An orphaned but connected pane
3755
+ requires the person to run `orca terminal switch --terminal <handle>` and refresh;
3756
+ it does not authorize another restore. Historical rows are retained, including
3757
+ invalid rows; current ownership is scoped to project, instance and peer, and a
3758
+ publication replaces only that scope's valid row. This current-source guarantee
3759
+ does not fence older binaries that directly rewrite the shared ledger.
3760
+
3761
+ Past-commit resumes do not repeat pre-commit instance
3674
3762
  compatibility checks. Abort and stop-and-archive validate historical receipt structure
3675
3763
  and authenticated runtime ownership independently of mutable declarations; restore,
3676
3764
  fresh and reattach retain current declaration authority.
@@ -37,7 +37,7 @@ related: 261004-review-agent-hub-switchyard-comparison.md, 261004-plan-agent-hub
37
37
  - 2단계: local worker 연결.
38
38
  - 3단계: Pi relay 연결.
39
39
  - 4단계: peer 진행 신호와 정체 판정(연구용).
40
- - 5단계: sidecar 존치 결정.
40
+ - 5단계: sidecar 제거 결정 (#553, 2026-10-20 승인).
41
41
  - **연구와의 관계**
42
42
  - 4단계의 peer 진행 신호는 연구 계획 v2의 NC3(실행 신호로 조정 방식 전환) 자료.
43
43
  - Advisor gate 이식은 #42 AC2 감독자 regime과 NC2 commit gate의 시제품 기반.
@@ -147,8 +147,8 @@ related: 261004-review-agent-hub-switchyard-comparison.md, 261004-plan-agent-hub
147
147
  - peer 관측용 어댑터: Codex `commandExecution`(명령, exit code, 출력)과 `fileChange`, Claude hook 도구 호출을 같은 신호 구조로 변환.
148
148
  - **E5 설정**
149
149
  - `routing.toml`에 `[hub_routes."<id>"]` 표를 새로 둠. 유형은 `stage`, `plan_execute`, `advisor`, `escalation`, tier는 OmniRoute 모델 id나 relay 별칭.
150
- - Switchyard sidecar용 `[routes.*]`와 분리해 sidecar 설정 생성기가 옮기지 않게 함.
151
- - local worker의 route가 `hub/` 접두면 hub 안에서, `sy/` 접두면 sidecar(있을 때)로 감.
150
+ - `[hub_routes]`만 실행. #553 이후 기존 `[targets]`·`[routes]`는 retired settings로 무시.
151
+ - local worker의 `hub/` route는 hub 안에서 처리, `hub/` 접두사가 없는 legacy route는 `fixed_model` 직접 호출로 폴백.
152
152
  - **E6 실패 처리**
153
153
  - 라우팅 판단이나 판정기 호출이 실패하면 기존 경로(`fixed_model`)로 폴백. turn과 전달을 늦추지 않음.
154
154
  - 판정기 호출에 deadline을 둠(inference와 같은 원칙).
@@ -240,9 +240,14 @@ related: 261004-review-agent-hub-switchyard-comparison.md, 261004-plan-agent-hub
240
240
  - **연구 쪽**: 벤치마크 ledger에 진행 신호 시계열을 넣어 NC3 분석 자료로 씀(관측 범위가 peer마다 다르다는 점을 함께 기록).
241
241
  - 완료 기준: Codex 각본 테스트, PII 제외 테스트, 판정기 장애 시 fail-open.
242
242
 
243
- ### 5단계: sidecar 존치 결정
243
+ ### 5단계: sidecar 제거 결정 (#553, 2026-10-20 승인)
244
244
 
245
- - 2·3단계 뒤 hub 자체 트래픽이 in-process 라우팅으로 충분하면, Switchyard sidecar를 선택 기능으로 낮춤. 이식하지 않은 알고리즘(composite 분류기, sub-agent)이 필요할 때만 사용.
245
+ - Switchyard 외부 프로세스·설정 생성·환경 변수·상태 행 제거. 모든 model route는 in-process 처리.
246
+ - `SWITCHYARD = 3` port offset은 예약 상태 유지, 사용하지 않음. 나머지 offset 변경 금지.
247
+ - `stage_router`는 hub `stage`의 `efficient`·`capable`·`confidence_threshold`로 대응. passthrough는 route 없이 class `fixed_model`로 대응.
248
+ - 기존 `[targets]`·`[routes]`는 retired settings로 읽고 무시, startup·doctor에서 대체 경로 안내. `hub/` 접두사가 없는 route는 `fixed_model`로 폴백하고 1회 기록. operator 파일 자동 수정 없음.
249
+ - template은 hub routes 사용. 내부 inference는 fixed model만 사용. doctor는 local·class fixed alias와 hub route의 모든 tier·judge alias를 inventory와 대조.
250
+ - PII campus admission·`onCampusOnly`, `src/models/route/` 라이선스 고지 및 upstream 점검 스크립트 보존. 미이식 알고리즘 추가는 이번 범위 밖.
246
251
  - 구독 에이전트 측정 프록시(비교 보고서의 D4)는 이 계획 밖. 별도 결정.
247
252
 
248
253
  ## 규모와 일정 (추정)
@@ -281,7 +286,7 @@ related: 261004-review-agent-hub-switchyard-comparison.md, 261004-plan-agent-hub
281
286
  - REDO 피드백을 local worker 이력에 남기는 정책.
282
287
  - 4단계의 자동 재배정 여부(권고: 하지 않음, 측정 뒤 결정).
283
288
  - 1단계의 차등 검증(Rust 빌드 필요) 실행 여부.
284
- - Switchyard sidecar의 장래(5단계).
289
+ - Switchyard sidecar의 장래는 #553에서 제거로 결정(5단계).
285
290
  - 착수 시점(ICSE Tool Demo 마감 10-23 이후 권고).
286
291
 
287
292
  ## 근거
@@ -303,7 +308,7 @@ related: 261004-review-agent-hub-switchyard-comparison.md, 261004-plan-agent-hub
303
308
  - REDO feedback stays in the completed local turn history. Failed turns never leave unmatched tool calls.
304
309
  - PII bypasses optional judges unless the exact on-campus gateway is positively confirmed; progress tracking is disabled while PII work is open.
305
310
  - Optional Pi advisor SSE replay and Rust differential build are deferred. No automatic peer reassignment.
306
- - Existing sidecar routes remain optional and backward compatible; `[hub_routes]` never enters generated sidecar TOML. The sidecar retirement decision remains evidence-driven.
311
+ - Phase 5 decision (#553, approved 2026-10-20): the sidecar is retired. All routes run in process; legacy tables remain readable only as retired diagnostics and every route without the `hub/` prefix selects fixed fallback. Keep port offset 3 reserved and unused.
307
312
  - Each required phase is a separate reviewable commit and PR. No release or production configuration changes are implied.
308
313
 
309
314
  ## Amendment: session-aware stay/switch (#197, 2026-10-09)