@staix/agent-mem 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +13 -0
- package/.claude-plugin/plugin.json +12 -2
- package/README.md +143 -18
- package/package.json +1 -1
- package/src/cli.ts +537 -39
- package/src/commands-table.ts +60 -16
- package/src/hook-spool.ts +24 -2
- package/src/hooks.ts +9 -5
- package/src/index.ts +20 -0
- package/src/ingest.ts +3 -2
- package/src/mcp-memory.ts +168 -33
- package/src/mcp.ts +43 -7
- package/src/memory/aliases.ts +4 -0
- package/src/memory/bridge.ts +8 -2
- package/src/memory/candidate-pending.ts +119 -0
- package/src/memory/candidates.ts +228 -47
- package/src/memory/capacity-split.ts +76 -0
- package/src/memory/capacity.ts +139 -11
- package/src/memory/capture-input.ts +8 -3
- package/src/memory/capture.ts +30 -5
- package/src/memory/catalog.ts +43 -10
- package/src/memory/commands.ts +98 -44
- package/src/memory/config.ts +167 -0
- package/src/memory/consolidate.ts +78 -0
- package/src/memory/consolidation-clusters.ts +56 -0
- package/src/memory/consolidation-model.ts +97 -0
- package/src/memory/cross-project.ts +75 -0
- package/src/memory/curated-read.ts +29 -0
- package/src/memory/decay-review.ts +136 -0
- package/src/memory/doctor.ts +415 -0
- package/src/memory/editor.ts +58 -0
- package/src/memory/eval-fixture.ts +145 -0
- package/src/memory/eval-set-ko.json +90 -0
- package/src/memory/eval-set.json +443 -0
- package/src/memory/eval.ts +152 -0
- package/src/memory/evidence.ts +76 -0
- package/src/memory/external-import.ts +267 -0
- package/src/memory/gc.ts +97 -0
- package/src/memory/generation-index.ts +22 -17
- package/src/memory/generation-sync.ts +3 -0
- package/src/memory/goal-promotion.ts +71 -0
- package/src/memory/home.ts +15 -4
- package/src/memory/intent-brief.ts +89 -0
- package/src/memory/lifecycle-log.ts +54 -0
- package/src/memory/lifecycle.ts +61 -0
- package/src/memory/metrics.ts +2 -2
- package/src/memory/note-add.ts +100 -0
- package/src/memory/path-glob.ts +94 -0
- package/src/memory/plan-guard.ts +47 -0
- package/src/memory/platform.ts +12 -1
- package/src/memory/policy.ts +44 -13
- package/src/memory/private-spans.ts +32 -0
- package/src/memory/project-export.ts +157 -0
- package/src/memory/project-import.ts +147 -0
- package/src/memory/recall-staleness.ts +56 -0
- package/src/memory/recall.ts +74 -0
- package/src/memory/repo-scope.ts +404 -0
- package/src/memory/shard-sync-format.ts +116 -0
- package/src/memory/shard-sync.ts +421 -0
- package/src/memory/store.ts +344 -35
- package/src/memory/sync-cadence.ts +149 -0
- package/src/memory/sync-screen.ts +86 -0
- package/src/memory/sync.ts +91 -11
- package/src/memory/usage.ts +35 -0
- package/src/memory/use-telemetry.ts +51 -0
- package/src/memory/work-lists.ts +10 -4
- package/src/session-brief-worker.ts +3 -2
- package/src/session-brief.ts +74 -15
- package/src/setup-owned.ts +120 -0
- package/src/setup-toml.ts +296 -0
- package/src/setup.ts +327 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-mem",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Bounded project memory
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Bounded project memory tools and seven deterministic capture hooks, without a resident worker.",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"agent-mem": {
|
|
7
|
+
"command": "bun",
|
|
8
|
+
"args": [
|
|
9
|
+
"--smol",
|
|
10
|
+
"${CLAUDE_PLUGIN_ROOT}/bin/amem.js",
|
|
11
|
+
"mcp"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
5
15
|
}
|
package/README.md
CHANGED
|
@@ -2,23 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
Curated memory with append-only JSONL logs, a derived SQLite index, short- and long-term tiers, a typed graph, goals with leases, and git sync between machines. Extracted from [agent-hub](https://github.com/STAIxBWLB/agent-hub), with compatible storage and entry IDs.
|
|
4
4
|
|
|
5
|
-
Version: 0.
|
|
5
|
+
Version: 0.8.0. Requires Bun 1.4.2 or later; no runtime dependencies.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Quick start
|
|
8
8
|
|
|
9
|
-
Install the
|
|
9
|
+
Install the package, preview setup, then apply it for your client in a person's terminal:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
12
|
bun add -g @staix/agent-mem
|
|
13
13
|
amem --version
|
|
14
|
-
amem
|
|
14
|
+
amem setup all
|
|
15
|
+
amem setup claude --apply
|
|
15
16
|
```
|
|
16
17
|
|
|
18
|
+
Replace `claude` with `codex`, `kimi` or `pi` for that client. Setup shows its changes and asks for confirmation. Complete the client's own hook-trust review when prompted.
|
|
19
|
+
|
|
20
|
+
Agents submit pending memory candidates through MCP. Inspect them after a session and accept or reject each one:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
amem review list
|
|
24
|
+
amem review accept CANDIDATE_ID # or: amem review reject CANDIDATE_ID
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Replace `CANDIDATE_ID` with an id from the list. Acceptance is person-only and applies the configured privacy policy.
|
|
28
|
+
|
|
29
|
+
Claude's registered hooks capture events during the session. The configured Codex/Kimi SessionEnd hooks and Pi shutdown extension ingest transcript tails incrementally at session end. If those integrations are unavailable, run `amem ingest codex` (or `kimi` / `pi`) after each session, then repeat the review loop. The [setup section](#setup--plugin) describes the registration and native trust steps.
|
|
30
|
+
|
|
17
31
|
From a checkout, install development dependencies with `bun install` and run `bun src/cli.ts --help`.
|
|
18
32
|
|
|
33
|
+
## Setup / plugin
|
|
34
|
+
|
|
35
|
+
Preview the exact configuration files, native registration commands and private undo receipts before changing anything:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
amem setup all
|
|
39
|
+
amem setup claude --apply
|
|
40
|
+
amem setup codex --apply
|
|
41
|
+
amem setup kimi --apply
|
|
42
|
+
amem setup pi --apply
|
|
43
|
+
amem setup all --undo
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`--apply` and `--undo` require a person's terminal and an explicit `yes`; agent markers refuse them. The default is a read-only plan. Native Claude, Codex and Pi MCP registration commands run in a private temporary HOME before the listed files are published; current Kimi Code uses its MCP JSON file. No AGENTS.md, CLAUDE.md or rule files are edited. Repeated apply is a no-op even after routine CLI metadata writes. Registrations use the absolute Bun path found on PATH, preserving its stable symlink rather than a versioned Cellar target; temporary package entries (such as bunx caches) are refused; reapply refreshes moved package/runtime locations automatically. Private receipts restore original bytes, permissions and absent-file state when files are untouched. When a CLI updates its own metadata, undo restores only the managed MCP entry and hook rows while preserving those updates; edits to those managed entries refuse rather than overwrite. Config files have an 8 MiB bound and generated TOML is validated before publication.
|
|
47
|
+
|
|
48
|
+
Claude receives its seven bounded hooks. Codex and Kimi receive SessionEnd incremental ingest; Pi receives a session_shutdown extension. [Codex's native `/hooks` review](https://learn.chatgpt.com/docs/hooks) remains a pending person step: setup never grants or bypasses hook trust. Native registration requires a CLI exposing `mcp` administration (Claude, Codex and Pi are checked through their help before any registration command); current Kimi Code uses its documented config files. `setup all` skips and reports missing CLIs; an individual missing target refuses. Setup does not download tools or start a resident worker.
|
|
49
|
+
|
|
50
|
+
For a Claude Code plugin installation instead of direct setup registration:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
claude plugin marketplace add STAIxBWLB/agent-mem
|
|
54
|
+
claude plugin install agent-mem@agent-mem
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The plugin packages MCP plus the existing `hooks/hooks.json` once, using Claude's default hook discovery. It needs no `--plugin-dir`. Choose either plugin installation or direct Claude setup to avoid registering the same hooks twice. The direct setup dry run names an installed agent-mem plugin and warns about duplicate MCP and hook registrations.
|
|
58
|
+
|
|
19
59
|
## CLI
|
|
20
60
|
|
|
21
|
-
The flat `amem` commands
|
|
61
|
+
The flat `amem` commands are the memory commands of the library (a host such as agent-hub exposes the same ones under its own name); goals use `amem goal`:
|
|
22
62
|
|
|
23
63
|
```sh
|
|
24
64
|
amem list --json
|
|
@@ -30,21 +70,45 @@ amem goal list --all --json
|
|
|
30
70
|
amem goal new "Ship a release" --milestone "Review changes"
|
|
31
71
|
```
|
|
32
72
|
|
|
33
|
-
`amem goal link`
|
|
73
|
+
`amem goal link` needs a host that has a task board; amem alone has none, so it refuses.
|
|
34
74
|
|
|
35
|
-
Memory commands: `list`, `search`, `show`, `graph`, `suggestions`, `confirm`, `reject`, `promote`, `demote`, `forget`, `supersede`, `rebuild`, `dedupe`, `sync`, `machine`, and `import`. Use `--json` where
|
|
75
|
+
Memory commands: `list`, `search`, `show`, `graph`, `suggestions`, `confirm`, `reject`, `promote`, `demote`, `forget`, `supersede`, `rebuild`, `dedupe`, `sync`, `machine`, and `import`. Use `--json` where the command supports it. `dedupe` previews candidates; `dedupe --apply` appends reconciliation operations. `amem alias add|list` manages person-reviewed project key aliases; `amem capacity` reports and proposes capture bounds (see below).
|
|
36
76
|
|
|
37
|
-
Ordinary `amem` commands are for the person and refuse agent-session markers, including `AGENTHUB_PEER_ID`, `CLAUDECODE`, and `CODEX_THREAD_ID`. `amem mcp`, `amem hook` and `amem ingest` are intentional session entrypoints. MCP supplies scoped reads and bounded candidate/work-list writes; hooks and ingest write capture only. None writes curated notes directly.
|
|
77
|
+
Ordinary `amem` commands are for the person and refuse agent-session markers, including `AGENTHUB_PEER_ID`, `CLAUDECODE`, and `CODEX_THREAD_ID`. `amem mcp`, `amem hook` and `amem ingest` are intentional session entrypoints. MCP supplies scoped reads and bounded candidate/work-list writes; hooks and ingest write capture only. None writes curated notes directly. `amem --help` and `amem --version` only print text, so they also work in an agent shell; every other command is refused there, with a message that points an agent at `amem mcp` and the person at `amem review`. Environment-marker detection is an honest-session guard, not authentication. Ordinary CLI mutations remain person-only and require a TTY on stdin.
|
|
38
78
|
|
|
39
|
-
|
|
79
|
+
## Configuration and privacy policy
|
|
40
80
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
amem
|
|
44
|
-
|
|
81
|
+
amem applies one privacy policy everywhere it reads or writes on its own: capture (hooks and `amem ingest`), the MCP server, the SessionStart brief, `amem review accept`, `promote`, `import`, `sync`, repo promotion and the vault-candidate listing. The policy is the built-in secret and personal-data screen plus what you configure. Ordinary person reads (`list`, `search`, `show`) show stored data without screening.
|
|
82
|
+
|
|
83
|
+
The machine-local file `amem.config.json` in the memory home (see `amem status` for where that is) holds your own policy:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"denyPaths": ["confidential-vault", "customers/"],
|
|
88
|
+
"piiPatterns": ["codename-zebra", "acme corp"],
|
|
89
|
+
"briefBytes": 900,
|
|
90
|
+
"excerptBytes": 800,
|
|
91
|
+
"capturePaths": 16,
|
|
92
|
+
"capture": { "capMb": 256, "retentionDays": 30 }
|
|
93
|
+
}
|
|
45
94
|
```
|
|
46
95
|
|
|
47
|
-
|
|
96
|
+
- `denyPaths`: substrings of a path that are never captured and that decline any note, candidate or MCP answer naming them. `piiPatterns`: case-insensitive literal substrings (never regular expressions) that decline a value and redact a capture excerpt. Each list holds up to 64 entries of up to 256 bytes.
|
|
97
|
+
- `briefBytes`, `excerptBytes`, `capturePaths` can only lower the built-in ceilings (the 1200-byte SessionStart context, 2000-character capture excerpt, 32 captured paths). `capture.capMb` and `capture.retentionDays` are the lowest layer of the capture size and retention bounds: a shard's own `config.json` and the home's `capture-config.json` still override them.
|
|
98
|
+
- A file with an unknown key, a bad value, a link, or more than 64 KiB is an error that names the file. An unreadable policy is never treated as no policy.
|
|
99
|
+
- `<private>...</private>` spans are dropped before anything else reads the text, at every write boundary (capture, MCP candidates and work lists, notes, goals, promotion and import), so they are never screened, counted or stored. Tags match like HTML (case-insensitive, with attributes or spaces, and `<private/>` opens a span); an unclosed span drops the rest of the text.
|
|
100
|
+
|
|
101
|
+
A repository may add `.amem/config` (same shape, only `denyPaths`, `piiPatterns`, `briefBytes`, `excerptBytes` and `capturePaths`). It can only narrow: its lists are added to yours and its budgets count only when lower. It cannot remove an entry, raise a budget, move the home or change the capture bounds, and any other key is an error (a malformed repository file stops amem in that repository until fixed; it never falls back to no policy). A stricter policy a host passes through the library always wins over both files.
|
|
102
|
+
|
|
103
|
+
`amem sync` reads this machine's own long-term notes through the configured policy before it stages anything. One declined note refuses the whole sync and names its id (never its text); the log is append-only and sync never skips a row. It does not read other machines' segments, which were screened where they were written.
|
|
104
|
+
|
|
105
|
+
`--policy-free` is still accepted by `promote`, `import`, `sync` and `review accept` so existing scripts keep working, and it means nothing more: the configured policy always applies. It does not bypass the secret screen or the sync remote's privacy checks. Forgetting a shared note appends an operation; its previous content remains in the remote's git history.
|
|
106
|
+
|
|
107
|
+
## Writing notes and reviewing candidates
|
|
108
|
+
|
|
109
|
+
`amem note add [--kind decision|finding|contract|fail] [--tier short|long] [--scope project|shared] [--title T] [text... | -]` writes one curated note as the person (agent-mem#40 F6). The text is the arguments, `-` to read it from the terminal until end of input, or nothing to open `$VISUAL` (an empty one is skipped) or `$EDITOR` on a private temporary file (the command is run without a shell: an existing path with spaces works as is, otherwise quotes and backslashes split it, and Ctrl-C reaches the editor while it runs). `-` reads the text you type on the terminal until end of input: `note add` needs a terminal on stdin, so it cannot read a pipe, and that differs from a literal reading of "from stdin" in the issue. A `fail` note takes its five fields instead of free text: `--approach`, `--symptom`, `--cause`, `--evidence` (repeatable) and `--retry-when`. All five are required, and the command names the ones that are missing; with none given it opens a labelled template in the editor. The configured policy screens the title and text first, and a refusal never echoes them. Like every ordinary command it needs a terminal and refuses an agent session.
|
|
110
|
+
|
|
111
|
+
The SessionStart brief carries one line when candidates wait, for example `2 candidates pending, oldest expires in 27 days` (agent-mem#40 F7). It holds numbers only, is read-only (the hook never marks anything shown), comes out of the brief's own byte budget, and is left out when it does not fit. `amem status` adds a `candidates` object (or `unavailable` when `candidates.db` exists but cannot be read, never a false zero) and a warning when any is within 7 days of expiry. `amem review walk` needs a terminal on both stdin and stdout (it refuses when stdout is redirected to a file or a pipe, because nobody would see what it asks about). It prints each pending candidate, oldest first, and only then asks accept, edit, reject, skip or quit: accept goes through the normal policy, edit opens the text in your editor and writes it as your own note (a candidate proposed as a fail note opens the labelled template), and what it prints has control characters removed. Nothing expires unseen: a candidate counts as shown only when `review walk` has printed it (the plain `review list` prints no content and runs in scripts, and the brief line cannot know you read it, so neither marks anything). A candidate is kept past its 30 days until it has been shown, and then for 7 more days. When 500 unseen candidates wait, a new one is refused with a named error (an MCP tool reports a fixed "the review queue is full, ask the person to run `amem review`" reply) instead of dropping an unseen one.
|
|
48
112
|
|
|
49
113
|
## Candidate review
|
|
50
114
|
|
|
@@ -57,11 +121,11 @@ amem review accept <full-candidate-id> --policy-free --json
|
|
|
57
121
|
amem review reject <full-candidate-id> --json
|
|
58
122
|
```
|
|
59
123
|
|
|
60
|
-
All review commands remain person-only; acceptance and rejection require a terminal on stdin. Acceptance
|
|
124
|
+
All review commands remain person-only; acceptance and rejection require a terminal on stdin. Acceptance screens the candidate and the proposed note with the configured policy (`--policy-free` is still accepted and ignored). The CLI refuses secret-looking candidate input or proposed note content. Listing does not open the ordinary memory index, and acceptance defers unrelated held-note promotions.
|
|
61
125
|
|
|
62
126
|
Candidates live in `candidates.db` beside the memory logs. This sidecar is machine-local: it is never synced, imported, or read by recall, briefs, ask or graph evidence. Hosts must supply an explicit public privacy verdict before putting candidates or counters; private, PII and unknown verdicts are refused. Library APIs are available, but this phase adds no automatic candidate producer or telemetry consumer. Candidate retention is 500 per project and 30 days; counters retain 10,000 per project and 90 days. Candidate payloads are capped at 16 KiB.
|
|
63
127
|
|
|
64
|
-
Acceptance creates ordinary v1 memory data. A correction retains its target's scope, project and storage reach, writes the corrected note and applies the existing `supersede` operation; its exact current target revision must match. A local target stays short-term; a synced target stays synced, including a demoted target whose effective tier is short. A fail becomes a long-term project note. The accepted text binds the candidate ID, target revision and target ID; a fail proposal without a target records `none` for both target fields. Both paths require the actual MemoryStore promotion screen to return `promote` at acceptance time; `keep`, `skip` or an absent screen refuses acceptance without falling back to short-term storage. Accepted v1 notes and operations follow the existing storage and sync rules; the candidate/counter sidecar remains local and outside evidence. No new v1 operation is introduced. Decay candidates are listing-only and cannot be accepted; a person may separately choose `
|
|
128
|
+
Acceptance creates ordinary v1 memory data. A correction retains its target's scope, project and storage reach, writes the corrected note and applies the existing `supersede` operation; its exact current target revision must match. A local target stays short-term; a synced target stays synced, including a demoted target whose effective tier is short. A fail becomes a long-term project note. The accepted text binds the candidate ID, target revision and target ID; a fail proposal without a target records `none` for both target fields. Both paths require the actual MemoryStore promotion screen to return `promote` at acceptance time; `keep`, `skip` or an absent screen refuses acceptance without falling back to short-term storage. Accepted v1 notes and operations follow the existing storage and sync rules; the candidate/counter sidecar remains local and outside evidence. No new v1 operation is introduced. Decay candidates are listing-only and cannot be accepted; a person may separately choose `amem demote` or `amem forget`. The sidecar receipt, note append and correction's supersede append are separate durable writes. An interrupted acceptance can leave a note without its supersede; it is held for explicit inspection and never replayed automatically.
|
|
65
129
|
|
|
66
130
|
The library entry exports `CandidateStore({ home?, now? })`, `put(input, verdict)`, `recordCounter(key, values, verdict)`, and `accept(id, { project, actor, verdict, store, policy })`. `home` retains the parent-home convention (`<home>/memory`); the default uses `memoryHome()`. The host must authenticate person intent, bind the current project and privacy verdict, and supply a current acceptance policy plus the ordinary store's actual promotion screen. An `actor` string or caller-supplied public verdict alone is not authentication. Counter writes are idempotent snapshots for the same contextual key, not increments: observed `0` stays zero, while `null` means unknown (a later unknown value does not erase an observed value).
|
|
67
131
|
|
|
@@ -127,7 +191,7 @@ These interactive checks remain unrun until a person checks them in each install
|
|
|
127
191
|
|
|
128
192
|
## Data directory
|
|
129
193
|
|
|
130
|
-
`memoryHome()` selects `AMEM_HOME` first,
|
|
194
|
+
`memoryHome()` selects `AMEM_HOME` first; then the `home` of a pointer file `amem.config.json` in the default home (a file with `home` carries nothing else, and a redirected home's own file cannot redirect again); then `$AGENTHUB_HOME/memory`, otherwise `~/.agenthub/memory`. `amem status` prints the home in use, which of these chose it, and the configuration files in effect. `AMEM_HOME` names the memory directory itself; `AGENTHUB_HOME` names its parent. Existing agent-hub memory directories need no migration. An explicit `StoreOptions.home` retains the parent-directory convention and appends `/memory`.
|
|
131
195
|
|
|
132
196
|
The JSONL segments are authoritative. `index.db` is derived and can be rebuilt. Entries are immutable; changes append operations. The compatibility schema remains `agent-hub.memory/v1` because it participates in entry IDs.
|
|
133
197
|
|
|
@@ -139,8 +203,47 @@ Project identity (#15 D2) keeps `memoryProjectKey`: a worktree, a submodule and
|
|
|
139
203
|
|
|
140
204
|
`store.search(query, filter)` searches the curated tier and the current project's capture shard with SQLite FTS5 and BM25 plus tier/recency weighting: natural multi-word queries match partial term sets (the substring filter required every word), and a curated row outranks the same text in the capture tier. Prefix terms match inflections and Korean particles in either direction by retaining the original query term and adding a common-particle-stripped alternative. The FTS copy of the curated rows lives in its own derived `search.db` (`index.db`'s table set is a frozen contract and gains nothing); it is written from the same apply path as the index, and a prune, crash or rebuild is detected and resynced, so the incremental FTS copy equals a full resync. Where FTS5 is unavailable the engine falls back to the substring filter and reports `engine: "substring"`; capture recall needs FTS5 and is skipped in the fallback. The library entry exports `CaptureStore`, `captureSearch`, `readCaptureConfig`, `writeCaptureConfig`, `SearchIndex`, `fts5Available`, the shard helpers (`resolveAlias`, `appendAlias`, `listAliases`, `shardKeyHash`), the catalog (`rebuildCatalog`, `readCatalog`, `scanCatalog`) and the capacity advisor (`capacityReport`).
|
|
141
205
|
|
|
206
|
+
`amem eval [--set ko|<file>] [--k 1,5,10] [--json]` measures recall with no model and no network (agent-mem#40 F13): it replays a question set and reports recall@k, hit@k and MRR. It scores the ranked `store.search` (BM25 with tier and recency weights), which is what the MCP `amem_search` tool and hub recall rank with and what #13 AC4 measured. It is not the `amem search` command: that one is a substring filter (every word must appear, newest first), so `amem search lighthouse beacon` can print nothing for a note that `amem eval` finds. Whether `amem search` should use the ranked search is a separate question. Without `--set` it scores the built-in synthetic set (26 invented harbour entries, long and short tier, written a day apart, and 32 questions, shipped in `src/memory/eval-set.json`), which is seeded into a throwaway store, so it never reads or writes your memory home and gives the same numbers on every machine. A set of your own either carries its own `entries` (self-contained, named by `key`) or, without entries, names real entry ids (or a prefix of 8 or more hex characters) and is scored against this project's store; each name is resolved like `amem show` does (a promoted short-term id finds its long-term copy, and a superseded id stands for the entry that replaced it, since search returns only live entries), and a prefix that matches more than one live entry is refused. `mem:<id>` as a brief prints it is accepted. Questions whose expected entry is not in the store score 0 in `all` and are left out of `answerable`, and `answerable` is scored over the entries the store holds, so a ranking problem and a coverage problem stay apart (`n/a` when no question is answerable). `--set ko` scores the second built-in set instead: 37 invented Korean entries and 38 questions (particle variants, spaced and unspaced compounds, mixed Korean-English terms, Latin abbreviations, short and long tier), with its baseline recorded in `docs/contracts.md` (agent-mem#62). Set format: `docs/contracts.md#recall-evaluation-f13`.
|
|
207
|
+
|
|
142
208
|
The R7 capacity advisor reports this machine's load and proposes capture bounds: `amem capacity [--json]` shows capture size and growth, disk, memory, the rolling `metrics.jsonl` readings and the proposal with its binding constraint; `amem capacity apply [--cap-mb N] [--retention-days N]` saves a preview, and only a matching later `--confirm` writes the home default config. Applying is person-only (agent markers refused, a terminal required); an agent may read the proposal through the library, never apply it. Under the 2026-10-17 owner decision (item 2) the proposal is the largest cap and retention that fit, and it may be above the current values: the cap is the disk-share bound itself (default 2% of free space, `--disk-share`), and retention is the share divided by the measured daily growth, up to a 365-day ceiling (a `ponytail:` constant, a person setting is the upgrade path). A hook p95 over 150 ms or hook RSS over 100 MB halves the proposed cap, and search RSS over 200 MB halves the retention. The reason names every binding constraint: `disk-share retention` or `retention ceiling`, `disk-share cap`, `R3 search RSS`, `R2 hook cost`. A proposal is never written by the advisor, and `apply` still takes `--cap-mb` and `--retention-days` to override it. Benchmark samples stay in the measuring script's synthetic home; only real hook and search invocations feed the advisor's rolling metrics.
|
|
143
209
|
|
|
210
|
+
## Repo scope (#15 T4)
|
|
211
|
+
|
|
212
|
+
Team-shareable decisions, contracts and `fail` lessons can live in the repository itself, in `.amem/log.jsonl`, and travel with git. Nothing enters it by itself.
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
amem promote <id> --to repo --policy-free # preview: the exact line that would be committed, and the repository's visibility
|
|
216
|
+
amem promote <id> --to repo --policy-free --confirm # append it (private repository)
|
|
217
|
+
amem promote <id> --to repo --policy-free --confirm --confirm-public <id prefix> # public or unverified repository: per entry
|
|
218
|
+
amem repo status # the file, its entries, what the screen withheld
|
|
219
|
+
amem repo attributes [--confirm] # add `.amem/log.jsonl merge=union` to .gitattributes, only when you ask
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
It is person-only and needs a terminal. The entry is screened (secrets, personal data, private paths, the repository's own `local.deny` and `pii.paths` with agent-hub's glob rules), and amem never commits or pushes: you review and commit the file. A repository counts as private only when every remote's effective URL (after `insteadOf`) is a `github.com` repository that `gh` reports private; any other host is unknown and asks for the per-entry confirmation. The file is read as untrusted input: the newest 1 MiB, no control characters, a future date shown as now. Recall, brief and MCP search include the current repository's `.amem/` when that repository is the project, never another project's.
|
|
223
|
+
|
|
224
|
+
## Managing the project shards (#15 T5)
|
|
225
|
+
|
|
226
|
+
One catalog view across every project shard of the memory home, person-only for anything that changes state (an agent marker is refused, and a mutation needs a terminal on stdin):
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
amem projects [--all] [--json] # key, state, aliases, capture and curated sizes, disk use; forgotten ones only with --all
|
|
230
|
+
amem usage [--limit N] [--all] [--json] # totals and the largest shards
|
|
231
|
+
amem gc [--confirm] [--batches N] # each shard's own retention and cap, in bounded batches; a preview without --confirm
|
|
232
|
+
amem project export <key> --to <file> # one project's capture, notes, goals and work lists as one new 0600 bundle (never overwrites)
|
|
233
|
+
amem project archive|unarchive <key> # take a cold project out of recall, or bring it back (append-only ops)
|
|
234
|
+
amem project forget <key> [--confirm] # append-only record that takes one project out of recall and every listing for good; preview first
|
|
235
|
+
amem search --all-projects <words> # the reply begins "crossed projects" and every hit names its project; --include-archived reads cold ones
|
|
236
|
+
amem vault-candidates [--all-projects | --project <key>]
|
|
237
|
+
amem capacity apply --split [--confirm] # each shard's share of the global capture budget, written to its own config after a preview
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- `archive`, `unarchive` and `forget` are append-only ops in `lifecycle.jsonl` of the memory home (local, never synced, like `aliases.jsonl`): nothing rewrites a line and nothing deletes a shard. The latest op wins and `forgotten` is terminal. An alias resolves to its one canonical project, and the reply names it. An archived or forgotten project has no writable capture store, no SessionStart brief and no MCP server; `unarchive` restores an archived one. A forgotten project's bytes stay on disk: `forget` is the person's record, not an erase. An alias shard of an archived or forgotten project counts as that state, `alias add` to or from a forgotten key is refused, and hooks that staged capture for a project before it was archived or forgotten are retired at the next drain (counted as `project_archived` or `project_forgotten`). `openShardStore` refuses such a project at the open, so a library caller such as agent-hub is gated too once it pins this release.
|
|
241
|
+
- `gc` opens only the capture store of each shard and calls its bounded `evict()` (at most four batches of 256 rows and 25 ms per call), so a cap shrink never takes a shard in one pass: the report says `remainingOverCap` and the next run continues. It never opens a curated store, a goal or a work list. An archived shard is still bounded; a forgotten one is skipped and listed as skipped.
|
|
242
|
+
- `export` changes no content under the memory home: it opens the capture databases read-only and reads the curated store, and the only files that may appear or refresh are derived (the curated `index.db` and `search.db`, SQLite sidecars and `catalog.db`), which every reader rebuilds from the logs. A destination inside the home is refused. The bundle is screened like sync and promotion are, so a secret-looking value, a PII pattern or a denied path is withheld and counted in `withheld`.
|
|
243
|
+
- `search --all-projects` runs one ordinary project-scoped search per active project and merges by score. It is a person command: the MCP server stays bound to one project and has no all-projects argument, and the raw memory command (what a host exposes as its own `memory` command) refuses the flag.
|
|
244
|
+
- `capacity` also prints a per-shard split of the global disk-share budget. Each shard's weight halves every 14 days since its last capture, so recently used projects keep more capture and cold ones shrink first; the split never exceeds the budget, gives every shard at least 1 MB, keeps every shard at what it holds whenever the total fits, never proposes a retention shorter than a shard's idle time plus 30 days, and says when the budget forces a shard below what it holds. When the budget holds fewer whole MB than there are shards, no split is proposed and `capacity` says why (`splitRefused`). It proposes capture bounds only, and never evicts curated notes, goals or work lists. `capacity apply --split` previews, then `--confirm` writes exactly the previewed values to each shard's `config.json`, and only if the preview is under an hour old and the set of shards and every shard's config are unchanged since then (a `--disk-share` given only to `--confirm` changes nothing that is written).
|
|
245
|
+
- amem lists vault candidates and never writes the vault.
|
|
246
|
+
|
|
144
247
|
Resource budgets are acceptance criteria, measured by `bun scripts/measure-budget.ts` in CI on Ubuntu and macOS: a hook-like append (including the spool-and-exit path under contention) must stay under 150 ms p95 wall time and 100 MB peak RSS (R2), the search path under 100 MB idle and 200 MB peak RSS (R3), and no amem process may remain after exit (R1). The script prints a machine-readable verdict per budget and its CI log is also the FTS5 availability evidence for Linux (R5). The AC4 retrieval re-check of the frozen P0 query set is in [docs/p1-ac4-recheck.md](https://github.com/STAIxBWLB/agent-mem/blob/main/docs/p1-ac4-recheck.md).
|
|
145
248
|
|
|
146
249
|
## Claude Code hooks and SessionStart context
|
|
@@ -175,6 +278,12 @@ A private admission cursor rotates bounded inspection across pending records (at
|
|
|
175
278
|
|
|
176
279
|
Successful native project identities have a private, bounded derived cache (128 cwd entries, 4 KiB each). A hit must match the current project scope, stricter Git-validity metadata and executable revision; a miss still uses the shared hardened Git resolver. It stores no capture or context, and does not guess an identity when Git fails. The hook uses the selected developer Git directly only when PATH selects Apple's root-owned shim; custom PATH binaries remain selected. Cache ceilings and invalidation are in [the P3 contracts](docs/contracts.md#hook-project-identity-cache).
|
|
177
280
|
|
|
281
|
+
## Doctor and the sync cadence
|
|
282
|
+
|
|
283
|
+
`amem doctor` prints one read-only report of this project's memory: duplicate and stale notes, pending candidates, capture freshness and hook registration for each CLI (a CLI with no hook is `manual`), the SessionStart brief's token cost, whether the index equals a rebuild of the logs, reader leases and the policy in effect. `amem doctor --json` is a stable machine-readable form (`amem.doctor/v1`). It writes nothing into the memory home (every check runs on a temporary copy of the logs) and creates nothing in a home you never used.
|
|
284
|
+
|
|
285
|
+
Intake runs at the end of each Codex, Kimi and Pi session once `amem setup <cli> --apply` has registered it. Sync is off unless you ask for it: amem registers no sync hook and runs nothing between sessions. To sync on your own schedule, call `amem sync --if-due` from your shell (for example an EXIT trap in `~/.zshrc`; it needs a terminal). It syncs at most hourly, backs off 15 minutes doubling to a day after failures, stops after 30 seconds, and does nothing without a remote set by `amem sync --remote <private git url>`. `--force` skips the schedule and the backoff.
|
|
286
|
+
|
|
178
287
|
## Native transcript intake
|
|
179
288
|
|
|
180
289
|
`amem ingest` reads existing Codex, Kimi or Pi transcripts and writes screened deterministic capture rows, never curated notes. Run it from the project to bind intake to that working directory; the CLI exposes no project/home override or policy bypass:
|
|
@@ -258,7 +367,7 @@ Real migration remains blocked until the separately reviewed generation-capable
|
|
|
258
367
|
|
|
259
368
|
## Curated shard migration (#15 T2)
|
|
260
369
|
|
|
261
|
-
`amem migrate --shards` moves the curated single home into `projects/<key-hash>/` and `user/` through a resumable generation journal. The original single-home log bytes are never rewritten. A populated selected generation is supported and keeps its generation ID. Each shard keeps private legacy inputs in read-only `legacy/`, public-main inputs in read-only `legacy-main/`, and generation-born rows in its own `log/` and `local/`. It has its own `index.db`, `search.db` and mutation lock; original IDs, hashes, placement and own/local/public provenance are preserved. The source generation bytes stay unchanged. Immutable prune proofs are validated before effects; a multi-shard prune is translated into one deterministic row per shard, with a receipt naming the original row. Existing capture and `work/` directories are not migration inputs and are left untouched. `catalog.db` remains derived and rebuildable, now including curated entry counts and last use.
|
|
370
|
+
`amem migrate --shards` moves the curated single home into `projects/<key-hash>/` and `user/` through a resumable generation journal. The original single-home log bytes are never rewritten. A populated selected generation is supported and keeps its generation ID. Each shard keeps private legacy inputs in read-only `legacy/`, public-main inputs in read-only `legacy-main/`, and generation-born rows in its own `log/` and `local/`. It has its own `index.db`, `search.db` and mutation lock; original IDs, hashes, placement and own/local/public provenance are preserved. The source generation bytes stay unchanged. Immutable prune proofs are validated before effects; a multi-shard prune is translated into one deterministic row per shard, with a receipt naming the original row. Existing capture and `work/` directories are not migration inputs and are left untouched. `catalog.db` remains derived and rebuildable, now including curated entry counts and last use. Split curated stores sync the opened canonical project and user overlay to separate private-remote refs (`amem/p/<key-hash>` and `amem/u`) with `amem sync --policy-free`; unopened projects and machine data are not fetched. Original IDs and attribution survive independent generations. Foreign-generation prune rows refuse the whole sync before publication/import; cross-generation tombstone sync remains the explicit T3 ceiling (see `docs/contracts.md`, "Per-shard private sync").
|
|
262
371
|
|
|
263
372
|
Before splitting a live home, every installed host must open it through `openShardStore({ project })` or the explicit user shard. Plain `new MemoryStore()` refuses any split marker, including an interrupted split, so it cannot silently keep writing the old layout. No host capability registry currently exists; migration preflight output states that limitation. Do not release T2 for agent-hub use until its memory factory is integrated with `openShardStore` and that host version is installed. Existing unannounced older builds remain the documented unchanged-source ceiling.
|
|
264
373
|
|
|
@@ -333,3 +442,19 @@ CI runs the typecheck, boundary check and tests on Ubuntu and macOS with Bun 1.4
|
|
|
333
442
|
After setup, a `v*` tag triggers both operating-system gates. The tag must equal `v` plus `package.json`'s version. Only after both gates pass does the release job install npm 11.20.0 (trusted publishing needs 11.5.1 or later) and run `npm publish --access public`, authenticated through GitHub OIDC with `id-token: write`; npm adds provenance itself. The workflow uses no `NPM_TOKEN`.
|
|
334
443
|
|
|
335
444
|
License: MIT.
|
|
445
|
+
|
|
446
|
+
## Intent recall and history
|
|
447
|
+
|
|
448
|
+
Use `amem brief --for "repair the writer" --paths src/writer.ts` for a byte-capped, fail-first brief. Repeat `--paths` or `--symbols` for explicit scope; `--bytes 2048` sets the complete brief-body cap. Agents use the project-bound `amem_brief` MCP tool. Memory is untrusted data, not instructions.
|
|
449
|
+
|
|
450
|
+
Current recall omits superseded and expired entries. `amem show <id> --history` shows retained versions, `amem graph <id> --history` follows retained history, and `amem search writer --as-of 2026-10-20 --history` reads retained state at UTC midnight. Current forget/prune removals still hide the text at every past date. Promotion event time is not serialized, so affected history reports `promotion time unknown: tier before promotion cannot be recovered`. `(stale?)` is only a bounded path-metadata hint. See `docs/contracts.md` for retention and history limits.
|
|
451
|
+
|
|
452
|
+
The library exports `buildIntentBrief`, `planGuardNodes`, `matchingPlanFails` and nullable usage callbacks. Emitted output may record rendered counts; delivered and cited counts need explicit host observations. Missing evidence stays unknown. Agent-hub adoption is a separate follow-up after a published library release.
|
|
453
|
+
|
|
454
|
+
## Consolidation and evidence
|
|
455
|
+
|
|
456
|
+
Run `amem consolidate [--project <current-key>]` (another project is refused) to queue deterministic proposals for `amem review`. It compares a bounded project-local window using indexed text overlap, shared paths and current supersession-chain metadata. It does not edit curated notes. The person reviews each merge, replacement or recurring-failure summary, and acceptance rechecks every source revision. A recurring-failure summary preserves its source failures and decisions. Large source quotations are counted as too large instead of silently shortened into a proposed note.
|
|
457
|
+
|
|
458
|
+
The CLI runs the deterministic pass only. A library host may explicitly enable an injected model callback for the person; no provider is built in. It has batch, byte, deadline and persistent daily limits. Model answers remain marked machine-local candidates that cannot be accepted; `amem note add` is the separate path for an independently authored note. A non-answer is never saved.
|
|
459
|
+
|
|
460
|
+
Agents use `amem_evidence` with a question to get cited, live curated entries, not an answer. The complete JSON bundle is byte-capped (`maxBytes`, default 4096, range 256..8192); excerpted entries retain an exact `mem:` reference for fetching the full note. Capture, repository-only rows, pending candidates and model answers are excluded. amem makes no model call for this tool. Rendering a citation in the bundle records only `rendered` after successful output, never `delivered` or `cited`.
|