@staix/agent-hub 0.12.29 → 0.12.30

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 (62) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +79 -219
  3. package/docs/agent-notes/README.md +12 -0
  4. package/docs/agent-notes/adapters.md +1 -1
  5. package/docs/agent-notes/bus.md +3 -1
  6. package/docs/agent-notes/daemon.md +10 -0
  7. package/docs/agent-notes/local-worker.md +2 -2
  8. package/docs/agent-notes/tasks.md +9 -0
  9. package/docs/agent-notes/tests.md +1 -1
  10. package/docs/memory.md +70 -152
  11. package/docs/operations.md +199 -14
  12. package/docs/plans/2026-10-16-amem-failure-registry.md +101 -0
  13. package/docs/plans/2026-10-16-amem-p0-baseline.md +110 -0
  14. package/docs/plans/2026-10-16-amem-p2-read-tools.md +28 -0
  15. package/docs/plans/amem-cases.json +482 -0
  16. package/docs/quickstart.md +9 -2
  17. package/docs/security.md +13 -1
  18. package/docs/smoke.md +11 -0
  19. package/docs/specs/2026-09-19-agent-hub-design.md +105 -4
  20. package/docs/verification/2026-10-17-0.12.30.md +70 -0
  21. package/docs/verified.json +13 -13
  22. package/package.json +5 -2
  23. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  24. package/plugins/agent-hub/server.js +64 -44
  25. package/src/cli/console-state.ts +92 -20
  26. package/src/cli/console.ts +22 -5
  27. package/src/cli/help.ts +3 -2
  28. package/src/cli/identity.ts +1 -1
  29. package/src/cli/main.js +2 -2
  30. package/src/cli/main.ts +36 -14
  31. package/src/cli/memory.ts +87 -187
  32. package/src/cli/output.ts +6 -0
  33. package/src/cli/recovery-package.ts +59 -10
  34. package/src/cli/upgrade-runtime.ts +54 -17
  35. package/src/cli/upgrade.ts +8 -0
  36. package/src/hub/board.ts +2 -1
  37. package/src/hub/bus.ts +33 -12
  38. package/src/hub/daemon.ts +134 -27
  39. package/src/hub/envelope.ts +30 -0
  40. package/src/hub/git.ts +2 -38
  41. package/src/hub/hub-tools.ts +5 -1
  42. package/src/hub/manager.ts +1 -1
  43. package/src/hub/memory-candidates.ts +103 -0
  44. package/src/hub/memory-fail.ts +25 -0
  45. package/src/hub/memory-observations.ts +47 -0
  46. package/src/hub/memory-plan-guard.ts +33 -0
  47. package/src/hub/memory-policy.ts +42 -0
  48. package/src/hub/memory-profile-check.ts +170 -0
  49. package/src/hub/memory-profile.ts +192 -0
  50. package/src/hub/memory-staleness.ts +55 -0
  51. package/src/hub/path-policy.ts +8 -0
  52. package/src/hub/project.ts +4 -37
  53. package/src/hub/read-tools.ts +127 -0
  54. package/src/hub/tasks.ts +201 -41
  55. package/src/local/deny.ts +4 -17
  56. package/src/memory/brief.ts +8 -5
  57. package/src/memory/capture.ts +2 -1
  58. package/src/memory/recall.ts +50 -110
  59. package/src/pi/process-signature.ts +2 -42
  60. package/src/memory/import.ts +0 -90
  61. package/src/memory/store.ts +0 -805
  62. package/src/memory/sync.ts +0 -145
package/docs/memory.md CHANGED
@@ -1,8 +1,28 @@
1
- # Hub memory
1
+ # Hub memory integration
2
2
 
3
- The hub's own memory (#258): notes the agents and the hub save while they work, kept in two tiers and linked into a
4
- graph by what they are about. It sits beside claude-mem, which stays the capture and session-recall source where it is
5
- installed. Every machine keeps its own copy in `~/.agenthub/memory/` (directory 0700, files 0600).
3
+ Agent-hub consumes the exact-pinned first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library (current pin `0.3.0`, initial pin `0.1.1`). The package owns portable storage, tiers, graph, goals, sync, import and raw commands. Agent-hub owns caller authority, current project privacy policy, task lifecycle integration and presentation. Optional claude-mem remains a separate capture and session-recall source.
4
+
5
+ ## Portable core and data directory
6
+
7
+ The [agent-mem repository](https://github.com/STAIxBWLB/agent-mem) is the reference for the data model, immutable entry IDs, append-only segments, derived SQLite index and rebuild equality, home selection, import cursors, dedupe and machine sync. Shared v1 remains the default and needs no migration: schema `agent-hub.memory/v1`, canonical hashes and log/machine/lock paths remain compatible. `memoryHome()` comes from the package and selects `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`; an explicit `StoreOptions.home` keeps its parent-directory meaning. The test preload (`test/hub-home.ts`) clears inherited `AMEM_HOME` and supplies an isolated `AGENTHUB_HOME` when none is provided.
8
+
9
+ `src/memory/` contains hub integration only: briefs, recall, the claude-mem worker client and capture. `src/hub/memory-policy.ts` supplies `withoutPii`, `promotionScreen` and `mentionsPrivatePath`; `src/hub/read-tools.ts` supplies screened peer reads. Deny, hardened git, realPath and process identity come from the package's Node-safe `@staix/agent-mem/platform` entry and are re-exported through the existing local/hub/Pi modules. No core implementation is vendored in agent-hub. The `0.1.1` platform subpath is required because `0.1.0` exposes only a root entry that loads `bun:sqlite`, which Node-hosted Pi extensions cannot import. This is a packaging compatibility export; the platform algorithms are unchanged.
10
+
11
+ ## Selected memory profile
12
+
13
+ The memory root `M` is independent of the project state directory. The package's `M/profile.json` selects shared v1 or an explicit generation `G`; an absent selector means shared v1. The hub opens the selected profile with its current privacy screen and configured TTL. A generation uses `M/generations/G`, reads legacy prefixes through the package's readonly bridge, and writes its own rows and sync history there. The bridge does not migrate legacy bytes or use the legacy index. Exact-entry prune remains the package's person-only operation; it adds no shared-v1 operation and gives hub tools no prune authority.
14
+
15
+ All hub consumers share that binding: Tasks notes and lifecycle operations, leases, suggestions, briefs, recall, peer read tools, dashboard, ask and automatic sync. Standalone `amem` and `ahub memory`/`ahub goal` must resolve the same root and profile to see one another's writes. An invalid selector or missing/corrupt generation disables native memory without taking down the bus or task board; it never silently opens shared v1. The daemon reports its package/profile binding and availability separately from ordinary hub health.
16
+
17
+ Person-facing status and memory output show the profile and nonzero malformed-row count. Allowed agent-shell reads retain current row screening and receive only a short profile label; full generation IDs, the memory home and home-wide skipped totals are available through authenticated person status and person CLI receipts, never the shell-readable status.json file or agent CLI output. Domain JSON shapes are unchanged; the additive `memoryProfile` receipt belongs to status, profile-check and the person-only decay object. Profile-check reports the registry scope it inspected; inventory separate custom registries too before treating zero live claims as quiescence.
18
+
19
+ A running daemon keeps its original binding. Explicit checks at hub operation entry and before sidecar writes fence later operations when the selector changes or becomes unavailable; they do not switch stores in place. These checks are not atomic with an in-flight core operation: changing selection during such an operation is outside the supported cutover contract. Before migration or a selector change, quiesce every consumer of this memory home and close its handles; reopen through the supported session-preserving lifecycle afterward. A public core pre-write hook is the upgrade path for atomic in-flight cutover. Current policy screening, PII-turn refusal, capture exclusion and fail-open task delivery remain in force.
20
+
21
+ Candidate/counter data stays at `M/candidates.db`, independently of the selected note profile, using the package's explicit sidecar home convention. It is not copied into `G`, migrated or synced. The hub fences sidecar producers with the note-profile binding. Standalone `amem review` still refuses a selected generation; this integration does not add generation candidate acceptance or dashboard acceptance.
22
+
23
+ Source and synthetic fixture checks do not authorize migration of the person's memory home. Before real migration, every hub sharing the memory home must run 0.12.30. Real rollout still needs separately approved installation, matching live package/root/profile/selector bindings, synthetic cross-write and prune readbacks, and a supported quiesce/close/reopen sequence before separately confirmed real migration. 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. Real account/device sync, native capture and live memory receipts remain distinct acceptance legs. See [agent-mem migration and prune](https://github.com/STAIxBWLB/agent-mem#generation-migration-and-exact-entry-prune) for the person CLI and recovery limits.
24
+
25
+ For a standalone CLI refusal, inspect the selector and generation state and run `ahub memory profile-check`. If deliberately abandoning the selected generation, use the person-only `amem migrate --deactivate` recovery path; a CLI error does not itself require restarting a hub.
6
26
 
7
27
  ## Turning it on and off
8
28
 
@@ -12,28 +32,9 @@ short-term entries live after their task is approved, and `memory.sync_interval_
12
32
  hub syncs once a remote is set. Writing and
13
33
  reading are fail-open: a store that cannot be written costs one notice, and never a tool call or a turn.
14
34
 
15
- ## Tiers
16
-
17
- - **Short-term**: notes saved against a task that is still open, and notes saved with no task. They stay on this
18
- machine (`local/<machine>.jsonl`) and leave the index `short_ttl_days` after their task is approved (a note with no
19
- task, or saved with `horizon: "short"` on purpose: after it was written, and it is never promoted; a demoted entry:
20
- after the demotion; a note on a task that is never approved: 180 days after it was written, the same for every
21
- reader). The deadline is written into the entry (`expires`)
22
- or the op (`settle`, `demote`) when it is set, so every process reading the store prunes the same rows whatever its
23
- own setting. Their log lines stay on disk.
24
- - **Long-term**: decisions, contracts and fails that outlive their task. When a task is approved, its decision, contract
25
- and fail notes are copied to long-term automatically (findings are not), and the short-term originals are hidden.
26
- Each is screened again first under the project's current PII patterns and `pii.paths`; one that fails stays
27
- short-term on this machine and expires. An
28
- agent can save straight to long-term with `hub_remember(..., horizon: "long")`; a note against an already approved
29
- task that is a decision, contract or fail is long-term at once. Long-term entries go to `log/<machine>.jsonl`, the
30
- segment machine sync will carry.
31
- - **Scope**: `project` (default) entries are read only in the project that wrote them; `shared`
32
- (`hub_remember(..., scope: "shared")`) entries are read from every project on the machine.
33
-
34
- A project is named by its `origin` remote, normalized to host and path (`github.com/owner/repo`, lowercased on GitHub, whose
35
- names ignore case; a URL keeps a port that is not its scheme's default), the same on every machine; a repository without one is named by its git chain plus a hash of its directory, so two unrelated
36
- repositories with the same name never share entries (that name is this machine's only).
35
+ ## Task lifecycle and host policy
36
+
37
+ Notes on open tasks stay short-term. Approval settles the task and can promote decision, contract and fail notes; findings stay short-term. The hub injects its current project screen through `StoreOptions.screen`: another project's notes are left to that project's processes, and secret-looking text, current PII patterns and private paths keep a note local rather than promoting it. A task's current paths are screened as well as its stored nodes. Task-specific deadlines retain the existing configured TTL and package lifecycle semantics.
37
38
 
38
39
  ## What gets written
39
40
 
@@ -44,57 +45,15 @@ repositories with the same name never share entries (that name is this machine's
44
45
  same PII and secret rules. Nothing about a PII task is written.
45
46
  - Notes still go to claude-mem when it is enabled, as before; the other peers get the note line on their next delivery.
46
47
 
47
- ## Entries (`agent-hub.memory/v1`)
48
-
49
- One JSON line each. The id is the first 32 hex characters of the SHA-256 of the entry's canonical JSON (sorted keys,
50
- without `id`), so the same content always has the same id and a line whose id does not match is skipped. Nothing is
51
- ever rewritten: a change is a new entry.
52
-
53
- | `op` | Fields |
54
- | --- | --- |
55
- | `note` | `project`, `scope`, `tier`, `kind` (`decision`, `finding`, `contract`, `fail`), `peer`, `title?`, `text` (at most 4000 characters), `task?` (`<project>#<id>@<created ms>`), `about` (nodes), `supersedes?`, `promotedFrom?`, `source` (`hub`, `person`, `claude-mem`), `expires?` (a short-term note with no task, on a task already settled, or saved short-term on purpose: its own deadline, whatever the task does); a promoted copy is dated as its source and keeps its fields, whoever promotes it |
56
- | `forget` | `target`: the entry is gone from the index, and so is its promoted copy or original |
57
- | `supersede` | `target`, `by`: the newer entry; superseded entries are dropped from briefs and recall while the newer one is live (forgetting it shows the old one again) |
58
- | `demote` | `target`, `by`: a long-term entry becomes short-term and leaves the index at `by` |
59
- | `settle` | `target`: a task key, `by`: when its short-term entries leave the index (written when the task is approved; a settlement a crash cut short is finished at the next read, and a held decision, contract or fail whose replacement is forgotten or expires moves to long-term then too) |
60
-
61
- Every entry also has `schema`, `id`, `at` (ISO time) and `machine` (a random id per `~/.agenthub`, in
62
- `memory/machine.json`, with a label a person can edit).
63
-
64
- ## The graph
65
-
66
- Nodes are entries (`mem:<id>`), files (`path:src/a.ts`, relative to the project root), symbols (`symbol:parse@src/a.ts`,
67
- with the path when the task's plan names exactly one), issues (`issue:owner/repo#12`, the repo from the project's
68
- `origin` remote on GitHub, else the project name), board tasks (`task:<project>#3@<proposal ms>` for the task a note was saved against, so a task id reused after
69
- `ahub reset --all` is another node; `task #N` in a text names `task:<project>#N`), peers, machines and projects.
70
- Edges are deterministic and fixed when the entry is written, so a rebuild never depends on the file system:
71
-
72
- - `about`: from the task's refs and plan paths, plan symbols, paths named in the text that exist in the project at
73
- write time (denylisted paths are left out), `#N` and `task #N`, and the task the note was saved against. A `fail`
74
- draws them as `rules_out`.
75
- - `by` (peer, machine) and `in` (project), from the entry's fields; `supersedes` between entries.
76
-
77
- - `part_of` (board task to long-term task) and `depends_on` (between long-term tasks), from the links below.
48
+ ## Graph integration and suggestions
78
49
 
79
- - Model-suggested edges, off by default (`"memory": { "suggest_edges": true }`): after a note is saved, the hub's model
80
- (the same route as digest condensation) names `contradicts` or `same_topic` between it and up to six near entries.
81
- Its answer is read line by line against that closed list and stored as a suggestion by `hub-model` (`suggest`,
82
- `confirm`, `reject` entries). Suggestions are drawn dashed on the dashboard and are never evidence: briefs, recall and
83
- `ahub ask` ignore them until a person confirms one (`ahub memory confirm <id>`); a rejected one disappears.
50
+ The hub supplies task refs, plan paths/symbols and note context when extracting deterministic nodes. Briefs and ask use the package graph; the portable node and edge model is documented in agent-mem.
84
51
 
85
- ## Long-term tasks
52
+ Model-suggested edges are off by default (`"memory": { "suggest_edges": true }`). After a note is saved, the hub's model (the digest-condensation route) may name `contradicts` or `same_topic` between it and up to six near entries. Answers are checked against that closed list and stored as `hub-model` suggestions. They are dashed on the dashboard and excluded from briefs, recall and `ahub ask` until a person confirms one (`ahub memory confirm <id>`); a rejected suggestion disappears.
86
53
 
87
- A long-term task (a goal) spans sessions, board tasks, projects and machines: an epic issue, a migration. It has a
88
- state (`open`, `active`, `blocked`, `done`, `dropped`), milestones, the long-term tasks it depends on, its linked board
89
- tasks (project, task id and proposal time, so a reset's reused ids never join it) and a lease. All of it lives in the
90
- synced log as entries:
54
+ ## Long-term task integration
91
55
 
92
- | `op` | Fields |
93
- | --- | --- |
94
- | `goal` | `project`, `scope`, `title`, `milestones`, `dependsOn`, `actor` |
95
- | `goal_state` | `target`, `by`: the state |
96
- | `goal_link` | `target`, `by`: the board task key |
97
- | `lease`, `lease_force` | `target`, `by`: until when (ISO) |
56
+ Goal storage and leases are supplied by agent-mem. The hub integrates them with board tasks:
98
57
 
99
58
  - **Titles** and milestones are kept to one line (whatever another machine wrote), and recall lines are sanitized, so
100
59
  they cannot carry a forged message header into an agent's context.
@@ -124,39 +83,11 @@ ahub goal state 1a2b3c4d blocked
124
83
  ahub goal done 1a2b3c4d
125
84
  ```
126
85
 
127
- ## Machine sync
86
+ ## Automatic sync integration
128
87
 
129
- The memory directory is a git repository that tracks only `log/*.jsonl` (and its `.gitignore`); the index, the local
130
- short-term segment and `machine.json` never leave the machine. Each machine commits only its own segment and starts
131
- its own history, so merging another machine's history never conflicts.
88
+ A person configures the private remote with `ahub memory sync --remote <private-git-url>` and can sync immediately with `ahub memory sync`. Machine labels use `ahub memory machine --label <name>`.
132
89
 
133
- ```bash
134
- ahub memory sync --remote git@github.com:you/agent-hub-memory.git # once per machine, a person's
135
- ahub memory sync # pull, then push, now
136
- ahub memory machine --label "office mac" # a name shown beside the id
137
- ```
138
-
139
- - A sync commits this machine's segment, fetches, merges the remote's history, pushes (once more after a merge when
140
- another machine pushed in between) and indexes what arrived. One sync runs per machine at a time.
141
- - A running hub syncs 5 seconds after it starts and every `memory.sync_interval_min` minutes (default 10; 0 turns it
142
- off) once a remote is set. A failure is one `hub.log` line and never blocks a turn; a store without a remote does
143
- nothing and creates nothing.
144
- - The remote must be private: long-term entries hold note text. A GitHub remote is checked with `gh repo view` and a
145
- public one is refused; any other host, or a `gh` that cannot answer, is unknown and the sync goes on with a warning.
146
- - Use an SSH remote, or HTTPS with a credential helper in your global git config: the hub keeps your credential helper
147
- for this repository only, and never prompts.
148
- - A forgotten entry stays in the remote's git history; removing it from there is a person's history rewrite.
149
-
150
- ## Index
151
-
152
- `index.db` (SQLite) is derived from the logs: entries, ops and edges are inserted with `INSERT OR IGNORE`, and every
153
- state (superseded, forgotten, promoted, demoted, expired) is computed from those rows, so reading the segments in any
154
- order gives the same index, and the index each hub keeps up to date as it writes equals a rebuild from the logs. Each
155
- reader keeps the byte offset it reached per segment and reads only what was appended since; a line still being written
156
- waits for the next read. A segment that got shorter, disappeared or was rewritten in place (the indexed part no longer
157
- hashes to what was indexed) makes the reader rebuild the index from every segment. Malformed lines and lines whose id does
158
- not match their content are skipped with one log line.
159
- `ahub memory rebuild` drops the rows and reads every segment again; deleting `index.db` does the same on the next start.
90
+ A running hub syncs 5 seconds after start and every `memory.sync_interval_min` minutes (default 10; 0 disables it) once a remote is set. Failure produces one log line and never blocks a turn; without a remote it does nothing and creates nothing. The package retains one sync per machine, public-GitHub-remote refusal and unknown-visibility warnings. Credential-helper use is restricted to the memory repository the hub creates, never a project's git repository, and never prompts. Transport, segment merge, lock and retained-history semantics belong to agent-mem.
160
91
 
161
92
  ## Reading it
162
93
 
@@ -164,12 +95,11 @@ not match their content are skipped with one log line.
164
95
  the task's paths, symbols, issue and task: a linked fail first, then nearer, then newer; superseded entries dropped;
165
96
  other projects' entries only when shared), then claude-mem's, `memory.brief_items` lines in all. A peer is never
166
97
  shown the same entry twice in one hub run.
167
- - **Session-start recall**: this project's long-term decisions, contracts and fails (and shared ones), newest first,
168
- lead the recall block; claude-mem's recent work follows. Each gets half of `memory.inject_tokens` when both answer.
98
+ - **Session-start recall**: this project's long-term decisions, contracts and fails (and shared ones) lead the recall block; display-only filtering omits automatic approval notes, while changes-requested notes show their nonempty reasons. Lines preserve their headers and end at word boundaries with a visible marker when capped; each delivery deduplicates memory ids across prefaces and note lines. Stored notes and brief evidence are unchanged. Claude-mem's recent work follows; each gets half of `memory.inject_tokens` when both answer.
169
99
  - **`ahub ask`**: entries that match the question and the graph neighbourhood of what they are about are evidence, cited
170
100
  as `[mem:<8 hex>]`. A question that carries PII skips them. `ahub ask --remember` still saves only to claude-mem, so a
171
101
  model's answer never becomes hub memory.
172
- - **`ahub memory`** (no hub needed; reading is anyone's):
102
+ - **`ahub memory`** needs no running hub. The hub CLI identity gate allows screened list/search/show reads in agent shells; graph and unscreened views are a person's. Managed peers use the bounded `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools through the daemon, with project scope, `memory_read`, PII-turn refusal and capture exclusion:
173
103
 
174
104
  ```bash
175
105
  ahub memory # this project's entries, newest first, with tier counts
@@ -200,54 +130,15 @@ ahub memory dedupe # inspect legacy claude-mem duplicate gro
200
130
  ahub memory dedupe --apply # append supersede relations, never rewrite log bytes
201
131
  ```
202
132
 
203
- A forgotten long-term entry stays in the log file and in the sync remote's git history; removing it from there is a
204
- person's rewrite of that history. Forgetting a claude-mem import forgets every known non-forgotten member of its tuple group (source, project, source timestamp and text), including hidden duplicates, so they cannot reappear when their replacement is forgotten.
133
+ For storage-level forget, supersede, expiry and dedupe semantics, see [agent-mem](https://github.com/STAIxBWLB/agent-mem). A forgotten shared entry remains in log and remote git history; removing that history is a person's operation. The hub keeps current project privacy checks around every raw command callback. The standalone `amem --policy-free` choice is not available through `ahub memory` and never grants a peer extra authority.
205
134
 
206
135
  ## Importing from claude-mem
207
136
 
208
- `ahub memory import claude-mem [--db <path>] [--limit N] [--restart]` (a person's) copies the hub's own notes from claude-mem
209
- (observations whose metadata names a `peer`: `hub_remember` and the hub's task notes) into long-term hub memory as
210
- `source: claude-mem`, for this project only. The SQL filters by the repository's basename before applying the limit;
211
- a superproject's notes stay out, while two unrelated repositories with one basename still share claude-mem's namespace.
212
- Model answers from `ahub ask --remember`, PII-pattern text, text about a path made private by the project's `pii.paths`
213
- and secret-looking text are skipped. The source database is opened read-only and never changed.
214
-
215
- - Import graph nodes are derived from text only: no checkout existence check and no machine-local `local.deny`.
216
- Built-in denylisted names, relative-path normalization, absolute/parent exclusion, sorting and the 64-node cap still apply.
217
- The source's timestamp and the existing content hash stay unchanged. Matching observations on different checkouts now
218
- produce the same id; old ids, references and content validation remain valid. The local tuple fallback keeps re-import
219
- idempotent even for legacy ids (its known ceiling is that the tuple excludes title).
220
- - A machine-local `memory/import-state.json` records the last fully handled row, including all skips. The cursor key
221
- combines the source DB's real path, claude-mem project name and hub project key. The file uses mode 0600 and atomic
222
- replacement; it never syncs. Unreadable or malformed state replays from zero, write failure logs a warning and preserves
223
- the import result, and a source whose maximum candidate id for this project is below the cursor resets it (unrelated projects cannot mask a recreated source). Completed progress is saved even if a later
224
- row throws. `--restart` starts from zero; use it after changing a PII policy to revisit rows skipped under the old policy.
225
- - The default limit is 10000. JSON reports `read` (project-matched, peer-tagged SQL rows this run), `added`,
226
- `skipped.model`, `skipped.pii`, `skipped.secret`, `cursor` (last handled id), and `more` (`read === limit`). Malformed
227
- metadata, non-string peers and empty text count only in `read`; already imported rows add nothing. `more` asks for
228
- another run, which can return zero rows when the last batch exactly filled the limit. There is no project-skip counter.
229
- - Repository identity still depends on the local real path without an origin remote, and non-GitHub remotes spelled
230
- differently can yield different project keys. These affect all hub memory and remain outside this import fix.
231
-
232
- ### Legacy duplicate imports
233
-
234
- `ahub memory dedupe [--apply] [--json]` is console-only, including the listing, because it shows unscreened note text.
235
- It groups this project's non-forgotten claude-mem entries by `(source, project, at, text)`. Listing reports only actionable
236
- groups, their members, heads and chosen target; `--apply` appends ordinary supersede operations in this machine's log.
237
- Historical log bytes are never rewritten. Forget operates on the whole known tuple group.
238
-
239
- - Heads have no current replacement. Existing replacement relations, including a person's distinct outside notes, stay
240
- intact. If every head can reach a valid synced outside terminal (shared when any head is shared), the newest qualifying
241
- terminal by `(at, id)` replaces all heads, even a single head. Otherwise multiple heads converge onto the smallest id
242
- among the heads; zero heads or one head without a qualifying outside terminal require no writes. A refused supersede
243
- is reported as an error. Re-running after convergence writes nothing.
244
- - When `A -> B` already exists inside a group, dedupe connects another head `C -> B`, without trying the cyclic `B -> A`.
245
- When `B -> X` and `C -> Y` name outside user replacements, `X` and `Y` both remain visible; a remaining head moves to
246
- the newer qualifying terminal. A later outside replacement can make a previously settled group actionable again.
247
- - Old or mixed extractor versions can still introduce duplicate ids. Run dedupe after syncing them. Two machines acting
248
- on divergent prior relations can choose different targets; sync folds relations deterministically using the existing
249
- latest-relation and cycle guards. If several group members remain visible, one more dedupe run converges them. New
250
- same-version imports with stable project keys converge automatically in either sync order.
137
+ `ahub memory import claude-mem [--db <path>] [--limit N] [--restart]` is a person's command. It imports peer-tagged hub observations for the current project through the package's read-only SQLite importer. The hub supplies the current routing PII patterns and `pii.paths` screen; model answers and secret-looking text remain excluded. The source database is never changed.
138
+
139
+ The package owns deterministic import identity, text-only graph extraction, cursor bookkeeping, tuple fallback and result counts. `--restart` revisits rows skipped under an earlier policy. Claude-mem's basename namespace can still collide between unrelated repositories; a superproject's observations stay out. A repository without an origin still uses machine-local project identity. These ceilings are unchanged by the library switch.
140
+
141
+ `ahub memory dedupe [--apply] [--json]` remains console-only, including listing, because it shows unscreened note text. The package's reconciliation appends ordinary supersede operations; historical log bytes are never rewritten.
251
142
 
252
143
  ## Measuring it
253
144
 
@@ -266,3 +157,30 @@ Whether hub memory helps is measured, not assumed (#258 AC11, with #248):
266
157
  - Nothing about a PII task enters the store: the tool refuses it and the hub's own notes skip it. Text that matches a
267
158
  PII pattern, that the PII screen does not clear, or that looks like a secret is refused or skipped with a notice.
268
159
  - Hub log lines about memory carry ids and counts only.
160
+
161
+
162
+ ## Stage A plan guard and fail notes (#412 P7-1)
163
+
164
+ A plan's explicit paths/symbols and refs paths are matched against live, screened fail `about` nodes (the source of v1 `rules_out` graph edges). Matching fails lead the owner's brief and the review delivery's checklist; accepting a plan or claiming work returns the same current advice. Existing related-memory fails-first ordering is retained. Unrelated fails do not gain plan priority. The guard informs, never blocks or changes the task, and PII tasks never invoke it.
165
+
166
+ `hub_remember` accepts optional string fields `approach`, `symptom`, `cause`, `evidence`, and `retry_when` only with `kind: "fail"`. Provided fields appear in that order with labels `Approach`, `Symptom`, `Cause`, `Evidence`, and `Retry when`, after optional plain text. A structured-only fail is valid; plain text remains valid. The completed text is validated before policy, storage or display: at most 4000 UTF-8 bytes, with actual/limit byte counts on refusal and no truncation. Every composed field passes the existing secret/PII/private-path checks.
167
+
168
+ ## Decay review
169
+
170
+ `ahub memory decay [--days N] [--json]` is person-only, including in delegated live-test sessions. Default age is 90 days. It lists this project's long-term, live, non-superseded notes strictly older than the cutoff, excluding links to open/active/blocked goals through the note's task or `about` nodes. Reads use current privacy screening and defer automatic promotions; authoritative JSONL bytes do not change. Index maintenance remains derived-only. A person acts separately through existing `demote` or `forget`. These are review rows, with no candidate-sidecar write.
171
+
172
+ ## Read-time staleness hint
173
+
174
+ Recall lines, native brief items, `hub_memory_show` lines and human `ahub memory show` output mark `(stale?)` when an allowed `path:` or symbol-file path is missing. CLI `--json` keeps the v1 row unchanged. The marker is never stored or used as evidence of correctness.
175
+
176
+ Each rendered batch has a shared cache and a hard bound of 32 metadata probes. On a tree not modified during the call, no hint probe reaches a `pii.paths`/`local.deny` path: declared private paths and the common deny policy are checked lexically first, including folded spellings and globs. Absolute/escaping paths, symlinks observed by a probe, denied paths, unreadable metadata and exhausted budgets are unknown and produce no marker. A concurrent ancestor swap by a process running as you can expose one existence bit; it is outside the [security threat model](security.md), and the deterministic ceiling witness is retained in the tests. Descriptor-anchored traversal is needed before supporting that scenario. Symbol nodes are checked only through their allowed file; this hint does not parse source symbols or read file contents. Current privacy screening still precedes rendering and peer-output byte caps. No new memory operation or candidate writer is introduced.
177
+
178
+ ## Review candidates and local usage counters
179
+
180
+ Agents propose a correction with `hub_memory_correct {id, text, reason}`. The target is an exact id or unique visible prefix, screened under this project's current policy. The tool requires `memory_correct`, refuses private turns, and records a candidate with the full target id and revision. It does not edit a note or notify the other peers with the proposed text. Arguments and results are excluded from capture.
181
+
182
+ A `changes_requested` review with unmet items can create one fail candidate. Tasks screens the public task title, unmet items and review evidence before the sidecar write; private tasks, private turns, withheld text and failed or unknown screens produce no candidate. Cause and retry conditions are explicitly unknown unless a person supplies them when reviewing.
183
+
184
+ Candidates and rendered/delivered/cited counters live only in agent-mem's machine-local `M/candidates.db` (package retention: 500 candidates/project for 30 days, 10,000 counters/project for 90 days). They are never imported or synced and are not evidence for recall, briefs, ask or graph. For shared v1, a person reviews them with `amem review` and accepts or rejects through that CLI's TTY and policy checks; generation review remains unsupported and refused. Dashboard acceptance is a later step. Accepted results are ordinary v1 notes under the package's acceptance contract; the hub tool has no acceptance authority.
185
+
186
+ Rendered observations use the final journal payload, including memory lines in prefaces and briefs, with the real delivery id. Delivered observations require the journal's first positive transport acknowledgement (or the pull path's completed read receipt). Failed or uncertain handoffs do not establish delivery; journal-less transports leave it unknown. Citation observations resolve `mem:<id8+>` in a peer message or task completion summary against screened project-visible entries. Citations use explicit `message:<envelope-id>` or `summary:<task>:<history-index>` observation keys when the source delivery is not proven; their rendered/delivered values remain unknown. They do not establish a per-delivery causal ratio. Duplicate observations are idempotent per entry/context/observation, not cumulative usage claims. Unavailable task, turn or session identity and unobserved metrics remain unknown, never zero. No observation is written for a private envelope, PII task or PII turn. These are delivery and citation proxies, not proof that an agent understood or used a note correctly.
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.12.29 and control protocol 16. Live verification
3
+ This guide describes ahub 0.12.30 and control protocol 16. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Agent-operated live verification
@@ -176,6 +176,48 @@ decisions and a never-ask permission mode require confirmation; delivery resolut
176
176
  status state counts and never fetches the board. Leaving restores
177
177
  the terminal and returning from panels replays the bounded stream buffer.
178
178
 
179
+ Tasks and Queue selections follow the selected id across refreshes. If that row
180
+ disappears, no replacement row inherits the action. Task actions also bind the
181
+ row's `flowRevision` (its history length); queue actions bind the delivery
182
+ revision. The id and revision in a row-started command cannot be edited. Peer,
183
+ reason and the final role/action remain editable, and the confirmation names the
184
+ validated result. An older hub without `flowRevision` refuses revision-bound task
185
+ row actions instead of silently running them unchecked.
186
+
187
+ In Tasks, `a` assigns the owner in ordinary open states and the reviewer in
188
+ `in_review`; a waiting task says when assignment is a reservation. `u` unassigns
189
+ the corresponding role, and `r` reviews only in `in_review`. In Queue, `r`
190
+ prefills `discard` for a queued row and `retry` for a needs_review row, with the
191
+ action still editable before confirmation. `completed` is never the default.
192
+ Every resolution needs a reason. Queue `discard` removes queued originals or
193
+ settles a needs_review receipt; it never cancels a task or native turn.
194
+
195
+ `ahub task unassign <id> [--role owner|reviewer] [--revision N]` is the terminal
196
+ equivalent. Owner removal is allowed in `proposed`, `in_progress` and
197
+ `changes_requested`, returns active work to `proposed`, and clears any owner
198
+ reservation. It is refused in `in_review`, `approved` and `backlog`. Reviewer
199
+ removal retains the state, including `in_review`, and refuses to remove the human
200
+ reviewer of PII work. No replacement is routed and no native process or external
201
+ effect is rolled back. A person-unassigned task stays out of automatic owner
202
+ offers across dependency release, planner-pull processing, release timers and
203
+ restarts until an explicit assignment or reservation. The later unassign also
204
+ fences planner undo, so undo and re-pull cannot restore the removed reservation.
205
+ Former queued assignment or review requests are withdrawn without resolving
206
+ uncertain deliveries, and the former agent loses current authority. A withdrawal
207
+ notice uses delivery evidence or that peer's own accept/self-claim after the
208
+ latest assignment; another owner's in-progress state or an earlier acceptance
209
+ does not count. Withdrawing an unread queue alone sends none. If a later person
210
+ assignment supersedes an escalation or offline relay, the stale success result
211
+ and decision note are suppressed, but the former owner is still told the current
212
+ owner and to stop working.
213
+
214
+ `ahub task assign <id> <peer> [--role owner|reviewer] [--revision N]` uses a
215
+ checked console operation when a revision is present. Without it, existing CLI
216
+ behavior is unchanged. `ahub queue resolve ... [--revision N]` uses the supplied
217
+ revision instead of a fresh show. Revision mismatches and superseded post-await
218
+ publications fail without a board or delivery side effect; a reviewer-only change
219
+ keeps a valid owner assignment but refreshes its reviewer content.
220
+
179
221
  The Tasks panel summarizes the whole public board with an approved/all fraction,
180
222
  a 20-cell ASCII bar and state counts. Proposed tasks with unapproved or missing
181
223
  `after` dependencies count as waiting; PII stubs count normally. Each task has a
@@ -388,9 +430,136 @@ The Stop hook acknowledgement is not a counted completion. The daemon checks the
388
430
  native transcript after the hook can return; missing or changed-session evidence
389
431
  stays unknown.
390
432
 
433
+ ## Pi with DGX and MLX
434
+
435
+ Pi is optional. Install Pi separately and set `pi.enabled` to `true` in
436
+ `.agenthub/config.json`. Set `pi.auto_start` to `true` to start it with the hub.
437
+ Pi starts in its TUI by default; `ahub pi --headless`, or the start mode
438
+ setting (`ahub settings set peers.pi.start_mode headless`), asks for the
439
+ background peer instead. The project uses its existing OmniRoute connection for DGX;
440
+ `pi.dgx_coding` and `pi.dgx_fast` select the gateway's configured model IDs.
441
+
442
+ ```bash
443
+ ahub models setup # prepare bounded Qwen3.5 4B MLX in an existing Ollama service
444
+ ahub models start # verify loopback Ollama/model readiness (no daemon spawn)
445
+ ahub models status
446
+ ahub pi --headless --backend auto
447
+ ahub say @pi "Read the project and summarize the next small implementation task"
448
+ ahub pi # its TUI in this terminal; an idle headless session moves into it
449
+ ```
450
+
451
+ The default routing template assigns implementation, bulk edits and tests to
452
+ DGX, and summaries/triage to MLX. Pi and the existing local worker take priority
453
+ before cloud peers; planning and final review stay with Claude/Codex. Existing
454
+ projects keep their routing file, so add `pi` to the relevant `peers` lists and
455
+ set `pi_backend = "dgx"` or `"mlx"` for each class. PII tasks remain local-worker
456
+ only. `--backend dgx` or `--backend mlx` explicitly pins a Pi session's backend.
457
+
458
+ Pi connects to an authenticated relay with model aliases `dgx/coding`,
459
+ `dgx/fast`, and `mlx/fast`; upstream gateway credentials stay in the hub.
460
+ Its managed tools use the existing path guards, shell sandbox and terminal
461
+ approval flow. Keep `ahub console` open to approve write/edit/shell operations:
462
+ a request nobody answers expires, and two in a row end Pi's turn.
463
+ Tool-call receipts survive daemon restart; an interrupted operation with an
464
+ unknown outcome must be reconciled before repeating it. Cancelling a Pi turn
465
+ does not automatically hand its task to a cloud peer.
466
+
467
+ The `mlx/fast` alias now defaults to an externally managed Ollama MLX backend.
468
+ Install and start Ollama separately, with `OLLAMA_NO_CLOUD=1`,
469
+ `OLLAMA_NUM_PARALLEL=1`, `OLLAMA_MAX_LOADED_MODELS=1`, and a finite
470
+ `OLLAMA_KEEP_ALIVE` (five minutes is the tested setting). `models setup`
471
+ prepares `agenthub-fast-mlx:4b-8k` from `qwen3.5:4b-mlx`, with an 8K total
472
+ context and a 2K output cap. The relay budgets input separately at 6,000
473
+ estimated tokens and serializes local requests across project hubs.
474
+
475
+ `models status` distinguishes model availability from memory residency.
476
+ `models stop` refuses to unload an externally managed Ollama model: other
477
+ clients may be using it. Ollama owns idle eviction; stopping a hub never
478
+ kills Ollama. The OpenAI compatibility endpoint does not enforce a request
479
+ `keep_alive` field, so configure the server and verify its expiry readback.
480
+ The relay can fall back to DGX before streaming starts as before. See the
481
+ [Ollama migration procedure](#ollama-mlx-migration-issue-51)
482
+ for existing Python runtime/configuration cleanup and rollback.
483
+
484
+ A project has one managed Pi session owner. Mode changes require the agent to
485
+ settle and preserve the session file. Before the first generation, a live source
486
+ verified to be empty can instead retain its exact session ID without a file.
487
+ Missing history from a nonempty session blocks handover. Protocol 9 adds Pi
488
+ recovery metadata. Protocol 10, released in 0.7.0, adds durable delivery
489
+ receipts and conservative queue recovery; it is verified live (see the
490
+ [smoke checklist](smoke.md)).
491
+ Older protocol hubs still require their matching CLI and an attended bootstrap.
492
+
493
+ ## Local dashboard
494
+
495
+ With a running daemon, `ahub ui` opens a local dashboard for messages, peer states
496
+ and queues, the task board, budget windows, pending approvals and this machine's
497
+ benchmark runs. Use `ahub ui --no-open` to print a single-use link when the
498
+ browser cannot be opened.
499
+ Open the link within 60 seconds; the browser session lasts one hour.
500
+
501
+ The dashboard can send console messages, propose and assign tasks, pause or resume
502
+ peers, answer approvals, start a peer in its start mode and show the settings.
503
+ An ordinary session changes the safe switches and lowers a mode to `ask`; the
504
+ rest needs `ahub ui --settings` or `ahub settings` in a terminal. See
505
+ [Settings](#settings-from-the-dashboard-and-the-terminal) and
506
+ [Start modes](#start-modes-tui-by-default-headless-as-opt-in).
507
+ Budget pauses still require the terminal override.
508
+ Private envelopes and PII tasks stay redacted. Local-worker and Pi tool details and
509
+ allow decisions stay in `ahub tail` / `ahub permit`; the page can deny them.
510
+ No dashboard port is opened until requested. See [the session security model](security.md#local-dashboard-sessions-issue-6).
511
+
512
+ ## Multiple projects and worktrees
513
+
514
+ Version 0.4.0 adds one independent hub per repository or Git worktree.
515
+ Subdirectory commands resolve to that project; symlink aliases resolve to the same
516
+ hub. Explicit selection works from any directory:
517
+
518
+ ```bash
519
+ ahub --project /path/to/project-a init
520
+ ahub --project /path/to/project-b init
521
+ ahub --project /path/to/project-a up
522
+ ahub --project /path/to/project-b up
523
+ ahub projects
524
+ ahub status --all
525
+ ahub --project /path/to/project-a codex
526
+ ahub ui --all
527
+ ```
528
+
529
+ The unified dashboard lists registered roots and opens their boards, streams,
530
+ messages, approvals and budgets. It can start hubs and stop a selected hub after
531
+ confirmation. Its Start control opens a peer's TUI only through a terminal provider
532
+ (`terminal.open` or Orca); without one, launch Claude/Codex TUIs from a terminal. The manager stays available
533
+ when a project stops; `ahub ui --all --stop` stops only the manager. Existing
534
+ `ahub ui` still opens the current project's dashboard.
535
+
536
+ `ahub projects --json` returns machine-readable live status. Use a listed ID or a
537
+ path with `--project`; the selector must precede the command. `ahub projects remove
538
+ <id>` forgets a stopped registration without deleting project data. A moved root
539
+ gets a new registration. Missing roots remain visible until removed.
540
+
541
+ Runtime state stays in each project's `.agenthub/state/`. The machine registry is
542
+ `~/.agenthub/registry.db`; existing `ports.json` allocations are imported once.
543
+ `AGENTHUB_HOME` selects an isolated machine registry (also used by tests). An
544
+ inherited state directory from a different project cannot redirect a new CLI
545
+ invocation. Explicit custom state requires a matching project marker or the
546
+ initialization pair `AGENTHUB_PROJECT_DIR` / `AGENTHUB_STATE_DIR`.
547
+
548
+ Manager-started hubs use attended mode and project configuration, including
549
+ credential-file references. They do not inherit the launching project's gateway
550
+ URL/key overrides. Native memory remains shared according to claude-mem aliases;
551
+ worktrees recall their parent and composite identity. A shared-alias notice does
552
+ not mean that task boards or agent sessions are shared. Provider quota readings
553
+ may describe the same account and should not be added across projects.
554
+
555
+ Before upgrading an older hub, inspect the plan with the matching CLI.
556
+ Incompatible or unverified processes are shown explicitly and are never killed by
557
+ PID-name matching. See the operations guide and the
558
+ [multi-project specification](specs/2026-09-19-multi-project-design.md).
559
+
391
560
  ## Install and start
392
561
 
393
- Use Bun 1.3 or newer. Install the released package and install its Claude
562
+ Use Bun 1.4.2 or newer. Install the released package and install its Claude
394
563
  channel plugin:
395
564
 
396
565
  ```bash
@@ -472,8 +641,9 @@ does not list), needs its directory in `local.read_allow`, for example
472
641
  hub's tools: list a peer and it keeps only the capabilities named, from
473
642
  `propose` (`hub_task_propose`), `assign` (proposing with another peer as owner),
474
643
  `remember` (the `hub_remember` tool; the notes the hub itself keeps of done
475
- summaries and review verdicts are not gated) and `important` (`[IMPORTANT]`
476
- messages). For example `"capabilities": { "local": ["propose", "remember"] }`.
644
+ summaries and review verdicts are not gated), `memory_correct` (propose a local correction candidate for person review), `memory_read`
645
+ (`hub_memory_search`, `hub_memory_show`, `hub_memory_graph`) and `important`
646
+ (`[IMPORTANT]` messages). For example `"capabilities": { "local": ["propose", "remember"] }`.
477
647
  A peer that is not listed keeps all of them, a listed peer whose value is not a
478
648
  list gets none, and an unknown capability name grants nothing; `hub.log` says
479
649
  so for each, and logs every refusal (`capabilities:`). A refused tool call says
@@ -523,8 +693,7 @@ resolves an uncertain delivery automatically. The same rule holds on every path
523
693
  assigns a task (#297): once the move is saved on the board, a failed delivery prints
524
694
  exactly one console notice ("the move is saved... delivery is not confirmed"), printed
525
695
  where the error is thrown, and the caller (a peer in its turn, or the CLI) still gets
526
- its error. Two exceptions, by construction: a move superseded while the assignment was being prepared, meaning a later history entry that moves or settles the task (another assignment, an unassign, the owner's accept, done, approval, close; the hub's quiet bookkeeping such as a late check result or a reviewer change does not supersede), is
527
- the caller's error as before, with no notice naming either owner; and a claim is its own hand-over, so its
696
+ its error. Two exceptions, by construction: a move superseded while the assignment was being prepared, meaning a later history entry that moves or settles the task (another assignment, an unassign, the owner's accept, done, approval, close; the hub's quiet bookkeeping such as a late check result or a reviewer change does not supersede), returns the current task without a failed tool call and with no notice naming either owner; and a claim is its own hand-over, so its
528
697
  accepted write and line stay outside the delivery rule (its overlap and routing announcements to earlier
529
698
  owners are deliveries and share it). The repair is always
530
699
  to look for a queued or needs_review delivery with
@@ -735,7 +904,7 @@ ahub task label 12 reverted # a later verdict (ok, regressed, reverted, i
735
904
 
736
905
  ## Hub memory
737
906
 
738
- The hub keeps its own memory in `~/.agenthub/memory/` beside claude-mem ([how it works](memory.md)): notes saved
907
+ The hub opens the selected memory profile under `memoryHome()` (default `~/.agenthub/memory/`) beside claude-mem ([how it works](memory.md)): notes saved
739
908
  against an open task are short-term; when the task is approved its decisions, contracts and fails become long-term.
740
909
  Task briefs, session-start recall and `ahub ask` read it first, and the dashboard has a Memory section.
741
910
 
@@ -748,6 +917,20 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
748
917
 
749
918
  `"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
750
919
 
920
+ The exact-pinned `@staix/agent-mem` 0.3.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.
921
+
922
+ 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`; `amem review` on a generation is still refused. 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).
923
+
924
+ Run `ahub memory profile-check [--json]` from the person's terminal for the read-only preflight. It identifies live registry owners by signed process identity and authenticated daemon replies, then reports their loaded memory package and bound home/profile. The receipt names the current `AGENTHUB_HOME` registry scope; zero results establish no live daemon in that inventory, not system-wide process absence. Inventory and check separate custom registries too when they share `AMEM_HOME`. A missing or unreadable runtime receipt never counts as a different home or permission to migrate.
925
+
926
+ Before real migration, first upgrade every hub sharing the home to 0.12.30. Obtain positive ready or unbound/quiesced receipts in every relevant registry; then quiesce consumers and close bound handles before changing the selector. A matching ready handle proves compatibility but is still an open handle. Verify synthetic cross-writes before the separately approved real migration, then reopen through the supported session-preserving lifecycle afterward. Merely installing this release does not authorize migration or provide live capture/device-sync evidence.
927
+
928
+ Managed agents read through `hub_memory_search`, `hub_memory_show` and `hub_memory_graph`, with the server's project identity, the `memory_read` capability and current privacy screening. They cannot supply another project; private turns are refused. The tools bound UTF-8 input/output, screen before counts and truncation, and exclude their arguments/results from hub-managed capture. `hub_memory_correct` has its own `memory_correct` capability and records a screened correction candidate, not a note change. See [the complete memory contract](memory.md) for caps and refusal semantics.
929
+
930
+ Review candidates and rendered/delivered/cited counters stay in machine-local `M/candidates.db`, outside sync, import, recall, briefs, ask and graph evidence. Public changes-requested reviews with unmet items may propose a fail candidate; missing cause/retry details stay unknown. Shared-v1 acceptance is a person's `amem review` action with its TTY/policy checks; selected generations still refuse that command. Read-time `(stale?)` hints and plan-fail advice never change stored rows or task authority. Structured fail notes are validated against 4000 UTF-8 bytes before screening or writes, with no silent truncation.
931
+
932
+ Recall presentation omits generated task-approval outcome notes and shows only nonempty reasons from generated changes-requested notes; an ordinary decision that merely resembles a header stays visible. Contracts and fails remain eligible. Capped recall lines preserve memory headers and end at word boundaries, and one delivery deduplicates ids across restored/current prefaces and shared note lines. Stored notes and brief evidence are unchanged; platform recall retains its budget split.
933
+
751
934
  Between your machines, through a private git remote, with long-term tasks that span them:
752
935
 
753
936
  ```bash
@@ -1659,21 +1842,21 @@ section is what they run.
1659
1842
 
1660
1843
  Upgrade running projects with the target release's own coordinator. It accepts
1661
1844
  a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
1662
- 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.29), and only
1845
+ 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.12.30), and only
1663
1846
  a target on its own protocol, so the target's coordinator fits every supported
1664
1847
  source and carries every recovery fix released up to it. Protocol 8 and older
1665
1848
  (0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
1666
1849
  project directory, without replacing the global CLI first:
1667
1850
 
1668
1851
  ```bash
1669
- bunx --package @staix/agent-hub@0.12.29 ahub upgrade --to 0.12.29 --dry-run
1670
- bunx --package @staix/agent-hub@0.12.29 ahub upgrade --to 0.12.29 --yes
1852
+ bunx --package @staix/agent-hub@0.12.30 ahub upgrade --to 0.12.30 --dry-run
1853
+ bunx --package @staix/agent-hub@0.12.30 ahub upgrade --to 0.12.30 --yes
1671
1854
  ```
1672
1855
 
1673
1856
  | Running now | Coordinator to use |
1674
1857
  | --- | --- |
1675
1858
  | 0.6.x (protocol 9) | the target's, through `bunx` as above |
1676
- | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.29 (protocol 16) | the target's, through `bunx` as above |
1859
+ | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.12.30 (protocol 16) | the target's, through `bunx` as above |
1677
1860
  | any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
1678
1861
  | 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
1679
1862
 
@@ -1705,14 +1888,14 @@ projects first:
1705
1888
 
1706
1889
  ```bash
1707
1890
  ahub restart --dry-run
1708
- ahub upgrade --to 0.12.29 --dry-run
1891
+ ahub upgrade --to 0.12.30 --dry-run
1709
1892
  ```
1710
1893
 
1711
1894
  Apply only after reviewing the plan:
1712
1895
 
1713
1896
  ```bash
1714
1897
  ahub restart --yes
1715
- ahub upgrade --to 0.12.29 --yes
1898
+ ahub upgrade --to 0.12.30 --yes
1716
1899
  ahub recovery status [<operation-id>]
1717
1900
  ahub recovery resume [<operation-id>]
1718
1901
  ahub recovery abort [<operation-id>]
@@ -1777,7 +1960,9 @@ release. The global CLI is promoted last.
1777
1960
 
1778
1961
  A lost commit or start reply is reconciled against the actual daemon identity,
1779
1962
  package digest, terminal binding, and phase receipt. An uncertain terminal
1780
- creation is never repeated blindly. If Claude shows its development-channel confirmation screen, confirm it in
1963
+ creation is never repeated blindly. Recovery retains the exact-pinned first-party memory package with the preserved runtime, includes its identity and bytes in the digest, and refuses missing, symlinked or changed retained dependencies instead of resolving a mutable ancestor/global installation. It does not copy the full dependency tree.
1964
+
1965
+ After creating a managed terminal, the coordinator saves its binding before waiting up to 90 seconds for authenticated attachment to the expected target hub and conversation. An idle terminal alone is not attachment proof. Timeout retains the terminal and its diagnostic receipt; resume waits on that same binding rather than opening a duplicate. The zero-turn Claude exception still applies only when no original transcript exists. If Claude shows its development-channel confirmation screen, confirm it in
1781
1966
  the captured terminal, then run `ahub recovery resume <operation-id>`; resume must not open a duplicate terminal.
1782
1967
  Kimi and local sessions may start fresh with preserved routing and task context.
1783
1968
  A Claude session that never persisted a transcript (zero turns) also starts fresh: