@staix/agent-hub 0.12.28 → 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 (68) hide show
  1. package/CHANGELOG.md +24 -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 +13 -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 +237 -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 +39 -2
  18. package/docs/smoke.md +65 -0
  19. package/docs/specs/2026-09-19-agent-hub-design.md +113 -5
  20. package/docs/verification/2026-10-16-0.12.29.md +25 -0
  21. package/docs/verification/2026-10-16-273-live-verification.md +150 -0
  22. package/docs/verification/2026-10-17-0.12.30.md +70 -0
  23. package/docs/verified.json +13 -13
  24. package/package.json +5 -2
  25. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  26. package/plugins/agent-hub/server.js +64 -44
  27. package/src/cli/console-state.ts +92 -20
  28. package/src/cli/console.ts +25 -6
  29. package/src/cli/help.ts +4 -2
  30. package/src/cli/identity-audit.ts +2 -2
  31. package/src/cli/identity.ts +15 -1
  32. package/src/cli/main.js +2 -2
  33. package/src/cli/main.ts +78 -39
  34. package/src/cli/memory.ts +87 -187
  35. package/src/cli/output.ts +6 -0
  36. package/src/cli/recovery-package.ts +59 -10
  37. package/src/cli/upgrade-runtime.ts +60 -18
  38. package/src/cli/upgrade.ts +8 -0
  39. package/src/hub/board.ts +2 -1
  40. package/src/hub/bus.ts +33 -12
  41. package/src/hub/daemon.ts +141 -31
  42. package/src/hub/envelope.ts +30 -0
  43. package/src/hub/events.ts +1 -1
  44. package/src/hub/git.ts +2 -38
  45. package/src/hub/hub-tools.ts +5 -1
  46. package/src/hub/lifecycle.ts +2 -2
  47. package/src/hub/manager.ts +15 -11
  48. package/src/hub/memory-candidates.ts +103 -0
  49. package/src/hub/memory-fail.ts +25 -0
  50. package/src/hub/memory-observations.ts +47 -0
  51. package/src/hub/memory-plan-guard.ts +33 -0
  52. package/src/hub/memory-policy.ts +42 -0
  53. package/src/hub/memory-profile-check.ts +170 -0
  54. package/src/hub/memory-profile.ts +192 -0
  55. package/src/hub/memory-staleness.ts +55 -0
  56. package/src/hub/path-policy.ts +8 -0
  57. package/src/hub/project.ts +4 -37
  58. package/src/hub/read-tools.ts +127 -0
  59. package/src/hub/tasks.ts +201 -41
  60. package/src/hub/ui.ts +10 -7
  61. package/src/local/deny.ts +4 -17
  62. package/src/memory/brief.ts +8 -5
  63. package/src/memory/capture.ts +2 -1
  64. package/src/memory/recall.ts +50 -110
  65. package/src/pi/process-signature.ts +2 -42
  66. package/src/memory/import.ts +0 -90
  67. package/src/memory/store.ts +0 -805
  68. 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.