@staix/agent-hub 0.12.29 → 0.12.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +92 -218
- package/docs/agent-notes/README.md +12 -0
- package/docs/agent-notes/adapters.md +3 -2
- package/docs/agent-notes/budget.md +1 -1
- package/docs/agent-notes/bus.md +7 -1
- package/docs/agent-notes/daemon.md +14 -1
- package/docs/agent-notes/local-worker.md +4 -2
- package/docs/agent-notes/tasks.md +14 -0
- package/docs/agent-notes/tests.md +6 -3
- package/docs/memory.md +70 -152
- package/docs/operations.md +317 -25
- package/docs/plans/2026-10-16-amem-failure-registry.md +101 -0
- package/docs/plans/2026-10-16-amem-p0-baseline.md +110 -0
- package/docs/plans/2026-10-16-amem-p2-read-tools.md +28 -0
- package/docs/plans/amem-cases.json +482 -0
- package/docs/quickstart.md +24 -2
- package/docs/security.md +42 -2
- package/docs/smoke.md +11 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +142 -7
- package/docs/specs/2026-09-20-upgrade-recovery-design.md +84 -10
- package/docs/verification/2026-10-17-0.12.30.md +70 -0
- package/docs/verification/2026-10-17-0.12.31.md +71 -0
- package/docs/verification/2026-10-17-418-probes.md +21 -0
- package/docs/verified.json +13 -13
- package/package.json +5 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +129 -62
- package/src/adapters/claude-channel.ts +29 -11
- package/src/adapters/local-worker.ts +2 -2
- package/src/adapters/pi.ts +5 -0
- package/src/cli/attention.ts +5 -1
- package/src/cli/console-state.ts +145 -29
- package/src/cli/console.ts +40 -10
- package/src/cli/help.ts +3 -2
- package/src/cli/identity.ts +4 -3
- package/src/cli/jcode-mcp.ts +1 -1
- package/src/cli/main.js +2 -2
- package/src/cli/main.ts +43 -14
- package/src/cli/memory.ts +87 -187
- package/src/cli/output.ts +19 -2
- package/src/cli/recovery-package.ts +59 -10
- package/src/cli/upgrade-interactive.ts +2 -1
- package/src/cli/upgrade-restore-state.ts +133 -0
- package/src/cli/upgrade-runtime.ts +204 -63
- package/src/cli/upgrade.ts +57 -24
- package/src/hub/board.ts +83 -24
- package/src/hub/bus.ts +108 -38
- package/src/hub/check-runner.ts +514 -0
- package/src/hub/child-process.ts +1 -0
- package/src/hub/control-client.ts +5 -1
- package/src/hub/daemon.ts +529 -89
- package/src/hub/delivery-hints.ts +30 -0
- package/src/hub/delivery-journal.ts +185 -12
- package/src/hub/delivery-superseded.ts +52 -0
- package/src/hub/envelope.ts +31 -1
- package/src/hub/git.ts +2 -38
- package/src/hub/host-executable.ts +11 -0
- package/src/hub/hub-tools.ts +14 -2
- package/src/hub/manager.ts +1 -1
- package/src/hub/memory-candidates.ts +103 -0
- package/src/hub/memory-fail.ts +25 -0
- package/src/hub/memory-observations.ts +47 -0
- package/src/hub/memory-plan-guard.ts +33 -0
- package/src/hub/memory-policy.ts +42 -0
- package/src/hub/memory-profile-check.ts +170 -0
- package/src/hub/memory-profile.ts +192 -0
- package/src/hub/memory-staleness.ts +55 -0
- package/src/hub/path-policy.ts +8 -0
- package/src/hub/peer-catalog.ts +3 -0
- package/src/hub/project.ts +4 -37
- package/src/hub/read-tools.ts +127 -0
- package/src/hub/routing.ts +35 -16
- package/src/hub/settings.ts +2 -1
- package/src/hub/spec-read.ts +120 -0
- package/src/hub/supervision.ts +21 -6
- package/src/hub/task-sweep.ts +7 -6
- package/src/hub/tasks.ts +370 -71
- package/src/local/deny.ts +4 -17
- package/src/memory/brief.ts +8 -5
- package/src/memory/capture.ts +3 -1
- package/src/memory/recall.ts +50 -110
- package/src/pi/process-signature.ts +2 -42
- package/src/ui/index.html +12 -9
- package/src/ui/needs.ts +7 -5
- package/src/ui/office-model.ts +4 -1
- package/src/ui/task-board.ts +1 -1
- package/src/ui/task-progress.ts +6 -2
- package/templates/AGENTS.block.md +1 -1
- package/templates/profiles/general.json +1 -1
- package/templates/profiles/research.json +1 -1
- package/templates/profiles/writing.json +1 -1
- package/src/memory/import.ts +0 -90
- package/src/memory/store.ts +0 -805
- package/src/memory/sync.ts +0 -145
|
@@ -5,15 +5,18 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
5
5
|
- Tests that are not about batching build the bus with `batchMs: 0`; with the default 15 s window a lone status envelope looks like a lost message.
|
|
6
6
|
- In Bun 1.3.14 a test timeout that fires while `Bun.spawnSync` runs can start the next test inside spawnSync's own event loop, where another spawnSync then spins at full CPU for good (trigger not pinned down; see README). Bun 1.4.2 still runs the next test inside the outer spawnSync's event loop. `scripts/check.sh` runs tests with a 20 s timeout and under `scripts/hang-watch.sh`; a test that spawns and reads the process table gets a timeout of its own.
|
|
7
7
|
- The Switchyard sidecar, sandboxed commands and the MLX server get a fixed environment (`PATH`, `HOME` and their own keys), so test knobs for their fakes travel in a wrapper script or argv, not in `process.env`; native peer children inherit the hub's environment minus the agent markers (`peerChildEnv`).
|
|
8
|
-
- Bind agent identity markers explicitly in native/CLI fixture children. The full gate starts the test runner without inherited agent markers and without an `ahub claude` session's hub variables (`AGENTHUB_PROJECT_DIR`, `AGENTHUB_STATE_DIR`, `AGENTHUB_INSTANCE_ID`, `AGENTHUB_LAUNCH_ID`); a new variable a native session exports and a fixture child reads joins that `env -u` list in `scripts/check.sh`. Never weaken production marker detection to make them pass.
|
|
9
|
-
- `bunfig.toml` preloads `test/hub-home.ts`, which gives every test run (the gate and `bun test <file>` alike) a temp `AGENTHUB_HOME` unless one is set
|
|
8
|
+
- Bind agent identity markers explicitly in native/CLI fixture children. The full gate starts the test runner without inherited agent markers (including `AGENTHUB_DELIVERY_ONLY`) and without an `ahub claude` session's hub variables (`AGENTHUB_PROJECT_DIR`, `AGENTHUB_STATE_DIR`, `AGENTHUB_INSTANCE_ID`, `AGENTHUB_LAUNCH_ID`); a new variable a native session exports and a fixture child reads joins that `env -u` list in `scripts/check.sh`. Never weaken production marker detection to make them pass.
|
|
9
|
+
- `bunfig.toml` preloads `test/hub-home.ts`, which first clears inherited `AMEM_HOME` and gives every test run (the gate and `bun test <file>` alike) a temp `AGENTHUB_HOME` unless one is set; scoped tests may set their own `AMEM_HOME` after preload. Hub memory (#258) is on with any project config, and no test may write the person's `~/.agenthub`. Do not remove it; a test that needs its own home sets and restores the variable.
|
|
10
10
|
- A free-port probe binds `hostname: "127.0.0.1"`, matching the loopback server that takes the port (#232).
|
|
11
11
|
- `ahub upgrade` without `--to` asks the registry (`npm view`) and hands a target newer than the CLI to `bun x` (#272): a test that reaches either puts a fake `npm` first on `PATH` and a recording stand-in for `process.execPath`, as `test/upgrade-cli.test.ts` does. The interactive screens are tested through a scripted `ScreenIO`; a CLI test that needs them marks `process.stdin` and `process.stdout` as terminals in a wrapper script.
|
|
12
12
|
- Cross-process fixtures wait for the consuming client to process its handshake (the channel connected diagnostic), not merely the daemon's attached/idle state. Deadline-routing fixtures hold and release responses at the fallback decision; do not place a fixed sleep between deadline fractions and assume scheduler headroom (#287). Receipt-order fixtures use the normal watchdog for ordinary I/O, trigger expiry after observed native acceptance, and await a consumed transport barrier before asserting on late results.
|
|
13
13
|
- A bounded polling wait must assert its condition after the last attempt, or document why giving up is intentional. Append-only event histories wait for a correlated closing event, not removal of the opening event. Before changing a slow wait, identify the unreached state and repair its cause (#296).
|
|
14
14
|
- A request that a test expects a stop or shutdown to cut gets its rejection handler where the request is created, never after the awaited teardown: a late `catch` races the reset and the run fails on an unhandled rejection.
|
|
15
|
-
- Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs
|
|
15
|
+
- Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs it as four sequential Linux shards (`--shard i/4`) beside the ordinary check, separately from `scripts/check.sh`; the required `seeded guards` job passes only when every shard passed. Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457).
|
|
16
16
|
|
|
17
17
|
- `scripts/check.sh` holds the repo's heavy-job slot (`<git common dir>/ahub-heavy.lock`, #398) for the whole run, so a test never runs the real gate; `test/check-lock.test.ts` runs a copy in a scratch repo.
|
|
18
18
|
- The whole-suite watchdog is `AHUB_CHECK_HANG_S` (default in `scripts/check.sh`, hosted macOS value in `.github/workflows/check.yml`; #378, macOS CI hit it while tests still passed): raise it only when the suite was still progressing at the cutoff, never for a hung test, and keep the per-test 20 s timeout.
|
|
19
19
|
- A seed's named test must run on Linux, where the seeded job runs: a darwin-only test reports no result there and fails the job (#382). Cover a macOS-only protection with its regression test instead.
|
|
20
|
+
|
|
21
|
+
- Keep CI's Bun version sourced from `.bun-version` through the pinned setup-bun action; update the version file rather than adding per-job version literals.
|
|
22
|
+
- Release verification requires both OS checks and the aggregate `seeded guards` success for an identical tree, a tag matching `package.json` on `origin/main`, and fresh ancestor-bound documentation stamps. Preserve the separate npm OIDC publish job and GitHub Release write job; neither job gets the other's credential authority.
|
package/docs/memory.md
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
|
-
# Hub memory
|
|
1
|
+
# Hub memory integration
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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 or later. 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
|
-
##
|
|
16
|
-
|
|
17
|
-
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
86
|
+
## Automatic sync integration
|
|
128
87
|
|
|
129
|
-
|
|
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
|
-
|
|
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),
|
|
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`**
|
|
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
|
|
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]`
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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.
|