npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@staix/agent-mem

v0.18.0

Published

Curated, shared memory for coding agents: append-only logs, a derived index, tiers and a typed graph.

Readme

agent-mem

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, with compatible storage and entry IDs.

Version: 0.18.0. Requires Bun 1.4.2 or later; no runtime dependencies.

Quick start

Install the package, preview setup, then apply it for your client:

bun add -g @staix/agent-mem
amem --version
amem setup all
amem setup claude --apply

Replace claude with codex, kimi, pi, qwen, jcode, opencode or gemini for that client. The default command shows a read-only plan; --apply writes it without an interactive prompt. Complete the client's own hook-trust review when prompted.

Agents can submit pending memory candidates through MCP. Review each candidate with the CLI; acceptance and rejection recheck the configured policy and record the caller's actor and peer:

amem review list
amem review accept CANDIDATE_ID  # or: amem review reject CANDIDATE_ID

Replace CANDIDATE_ID with an id from the list. MCP remains proposal-only: it never accepts a candidate or writes a curated note.

Claude's registered hooks capture events during the session. The configured Codex/Kimi/Qwen 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 / qwen) after each session, then repeat the review loop. The setup section describes the registration and native trust steps.

From a checkout, install development dependencies with bun install and run bun src/cli.ts --help.

Setup / plugin

amem setup agy registers MCP only in the current git repository's .agents/mcp_config.json, with an explicit absolute repository cwd and a private receipt per repository. setup all excludes agy; run it separately in each repository. Setup refuses tracked config files and JSONC, and warns when the file is not ignored. Restart Antigravity after applying, use one workspace per session, and ensure no global, plugin or running server is also named amem. No global configuration, ingest command or hook is installed. Undo removes .agents only when setup created it and it is still empty; pre-existing or nonempty directories remain. Older receipts still restore their configuration without directory cleanup. See the Antigravity contract and capture limitation.

amem setup jcode registers MCP only, using Jcode v0.99.1's global JCODE_HOME/mcp.json (default ~/.jcode/mcp.json). An explicitly set JCODE_HOME must be absolute for amem setup: empty, relative and literal ~ values are refused. Unset uses the default, and absolute values retain their literal spelling without trimming. It owns only servers.amem, sets shared: false for a workspace-bound session, and preserves unrelated configuration bytes. Jcode has no native MCP add/remove command in this release, so setup edits the owned entry directly. No Jcode ingest command or capture hook is installed: saved messages lack per-message origin while the session cwd can change. Launch Jcode with JCODE_DISABLE_CLAUDE_MCP=1 and without a same-name project override, which could otherwise replace the private binding. Use a new workspace-bound session when changing projects. See the Jcode contract and capture limitation.

amem setup gemini registers MCP only, at user scope in Gemini CLI 0.61.0's ~/.gemini/settings.json (GEMINI_CLI_HOME, which must be absolute, moves it). It runs the native gemini mcp add --scope user (and remove for an earlier entry) in a private staging HOME, owns only mcpServers.amem and keeps every unrelated setting. Antigravity's ~/.gemini/config/mcp_config.json is a different client's file and is never read or written; amem setup agy stays per repository. A workspace .gemini/settings.json that defines mcpServers.amem overrides the user entry. Setup refuses settings that are not strict JSON (Gemini allows comments, but setup's exact undo needs strict JSON) and runtime paths containing $, which Gemini expands. No hook, ingest command or GEMINI.md edit: see the Gemini CLI contract for why transcripts are not ingested. setup all includes Gemini CLI when it is installed.

amem setup opencode registers MCP only in OpenCode 1.18.35's global opencode.json (default ~/.config/opencode/opencode.json; an absolute XDG_CONFIG_HOME moves it and is used literally, untrimmed, as OpenCode does; a relative value is refused). It owns only mcp.amem, a type: "local" server with no cwd, so OpenCode starts it in each session's project directory, and it preserves unrelated configuration bytes. OpenCode's mcp add is interactive and has no remove, so setup edits the owned entry directly. Setup refuses strict-JSON violations (JSONC), a sibling opencode.jsonc or config.json in the same directory (OpenCode merges them over this file) and OPENCODE_CONFIG, OPENCODE_CONFIG_DIR or OPENCODE_CONFIG_CONTENT. A project opencode.json that defines mcp.amem overrides the global entry. No plugin, hook or ingest command is installed: see the OpenCode contract for why its session database is not ingested. setup all includes OpenCode when its CLI is installed.

amem setup qwen previews a user-scope MCP registration and a bounded SessionEnd ingest hook in the same QWEN_HOME/settings.json (default ~/.qwen/settings.json). --apply and --undo follow the same confirmed, private-receipt contract as the other targets. Unrelated settings, servers and hooks are preserved. Qwen runs this hook on normal interactive exit; use amem ingest qwen when the session ends without that hook. Native-client operation is source-verified; the installed-package tests use stub registration CLIs.

Preview the exact configuration files, native registration commands and private undo receipts before changing anything:

amem setup all
amem setup claude --apply
amem setup codex --apply
amem setup kimi --apply
amem setup pi --apply
amem setup all --undo

--apply and --undo are explicit writes; the default remains a read-only plan. They do not require a TTY prompt, and an agent marker supplies identity rather than a tool refusal. Follow workspace authorization for changes to machine configuration. Native Claude, Codex, Pi and Qwen 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.

Claude receives its seven bounded hooks. Codex, Kimi and Qwen receive SessionEnd incremental ingest; Pi receives a session_shutdown extension. Codex's native /hooks review remains a pending person step: setup never grants or bypasses hook trust. Native registration requires a CLI exposing mcp administration (Claude, Codex, Pi and Qwen 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.

For a Claude Code plugin installation instead of direct setup registration:

claude plugin marketplace add STAIxBWLB/agent-mem
claude plugin install agent-mem@agent-mem

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.

CLI

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:

amem list --json
amem search "release decision" --limit 10
amem show <id> --json
amem graph <id-or-node> --hops 2 --json
amem suggestions
amem goal list --all --json
amem goal new "Ship a release" --milestone "Review changes"

amem goal link needs a host that has a task board; amem alone has none, so it refuses.

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 reviewed project key aliases; amem capacity reports and proposes capture bounds (see below).

amem import codex reads Codex's Markdown memory as review candidates. It supports both on-disk layouts: memories v1 under $CODEX_HOME/memories (else ~/.codex/memories) and memories v2, the default for fresh profiles since Codex 0.165.0, under the sibling memories_v2/ (memory_summary.md, rollout_summaries/, extensions/; v2 titles start with memories_v2/). The v2 SQLite state is never read. With --from only the given directory is read. When neither layout exists, the receipt names the locations it checked.

Agent markers are honest-session identity context, not authentication. Marked amem note add writes use the existing peer field for attribution; its memory reads use the screened policy and historical reads are refused. Unmarked reads retain their existing unscreened behavior. amem mcp remains scoped to its startup project and can write only candidate and work-list data; hooks and ingest append capture only. Curated candidate acceptance and other CLI writes use the current configured policy. The CLI no longer has a general agent-marker refusal or stdin-TTY gate; use each command's explicit preview and --confirm/--apply flow where provided. amem review walk stays interactive and requires terminal input and output; $EDITOR opens only when a terminal is available.

Configuration and privacy policy

The configured privacy policy screens capture, MCP reads, the SessionStart brief, candidate acceptance, promotion, import, sync, repo promotion and vault-candidate selection. Agent-marked CLI memory reads use the same screened boundary as MCP. Unmarked CLI reads (list, search, show) retain their existing unscreened behavior. The policy combines the built-in secret and personal-data screen with your configured rules.

The machine-local file amem.config.json in the memory home (see amem status for where that is) holds your own policy:

{
  "denyPaths": ["confidential-vault", "customers/"],
  "piiPatterns": ["codename-zebra", "acme corp"],
  "briefBytes": 900,
  "excerptBytes": 800,
  "capturePaths": 16,
  "capture": { "capMb": 256, "retentionDays": 30 }
}
  • 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.
  • 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.
  • 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.
  • <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.

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.

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.

--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.

If the screen misses a secret or personal data, amem forget <id> hides the entry from every reader but does not remove its bytes. docs/purge.md is the procedure for rotating the secret, removing local copies, cleaning machine-local tiers and replacing the sync remote; amem forget lists nearby capture rows, candidates and work items by id, and amem doctor reports forget and prune counts as a reminder.

Writing notes and reviewing candidates

amem note add [--kind decision|finding|contract|fail] [--tier short|long] [--scope project|shared] [--title T] [text... | -] writes one curated note with CLI peer attribution (agent-mem#40 F6). The text is the arguments, - to read stdin through end of input, or nothing to open $VISUAL (an empty one is skipped) or $EDITOR on a private temporary file when a terminal is available (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). 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 before anything is written, and a refusal never echoes them.

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 --json adds a candidates object (bare amem status shows the same as a labelled row and next action) (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 a note under the current CLI actor (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 returns a fixed queue-full response) instead of dropping an unseen one.

Candidate review

amem review lists this project's pending machine-local candidates. They are proposals, not memory notes and not evidence. Use JSON to inspect the complete candidate payload before accepting or rejecting its full 64-character ID:

amem review --json
amem review list --kind correction --status all --limit 50 --json
amem review accept <full-candidate-id> --policy-free --json
amem review reject <full-candidate-id> --json

Review commands accept or reject candidates by their full id. CLI actions derive actor and peer from the session and screen both the candidate and proposed note with the configured policy (--policy-free is still accepted and ignored). amem mcp remains proposal-only. The CLI refuses secret-looking candidate input or proposed note content. Plain listing does not open the ordinary memory index; JSON listing reads an existing curated store for overlap hints without initializing an absent one. Acceptance defers unrelated held-note promotions.

Pending candidates include up to three overlapping current notes from the same project as a review hint. amem review list --json adds overlaps with each note's id, title and score; amem review walk prints one line per overlap before its prompt. amem_remember adds an overlaps array of note ids to its candidate receipt. Hints reuse consolidation's deterministic word-overlap measure, with identical text scoring 1, and never merge, reject or block a candidate. Superseded, expired, forgotten, other-project and policy-refused notes are excluded before selection, with no excluded count or id returned. Hints are computed on demand, are not stored with the candidate, and make no model call.

JSON listing screens and tokenizes each current project note once for the whole candidate batch, retaining only three matches per candidate. The score divides shared terms by the smaller term set: a one- or two-term candidate can therefore score 1 against a longer note containing those terms. A score is an overlap hint, not a verdict that the notes are equivalent.

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.

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 CLI caller 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.

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 derive actor and peer from its session, 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).

Open a review-only MemoryStore with deferPromotions: true to prevent unrelated automatic promotions during construction and reads. Explicit candidate acceptance still runs its promotion screen. This package registers no capture observer or host candidate tools; capture exclusion and authenticated producers must be established by the host before those integrations are enabled.

Acceptance normalizes its note once, including timestamp, content ID and short-term expiry, then screens and appends that exact body. The final target-revision check and correction appends hold the same machine-local mutation-lock.db writer transaction used by this version's ordinary memory, import and sync writers. This lock is independent of the derived index, so an ordinary write still persists when SQLite is busy. External or older writers that edit segments without this lock do not participate in serialization. The logs remain authoritative after an interrupted acceptance; the next read refreshes the derived index.

Console (acon)

@staix/agent-mem/ui-plugin is a plugin for the local agent console acon (STAIxBWLB/agent-console, contract api 1). It adds a Memory area with the panels Review, Recap, Search, Timeline, Work lists, Goals and Capacity, puts pending candidates (group candidate) and a capture capacity proposal (group alert) in the console inbox, and answers the console palette with memory search hits.

amem setup console           # preview
amem setup console --apply   # write $AGENTHUB_HOME/console/plugins/amem.json
amem setup console --undo    # remove it (only when it points at this install)

The manifest holds the absolute path and sha256 of src/ui/plugin.ts, in a 0700 directory and a 0600 file, as acon requires. acon refuses a module whose hash changed, so run amem setup console --apply again after upgrading amem. setup all does not include it.

  • Reads are screened. Search, Timeline and Work lists call the in-process amem_search, amem_timeline and amem_work_state_read handlers for the chosen project and forward every option the tool declares, so console search returns exactly the amem_search set for the same query. Goals and the review queue pass the configured policy; a candidate that fails it, or whose target fails it, is not shown (the panel counts it and points to amem review walk). The console never shows unscreened content, also not to the person. It runs in no repository, so a repository's .amem/config narrowing does not apply; the machine's amem.config.json does.
  • Recap computes the current SessionStart brief only on its own panel snapshots, using machine policy. Curated and work inputs use bounded private scratch copies; capture is read-only, and no live reader lease is taken. It shows the full context, coverage, partial status, token upper bound and generation time; it is not a stored copy of a prior session's injected brief. The console has no repository binding, so the repository section is absent. No recap or new capture field is stored in the memory home.
  • Review actions are the CLI's. Accept runs the same acceptance as amem review accept and writes the same note, edit-then-accept is review walk's edit (the person's own note, then the candidate closed), reject is review reject. The console stamps the person as the actor (peer person). A privacy-screen hit refuses with a named reason and writes nothing. agent-mem keeps no reject reason, so a reason typed in the console is not recorded, and the reply says so.
  • Undo is offered for 10 seconds and stays bound to what that console just did: undoing an accept forgets the accepted note (an append-only forget op; a note a correction replaced shows again), undoing a reject makes the candidate pending again. A promotion has no undo. merge_into is not offered: the library has no merge operation.
  • Destructive forms (gc, capacity_apply, project_forget, decay_apply, prune) answer needsConfirm with the preview and changes[] and never write on the first call. The console's single-use confirm token carries the fingerprint back; the plugin recomputes the preview and writes only when it still matches. prune needs a generation home, as amem prune does.

Shell completion

Generate completion for zsh, bash, or fish with amem completion <shell>. Generation prints a script to stdout; it does not install files or edit shell configuration. The following commands configure shell completion.

zsh

mkdir -p ~/.zfunc
amem completion zsh > ~/.zfunc/_amem

Enable it by adding these lines to ~/.zshrc, placing fpath before your existing compinit call (use only one compinit call):

fpath=(~/.zfunc $fpath)
autoload -Uz compinit
compinit

Start a new shell. After upgrading amem, regenerate ~/.zfunc/_amem with the same command and start a new shell. To remove it, delete ~/.zfunc/_amem and start a new shell; remove the added fpath entry only if no other completions use that directory.

bash

mkdir -p ~/.local/share/bash-completion/completions
amem completion bash > ~/.local/share/bash-completion/completions/amem

Enable it by adding this line to ~/.bashrc:

source ~/.local/share/bash-completion/completions/amem

Source that file or start a new interactive Bash. After upgrading amem, regenerate it and source it again. To remove it, delete that file, remove its source line from ~/.bashrc, and run complete -r amem in the current shell (or start a new shell).

fish

mkdir -p ~/.config/fish/completions
amem completion fish > ~/.config/fish/completions/amem.fish

Fish loads this directory automatically. Start a new fish shell. After upgrading amem, regenerate the file and start a new shell. To remove it, delete ~/.config/fish/completions/amem.fish and start a new shell, or run complete -c amem -e to clear the current shell's definitions.

Manual Tab checks

These interactive checks remain unrun until a person checks them in each installed shell; automated syntax/candidate tests do not establish terminal behavior.

  • Type amem and press Tab: top-level commands appear.
  • Type amem goal and press Tab: goal subcommands appear.
  • Type amem list --tier and press Tab: short and long appear.
  • Type amem goal state <id> and press Tab: goal states appear.
  • Type amem import claude-mem --db and press Tab in a directory containing a filename with spaces: the path remains one argument.
  • Type amem completion and press Tab: zsh, bash, and fish appear.

Data directory

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.

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.

Capture tier and full-text search

From issue #13 (phase P1), the package carries a second, machine-local tier beside the curated store, born sharded (#15 T1): one capture shard per project key at projects/<key-hash>/machine/<machine-id>/ under the memory home, with its own locks so two projects never contend. The capture tier records deterministic tool-event rows (project, agent, event, tool, paths, exit status and a bounded, secret-screened excerpt) in the shard's capture.db. It is never synced, never read as curated evidence, and never writes a curated log, so its maintenance can never evict notes, goals or work lists. Capture is bounded by a soft size cap and a retention window (defaults 512 MB per shard, 90 days), resolved at every invocation from the library options, then the shard's config.json, then the home default capture-config.json; eviction removes the oldest capture rows first in bounded batches. The 2026-10-17 owner decision keeps the P1 cap per shard; the machine-wide total is #15 T5. On lock contention a writer publishes a complete immutable per-writer spool segment and exits fast; the next uncontended writer replays the spool idempotently. The capture path makes no network or model call. The pre-T1 machine-wide capture/ (an unreleased layout of this branch) is migrated per project on open and removed once empty.

Claude's PreToolUse, PostToolUse and PostToolUseFailure excerpts start with a deterministic tool-input line before the existing content. String values use this key order: command, pattern, query, url, description, prompt, skill, subject, then other string fields in sorted key order. Bulk content, old_string, new_string and edits are omitted. Private spans are removed before clipping; the line is at most 512 UTF-8 bytes and the combined tool excerpt is at most 2,000 bytes or the lower configured excerptBytes limit. Existing envelope, secret/PII and path screens remain in force; a refused tool excerpt drops the row. Legacy rows and non-tool/other-client shaping are unchanged.

Selected-project store.search and amem_search first reserve the existing unweighted result's non-capture cohort and capture slots. Only the capture slots are refilled: Stop and UserPromptSubmit receive a fixed 1.25x ordering weight, and only the best matching row per existing valid session hash is kept. A row without a hash is its own group. If distinct sessions run out, unused capture slots are omitted, yielding a shorter dense result while retaining the curated cohort and its relative order. Returned scores and explanations retain their unweighted priority values; the capture-slot ordering is separate. Collapsed rows remain accessible through timeline/show. The standalone captureSearch uses weighted session representatives too; --all-projects retains its existing separate merge. The SessionStart brief continues to use chronological capture pages, not this ranking or session collapse.

Project identity (#15 D2) keeps memoryProjectKey: a worktree, a submodule and the main checkout of one repository share its key; two unrelated same-named repositories without a remote get distinct path-hash keys, flagged local in the shard's project.json and the catalog. A no-remote key hashes the main working root (using the actual top-level for a separate git dir), so keys written from the repository root keep their pre-#15 value. Known limit: a linked worktree of a no-remote separate-git-dir repository gets its own key, because git does not record that repository's main working root (probe: alpha and alpha/sub share alpha@<hash>, alpha-wt gets alpha-wt@<hash>); the key stays as is for root-key compatibility, and a reviewed alias reconciles its capture. Keys written by older builds from a subdirectory or worktree still used that invocation's cwd hash; an explicitly written alias maps such a key to its canonical one for capture, work lists and the CLI and MCP project binding. Curated rows written under the old key keep their key in the log (nothing is rewritten), and the CLI and MCP read them under the canonical project: the project filter of notes, goals, candidates, vault candidates and work lists covers the canonical key and every key aliased to it, and presents those rows under the canonical key (#27). The library MemoryStore still reads by the raw key unless a host passes reconcile, and shared rows stay visible as before. Writes still go to the key's own log and shard, and a settle or forget of an aliased row lands in the shard that holds it. amem alias add <from-key> <to-key> appends an op to aliases.jsonl (latest wins, never chained, never automatic), amem alias remove <from-key> appends a removal (the old key reads under itself again, the history stays), amem alias list shows the ops including removals. Both append an alias operation without a general TTY gate. An amem or hub older than this change ignores a removal and keeps the old binding until it is upgraded, so upgrade every host that shares the home first. A correction accepted on an aliased row lives under the canonical key, so after a removal the old key shows the superseded note again (see docs/contracts.md). catalog.db in the memory home (#15 D3's storage part) is derived only from the shards and the alias log and rebuilds to the same rows; the D3 management commands are #15 T5 and not built.

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).

amem search <words...> ranks the current project's current curated notes with store.search (FTS5/BM25 plus tier and recency weights). Natural multi-word queries can match partial term sets: amem search lighthouse beacon can return a note containing only one of those terms. Add --filter to retain the previous behavior for current-project curated notes: every query word must appear as a substring, and results are newest first. --history, --as-of and an empty query retain the previous filter/newest-first path. Repository-scope notes remain a separately screened append to these results. --all-projects keeps its existing ranked path and cannot be combined with --filter. JSON output retains the existing note fields.

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 against ranked store.search and reports recall@k, hit@k and MRR. The ordinary curated project amem search path now uses that ranked search; --filter selects the legacy substring behavior. 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.

External retrieval measurements for MCP, library search and a plain BM25 baseline are recorded in LongMemEval S/M. The full datasets stay outside the repository; CI runs only synthetic harness fixtures.

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. The CLI previews capacity changes and writes only when the matching command is repeated with --confirm; an agent marker does not refuse the command. The read-only proposal is also available through amem_capacity. 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.

Repo scope (#15 T4)

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.

amem promote <id> --to repo --policy-free            # preview: the exact line that would be committed, and the repository's visibility
amem promote <id> --to repo --policy-free --confirm  # append it (private repository)
amem promote <id> --to repo --policy-free --confirm --confirm-public <id prefix>   # public or unverified repository: per entry
amem repo status                                     # the file, its entries, what the screen withheld
amem repo attributes [--confirm]                     # add `.amem/log.jsonl merge=union` to .gitattributes, only when you ask

The CLI previews the entry; --confirm appends it. 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.

Managing the project shards (#15 T5)

One catalog view across every project shard of the memory home. Commands retain their own preview, confirmation and reader-admission fences; agent markers do not trigger a general refusal and no general TTY gate remains:

amem projects [--all] [--json]            # key, state, aliases, capture and curated sizes, disk use; forgotten ones only with --all
amem usage [--limit N] [--all] [--json]   # totals and the largest shards
amem gc [--confirm] [--batches N]         # each shard's own retention and cap, in bounded batches; a preview without --confirm
amem project export <key> --to <file>     # one project's capture, notes, goals and work lists as one new 0600 bundle (never overwrites)
amem project archive|unarchive <key>      # take a cold project out of recall, or bring it back (append-only ops)
amem project forget <key> [--confirm]     # append-only record that takes one project out of recall and every listing for good; preview first
amem search --all-projects <words>        # the reply begins "crossed projects" and every hit names its project; --include-archived reads cold ones
amem vault-candidates [--all-projects | --project <key>]
amem capacity apply --split [--confirm]   # each shard's share of the global capture budget, written to its own config after a preview
  • 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 an append-only lifecycle 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.
  • 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.
  • 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.
  • 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.
  • 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).
  • amem lists vault candidates and never writes the vault.

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.

Claude Code hooks and SessionStart context

The package includes a local Claude Code plugin: .claude-plugin/plugin.json and hooks/hooks.json. Install the package as above, then explicitly load its package root in a person's Claude Code session. A checkout can be loaded directly:

claude --plugin-dir /absolute/path/to/agent-mem

Use the directory containing .claude-plugin/, hooks/ and bin/, not the global executable directory. These are setup instructions, not an installation performed by this repository. Package installation alone does not edit Claude settings, register the plugin or disable any existing memory integration. Check for duplicate capture hooks before opting in; the separate P5 cutover owns changes to existing integrations.

The packaged executable uses #!/usr/bin/env -S bun --smol, and plugin commands explicitly pass bun --smol. An explicit bun bin/amem.js hook ... invocation without that flag replaces its bootstrap with execve before loading hook stores/policy. That fallback keeps the same PID, stdin, preload arguments and original wall deadline; it does not add a third Bun process. Process helpers load on demand, and machine identity lives in a small shared leaf, avoiding eager curated-store loading in the hook parent/work-list path.

The plugin observes seven events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop and SessionEnd. They are deterministic capture/context hooks, never permission hooks: they cannot deny a tool call, replace its arguments or turn a capture failure into a native failure. Input is capped at 64 KiB. Malformed, private, secret-looking, denied-path or unavailable input is skipped, and hook errors exit successfully without a native decision. All seven events may append screened rows to the current project's machine-local capture shard; only SessionStart adds context.

The standalone brief prioritizes open work lists, recent curated notes and then recent capture; native SessionStart uses the narrower deadline-safe sources described below. The context starts with an untrusted-data warning and per-section complete, partial or unavailable coverage, followed by JSON records. Capture records carry evidence: false. The default context bound is 1,200 UTF-8 bytes, counted conservatively as a token upper bound, including the header. Unicode-safe display limits flatten control/line separators; memory text cannot introduce a new header or instruction frame. Full rows and composed work views pass standalone and any stricter host policy before selection; displayed projections are screened again. Canonical project scope includes reviewed aliases for curated notes and work lists, excludes foreign shared-project rows, and is rechecked with profile/selector bindings after asynchronous policy calls.

Before optional work starts, the parent screens the complete hook input and bounded normalized capture fields, then closes and atomically publishes a private immutable record in <memory-home>/hook-spool/. It retains normalized capture data and a physical-cwd/scope stamp, never the raw native envelope or native argument vector. Non-SessionStart events use the fixed Bun --smol worker in a short-lived owned process group. The worker resolves the current project, rechecks scope, drains by a stable receipt, and acknowledges only after append or capture-spool publication. The parent reaps it, and it has its own deadline. SessionStart computes context in the already-running CLI, avoiding a second Bun boot. Its native metadata commands have owned shell supervisors with the same remaining deadline, an armed self-watchdog and group cleanup even if the CLI dies. A timeout leaves accepted capture pending for later replay; unresolved pending records never enter context.

Pending capture has an ordinary soft tier of 256 files/2 MiB and a separate private reserve of 32 files/256 KiB, admitted before optional recovery. Replay is globally oldest-first across both tiers, at most 32 files/256 KiB per drain. A record replays only when its original non-null scope positively matches. Null scope is never adopted later, even with a physical cwd receipt: protecting project privacy takes priority over recovering an unattested event. Alias/profile/shard changes invalidate the derived identity cache and reader binding without changing pending scope. Positive fresh native project identity may still supply known-work context when pending scope is unknown; it does not authorize replay of those records.

A valid pending body is eligible for retirement only after at least seven days and current evidence that its scope is unknown/mismatched or its original cwd is positively absent. A transiently removed or unmounted cwd younger than seven days keeps its data. A positively drainable foreign-project record is retained even when older than seven days; another project's admission does not make it undrainable. Retirement removes the body only after persisting a reason/count metric (captureDrops and captureDropReason, with cwd_removed, undrainable_expired or scope_unknown); failed counter persistence leaves the body pending. No retired body becomes a replay/context source. Malformed publications can be removed through bounded owned-file checks. Explicit current-policy rejection also retains only a numeric private receipt. This bounded loss policy prevents permanently undrainable records from disabling capture, while never rebinding them to a new project.

If both replayable tiers fill, or SessionStart's reserve alone fills, the current event can be dropped. The library receipt reports dropped: true, dropReason: "spool_full", dropCount: 1, and the metric reports captureDrops: 1 with captureDropReason: "spool_full". Non-SessionStart native stdout remains empty. Native SessionStart emits only hookSpecificOutput, placing a bounded notice with reason counts from the last 200 metric samples after its initial untrusted-data header inside additionalContext, not in new top-level fields. These are rolling-window counts, not all-time totals. The header stays first even when the brief is otherwise unavailable, and the combined context stays within its byte bound. Finite-capacity and retirement losses are observable rather than reported as captured. Exact-once replay applies to positively bound retained records that can progress; unlimited lossless intake is not promised. Soft-cap overshoot, unpublished .writing/ leftovers and power-loss durability retain the contracts ceiling.

SessionStart prioritizes known open work and defers unsafe curated refresh with a fixed coverage reason. Native capture reads the newest 128 rows through an existing read-only time index and marks excluded older history partial, instead of rejecting a larger store. It never creates schema/FTS or repairs indexes. Missing/locked/incompatible sources are explicitly unavailable. The standalone sessionStartBrief() retains all its original sources. Recovery failure cannot discard a computed brief. Work, reader-lease and capture host ports use zero busy-wait and cooperative deadline checks; ordinary library callers retain their original defaults, including 3,000 ms waits. No watcher, resident worker, model/network call, embedding job or vault access is added. Host policy callbacks remain unsupported by runHook, while the standalone brief composes them normally.

SessionStart admits directly to the reserve with at most 32 metadata entries and performs no active-tier scan, retirement or replay. Non-SessionStart hooks maintain and drain both tiers. This is bounded reserve admission, not O(1) accounting; an atomic slot-reservation protocol is the upgrade. Parent brief storage still contains synchronous filesystem/SQLite work between cooperative deadline checks; asynchronous/deadline-aware storage remains the upgrade, not a larger handler limit. The R2 limits remain 150 ms p95 and 100 MB summed main/helper peak RSS, with a 140 ms handler budget. Functional tests construct a programmatic host-only deadline; the actual executable exposes no environment or CLI deadline extension. Its exec-only boot-clock handoff can only shorten the deadline and is cleared before hook import. Dedicated budget scripts retain the actual CLI, original sample counts, cold attempt 0 and timeout/reaper assertions. The P3 receipt identifies prior-head evidence separately from current acceptance; the current rework has no newly claimed both-OS green CI result.

A private admission cursor rotates bounded inspection across pending records (at most 32 files/256 KiB per pass), so a healthy first slice cannot indefinitely hide later removed/unknown entries from retirement checks. Git include/conditional identity dependencies are traversed within depth four and 16 files. Pending scope hashes relevant semantic project-identity values rather than unrelated full-config or HEAD churn; derived cache validity may additionally hash raw configuration/HEAD bytes. Unconditional and gitdir includes bind semantic identity values. Dynamic onbranch/hasconfig or unknown conditional trees without identity keys bind nothing; if they contain origin/rewrite/worktree identity keys, scope stays unknown. Native read-only identity may still supply known work, but it never adopts that pending capture; counted scope_unknown retirement needs seven days. This conservative ceiling is upgraded by evaluating onbranch against HEAD at drain time, not by adding a condition interpreter in this PR.

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.

SessionStart honors Claude Code's source field. On resume the earlier brief is already in the restored transcript, so the hook emits only the untrusted-data header line, plus the capture-drop notice when there is one, and no brief body. startup, clear and compact (compaction removed the earlier brief), as well as a missing or unknown source, keep the full brief. source is read, never stored. Only the native SessionStart hook follows this rule; sessionStartBrief(), amem brief, MCP tools and other clients are unchanged.

Hook capture rows carry two optional fields: session, a 16-hex hash of Claude Code's session_id (never the raw id or transcript path), and subagent: true when the event fired inside a subagent (its payload carries agent_id). The SessionStart brief and sessionStartBrief() leave subagent rows out; search, timeline and show keep them and show the subagent field. Older rows read unchanged. Field sources and the mixed-version rule are in the contracts.

Doctor and the sync cadence

amem doctor, amem status and amem setup print a readable report: an overall state, one labelled row per check (OK, ATTENTION, SKIPPED, UNAVAILABLE) and the next actions. Color appears only on an interactive terminal and is turned off by NO_COLOR or TERM=dumb. Add --json for the machine-readable object (for status and setup this was the default before #125). Setup also shows each available native client's version against its recorded layout verification version; the advisory probe uses a private staging home, reports unknown or unrecognised versions without blocking apply, and is skipped for Antigravity and undo.

Changed default (#134): amem ingest, amem migrate, amem prune, amem capacity apply (including --split) and amem vault-candidates now print readable states, labelled facts and next actions. Ingest uses that format only when stdout is a terminal; ingest stays JSON when stdout is not a terminal, because native hooks read it. New Codex, Kimi and Qwen hook registrations explicitly request --json. Pass --json for automation and saved artifacts; it retains the previous objects and serialization without ANSI, including compact JSON for ingest. Capacity preview and confirmation receipts use the selected format. Exit codes, capture-only ingest and policy screening are unchanged; migrate and prune retain reader-admission and split-marker fences.

Prune apply still requires a sealed JSON plan. Save amem prune --dry-run --cutoff <UTC> --json > plan.json, then use amem prune --apply --plan plan.json in the same project (keep --user on both commands if selecting the user shard). The readable preview is not a plan file. Capacity confirmation keeps the same arguments and adds --confirm.

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, Qwen included (a CLI with no hook is manual; a registered Codex hook that never captured points to the /hooks trust review), recent capture drops by reason (counts only, from the bounded metrics tail), 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.

Intake runs at the end of each Codex, Kimi, Pi and Qwen 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 a schedule you have authorized, call amem sync --if-due from the chosen shell or scheduler; it has no TTY gate. This release registers no sync schedule or hook, and the configured privacy and remote-visibility checks still apply. 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.

V1 split-home sync durably holds verified foreign-generation prunes with a "held, not applied" warning. The opt-in v2 path below applies eligible complete same-realm public prunes across generations after durable proof and root-map commitment. Unsupported historical lineage and multi-realm translations remain held; targets can remain visible and agent-mem#15 AC4 stays open (see the sync contract).

Native transcript intake

amem ingest reads existing Codex, Kimi, Pi or Qwen 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:

amem ingest codex --since 2026-10-01T00:00:00Z --json
amem ingest kimi --json
amem ingest pi --since 2026-10-01T00:00:00Z --json
amem ingest qwen --json

Discovery uses CODEX_HOME or ~/.codex (sessions/ and archived_sessions/), KIMI_CODE_HOME or ~/.kimi-code (sessions/**/wire.jsonl), and PI_CODING_AGENT_DIR or ~/.pi/agent (sessions/). It does not follow directory symlinks. A native record must carry a recognized schema/time and an observed absolute working-directory binding that resolves to this canonical project, including reviewed aliases. Unknown working directories, unsupported Kimi wire versions, missing timestamps and unavailable source roots are reported rather than guessed as an empty successful history. Foreign/private/secret rows are not imported. Full native envelopes and extracted capture inputs both pass current policy.

Qwen Code uses QWEN_RUNTIME_DIR, then QWEN_HOME, then ~/.qwen, with bounded discovery of projects/*/chats/*.jsonl. Each row proves its own project through cwd; a sanitized directory name is not an identity. The reader supports the verified 0.26.1 native envelope, an internal format rather than a public schema, and excludes thought/media content. For advanced.runtimeOutputDir, export the matching QWEN_RUNTIME_DIR; amem does not reproduce merged Qwen settings or read .env. See the Qwen capture contract for source pins, cursor upgrade behavior and limitations.

A receipt with reason: "unsupported_version" and a version (for example 0.27.0 for Qwen or 1.6 for Kimi) means the client wrote a native format version this amem does not read (newer or older than the supported ones): nothing from those records is captured. Qwen records are read again once an amem update supports that version, because the Qwen cursor is keyed to its decoder contract. schema_or_time still means a malformed or untimed record (#136).

Per-source cursor databases and a fair rotation cursor live in the capture shard's private ingest/ directory. Filenames and source identities are hashes; cursor contents retain offsets, anchor hashes, scope/protocol/cutoff and ownership metadata, not raw transcript paths or text. Capture receipts make replay idempotent across a crash between append and cursor acknowledgement. Repeating the command resumes its bounded tour instead of repeatedly favoring the first files. Broadening --since rechecks earlier history while existing capture receipts prevent duplicates. The JSON receipt reports files, read, imported, skipped, spooled, remaining, errors and an optional bounded reason; unavailable coverage is not silently represented by zero imported rows.

The default invocation reads at most 4 MiB and attempts 32 sources; trusted library options can raise those limits to 16 MiB and 128 sources. It examines at most 4,096 records, bounds a record at 256 KiB, and caps discovery at 4,096 directory entries/depth 8. Re-run a receipt with remaining: true after inspecting its reason; ingest creates no polling timer or watcher. Oversized records, including an unbroken record longer than the 4 MiB default window, are counted once and discarded through their next newline using a durable byte offset/skip flag. Later valid records remain reachable without retaining oversized content; an unfinished oversized tail stays pending until its newline arrives. The cursor assumes append-only native files and verifies file identity plus the last 1,024 acknowledged bytes. Historical rewrites outside that anchor are a declared ce