@staix/agent-hub 0.29.0 → 0.30.0

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 (70) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -3
  3. package/docs/agent-notes/README.md +12 -0
  4. package/docs/agent-notes/adapters.md +2 -1
  5. package/docs/agent-notes/bus.md +1 -0
  6. package/docs/agent-notes/daemon.md +2 -1
  7. package/docs/agent-notes/tasks.md +4 -2
  8. package/docs/agent-notes/tests.md +2 -0
  9. package/docs/design/adapters.md +56 -0
  10. package/docs/design/agent-hub-architecture.md +12 -0
  11. package/docs/design/bus.md +8 -0
  12. package/docs/design/task-board.md +18 -0
  13. package/docs/dogfood.md +7 -4
  14. package/docs/operations.md +106 -11
  15. package/docs/quickstart.md +10 -3
  16. package/docs/sdlc.md +1 -1
  17. package/docs/security.md +28 -2
  18. package/docs/smoke.md +13 -2
  19. package/docs/specs/2026-10-12-office-view-design.md +86 -3
  20. package/docs/verification/2026-11-03-0.30.0.md +54 -0
  21. package/docs/verification/2026-11-03-agy-1.4.12-launcher-nonce.md +42 -0
  22. package/docs/verification/2026-11-03-agy-1.4.12.md +90 -0
  23. package/docs/verification/2026-11-03-agy-tui-close.md +49 -0
  24. package/docs/verification/2026-11-03-grok-1.0.62-t0.md +150 -0
  25. package/docs/verification/2026-11-03-pi-150-compat.md +40 -0
  26. package/docs/verification/2026-11-03-pi-title-api.md +26 -0
  27. package/docs/verification/2026-11-03-qwen-0265-acp.md +55 -0
  28. package/docs/verification/2026-11-03-qwen-0265-dual-output.md +40 -0
  29. package/docs/verification/dogfood/2026-11-03.md +3095 -0
  30. package/docs/verification/pi-150-model-free.ts +57 -0
  31. package/docs/verification/pi-150-native.mjs +21 -0
  32. package/docs/verification/pi-150-title-api.mjs +31 -0
  33. package/docs/verified.json +14 -14
  34. package/package.json +2 -2
  35. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  36. package/plugins/agent-hub/server.js +193 -29
  37. package/src/adapters/compat.ts +10 -10
  38. package/src/adapters/definitions/builtin-metadata.ts +2 -2
  39. package/src/adapters/local-worker.ts +22 -6
  40. package/src/adapters/pi.ts +92 -10
  41. package/src/adapters/registry-types.ts +4 -0
  42. package/src/adapters/registry.ts +2 -0
  43. package/src/cli/autonomy.ts +66 -1
  44. package/src/cli/console-state.ts +6 -2
  45. package/src/cli/help.ts +2 -0
  46. package/src/cli/identity.ts +8 -1
  47. package/src/cli/main.ts +39 -0
  48. package/src/cli/worktree-tidy.ts +158 -0
  49. package/src/cli/worktrees-output.ts +43 -0
  50. package/src/hub/board.ts +28 -3
  51. package/src/hub/bus.ts +4 -4
  52. package/src/hub/conductor.ts +4 -3
  53. package/src/hub/config-apply.ts +2 -1
  54. package/src/hub/daemon.ts +237 -19
  55. package/src/hub/dogfood-read.ts +62 -9
  56. package/src/hub/hub-tools.ts +11 -5
  57. package/src/hub/manager.ts +9 -1
  58. package/src/hub/peer-doing.ts +33 -3
  59. package/src/hub/peers.ts +7 -0
  60. package/src/hub/settings.ts +3 -0
  61. package/src/hub/tasks.ts +30 -8
  62. package/src/hub/ui.ts +2 -0
  63. package/src/hub/worktree-prs.ts +100 -0
  64. package/src/hub/worktree-shelf.ts +246 -0
  65. package/src/memory/capture.ts +1 -1
  66. package/src/pi/extension.ts +43 -3
  67. package/src/ui/index.html +133 -10
  68. package/src/ui/office-model.ts +64 -8
  69. package/src/ui/office-space.ts +1 -0
  70. package/src/ui/plugin.ts +14 -3
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.29.0 and control protocols 16 through 17. Live verification
3
+ This guide describes ahub 0.30.0 and control protocols 16 through 17. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Agent-operated live verification
@@ -278,6 +278,42 @@ dashboard's peer rows and the conductor's `hub_status` show the same value; it
278
278
  lives in the daemon's memory only, never in `status.json`, a log, an event or
279
279
  memory capture. Board WIP is unchanged.
280
280
 
281
+ Pi and the local worker can name an unnamed session with `hub_set_title`
282
+ (#1055). A normalized title is 1-80 characters; the first agent name is final.
283
+ The person replaces it with `ahub title <peer> <text...>`, or selects the peer
284
+ in the console and presses `t`. The rename input starts empty after the command
285
+ prefix, even when an unnamed label is visible. Other peers rename in their
286
+ native UI. No helper model is called, and task, branch and instance names are
287
+ independent. A private turn, an open PII owner/reviewer assignment, a sensitive
288
+ title or a failed metadata screen refuses the write.
289
+
290
+ Every refused title call includes its reason code plus a public cause and next
291
+ step. An unconfirmed write can have landed: check `ahub status` before retrying
292
+ when the error says it may have been applied. A person rename on an idle Pi
293
+ session that has never exchanged a message creates native title metadata but
294
+ may not create a session file yet. Recovery preserves that state by refusing
295
+ ID-only recreation; exchange one message or explicitly start a new session,
296
+ then retry recovery.
297
+
298
+ An unnamed session displays a muted label from the first acknowledged hub
299
+ delivery, at most 60 normalized characters. Queued or unaccepted messages do not
300
+ supply it. The console and configured conductor can see this incoming-message
301
+ label; other agent status readers see `unnamed`. Its source note distinguishes
302
+ the incoming message from text written by the recipient peer. The label is
303
+ computed only for live status and dashboard reads;
304
+ `status.json`, events, logs and memory receive no derived label. The daemon
305
+ stores only the peer's title-session ID and acknowledged delivery/envelope IDs
306
+ for native-session peers in `session-title-inputs.json`. The
307
+ [storage contract](design/adapters.md#session-title-slots) defines its bounds
308
+ and lifetime. Removed instances are pruned when their removal applies, and
309
+ Local bindings are discarded because the worker starts a new memory session.
310
+ After restart it recomputes the label only when
311
+ the same session and retained journal original match; a new session or pruned
312
+ original shows no inherited label. Pi's real name stays in Pi's native session;
313
+ the local worker's name lives only with that worker's in-memory session. The
314
+ native setter/readback probe is separate from the still-pending live restart
315
+ receipt tracked in #1143 and `docs/smoke.md`.
316
+
281
317
  In Peers, select a row and press `w` to edit `tasks.wip_limits.<peer>`, prefilled
282
318
  with its current limit. The editor stays on Peers and uses the existing Settings
283
319
  value, daemon preview, nonempty reason and `y` confirmation flow; Esc cancels without writing. The
@@ -453,7 +489,7 @@ OS color preference through CSS. Both inline scripts and the style retain CSP
453
489
  hashes; there are no external chart/theme assets. Live theme/readability inspection
454
490
  is tracked in `docs/smoke.md` and remains unverified until performed.
455
491
 
456
- The dashboard Office section (#264, phase 1 of 4) draws the same snapshot as a
492
+ The dashboard Office section (#264, phase 1) draws the same snapshot as a
457
493
  static flat office: one desk per peer in a stable slot (claude, codex, kimi, pi,
458
494
  local, then others by id; a peer going offline dims its desk and never moves
459
495
  another), rooms for the board columns in a fixed order (To do, Work floor,
@@ -480,6 +516,16 @@ projection without animating; the settle rule and reduced motion apply
480
516
  unchanged. The live browser look is tracked in `docs/smoke.md` and
481
517
  remains unverified until performed.
482
518
 
519
+ Phase 5 (#1152) adds a Backlog room before To do: cards in rank, then
520
+ priority, then id order, at most 8 with "+N more" opening the backlog filter,
521
+ each with its reserved, pinned and waiting marks, and a count equal to the
522
+ board's backlog count. Up to three release docks (planning, active or frozen
523
+ releases, then "+N releases") show approved/scope and the cut date; activating
524
+ a dock sets the task list's release filter. Each planner-role desk carries the
525
+ number of unplanned backlog cards (no rank, no reservation), and the wall shows
526
+ that count when no planner desk is visible. All of it is read from the existing
527
+ snapshot; the design is `docs/specs/2026-10-12-office-view-design.md`.
528
+
483
529
  Motion (phase 2): card moves between rooms and desks, message arcs between
484
530
  desks (from the hub or the console they start at an anchor point), delivery
485
531
  warning marks and the busy latch all follow the snapshot. Motion is CSS only:
@@ -713,8 +759,8 @@ setting (`ahub settings set peers.pi.start_mode headless`), asks for the
713
759
  background peer instead. The project uses its existing OmniRoute connection for DGX;
714
760
  `pi.dgx_coding` and `pi.dgx_fast` select the gateway's configured model IDs.
715
761
  Every Pi start (headless, TUI, console or auto start) first runs the configured
716
- `pi.cmd` with `--version` (#847). Pi 1.4.0 is verified
717
- ([model-free receipt](verification/2026-10-26-pi-140-compat.md)); another
762
+ `pi.cmd` with `--version` (#847). Pi 1.4.0 and 1.5.0 are verified
763
+ ([1.4.0 model-free receipt](verification/2026-10-26-pi-140-compat.md), [1.5.0](verification/2026-11-03-pi-150-compat.md)); another
718
764
  version, or one that cannot be read, is refused before Pi starts, unless the
719
765
  person trusts that exact version with `ahub agents trust pi <version>`.
720
766
 
@@ -1764,7 +1810,7 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
1764
1810
 
1765
1811
  `"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
1766
1812
 
1767
- The exact-pinned `@staix/agent-mem` 0.18.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
1813
+ The exact-pinned `@staix/agent-mem` 0.20.1 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
1768
1814
 
1769
1815
  The daemon checks its selector at hub operation entry and before candidate/counter writes. It does not hot-switch handles or provide an atomic fence against selection changes during an in-flight core call. Quiesce consumers and close their handles before migration or selection changes; reopen through the supported session-preserving lifecycle afterward. Candidate/counter storage remains `M/candidates.db`, independent of `G`; since agent-mem 0.7.0 `amem review` also lists and accepts candidates on a selected generation. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Existing JSON memory/goal results remain unchanged; the additive binding receipt belongs to daemon status, profile-check and the decay object. This source integration does not authorize installing a new runtime, migrating real memory, restarting sessions or claiming real-machine sync/capture receipts. Migration/deactivation and their limits belong to [agent-mem](https://github.com/STAIxBWLB/agent-mem); see [the hub profile contract](memory.md#selected-memory-profile).
1770
1816
 
@@ -2241,7 +2287,7 @@ Run `ahub autonomy on` from a person's terminal. It previews the concrete settin
2241
2287
 
2242
2288
  On a running hub, the on confirmation retains peer-specific consent under the machine hub home, outside the project and its execution roots, authenticated and bound to this project and the exact per-peer permission-mode values the person confirmed. A project-file marker or a forged state row cannot confirm never-ask. Missing or changed evidence keeps the ordinary per-peer console confirmation; changing one peer's mode invalidates that peer's consent. Start defaults and action-policy changes do not invalidate mode consent. Off and preset undo remove retained consent, so restoring a never-ask value with undo needs a new on confirmation, or a hub start under a trusted machine opt-in when the value exactly matches the preset plan, to retain it again. An agent shell, a delegated live-test command, a tools connection and the conductor cannot turn autonomy on or off. No hub tool exposes it.
2243
2289
 
2244
- `ahub autonomy status` prints each peer's current effective mode, the preset's requested next-start default and every current floor class: hub-enforced, a named verified structural guard, or not enforced with a reason. `--json` returns the same report as data. Plugin, native policy, terminal provider and GitHub branch protection are reported as missing or unknown when no current evidence establishes them. Orca uses the existing registered worktree opener; a custom `terminal.open` template is preserved. A headless or CI invocation selects headless defaults where the kind supports them. With the hub stopped, status reads only the project configuration: effective modes and retained consent are reported unknown, and every floor class is not enforced.
2290
+ `ahub autonomy status` starts with a padded peer table of current effective modes and preset next-start defaults (`unmanaged` when no mode is requested). Guarded actions follow, grouped by identical class, verdict and reason, with the affected peers (or "all peers") and counts of classes and classes enforced for at least one peer. Shared unknowns are printed once with their peer lists; floor-shaped missing entries are not repeated. Machine-default and Claude-home notices are retained, and paths under the user's home appear with `~`. Reasons and unknowns keep their original text. `--json` returns the unchanged structured report; an older hub without structured peers keeps the legacy line output. Plugin, native policy, terminal provider and GitHub branch protection are reported as missing or unknown when no current evidence establishes them. Orca uses the existing registered worktree opener; a custom `terminal.open` template is preserved. A headless or CI invocation selects headless defaults where the kind supports them. With the hub stopped, status reads only the project configuration: effective modes and retained consent are reported unknown, and every floor class is not enforced.
2245
2291
 
2246
2292
  A controlled restart or upgrade checks target feature `project_autonomy` while preset metadata or retained consent is active. A target without it is refused before the source is held or stopped; it could not read the new config metadata. Turn the preset off before selecting such a target.
2247
2293
 
@@ -2386,6 +2432,7 @@ A guarded file edit that the daemon keeps at its accepted value remains visible
2386
2432
  | Routing | `routing.stay_switch`, `routing.classes.<class>.peers`, `.escalate_to`, `.route`, `.pi_backend` | `routing.local.toml` | at once |
2387
2433
  | Switches | `research.enabled`, `approvals.notify`, `snapshots.enabled` | `config.local.json` | live, without disturbing an active turn |
2388
2434
  | Switches | `coordination` | `config.local.json` | newly created tasks only |
2435
+ | Switches | `worktrees.quiet_days` (0-3650, default 7, 0 never shelves by quiet time), `worktrees.alert_over` (0-100000, default 10, 0 never alerts) ([worktree shelf](#the-worktree-shelf-1057)) | `config.local.json` | live |
2389
2436
  | Switches | `task_sweep.enabled`, `.auto_reassign`, `.interval_s`, `.unaccepted_min`, `.idle_min`, `.review_min`, `.ready_min`, `.ladder_min`, `.no_writes_turns` | `config.local.json` | live; `.no_writes_turns` sets the turn threshold independently; other keys reschedule the sweep timer |
2390
2437
  | Routing | `tasks.wip_limits.<peer>` | `config.local.json` | live |
2391
2438
  | Routing | `tasks.escalate_after` (1-10, default 3: consecutive `changes_requested` before a task moves to the next `escalate_to` peer) | `config.local.json` | live; the next verdict uses it |
@@ -3452,7 +3499,7 @@ version is older than the selected running daemon; it installs nothing.
3452
3499
  | Runtime | Control protocol window | Legacy status field |
3453
3500
  | --- | --- | --- |
3454
3501
  | 0.12.20 through 0.25.0 | 16 only | `protocol: 16` |
3455
- | 0.26.0 through 0.29.0 | 16 through 17 | `protocol: 16`, `protocolMin: 16`, `protocolMax: 17` |
3502
+ | 0.26.0 through 0.30.0 | 16 through 17 | `protocol: 16`, `protocolMin: 16`, `protocolMax: 17` |
3456
3503
 
3457
3504
  New clients choose the highest common protocol. The runtime supports N-1, so the
3458
3505
  published protocol-16 client and channel plugin remain usable; unrelated feature
@@ -4725,7 +4772,7 @@ selected roster and existing capabilities; the TUI is always a person's start.
4725
4772
  - If the hub cannot read the event file the peer is `detached`, shown as offline with a notice, never idle: Qwen itself keeps working, and the hub reattaches when the file is readable again.
4726
4773
  - Restart: if the hub restarts while your Qwen keeps running, it reads on from its saved offset (no event twice, no launch). If Qwen is gone, `ahub qwen --mode tui` starts `qwen -r <session id>` on the same two files; the earlier submits are not replayed and the recorded answers are not shared again. Recovery never restores this terminal as a headless worker, and `ahub stop qwen` refuses: end Qwen in its own terminal.
4727
4774
 
4728
- Receipt-verified native ACP identities are qwen-code 0.25.0 and 0.26.0 ([dated receipt](verification/2026-10-22-kimi-qwen-compat.md)), 0.26.1 ([0.26.1 receipt](verification/2026-10-26-qwen-0261-acp.md)) and 0.26.4 ([0.26.4 ACP receipt](verification/2026-10-31-qwen-0264-acp.md)). The separate [0.26.4 dual-output attempt](verification/2026-10-31-qwen-0264-dual-output.md) did not establish its queued-turn criterion, so `qwen-dual` stays at 0.26.1. A CLI version label alone
4775
+ Receipt-verified native ACP identities are qwen-code 0.25.0 and 0.26.0 ([dated receipt](verification/2026-10-22-kimi-qwen-compat.md)), 0.26.1 ([0.26.1 receipt](verification/2026-10-26-qwen-0261-acp.md)) 0.26.4 ([0.26.4 ACP receipt](verification/2026-10-31-qwen-0264-acp.md)) and 0.26.5 ([0.26.5 ACP receipt](verification/2026-11-03-qwen-0265-acp.md)). The separate [0.26.4](verification/2026-10-31-qwen-0264-dual-output.md) and [0.26.5](verification/2026-11-03-qwen-0265-dual-output.md) dual-output attempts did not establish their queued-turn criterion, so `qwen-dual` stays at 0.26.1. A CLI version label alone
4729
4776
  is insufficient: startup verifies its native identity/version and advertised mode
4730
4777
  ids before any prompt. The earlier local installation reported CLI 0.25.0 while
4731
4778
  its package/ACP source said 0.24.1; that was not a ready identity. The 0.26.0 receipt
@@ -4776,7 +4823,7 @@ Native policy is unmanaged and has NO hub sandbox; no permission_modes.jcode or
4776
4823
 
4777
4824
  ## Optional Antigravity (agy) peer
4778
4825
 
4779
- Antigravity is optional and off roster by default. Use `ahub roster add agy`, then run `ahub agy` in a terminal: it runs the Antigravity CLI as a fresh pull-only TUI in that terminal. It accepts the 1.4.x line: 1.4.2 and 1.4.7 are the verified versions ([1.4.7 receipt](verification/2026-10-26-agy-1.4.7.md)), any other 1.4.x launches with a stderr warning (agy updates itself), and other versions are refused (#589) unless the person trusts that exact version with `ahub agents trust agy <version>` (#691). The running hub must advertise `native_hook_launch`; an older hub refuses before any write with an upgrade-or-restart remedy. Only the person's launcher starts it: conductor starts are refused, there is no controlled restore or resume, and a declared agy extra instance is refused when the config loads (#625). Headless/print, resume (`--conversation`, `--continue`, `--new`), `--mode`, `--dangerously-skip-permissions` and `--unattended` are refused until their native mapping is verified; `--print-command` previews without writing.
4826
+ Antigravity is optional and off roster by default. Use `ahub roster add agy`, then run `ahub agy` in a terminal: it runs the Antigravity CLI as a fresh pull-only TUI in that terminal. It accepts the 1.4.x line: 1.4.2, 1.4.7 and 1.4.12 are the verified versions ([1.4.7 receipt](verification/2026-10-26-agy-1.4.7.md), [1.4.12 launcher nonce receipt](verification/2026-11-03-agy-1.4.12-launcher-nonce.md)), any other 1.4.x launches with a stderr warning (agy updates itself; set `AGY_CLI_DISABLE_AUTO_UPDATE=1` for a live run, #1140), and other versions are refused (#589) unless the person trusts that exact version with `ahub agents trust agy <version>` (#691). The running hub must advertise `native_hook_launch`; an older hub refuses before any write with an upgrade-or-restart remedy. Only the person's launcher starts it: conductor starts are refused, there is no controlled restore or resume, and a declared agy extra instance is refused when the config loads (#625). Headless/print, resume (`--conversation`, `--continue`, `--new`), `--mode`, `--dangerously-skip-permissions` and `--unattended` are refused until their native mapping is verified; `--print-command` previews without writing.
4780
4827
 
4781
4828
  The launcher writes exactly `plugin.json`, `mcp_config.json` and `hooks.json` in `.agents/plugins/agent-hub/`, refusing linked or foreign entries before any write, and removes only its own matching files when the TUI ends. It never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Run `ahub init` afterwards to ignore the untracked plugin directory; a tracked plugin is written with a warning. The native model sees the hub tools under the server name `agent-hub_agent-hub`.
4782
4829
 
@@ -4905,6 +4952,14 @@ A `schedules` entry in `.agenthub/config.local.json` takes either `every` (an in
4905
4952
  - **Upgrades.** The daemon advertises `calendar_schedules`. While a schedule uses `cron` or `tz`, a restart, upgrade or recovery onto a release without it is refused, naming the reason.
4906
4953
  - Out of scope: holiday and exchange calendars. A task whose first step finds a non-working day can end itself.
4907
4954
 
4955
+ ## Registered sources and request drafts (#1144)
4956
+
4957
+ `ahub source add` registers a bounded file or URL snapshot, and `ahub source list` lists its `src:` registry ID. For profiles that support `plan.sources`, task proposal and acceptance validate each ID against this registry before changing the board or notifying peers. An unknown ID is refused with `ahub source list` as the next step. Source overlaps use the registry IDs; a private task's public plan remains withheld.
4958
+
4959
+ The dashboard's Plan sheet includes a read-only registered-source list with ID, kind, byte size, hash prefix and registration time. It receives only those allowlisted metadata fields through the existing snapshot, with no source content or origin paths.
4960
+
4961
+ Use Capture a request in the same sheet, or `ahub intake`, to stage natural-language work as held draft Backlog cards. Optional source IDs refer to the registered snapshots. The dashboard uses the same Tasks intake path and public task projection as the CLI; nothing is offered until a draft is approved. Inspect drafts in Backlog, then use `ahub intake approve` or `ahub intake discard` in a terminal. A delegated `--live-test` dashboard session cannot create intake drafts. If submission is unconfirmed, read Backlog before trying again to avoid a duplicate request.
4962
+
4908
4963
  ## Staging work in Backlog (#263)
4909
4964
 
4910
4965
  Use `ahub task propose implement "Later work" --backlog --owner codex` to stage
@@ -5025,7 +5080,24 @@ reserved peer that cannot take it (offline, paused, excluded, at its WIP limit)
5025
5080
  refuses the pull before any change, naming the peer and its reason; it is not
5026
5081
  passed over to another peer. `ahub route explain` previews the same reserved-only
5027
5082
  choice. An unreserved pull keeps reserving a slot as above. A reserved offer that
5028
- was never started can return untouched and keeps its reservation.
5083
+ was never started can return untouched. Omitting planning fields retains its
5084
+ reservation, so `tasks.auto_pull` can offer it again as soon as the slot is free.
5085
+
5086
+ To park untouched work in one operation, the planner's
5087
+ `hub_backlog_return_untouched` accepts optional `fields` containing only
5088
+ `reserved` (a peer ID or `null`). For example:
5089
+
5090
+ ```json
5091
+ {"id":138,"revision":6,"reason":"wait for the person","fields":{"reserved":null}}
5092
+ ```
5093
+
5094
+ The return, reservation change, planning revision and previous reservation are
5095
+ saved together before capacity notices or auto-pull run. Read the returned
5096
+ `planningRevision` for a later planning change. Without `fields`, the planning
5097
+ revision and reservation are retained. Reservation updates use the existing
5098
+ person/planner, roster, exclusion and capability checks; a planner cannot clear
5099
+ or replace a person's reservation. The ordinary `ahub backlog return` form keeps
5100
+ its existing reservation-preserving behavior.
5029
5101
 
5030
5102
  Capacity defaults to one per peer. Each declared instance has its own `tasks.wip_limits.<instance>` key. `ahub settings set tasks.wip_limits.codex 2`
5031
5103
  uses the existing machine-local registry path; integers 0..32 apply live (the next
@@ -5531,7 +5603,7 @@ Native launcher connections retry transport timeouts/unclean closes with bounded
5531
5603
 
5532
5604
  ## amem runtime (agent-mem#13 P4)
5533
5605
 
5534
- The hub exact-pins @staix/agent-mem 0.18.0 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
5606
+ The hub exact-pins @staix/agent-mem 0.20.1 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
5535
5607
 
5536
5608
  ## Backlog issue identity and notes
5537
5609
 
@@ -5968,3 +6040,26 @@ kimi: not started: kimi starts in its TUI and the hub could not open a terminal
5968
6040
  - **Who and when:** only a person's own `ahub up` and the dashboard manager's "start" (which counts as one). An agent's `ahub up` is refused by the CLI before this runs. A daemon restart, a crash recovery, a config reload (`start` applies at the next `ahub up`) and the `ahub up` that an upgrade or recovery relaunches (it carries `AGENTHUB_RECOVERY_OPERATION`) start nothing from the list, and the running hub never reads it. `--live-test` starts none either.
5969
6041
  - **What can be listed:** `claude`, `codex`, `kimi`, `pi`, `local` and `jcode`. `qwen` is refused when saved ("the hub never opens its TUI"), as are `agy`, a declared instance, a Claude channel extra, an unknown id and a duplicate, each with a named reason; the loader and the settings writer say the same words. Pi in the list is the user-facing switch; `pi.auto_start` keeps working for configs that have it.
5970
6042
  - **Failure is local to one agent:** a refusal, a terminal provider that fails or a request that times out prints its own line and never stops the hub or the other agents. The lines come from the hub's own reply, so a provider's error text stays in `hub.log`.
6043
+
6044
+ ## The worktree shelf (#1057)
6045
+
6046
+ Implementers work in `.worktrees/<name>`, and after a guarded merge only the owner removes its local worktree and branch (`docs/sdlc.md`, Merge result and cleanup), so finished worktrees pile up. The shelf shows which ones are finished. It never removes anything itself.
6047
+
6048
+ ```
6049
+ ahub worktrees # live worktrees, then the shelf with each settled row's reason
6050
+ ahub worktrees --json
6051
+ ahub worktrees tidy # a preview: what is safe to remove, what is refused and why, and a fingerprint
6052
+ ahub worktrees tidy --confirm <fingerprint> # the person's own terminal: re-reads everything, then removes
6053
+ ```
6054
+
6055
+ - **Settled is derived, never stored.** The hub stores facts and two person overrides, and works the groups out on every read. Rules, in order: the main checkout and the declared instance worktrees (`.worktrees/<instance>`) are never shelved; an override that is still in force wins; a merged or closed PR with no work newer than its change time settles the worktree; `worktrees.quiet_days` without activity settles it (`quiet`); everything else is live. A settled row names its reason: `merged`, `closed`, `quiet` or `parked`; a row bound to a PII task shows `settled` and `task: private` instead of its PR reason, task id and PR.
6056
+ - **What counts as activity.** The latest of the HEAD commit time, the mtime of the worktree's own index (uncommitted editing) and the bound task's latest history entry written by anyone but the hub. The hub's own entries (the merge result, the owner notice) are left out, or a merge would always look like newer work. The tidy preview's own git reads run with `--no-optional-locks`, so previewing does not touch the index and make a settled worktree look active. A worktree whose commit time or index could not be read (a failed git call, or the read budget used up) is listed live with `complete: false` and is never shelved by a PR or by quiet time: unknown is not settled.
6057
+ - **Where the PR state comes from.** From the board only: a guarded merge's own record says `merged`. A cancelled task says nothing about its PR, and a task with no record reads `unknown`. Nothing is fetched from GitHub when you open the panel, run `ahub worktrees`, or when the inbox alert is computed.
6058
+ - **Park, Pin and Unset.** The Agents panel offers Park (move a live row to the shelf), Pin (keep a settled row live) and Unset (back to what the rules derive). An override counts only while the worktree has had no newer activity (`lastActiveAt <= at`); later work makes it inert and the next write removes it. They are control operations `worktree_override_set` and `worktree_override_clear`, console role only: an agent's tools connection is refused.
6059
+ - **Refresh PR states.** The one button that reads GitHub, and the control operation `worktrees_refresh_prs` behind it (console role). It reads each bound task's PR with the same `readSdlcPr` the guarded merge uses, at most 8 PRs, 8 s per call and 25 s in all, one refresh at a time, and keeps what it read in the hub's memory (a restart forgets it and those rows read `unknown` again). A PR GitHub could not answer for keeps its old state and is counted as unreadable. The plugin asks you to confirm it first, because it calls GitHub.
6060
+ - **The panel's list.** The Agents view gets a `worktrees` field from a cache at most 30 s old that a background read refreshes: a snapshot never waits for git. Paths are project-relative (or the basename outside the project root), never absolute. The panel shows removal as the command to copy, never as a button.
6061
+ - **The inbox alert.** acon's inbox raises one `alert` item per project when the settled count is above `worktrees.alert_over` (0 never). It carries the count and a link to the Agents panel, nothing else: no path, branch or task.
6062
+ - **Settings.** `worktrees.quiet_days` (default 7, 0 = never shelve by quiet time) and `worktrees.alert_over` (default 10, 0 = never alert), both live, both on the Switches group of `ahub settings` and the dashboard.
6063
+ - **Tidy is the person's.** `tidy` previews each settled row that is safe to remove and each refused row with its reasons: dirty, locked, commits not pushed to the upstream or no upstream, branch not merged into the default branch, the main checkout, an instance worktree, a bound task that is not approved or cancelled, or anything git could not tell. The preview ends with a fingerprint over the rows. `--confirm <fingerprint>` is console class (an agent can only preview, and `--live-test` refuses it), re-reads everything, refuses the whole run if the set changed, and runs exactly two argv shapes per row, `git worktree remove <path>` and `git branch -d <branch>`, then reads each result back. `--force` and `-D` never appear: git itself refuses a dirty tree or an unmerged branch. A worktree edited between the confirm's re-read and the removal is not guarded by anything but git's own refusal.
6064
+ - **The daemon deletes nothing.** The shelf's git port runs only `worktree list --porcelain -z`, `log -1 --format=%ct HEAD` and `rev-parse --absolute-git-dir`, and refuses any other argv before spawning. Removal exists only in the CLI process (`src/cli/worktree-tidy.ts`), which the daemon never imports; `test/worktree-shelf.test.ts` greps the daemon-reachable modules for a removal.
6065
+ - **Limits.** At most 256 worktrees are listed and 1024 overrides read; git reads run 4 at a time under a 3 s total budget (5 s per call), asynchronously, so a slow git never holds the daemon. The quiet threshold is the owner's call after a week of use.
@@ -17,7 +17,7 @@ ahub setup # installs the channel plugin and a pr
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.29.0
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.30.0
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -31,6 +31,12 @@ move a running project to a newer release later, run `ahub upgrade` in a
31
31
  terminal in that project: it reviews the plan with you, applies it and follows
32
32
  it; other projects keep their version.
33
33
 
34
+ ## Upgrading to 0.30.0
35
+
36
+ The hub now exact-pins `@staix/agent-mem` 0.20.1 (#1136). A setup receipt written by 0.20.1 can be v2, which 0.20.0 refuses to apply or undo; undo a setup with the release that wrote it.
37
+
38
+ Nothing else changes until you use something new. `ahub worktrees` lists the project's live and settled worktrees (settled: its PR merged or closed with no newer work, or quiet for `worktrees.quiet_days`, default 7), and `ahub worktrees tidy` previews which settled ones are safe to remove, with the reasons for each refused row; only you remove them, with `ahub worktrees tidy --confirm <fingerprint>` in your own terminal, which runs `git worktree remove` and `git branch -d` and never forces (#1057). The dashboard's Agents panel gets a Worktrees section with Park, Pin and "Refresh PR states", and an inbox alert fires above `worktrees.alert_over` (default 10). Pi and the local worker name their own session with `hub_set_title`; `ahub title <peer> <text...>` or the console's `t` editor replaces the name (#1157). A plan's `sources` must now name registered `src:<16 hex>` ids (`ahub source list`), or the proposal or acceptance is refused (#1151). Run agy with `AGY_CLI_DISABLE_AUTO_UPDATE=1`: without it agy replaced its own binary during a launch (#1140).
39
+
34
40
  ## Upgrading to 0.29.0
35
41
 
36
42
  Upgrade every `amem` that shares your memory home together with the hub: the hub now exact-pins `@staix/agent-mem` 0.20.0, and an `amem` 0.19.0 hook or CLI cannot drain 0.20.0's new hook spool records and can retire them after seven days (#1131). Formats, shards, candidates and indexes are unchanged.
@@ -171,7 +177,7 @@ The restarted Claude planner gets the release-train tools (`hub_release_new`, `h
171
177
 
172
178
  Jcode must be 0.98.0 or later, below 1.0.0; 0.94.0 is no longer accepted, so `ahub doctor` fails its Jcode row for an older install and the peer does not start (#529). Every owned jcode process runs with `JCODE_NO_TELEMETRY=1`.
173
179
 
174
- Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.20.0. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
180
+ Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.20.1. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
175
181
 
176
182
  From 0.12.30 or earlier, existing Jcode project MCP entries need one refresh before conductor or dashboard starts. Once per project, the person runs:
177
183
 
@@ -235,6 +241,7 @@ or set `OMNIROUTE_API_KEY`. The `.agenthub/routing.toml` that `ahub init` writes
235
241
  | `ahub settings edit <file> set <path> <json>` | change an instance, the review block or the `sdlc.toml` lifecycle with a validated diff and a backup; `ahub settings backups` lists the backups |
236
242
  | `ahub status` | peers, queues, pauses; `ahub budget` for quota windows |
237
243
  | `ahub usage [--by peer\|task\|issue\|pr\|release\|day\|model]` | tokens per group, each number with its source; `ahub usage limits` for pauses and resets |
244
+ | `ahub worktrees` | live and settled worktrees; `ahub worktrees tidy` previews what is safe to remove, and only you confirm it with `--confirm <fingerprint>` |
238
245
  | `ahub agents` | which installed agent CLI versions the hub accepts; `ahub agents trust <id> <version>` accepts an untested one on this machine |
239
246
  | `ahub memory profile-check` | a person's read-only check of running memory bindings in the current registry |
240
247
  | `ahub ask <question>` | what the board, shared memory and the log say, with the ids the answer rests on |
@@ -246,7 +253,7 @@ More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-de
246
253
 
247
254
  ## Shared memory
248
255
 
249
- The hub exact-pins the first-party `@staix/agent-mem` 0.20.0 core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
256
+ The hub exact-pins the first-party `@staix/agent-mem` 0.20.1 core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
250
257
 
251
258
  Before any real generation migration, upgrade every hub sharing that memory home to 0.12.30 or later and check its authenticated binding with `ahub memory profile-check` in each custom registry. Ready bindings prove compatibility, not an atomic live cutover: quiesce consumers and close their handles before migrating, then reopen through the supported session-preserving lifecycle. Invalid or changed profiles disable native memory without falling back to legacy writes; the bus and board continue. See [memory operations](operations.md#hub-memory) for the remaining rollout gates.
252
259
 
package/docs/sdlc.md CHANGED
@@ -178,7 +178,7 @@ Post-run reads make at most three attempts within a shared six-second budget. On
178
178
 
179
179
  Tasks stores host-only `sdlc.merge` evidence bound to the repository, PR and head. Lifecycle tools cannot submit this field, and artifact changes invalidate it. A change during an awaited operation leaves the previous attempt in history without overwriting the replacement artifact. Durable cleanup and owner-notice claims precede those effects. Merged or uncertain attempts are not automatically repeated after restart; a positively observed not-merged result can be retried by an explicit command. A crash before the first post-run record remains an untracked attempt requiring inspection.
180
180
 
181
- Remote cleanup reads the merged PR head, repository default, legacy protection and active rulesets. It skips missing, fork, default, protected, moved or unsupported refs. Its only ref write is a single GraphQL compare-and-swap deletion: `beforeOid` is the merged PR head and `afterOid` is zero. A moved head refuses deletion; unknown outcomes stay explicit. The hub sends the owner a public-view notice to remove only its task worktree and local branch when safe. The hub runs no local branch or worktree deletion.
181
+ Remote cleanup reads the merged PR head, repository default, legacy protection and active rulesets. It skips missing, fork, default, protected, moved or unsupported refs. Its only ref write is a single GraphQL compare-and-swap deletion: `beforeOid` is the merged PR head and `afterOid` is zero. A moved head refuses deletion; unknown outcomes stay explicit. The hub sends the owner a public-view notice to remove only its task worktree and local branch when safe. The hub runs no local branch or worktree deletion. Finding the worktrees that are left over is the worktree shelf (`docs/operations.md`, "The worktree shelf (#1057)"): the hub derives which ones are finished and shows them, and removal is the person's own `ahub worktrees tidy --confirm` in a terminal, never a daemon or panel action.
182
182
 
183
183
  ## Verdict mirror (#786)
184
184
 
package/docs/security.md CHANGED
@@ -673,7 +673,7 @@ withholds the scoped mode on these writes so an older hook grants nothing too.
673
673
 
674
674
  Agent CLI versions (#691). One policy gates the Codex and Kimi fresh reviewers, the headless Claude implementer, Jcode, agy, Qwen (ACP and the dual-output TUI) and Pi (#843): a receipt-verified version passes, Jcode and agy also start an untested version inside their compatible line with a note, and any other version is refused unless the person trusts that exact version on this machine with `ahub agents trust <id> <version>`. The record is `agent-compat.json` in the hub home (`~/.agenthub`), written atomically with mode 600 and at most 64 entries; an unreadable, linked, oversized or invalid record trusts nothing. Agent shells and `--live-test` may read `ahub agents` but cannot trust or revoke. Trust lifts only the version pin: the fresh reviewers' runtime proofs still run before every child, and an untested version's native contract stays unverified.
675
675
 
676
- Qwen Code's optional production ACP peer requires the qwen-code native identity, a compat-admitted version (receipt-verified 0.25.0, 0.26.0, 0.26.1 and 0.26.4)
676
+ Qwen Code's optional production ACP peer requires the qwen-code native identity, a compat-admitted version (receipt-verified 0.25.0, 0.26.0, 0.26.1, 0.26.4 and 0.26.5)
677
677
  and advertised mode ids before setting a policy or starting work. Its own tools
678
678
  have no hub sandbox; auto-edit approves file edits, yolo every tool. Configured
679
679
  never-ask still requires the person's confirmation. qwen_cmd is machine-local and
@@ -682,7 +682,7 @@ this production table. Startup diagnostics withhold native host/payload details.
682
682
 
683
683
  Jcode is optional, source/version-fenced and runs native tools without a hub sandbox or hub permission-mode mapping. .jcode joins the scoped-grant exclusion, including lexical/resolved aliases. Only a person writes its unique project MCP entry; foreign/override/symlink entries refuse. The hub owns a private daemon/socket and ACP group, never the global daemon, and copies no credentials. It accepts a plain jcode release from 0.98.0 up to, not including, 1.0.0 (#529; only 0.98.1, 0.99.0 and 0.99.1 are verified, and another version in that line starts with a note), or an exact version the person trusts (#691), and runs every owned jcode process (probe, serve, ACP and TUI) with `JCODE_NO_TELEMETRY=1`, because jcode's telemetry can report the provider account's email. Native diagnostics withhold payloads/hosts. Hub tools are bound to an owned launch (task 368): the shared project entry names no peer, each owned Jcode process carries its own peer id and launch id, the daemon admits a hub-tools connection for a Jcode peer only with the id of that peer's current owned launch, and a tools-mode server started without a peer id refuses every tool. Extra Jcode instances (#685, at most 8) are declared only through `instances` in an untracked `.agenthub/config.local.json`, start headless, and each needs its own `jcode_home`, which may not be the base home or another instance's, nor nested in or around one, however it is spelled. Unproved conversation loads refuse; no fresh replacement is called a restore. A finished task's advisory about the project root checkout names the branch only after the hub's whole public-text screen (`Tasks.publicMetadataText`: a PII pattern, a secret shape and a project-private path) passes it and it is a plain ref name; otherwise a generic sentence carries no part of it (#572). Native TUI launch exposes its private socket/session tuple only to the authenticated console, bound to the same live owned socket and session after read-only native persistence proof. Tools/status do not receive it. The TUI has no single-writer guard: person input may interleave with ACP. Native policy remains unmanaged and closing the frontend does not stop ACP. An `important` message reaches a busy Jcode as a soft interrupt written to the hub's own private server socket for the owned session (#572); the hub never cancels a turn to deliver, and a steer that was sent but not acknowledged, or whose turn ended first, is held as `needs_review`, never replayed. Jcode has no switch to choose its MCP servers, so the owned serve and ACP processes load every server named in `<JCODE_HOME>/mcp.json` and the project's `.jcode/mcp.json`, `.mcp.json` and `.claude/mcp.json`, besides the hub's own; `ahub doctor` lists their names only. Point `jcode_home` at a separate home, or remove a server from those files, to keep it out.
684
684
 
685
- Antigravity (`agy`, #485) is optional, cloud and off roster by default. The launcher accepts the Antigravity CLI 1.4.x line; only 1.4.2 and 1.4.7 have a native receipt ([1.4.7](verification/2026-10-26-agy-1.4.7.md)), so another 1.4.x patch launches with a warning that it is unverified, and every other version is refused before a hub connection or plugin write (#589) unless the person trusts that exact version (#691). It runs native tools under its own unmanaged permissions, without a hub sandbox or hub permission-mode mapping; headless, resume, unattended and native permission flags are refused. Its roles are capped at implementer and verifier, so it never plans, reviews or receives PII work. Only the person's `ahub agy` launcher writes its three plugin files in `.agents/plugins/agent-hub/` (`plugin.json`, `mcp_config.json`, `hooks.json`): linked or foreign entries refuse before any write, cleanup removes only that launch's matching files, and it never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Conductor starts are refused, and a declared agy extra instance is refused at config load (#625). Hook authority (`native_hook_launch`) comes only from that launcher, belongs to the console socket that began it and ends when that socket closes; hook input over 1 MiB is refused, and observations are bound to the daemon, launch, native session and project.
685
+ Antigravity (`agy`, #485) is optional, cloud and off roster by default. The launcher accepts the Antigravity CLI 1.4.x line; only 1.4.2, 1.4.7 and 1.4.12 have a native receipt ([1.4.7](verification/2026-10-26-agy-1.4.7.md), [1.4.12](verification/2026-11-03-agy-1.4.12-launcher-nonce.md)), so another 1.4.x patch launches with a warning that it is unverified, and every other version is refused before a hub connection or plugin write (#589) unless the person trusts that exact version (#691). It runs native tools under its own unmanaged permissions, without a hub sandbox or hub permission-mode mapping; headless, resume, unattended and native permission flags are refused. Its roles are capped at implementer and verifier, so it never plans, reviews or receives PII work. Only the person's `ahub agy` launcher writes its three plugin files in `.agents/plugins/agent-hub/` (`plugin.json`, `mcp_config.json`, `hooks.json`): linked or foreign entries refuse before any write, cleanup removes only that launch's matching files, and it never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Conductor starts are refused, and a declared agy extra instance is refused at config load (#625). Hook authority (`native_hook_launch`) comes only from that launcher, belongs to the console socket that began it and ends when that socket closes; hook input over 1 MiB is refused, and observations are bound to the daemon, launch, native session and project.
686
686
 
687
687
  Configured ACP adapters (#451 A3) are declared only by the person, in a Git-confirmed untracked `.agenthub/config.local.json`. The whole declaration (command, argv, identity, roles and mode maps) is machine-local authority: a shared or tracked declaration is ignored with a notice, settings never edit it, and changes need a hub restart. The hub runs only the declared argv, never a module or callback. Argv that selects a leader, attach, connect or remote mode is refused, and a Grok declaration needs `--no-leader`. Each start requires the pinned native identity, an exact `session/load` echo on resume, and positive ownership of the spawned process and its original process group; unknown ownership refuses, and stop signals only that verified group. An agent that hands its work to a process outside that group is outside this boundary. Without a mode map native policy is unmanaged, and there is no hub sandbox either way. Configured kinds are cloud peers (never PII), headless only and off roster by default, and the hub makes no quota, context or controlled-recovery claims for them. A read-only instance needs the native read-only mode acknowledged before a prompt and is limited to read and review hub tools, both in the published MCP list and at the host; its native non-read tool requests are rejected, and no setting or runtime change can raise it. These agents' stderr and error text are withheld from logs, and previews redact the executable, argv and session id.
688
688
 
@@ -835,6 +835,8 @@ What the sandbox can and cannot write is stated here so nobody relies on more. A
835
835
 
836
836
  On decline or cancel the hub removes the worktree and deletes the branch while it is still the empty base. A worktree with uncommitted changes, or that cannot be checked, is never force-removed: it is kept with its branch and reported as a console notice. A branch the hub committed to is kept, because its commit may be pushed and under review. A peer that makes its own worktree (Claude, Codex, Kimi) is untouched, and a PII task never gets a hub worktree. Residual: a same-user process outside the sandbox can still plant a ref under `.git/refs/heads`; the sandbox no longer lets a sandboxed command do it (#1123), and the hub refuses to commit or push on top of a moved ref either way.
837
837
 
838
+ Worktree shelf (#1057). Apart from the empty hub-made task worktree above, the daemon never removes a worktree or deletes a branch; the shelf only reads and records the person's Park or Pin. `ahub worktrees` and the `ahub worktrees tidy` preview are reads an agent may run outside a private turn; Park, Pin, "Refresh PR states" and `tidy --confirm` are the person's (the console, or the dashboard session the manager forwards for it; a live-test dashboard is refused, and `tidy --confirm` is person-only for an agent identity and refused under `--live-test`). The only removal path is `ahub worktrees tidy --confirm <fingerprint>` in the person's own CLI process (`src/cli/worktree-tidy.ts`, which the daemon does not import). It re-reads every row and refuses the whole run when the fingerprint changed, operates only on a path git itself lists, and runs exactly `git worktree remove <path>` and `git branch -d <branch>`, never `--force` or `-D`. A row is refused when it is the main checkout, an instance worktree, outside the project, dirty, locked, without a branch, unpushed, without an upstream, not merged into the default branch, bound to a task that is not approved or cancelled, or on a branch that changed since the preview, and whenever git cannot tell; a parked row passes the same checks. No read makes a GitHub call; only the explicit refresh does, for at most 8 PRs. A worktree bound to a PII task keeps that binding on the host, so it stays live and tidy refuses it, but its row carries no task id, PR or task state.
839
+
838
840
  Refs, HEAD and worktree admin writes (#1123). A hub-sandboxed peer (Pi, the local worker) cannot move what a branch, `HEAD` or a linked worktree points at. The profile (`gitRefRules` in `src/local/deny.ts`, built into `hubWriteRegexes`) refuses every write to `refs/**` (branches, tags, `refs/stash`, remote refs), `packed-refs` (and its lock and new files), `HEAD` (and `HEAD.lock`) and `worktrees/**` (a linked worktree's admin dir: its `HEAD`, `index`, `logs`, anything new, and a new admin dir). The rules sit directly under the git dir, so a project below a directory that happens to be named `refs`, `worktrees`, `HEAD` or `packed-refs` keeps working. They cover a plain `.git`, and each external git dir (the common dir and a linked worktree's own, whether the worktree is inside the project or beside it, and a bare repository's common dir that is not named `.git`). Every name is folded for the code points HFS+ ignores like the other git names, and each regex literal stays under seatbelt's 1024-byte limit at any project depth (`test/sandbox-git-refs.test.ts`, run with the real sandbox-exec in the main checkout, in a hub-made worktree, in a worktree outside its project and in a bare repository's worktree). The hub's own git is hardened and runs outside the sandbox, so #1115's hub commit and push are unaffected.
839
841
 
840
842
  What a sandboxed command can still write under `.git`, each with its reason, probed with the real profile: the main checkout's `index` and `index.lock` (`git add` and `git restore` stage there), `logs/HEAD`, `ORIG_HEAD`, `FETCH_HEAD`, `MERGE_HEAD`, `COMMIT_EDITMSG`, `info/exclude` and `description` (none can run a program or move a branch), and NEW loose objects under `objects/` (a staged blob, #864; existing objects, `objects/info` and `objects/pack` stay refused). Still refused as before: a gitfile, `config`, `config.worktree`, `hooks`, `commondir` (#270). Inside a linked worktree the sandbox cannot write the worktree's own admin dir, so `git add` there fails on its `index.lock`; the working tree itself is writable and `git status`, `diff`, `log`, `show`, `blame`, `ls-files`, `rev-parse`, `for-each-ref` and `cat-file` all still work.
@@ -1176,3 +1178,27 @@ path and no polling when no real cleanup occurred.
1176
1178
  Done/discard acknowledgement precedes the background room retry. A network retry
1177
1179
  failure or socket loss during that later work cannot report a committed local
1178
1180
  disposition as failed.
1181
+
1182
+ ## Hub-owned session titles
1183
+
1184
+ `hub_set_title` (#1055) is offered only to fixed Pi/local definitions with a
1185
+ hub-owned title slot. Publication grants no authority: the daemon checks the
1186
+ current adapter/session, private turn, open PII ownership or review and public
1187
+ metadata policy. An agent may write only while the native/current slot remains
1188
+ unnamed. A person's console rename may replace it, but cannot bypass privacy.
1189
+ The title and any unnamed display label are screened again when shown; unknown
1190
+ screening is `[private]`. Title tool inputs/results are excluded from capture
1191
+ and progress observations. No title crosses rooms, changes task/Git/instance
1192
+ records, or causes an extra model call.
1193
+
1194
+ A fallback label is never persisted. Its original message has the existing
1195
+ journal retention; the separate `session-title-inputs.json` stores only peer,
1196
+ title-session, delivery and envelope IDs written by positive delivery
1197
+ acknowledgement, never by a title tool. Restart resolves a still-retained
1198
+ original only for the same title-session ID. A missing or different session,
1199
+ invalid binding or pruned original yields no inherited label. Live status may
1200
+ show it only to the console or configured conductor; other readers see
1201
+ `unnamed`. Metadata screening does not grant audience membership. The label's
1202
+ source note identifies incoming-message text and never attributes it as the
1203
+ recipient peer's own speech. `status.json`, events, logs and memory never
1204
+ receive a derived copy. Removed-instance and Local bindings are not retained.
package/docs/smoke.md CHANGED
@@ -38,7 +38,7 @@ Autonomy T4 (#999 AC8): [2026-11-02 live inventory](verification/2026-11-02-auto
38
38
  | Character glyphs in real terminals and tmux (#280) | Console attention; Dashboard character | agent-evidence / person-verdict | glyph rendering in physical terminals |
39
39
  | Console Settings panel in a native terminal (#439) | Settings reload and console fixture | agent-evidence / person-verdict | the panel as a person sees it |
40
40
  | Reviewer pool native legs (#519) | Reviewer pool native legs | agent-executable | after #783 ships |
41
- | Antigravity TUI close (#485) | Antigravity P1 | agent-executable | |
41
+ | Antigravity TUI close (#485) | Antigravity P1 | agent-executable | launcher close leg passed on 2026-11-03 ([receipt](verification/2026-11-03-agy-tui-close.md)); still open: `hub_send` and a board read or proposal through the TUI, a real account |
42
42
  | Managed Pi live receipts (#418 AC8) | none yet (issue #418) | person-only, then agent-executable | the person opts in to a check runner and answers the approvals |
43
43
  | Kimi credential probe (#285 AC9) | Kimi usage counting | person-only | the owner's approval of the probe |
44
44
  | Six role instances (#525-6, hub card #138 I4) | Six role instances (#525-6) | person-only | native terminal/account starts, the person's sandbox and approval choices, and the live six-instance verdict |
@@ -2025,7 +2025,8 @@ Kimi usage ceiling: 2.2.0 print stream-json exposes no token counts. Keep unobse
2025
2025
 
2026
2026
  ## Antigravity P1 (#485)
2027
2027
 
2028
- - Run agy on the 1.4.x line (1.4.2 and 1.4.7 verified; other patches warn, #589); verify its executable/version and keep the actual TUI/IDE leg separate from the account-free T0 transport receipts ([1.4.2](verification/2026-10-19-agy-p1-t0.md), [1.4.7](verification/2026-10-26-agy-1.4.7.md)).
2028
+ - Run agy on the 1.4.x line (1.4.2, 1.4.7 and 1.4.12 verified; other patches warn, #589); verify its executable/version and keep the actual TUI/IDE leg separate from the account-free T0 transport receipts ([1.4.2](verification/2026-10-19-agy-p1-t0.md), [1.4.7](verification/2026-10-26-agy-1.4.7.md), [1.4.12](verification/2026-11-03-agy-1.4.12.md) and its [launcher nonce leg](verification/2026-11-03-agy-1.4.12-launcher-nonce.md)).
2029
+ - The hub launcher's own close leg ran in a scratch project with the real `ahub agy` and a fixture provider ([2026-11-03 receipt](verification/2026-11-03-agy-tui-close.md)): the launch slot is held while agy starts and runs, a normal `/exit` releases it so the next launch is admitted, only the managed three-file plugin is removed, and the real Gemini config hashes are unchanged. Set `AGY_CLI_DISABLE_AUTO_UPDATE=1` for any further live agy run: without it agy replaced `~/.local/bin/agy` during that run.
2029
2030
  - With a reviewed compatible daemon running and agy explicitly on roster, the person runs `ahub agy` in a terminal. Confirm peer `agy` is pull-only, native permissions unmanaged and no hub sandbox/budget source is claimed.
2030
2031
  - In the native TUI call `hub_inbox`, `hub_send`, and a permitted board read/proposal within a non-PII task. Confirm the tool audience and actor remain `agy`, the native model consumes the tool results, and no unsolicited push or PII task text reaches it.
2031
2032
  - End the TUI normally. Confirm its group/socket detaches and only its matching managed three-file plugin is cleaned up; unrelated project/global settings remain unchanged.
@@ -2163,3 +2164,13 @@ account fixture needs the person's account setup. Ask the planner before any
2163
2164
  quota-heavy run. Never publish prompts, tokens, credentials, endpoints, native
2164
2165
  transcripts or session ids as evidence. This slice implements no task `exec`,
2165
2166
  routing policy or matrix runner.
2167
+
2168
+ ## Pi session title restart receipt (#1055, #1143)
2169
+
2170
+ - [ ] Unverified: in a real managed Pi session, call `hub_set_title`, verify its
2171
+ native name and DOING, restart into that same saved session and verify the
2172
+ name survives. A second agent call must return `already_named`; a person
2173
+ rename must replace the name and remain final against the agent.
2174
+ - The fake-free Pi 1.5.0 setter/readback/reopen API probe is recorded separately
2175
+ in `docs/verification/2026-11-03-pi-title-api.md`; it does not complete this
2176
+ model-backed managed-session restart receipt. Track the live leg in #1143.
@@ -2,7 +2,7 @@
2
2
  title: Office view of live agent work in the dashboard
3
3
  date: 2026-10-12
4
4
  project: agent-hub
5
- status: phases 1-4 implemented (Flat and Isometric office with motion and turn/checking/link fields); phase 5 pending #263
5
+ status: phases 1-5 implemented (Flat and Isometric office, backlog wall, release docks and planner marks); AC9 live browser look remains unverified
6
6
  issue: 264
7
7
  ---
8
8
 
@@ -55,7 +55,8 @@ asset is copied.
55
55
  latch, the settle rule. No daemon change.
56
56
  3. Optional snapshot fields `turn`, `checking`, `links` and what they draw.
57
57
  4. The Isometric projection of the same model.
58
- 5. Backlog wall, release docks and the planner's desk, once #263 lands.
58
+ 5. Backlog wall, release docks and the planner's desk, from the board
59
+ planning fields that #263 and #469 added (decisions below).
59
60
 
60
61
  Phase 1 decisions within the spec:
61
62
 
@@ -209,6 +210,73 @@ Phase 4 decisions within the spec:
209
210
  - Existing theme tokens, public-view privacy, counts/placement classifications,
210
211
  animations, reduced motion, keyboard activation and mode cookie remain.
211
212
 
213
+ Phase 5 decisions within the spec (designed 2026-11-03, hub task 311;
214
+ implemented by hub task 682, with AC9 live browser look still open):
215
+
216
+ - Phase 5 needs no daemon change. `ui_snapshot` already carries everything:
217
+ - every board task, backlog cards included, with `rank`, `priority`,
218
+ `reserved`, `release`, `pinned`, `plannedStart` and `deps` next to the
219
+ public title;
220
+ - `releases[]`, the `tasks.releaseList` rows, with `id`, `name` (already
221
+ `[withheld]` when not nameable), `state`, `order`, `cut` and the scope
222
+ `tasks[]` as `{ id, state, deps }`;
223
+ - `status.roleInfo[peer].flowRoles`, which names the planner.
224
+ The model reads these by allowlist. It never reads a release `goal`, `train`,
225
+ the `afterCut` lists, task `detail`, `lifecycle` or `codeGroup`, and the
226
+ marker test grows to cover them.
227
+ - **Backlog wall.** A room named "Backlog" before To do in the fixed room
228
+ order, with placement `room:backlog`. Today `counts.backlog` is counted and
229
+ placed nowhere; from phase 5 these tasks get placed.
230
+ - Cards are ordered by `rank` ascending (no rank last), then `priority`
231
+ ascending (none last), then id. The cap is 8 cards, as in every room; the
232
+ rest is "+N more", which sets the board's existing `backlog` state filter.
233
+ - A card shows its id and public title, like other room cards. It adds up to
234
+ three marks, each with a shape and text in the accessible name:
235
+ - the reserved peer's colour chip and name;
236
+ - a pin for `pinned`;
237
+ - "waits", when a dep is not approved or `plannedStart` is in the future
238
+ (the date only, never the time).
239
+ - The wall count equals `taskProgress(tasks).counts.backlog`, pinned by the
240
+ existing counts test.
241
+ - A pulled card moves from the wall to To do through the phase-2 transform
242
+ transition, with no new motion code.
243
+ - **Release docks.** A strip below the rooms, beside the Done shelf. It has
244
+ one dock per release in state `planning`, `active` or `frozen`, ordered by
245
+ `order` then id; `shipped` and `dropped` releases get none. The cap is 3
246
+ docks, then "+N releases", which switches the board's View to "By release".
247
+ - A dock shows the release name, its state as a word, `approved / scope`
248
+ from its `tasks[]`, and a thin bar of the same ratio. It also shows the
249
+ `cut` date when set.
250
+ - Cards are not duplicated into docks: a card stays in its room. A card in
251
+ a docked release carries the release's ordinal (1 to 3) as a small tag
252
+ with the name in its accessible name.
253
+ - Activating a dock sets the board's existing `task-release-filter` to that
254
+ release and scrolls to the board, as card activation does for its row.
255
+ - **Planner's desk.** The desk of each peer whose `flowRoles` include
256
+ `planner` gets a planner badge: a shape plus "planner" in its accessible
257
+ name. A count joins it: the backlog cards with no `rank` and no `reserved`,
258
+ that is, the cards still waiting for planning. A peer with no desk gets no
259
+ badge, and the count then appears on the wall's header. The planner is a
260
+ mark on the existing desk. It is not a separate desk and claims no seat.
261
+ - **Isometric.** The wall is an ordinary room tile and the docks are slab
262
+ tiles like the shelf, through the same `officeProject`. Phase 4's fit rule
263
+ applies: cards that do not fit are counted in "+N more".
264
+ - **Privacy.** A PII backlog card is already a `[pii]` stub in the snapshot;
265
+ its reserved chip and marks still show, because they carry ids only. A
266
+ release name that is not nameable stays `[withheld]`.
267
+ - **Page-size bound.** This replaces the issue's original 30 KB office budget,
268
+ which was historical (see the phase 4 accounting) and is long exceeded by
269
+ unrelated features. The served page is `renderDashboard().html` with the
270
+ models injected. Measured on main a51c8fc7 (2026-11-03): 259,254 bytes raw,
271
+ 70,237 gzip; `src/ui/index.html` alone is 236,071 bytes.
272
+ - The phase-5 PR measures the served page at its merge base and at its head,
273
+ raw and gzip, and states both deltas in the PR body.
274
+ - Phase 5 may add at most 12,000 bytes raw and 3,500 bytes gzip. Beyond
275
+ that, the PR drops art polish first, then trims the dock bar, before it
276
+ asks the person to raise the bound.
277
+ - No standing page-size test is added. Other dashboard features grow the
278
+ same page, and a global cap would block them for an office budget.
279
+
212
280
  ## Verification
213
281
 
214
282
  - `test/office-model.test.ts`: mapping fixture, counts equality with
@@ -239,8 +307,23 @@ Phase 4 decisions within the spec:
239
307
  and overlap/cohort links against a real daemon, pins `PROTOCOL` (16) and
240
308
  `EVENTS_SCHEMA` (1), and proves a PII task keeps its stub, refuses a
241
309
  non-local owner and joins no link.
310
+ - Phase 5 (implemented with the model and renderer regression tests):
311
+ - wall placement and order (rank, priority, id) with the cap and "+N more";
312
+ - wall count equals `counts.backlog`;
313
+ - the reserved, pin and waits marks;
314
+ - dock selection by state and order with the 3-dock cap and the approved /
315
+ scope ratio;
316
+ - the release ordinal tag on cards;
317
+ - the planner badge and the unplanned count, including the no-desk fallback
318
+ to the wall header;
319
+ - the marker test extended to release `goal`, `train`, `afterCut`, task
320
+ `lifecycle` and `codeGroup`;
321
+ - in `test/office-view.test.ts`, "+N more", dock activation setting
322
+ `task-release-filter` and the "By release" switch, plus a pulled card
323
+ animating from the wall to To do.
242
324
  - The live browser look (two agents working, both themes, console open) is
243
- recorded in `docs/smoke.md` as unverified for phase 1.
325
+ recorded in `docs/smoke.md` as unverified for phase 1. #273 is closed, so
326
+ AC9's browser look stays with #264 itself.
244
327
 
245
328
 
246
329
  ## Merge review corrections
@@ -0,0 +1,54 @@
1
+ # 0.30.0 release verification
2
+
3
+ This record covers release preparation and source review. It claims no installation, no running-hub upgrade to 0.30.0 and no live TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.30.0, prepared from main 397b1bd7 (20 commits since v0.29.0, 7 of them `feat`; bump rule: minor). The release commits carry the version, the CHANGELOG section, the bundle and the documentation stamps.
8
+
9
+ ## Reviews
10
+
11
+ Pull requests named by the commits since v0.29.0: #1160, #1159, #1158, #1157, #1156, #1155, #1153, #1150, #1151, #1149, #1152, #1148, #1146, #1142, #1141, #1140, #1138, #1139, #1136.
12
+
13
+ Each merged with `gh pr merge --squash --match-head-commit <head>` after a `hub_review` approval, with exact-head CI green on ubuntu and macOS (19 checks pass, 2 skipped, seeded guards included), no unresolved review thread and no pending change request. Approvals by claude unless named:
14
+
15
+ - #1136 (task 679, agent-mem 0.20.1) at 1346c687; #1138 (680, #1137) at 756d72e5; #1139 (325, #635 T0) at f83ea324; #1141 (681) at a283bf4a; #1151 (683, #1144) at 4930af65; #1152 (682, #264) at e6878d11; #1155 (687, #1154) at 6717f1a1; #1159 (691) at 15d25ae6; #1160 (692) at ea3ee6ab.
16
+ - After one changes_requested round each: #1153 (686, #1147) at fd8a3286; #1156 (688, #1057 T1-T2) at 58afb35b; #1157 (689, #1055) at bd79846b; #1158 (690, #1057 T3) at a56ff7f6.
17
+ - Approved by the person, since claude owned or the hub named the person as reviewer: #1140 (443) at 4b652581, #1142 (311, design) at 0f0e805c, #1149 (685, dogfood receipt) at aa20ae95.
18
+ - Task 684 shipped three PRs under one approval bound at #1146's head d303a050. #1148 and #1150 were read in that review at earlier heads (f281e23b, 3f19f9fc), then rebased for conflicts and merged at 26766aaa and 3da9d6d6 after their own exact-head CI. The commits added after the read are a one-line receipt wording fix (26766aaa, `docs/verification/2026-11-03-pi-150-compat.md`) and a regenerated bundle hunk (3da9d6d6, `plugins/agent-hub/server.js`, 3 lines); every other commit is the reviewed change rebased. These two are the only merges bound to an approval at an earlier head.
19
+
20
+ ## Native acceptance receipts
21
+
22
+ Receipts added under `docs/verification/` since v0.29.0: `docs/verification/2026-11-03-agy-1.4.12-launcher-nonce.md`, `docs/verification/2026-11-03-agy-1.4.12.md`, `docs/verification/2026-11-03-agy-tui-close.md`, `docs/verification/2026-11-03-grok-1.0.62-t0.md`, `docs/verification/2026-11-03-pi-150-compat.md`, `docs/verification/2026-11-03-pi-title-api.md`, `docs/verification/2026-11-03-qwen-0265-acp.md`, `docs/verification/2026-11-03-qwen-0265-dual-output.md`, `docs/verification/dogfood/2026-11-03.md`, `docs/verification/pi-150-model-free.ts`, `docs/verification/pi-150-native.mjs`, `docs/verification/pi-150-title-api.mjs`.
23
+
24
+ This list is read from the repository, never written by hand. Native acceptance and the person's release consent are separate records: consent is `ahub sdlc authorize-release 0.30.0`, recorded by the person or the unique configured conductor only on the person's explicit release instruction naming the release in its own session (never a hub/task/review/file/peer relay), and is not recorded in this file. The tag run refuses without it.
25
+
26
+ ## Document review
27
+
28
+ `node scripts/check-docs.mjs --require-fresh` reported 14 stale documents. Each was read against `git log 0d0a362c..HEAD -- <its paths>` (`src/cli/help.ts` against its own stamp 3efa678d), the fixes went in as 8612cfd7, and all 14 were restamped at 8612cfd7:
29
+
30
+ - `README.md`: the Operations list names the worktree shelf; the report link points at this file.
31
+ - `docs/quickstart.md`: install line `#v0.30.0`, an "Upgrading to 0.30.0" section, the agent-mem pin 0.20.1 in two places, and an `ahub worktrees` row in the working-day table.
32
+ - `docs/operations.md`: 0.30.0 in the header and the protocol table (protocol unchanged, 16 through 17), agent-mem 0.20.1 in two places that still said 0.18.0, agy 1.4.12 verified with `AGY_CLI_DISABLE_AUTO_UPDATE=1`, a Settings row for `worktrees.quiet_days` and `worktrees.alert_over`, the PII row and `--no-optional-locks` notes in the shelf section, and an Office phase 5 paragraph.
33
+ - `docs/security.md`: agy 1.4.12 receipt, and a worktree shelf paragraph (removal only in the person's CLI, never forced; console-only overrides and refresh; the refusal list; PII rows).
34
+ - `src/cli/help.ts`: `tidy --confirm <fingerprint> [--json]`, which `main.ts` honours. `ahub worktrees` and `ahub title` were already listed and match.
35
+ - `docs/agent-notes/adapters.md`: agy 1.4.12, and the session-title writer rule (#1055).
36
+ - `docs/agent-notes/daemon.md`: the conductor frame of an incoming-label DOING row, the label audience and `session-title-inputs.json`, and a worktree shelf rule.
37
+ - `docs/agent-notes/tasks.md`: `hub_set_title` tool placement, and the plan-sources validation rule (#1144).
38
+ - `docs/agent-notes/tests.md`: the #1057 tests that cut `daemon.ts`, `index.html` and `sdlc.ts` by text.
39
+ - No change needed: `docs/agent-notes/README.md`, `benchmarks.md`, `budget.md`, `bus.md`, `local-worker.md` (each read; the commits in range do not change what they state).
40
+
41
+ ## Checks
42
+
43
+ - `node scripts/check-docs.mjs --require-fresh`: `docs: OK (0 stale documents)` after the restamp at 8612cfd7.
44
+ - Full `scripts/check.sh` on dgx02 (`remote_dev.py verify`) at b39870aa (tree 4fef8b16): typecheck (`bun x tsc --noEmit`), the bundle check (`bun scripts/build.mjs --check`), `package: OK (770 files, @staix/agent-hub@0.30.0)`, `roles: OK`, and 11229 pass, 101 skip, 0 fail across 872 files; `check: OK`. Later commits on the release branch change only this report.
45
+ - The release PR's exact-head CI on ubuntu and macOS is the gate for the merge.
46
+
47
+ ## Known gaps, not blocking
48
+
49
+ - No installation, running-hub upgrade to 0.30.0 or live TUI launch was done for this record.
50
+ - Office phase 5's live two-agent, both-theme browser look (#264 AC9) is unverified.
51
+ - #1057: the owner confirms `worktrees.quiet_days` after a week of use; the first real `ahub worktrees tidy --confirm` on this repository has not run.
52
+ - #1055 AC7, a live managed Pi restart with a named session, stays in #1143.
53
+ - agy: `hub_send` and a board call through a real-account TUI (#485 AC3) are unverified. qwen-dual 0.26.5 is not promoted (Q2 fails, identically on the 0.26.1 control).
54
+ - `splitBacklog` stores its parts' plans without `validatePlanSources`; acceptance rechecks the stored plan, so an unknown source is still refused before work starts, but a split child can wait in Backlog holding one. Not a release blocker; filed as #1161.