Memory

Knowledge graph with SurrealDB-backed persistent memory.


The memory subsystem is the largest command surface in mx. It provides a persistent knowledge graph backed by SurrealDB (embedded SurrealKV or networked WebSocket), with categories, tags, resonance levels, embeddings for semantic search, relationships between entries, and a wake ritual for identity bootstrap.

Every entry in the graph has a unique ID (prefixed kn-), a category, a title, body content, optional tags, a resonance level (1–10+), and timestamps. Entries can be linked via typed relationships, anchored to each other by embedding similarity, and surfaced through keyword or semantic search.

NOTE: The database schema is applied automatically on every connection, in both embedded and network mode. All schema statements are idempotent (IF NOT EXISTS / UPSERT), so no manual setup is required. Set MX_SKIP_SCHEMA=1 to skip auto-apply in environments with restricted DB permissions. Run mx migrate to explicitly apply the schema (it ignores MX_SKIP_SCHEMA).

Table of contents

Adding entries

mx memory add

Create a new entry in the knowledge graph. At minimum, provide a category and title (or a --type for ephemeral facts, which auto-routes the category and generates a title from content).

Flags

Flag Type Description
--category string Category name (run mx memory categories list for valid names). Required unless --type is provided.
-t, --title string Entry title. Required unless --type is provided.
--content string Inline content. Conflicts with --file.
-f, --file path Read content from a file. Also accepts --content-file.
--tags string Comma-separated tags.
-a, --applicability string Comma-separated applicability contexts.
-p, --project string Source project ID.
--source-agent string Source agent ID. Defaults to MX_CURRENT_AGENT env var.
--source-type string Source type: manual, ram, cache, agent_session. Default: manual.
--entry-type string Entry type: primary, summary, synthesis. Default: primary.
--session-id string Session ID to associate with this entry.
--ephemeral flag Mark entry as ephemeral.
-d, --domain string Domain/subdomain path.
--content-type string Content type: text, code, config, data, binary. Default: text.
--private flag Mark as private (only visible to owner). Shorthand for --visibility private.
--visibility string Set visibility: public or private.
--owner string Explicit owner. Defaults to source_agent or MX_CURRENT_AGENT if private.
--resonance int Resonance level (1–10, or higher for transcendent).
--resonance-type string Resonance type: foundational, transformative, relational, operational, ephemeral, session.
--wake-phrase string Wake phrase for memory ritual verification.
--wake-phrases string Multiple wake phrases (comma-separated).
--wake-order int Custom wake order (lower = earlier in sequence).
--anchors string Comma-separated bloom IDs this entry connects to.
--type string Fact type for ephemeral knowledge: decision, insight, person, quote, thread_opened, commitment, thread_closed. Auto-routes category and sets resonance_type=ephemeral.
--session string Session to link fact to via EXTRACTED_FROM relationship. Requires --type.
--thread-id string Thread ID for thread_closed operations. Requires --type.
--no-auto-anchor flag Skip automatic anchor generation.
--no-embed flag Skip synchronous embedding generation on write.
--allow-duplicate flag Write through even if an identical (same session, same owner) entry already exists this session. Without this flag, a recased/repunctuated near-duplicate is skipped (exit 0) rather than written – see Write-boundary deduplication below.
--json flag Output as JSON.

Examples

mx memory add --category recipe --title "Retry with backoff" \
  --content "Use exponential backoff with jitter..." \
  --tags "reliability,networking" --source-agent whistledown
mx memory add --category discovery --title "SurrealDB needs explicit NS" \
  --content "Always set namespace before queries" \
  --resonance 7 --resonance-type operational
# Ephemeral fact (auto-routes category, generates title)
mx memory add --type decision \
  --content "Chose Typst over mdBook for docs" \
  --session abc-123
# Content from file
mx memory add --category ingredient -t "API reference" -f api-notes.md

TIP: When --type is provided, --category and --title become optional. The fact type routes to an appropriate category and generates a title from the content automatically.

mx memory add-batch

Add multiple entries in a single invocation from a JSONL file or stdin. Each line is a JSON object whose fields match mx memory add arguments (category, title, content, tags, source_agent, type, etc.). The store is opened once and the  435 MB embedding model is loaded once for the whole batch, so a 30-entry bulk caller pays one cold-load instead of thirty. Malformed lines are skipped and reported at the end; one bad entry does not abort the rest (partial-success semantics). A duplicate-skip (write-boundary dedup) is NOT a malformed-line failure – it counts toward “already saved,” not “failed.” Exits non-zero only when a line was malformed or the write itself errored; exits 0 when every line either wrote or was a legitimate duplicate-skip.

Recommended delivery: pass a JSONL file via --file when calling through a sudo wrapper (e.g. the hearth _secret-exec proxy) — a file path survives wrapper layers where a piped stdin may not.

Flags

Flag Type Description
-f, --file path Path to a JSONL file (one JSON object per line). Reads from stdin when omitted.
--no-embed flag Skip embedding for the whole batch. Defers to the next mx memory embed --all run (e.g. a nightly cron). Keyword and tag search are unaffected; only --semantic search misses un-embedded entries until then.

Examples

# Stdin pipe
printf '{"category":"insight","title":"T1","content":"C1","source_agent":"soren"}\n{"type":"decision","content":"chose Rust","source_agent":"soren"}\n' \
  | mx memory add-batch
# File (safer through sudo wrappers)
mx memory add-batch --file /tmp/pocket-entries.jsonl
# Skip embedding — defer to nightly embed --all
mx memory add-batch --file entries.jsonl --no-embed

NOTE: Each JSONL line for add-batch is a self-describing payload: include all per-entry fields (category, title, content, source_agent, tags, private, resonance, type, etc.) on each line. There are no batch-wide field overrides except --no-embed. This design supports heterogeneous batches (facts, person nodes, summaries, blooms) in a single invocation — which is the primary batch-write use-case. Per line, allow_duplicate: true mirrors --allow-duplicate on the single-add path – see below.

Write-boundary deduplication

All four new-entry write funnels – mx memory add (standard), mx memory add --type (fact-routing), and both lines of mx memory add-batch (standard and --type) – check a normalized dedup_hash of title+body (lowercase, ASCII punctuation and common Unicode punctuation stripped – smart quotes, en/em dash, horizontal ellipsis – whitespace collapsed) against the writing agent’s OWN entries in the SAME session AND SAME CATEGORY before writing. Category is enforced by scoping the candidate query, not by folding it into dedup_hash itself – identical title+body re-filed under a different category is a distinct fact (a different shelf), never a dedup candidate, so it always lands as its own entry. A regenerated duplicate that differs only by case, punctuation, or whitespace – the common shape when an LLM re-derives the same fact across turns – is detected and the write is SKIPPED rather than landing as a second row. Title and body are hashed as a length-prefixed pair, so a duplicate that shifts the split point (e.g. a sentence folded into the title one turn, split title+body the next) is still caught.

A skip is treated as success, not failure: it means the content is already durably saved, and there is nothing further to do.

  • Plain mode: Already saved as kn-XXXX (identical entry this session — nothing to do). Deliberately distinct from the success line (Added entry: kn-XXXX) – a caller regexing on Added entry: (kn-\S+) should switch to --json rather than assume this line matches that pattern.

  • --json mode: {"id": "kn-XXXX", "skipped": true, "duplicate_of": "kn-XXXX", "status": "already_persisted", "note": "..."}, with clean JSON on stdout (no other text) and exit code 0. id mirrors every success payload’s id key so a caller reading .id unconditionally never gets a silent null on a skip.

  • add-batch per-entry line: [N] Already saved: kn-XXXX (title) — identical this session, no action – never the bare words “skipped” or “duplicate” – and the batch summary carries its own K already saved (no action needed) category, separate from added and failed. add-batch has no --json mode, so this is stdout text only.

  • Pass --allow-duplicate (single add) or allow_duplicate: true (per JSONL line) to force the write through when a re-add is deliberate. Caveat: on the fact-routing paths (and on the standard path when title is byte-identical to an existing entry’s), the write’s id is derived from path_hint:title (generate_id), so a byte-identical forced re-add overwrites the SAME row in place (new updated_at, same id) instead of creating a second entry – --allow-duplicate only produces a genuinely distinct row for a recased/reworded near-duplicate, not an exact repeat. This is accepted, not fixed: perturbing the id on a forced write was considered and rejected (it would make --allow-duplicate non-idempotent in a different, more surprising way). If you need a second row for byte-identical content, vary the title.

  • On a candidate-lookup failure (a transient DB read blip), the gate FAILS OPEN: it warns to stderr (--json mode also gets a "dedup_lookup_warning" field) and proceeds with the write rather than aborting it. A dedup mechanism that already tolerates a check-then-write race between two concurrent writers tolerates a lookup blip the same way – the write always wins.

NOTE: Coverage boundary, read this before assuming full coverage: the gate catches recased/repunctuated re-adds within the SAME session, SAME owner, and SAME category. It does NOT catch: cross-session regenerations (a fact re-derived in a later wake under a different session_id – open question, not yet confirmed: the  20 double-write pairs (W342–W445) that motivated this feature have not been checked against this scope; if most of them are cross-session rather than same-session, this fix closes only a minority of the motivating class, and owner-scoped matching with a bounded time window would be the sharper mechanism – flagged for follow-up, not resolved here), reworded duplicates (different words, same meaning – e.g. “shipped” vs “published“), a public+owned entry vs. a later public+unowned entry with identical content (owner is a hard AND-conjoined predicate, so these two visible-to-everyone rows never dedup against each other even though both are public), identical title+body filed under a DIFFERENT category (category is a hard AND-conjoined predicate too – a re-file under a new category is treated as a distinct fact, deliberately, never a duplicate), identical title+body with DIFFERENT tags (tags are edge-modeled, not a scalar field on the row, and are deliberately excluded from the dedup identity entirely – a tag-only-differing re-add still dedupes, same as any other identical-content re-add), or writes with no session_id at all (dedup is bypassed entirely when session_id is absent – a bypass signal is emitted on EVERY funnel: "dedup": "bypassed_no_session" in --json mode on the standard single-add path (mutually exclusive with the stderr note – json mode never prints both), a stderr note on all four funnels in every other mode, so the bypass is never silent). Keep marking back captured entries; this is a store-boundary backstop, not a substitute for careful write discipline.

NOTE: In-process, best-effort – not a database-level uniqueness guarantee. The dedup index is read-then-write within a single mx process invocation (TOCTOU: two concurrent mx invocations can still both write the same normalized content). It is NOT wired to the legacy content_hash field (title-only, import-time change detection, unenforced) and there is no DEFINE INDEX ... UNIQUE backing it at the schema level – a persisted dedup_hash plus a compound unique index would close the TOCTOU gap and make the “store-boundary” framing literally true, at the cost of mapping a constraint violation to a skip; this is a design option for a future PR, not implemented here – and, per finding 1, that unique index must key on (session, owner, category, dedup_hash), not just (session, owner, dedup_hash), to match the same category scope this fix adds to the in-process gate. The candidate query has no result limit, so a very long-lived session pays an O(n) rehash of every row ever written to its (session, owner, category) group on every future write in that group – bounded in practice (memory sessions aren’t usually thousands of rows), a follow-up if it ever isn’t.

NOTE: The write boundary confirms content existence under a claimed owner. A duplicate_of hit tells the caller “an entry with this exact normalized content already exists under this owner” – bounded to the pre-existing owner-claim authz (a caller can already assert their own owner scope; this exposes no more than that), but it is a new confirmation surface worth naming, and it is sharper on add-batch: each JSONL line supplies its own source_agent/owner, so one batch invocation can probe many owner+content combinations at once. When more than one pre-existing entry under a group already shares a hash (only possible from data written before this gate existed), duplicate_of reports whichever one iteration order happens to surface last – not the earliest or a canonical row.

Reading entries

Shared filter flags

Several read commands (search, list) share a common set of filter flags. These are documented once here and referenced below.

Flag Type Description
-c, --category string Filter by category (comma-separated).
--json flag Output as JSON.
--mine flag Show only your private entries.
--include-private flag Include private entries (requires matching owner).
--min-resonance int Minimum resonance level. Filtered on the time-decayed effective resonance (see note below on the basis divergence with wake).
--max-resonance int Maximum resonance level. Filtered on the time-decayed effective resonance.
--has-wake-phrase flag Filter to entries WITH a wake phrase.
--missing-wake-phrase flag Filter to entries WITHOUT a wake phrase.
--has-anchors flag Filter to entries WITH anchors.
--missing-anchors flag Filter to entries WITHOUT anchors.
--has-resonance-type flag Filter to entries WITH a resonance type.
--missing-resonance-type flag Filter to entries WITHOUT a resonance type.
--limit int Limit number of results.
--tags string Filter by tags (comma-separated, matches any).
--exclude-tags string Comma-separated tag prefixes to drop (e.g. tier/ excludes every entry tagged tier/<anything>). Specify once; comma-separate multiple prefixes – repeating the flag is a parse error. Same parsing and prefix-match semantics as wake-fetch‘s --exclude-tags.

NOTE: Visibility default: list and search run public-only by default. Your own private entries are omitted unless you pass --include-private (or --mine, which shows only your private entries). When a public-only query matches private entries you own but hides them, mx prints a best-effort stderr nudge — see the note below on the hidden-private hint. (wake, by contrast, includes owned-private blooms in its cascade.)

NOTE: Hidden-private hint: When MX_CURRENT_AGENT is set and a public-only list/search query matches private entries you own, mx writes a hint to stderr pointing at --include-private, e.g.:

note: 3 private entries of yours matched but are hidden; use --include-private to see them

The hint is stderr only — it never touches stdout, --json output, or the exit code, and any error computing it is swallowed silently. It fires only in the public-only case: passing --include-private or --mine silences it (they already show your private entries), and it is suppressed under search --semantic because the underlying count uses the keyword (BM25) predicate, which does not agree with vector similarity.

NOTE: Resonance basis divergence (Issue #404): list/search --min-resonance and --max-resonance filter on the time-decayed effective resonance, whereas wake --min-resonance filters on the raw stored value. The same numeric threshold can therefore admit an entry under wake but exclude it under list/search once decay is applied. foundational and transformative resonance types are decay-exempt, so they filter identically under both. This divergence is intentional for now; an explicit --resonance-basis raw|decayed flag is tracked in #404.

NOTE: --exclude-tags takes a comma-separated list of tag prefixes. Any entry carrying at least one tag that prefix-matches at least one supplied value is dropped – useful for excluding whole tag namespaces. Empty segments from trailing commas are ignored. Pass the flag once; repeating it (e.g. --exclude-tags a --exclude-tags b) is a parse error, not a silent override.

mx memory show

Display a single entry by ID.

Flags

Flag Type Description
--json flag Output as JSON.
--content-only flag Output only the body content (useful for piping).

Examples

mx memory show kn-abc123
mx memory show kn-abc123 --content-only | pbcopy

mx memory list

List entries, optionally filtered by category, tags, resonance, and other shared filter flags.

Examples

mx memory list -c recipe
mx memory list -c discovery,decree --min-resonance 5
mx memory list --missing-wake-phrase --limit 20
# Exclude a whole tag namespace
mx memory list --category person --exclude-tags 'tier/'

NOTE: list accepts all shared filter flags documented above, including the public-only visibility default and the hidden-private stderr hint.

mx memory recent

List recent ephemeral facts with decay. By default shows only ephemeral entries from the last 10 days. Use --all-types to surface all resonance types.

Flags

Flag Type Description
--days int Number of days to look back. Default: 10.
--json flag Output as JSON.
--resonance-type string Filter by resonance type. Defaults to ephemeral only when --all-types is omitted.
--all-types flag Surface all resonance types instead of ephemeral only.
--sort enum Sort order: chronological (default) or resonance (highest first).
--limit int Maximum number of results. Default: 100.

Examples

mx memory recent
mx memory recent --days 30 --all-types --sort resonance
mx memory recent --resonance-type foundational --limit 10

Updating entries

mx memory update

Update an existing entry. Supports replacing content entirely, appending, prepending, find-and-replace, and modifying any metadata field. Content mutation modes are mutually exclusive.

Flags

Flag Type Description
-t, --title string Update the title.
--content string Replace content entirely (inline).
-f, --file path Replace content entirely from file.
--append-content string Append text to end of existing content.
--append-file path Append content from file to end.
--prepend-content string Prepend text to start of existing content.
--prepend-file path Prepend content from file to start.
--find string Find text in content (requires --replace).
--replace string Replace text found by --find.
--replace-all flag Replace all occurrences (with --find/--replace).
--nth int Replace only the Nth occurrence (1-indexed).
--category string Update category.
--tags string Replace all tags (comma-separated).
--add-tag string Add a single tag to existing tags.
--remove-tag string Remove a specific tag.
-a, --applicability string Update applicability (comma-separated, replaces all).
--content-type string Update content type.
--resonance int Update resonance level (1–10+).
--resonance-type string Update resonance type.
--anchors string Replace all anchors (comma-separated bloom IDs).
--add-anchor string Add a single anchor.
--remove-anchor string Remove a specific anchor.
--wake-phrase string Update wake phrase.
--wake-phrases string Replace all wake phrases (comma-separated).
--add-wake-phrase string Add a single wake phrase.
--remove-wake-phrase string Remove a specific wake phrase.
--wake-order string Update wake order. Use '-' to clear.
--private flag Mark as private (shorthand for --visibility private).
--visibility string Change visibility: public or private.
--owner string Update owner (only valid when visibility is private).
--session-id string Update session ID (for retrofitting entries with wrong or missing session linkage).
--force flag Force dangerous visibility changes (e.g., making blooms public).
--no-auto-anchor flag Skip automatic anchor generation.
--no-embed flag Skip synchronous embedding generation on write.
--json flag Output as JSON.

Examples

mx memory update kn-abc123 --title "Better title"
mx memory update kn-abc123 --add-tag reliability
mx memory update kn-abc123 --find "old text" --replace "new text"
mx memory update kn-abc123 --append-content "\n\nUpdate: confirmed working"
mx memory update kn-abc123 --resonance 8 --resonance-type foundational

mx memory edit

Find-and-replace shortcut. Equivalent to mx memory update <id> --find ... --replace ... with a simpler interface.

Flags

Flag Type Description
--find string Text to find in content. Also accepts --old.
--replace string Replacement text. Also accepts --new.
--replace-all flag Replace all occurrences (default: error if multiple matches).
--nth int Replace only the Nth occurrence (1-indexed).
--no-auto-anchor flag Skip automatic anchor generation.
--no-embed flag Skip synchronous embedding generation on write.
--json flag Output as JSON.

Examples

mx memory edit kn-abc123 --find "old pattern" --replace "new pattern"
mx memory edit kn-abc123 --old "v1" --new "v2" --replace-all

mx memory append

Append content to the end of an entry’s body. Shortcut for mx memory update <id> --append-content ....

Flags

Flag Type Description
--content string Content to append (omit to read from stdin).
-f, --file path Read content from file. Also accepts --content-file.
--no-auto-anchor flag Skip automatic anchor generation.
--no-embed flag Skip synchronous embedding generation on write.
--json flag Output as JSON.

Examples

mx memory append kn-abc123 --content "\n\nAdditional note here."
mx memory append kn-abc123 -f addendum.md

mx memory prepend

Prepend content to the start of an entry’s body. Shortcut for mx memory update <id> --prepend-content ....

Flags

Flag Type Description
--content string Content to prepend (omit to read from stdin).
-f, --file path Read content from file. Also accepts --content-file.
--no-auto-anchor flag Skip automatic anchor generation.
--no-embed flag Skip synchronous embedding generation on write.
--json flag Output as JSON.

Examples

mx memory prepend kn-abc123 --content "IMPORTANT: "

mx memory restore

Restore entry content from a backup. Use --list to see available backups before restoring.

Flags

Flag Type Description
--list flag List available backups instead of restoring.
--no-auto-anchor flag Skip automatic anchor generation.
--no-embed flag Skip synchronous embedding generation on write.
--json flag Output as JSON.

Examples

mx memory restore kn-abc123 --list
mx memory restore kn-abc123

Deleting entries

mx memory delete

Remove an entry from the knowledge graph.

Flags

Flag Type Description
--json flag Output as JSON.

Examples

mx memory delete kn-abc123

Wake system

The wake system provides identity bootstrap for agents. It retrieves high-resonance entries (“blooms”) and presents them through a cascade that reconnects the agent to its knowledge. The default output is a plain-text cascade; a token-based ritual flow is available for programmatic use.

mx memory wake

Wake up with resonant identity cascade. Retrieves high-resonance blooms and presents them in the requested format.

Flags

Flag Type Description
-l, --limit int Number of blooms to return. Default: 20.
--min-resonance int Minimum resonance threshold – get ALL blooms >= this value (overrides --limit). Filtered on the raw stored resonance, unlike list/search, which use the decayed effective value (Issue #404).
-d, --days int Include memories activated in last N days. Default: 7.
--no-activate flag Do not update activation counts.
--begin flag Start token-based wake ritual. Returns first bloom and session token.
--bloom-id string Bloom ID for --respond.
--respond string Submit your one guess for this bloom. Maximum 2000 characters.
--session string Session token for chained ritual (required with --respond).
--wake int Wake number recorded on every guess row (with --begin). Optional; mx keeps no counter of its own. Must be positive. Refused when another ritual already logged guesses under it.
--force-wake flag Begin under a --wake number another ritual already logged guesses under.
--model string Model identifier recorded on every guess row (with --begin). Optional; mx cannot discover it.
--include-excluded flag Include entries tagged archive or wake-exclude, which are kept out of the wake set by default.

Examples

# Default wake -- top 20 blooms, text output
mx memory wake
# All blooms with resonance >= 7
mx memory wake --min-resonance 7
# Token-based ritual (for non-TTY / programmatic use)
mx memory wake --begin --wake 7 --model some-model-id
mx memory wake --bloom-id kn-abc --respond "your guess" --session tok-xyz

NOTE: MX_CURRENT_AGENT must be set for wake to function. The wake system reads blooms ordered by resonance and wake order.

Wake modes

  • Default (mx memory wake): plain text cascade output, blooms listed with titles and content.

  • Token-based (--begin, --respond): stateless chained ritual for non-interactive environments. Start with --begin, then loop with --respond using the returned session token and bloom ID.

The ritual flow

Each chunk gets exactly one guess, made from the title alone, after which the entry is shown. There is no hint ladder and no second attempt.

--respond returns status: "shown" with a bucket of either unhinted (the guess string-matched one of the chunk’s phrases, and the tool had given no hint) or revealed (it did not). match carries the mechanical facts: kind is exact (identical to a phrase after trimming), close (matched only after normalizing case, punctuation or spelling) or none, and phrase_index says which phrase matched. The bucket names what the tool did, not what the responder knew.

The begin response and every respond payload echo wake and model. --begin refuses a wake number another session already logged guesses under (pass --force-wake to proceed anyway) and adds a warnings entry when the number is below the highest the agent has logged, or more than one above it.

A step that is already logged is answered from the log rather than judged again: the response carries replayed: true, the logged guess, bucket and match, and the current token and next. The first guess counts.

A caller whose response was lost retries with the token it spent and gets that response again, with replayed: true and the token it never received — however far the lost call moved the ritual, including when it judged nothing: a lost bloom_missing or chunk_truncated is answered with the same status (without bloom). Only the last call can be resumed this way. A token older than that is refused with a pointer to the last one that worked, and the pointer is true: the token from the last successful response is always either current or resumable.

The final response carries a summary of bucket counts split by phrase source (authored, derived, auto, unphrased), the number of chunks walked, and unjudged — the steps where no guess was judged. chunks always equals the bucket totals plus unjudged, so the difference never has to be worked out by subtraction.

Two statuses judge no guess, write no row, and omit bucket, guess and match: chunk_truncated, when the entry shrank past the session’s chunk cursor, and bloom_missing, when the entry was deleted mid-ritual. Both still advance the step, so the token the caller spent stops verifying, and both are counted in unjudged.

The ritual is walked by the agent that began it: MX_CURRENT_AGENT must equal the session’s agent, and a caller naming no agent is refused as well.

A guess is refused outright, with no row written and no advance, when it is longer than 2000 characters (counted in characters, and never truncated) or carries no alphanumeric content at all. A refused call can simply be retried with the same session token.

A session created by a version of mx older than the one-guess ritual is refused rather than continued. A completed session is kept, with completed_at set.

Naming the wrong entry is refused with {status: "error", error: "invalid_bloom_id", message, expected_id?} on stderr and exit code 1, like every other error.

The guess log

Every --respond that judges a guess writes one wake_guess row, keyed by session and step, in the same transaction as the session advance: the agent, wake number, model, entry, chunk, both positions in the sequence, the title as shown, the guess, the phrases it was matched against, the match, the bucket, and the SHA-256 of the chunk text. If the transaction fails, the respond call fails and neither the row nor the advance lands.

Guess rows are scoped to the writing agent and are not reachable through mx memory export, which reads the knowledge table only.

Scoring and reading the guess log

mx memory wake-log scores the log and reads it back. Every command works on the calling agent’s rows only (MX_CURRENT_AGENT, required) and filters by agent in every query.

mx memory wake-log score

Score every pending guess row (scored_at unset, including rows written before scoring existed). Loads the embedding model once, and not at all when nothing is pending. Its entire stdout is {"status":"scored","rows":N,"skipped":M}: no similarity value, guess or title, so a ritual driver can run it as its last step. A row that fails to score stays pending, counts in skipped, and is retried next run. If the model cannot be loaded the command exits non-zero with the error on stderr, prints nothing on stdout, and leaves every row pending.

Examples

mx memory wake-log score

Per row it stores the guess embedding and:

  • Axis A (tunable; never identity evidence). sim_phrase: the best cosine similarity to the row’s own authored phrase snapshot (none for derived, auto or unphrased). sim_content: the best similarity to the entry’s stored vectors under the same embedding model, the mean-pooled entry embedding and every embedding chunk. sim_title: similarity to the title, with the ritual’s own (Part N/M) suffix removed.

  • Axis B. sim_prior: similarity to the centroid of the 5 most recent earlier scored guesses on the same entry and the same shown title (suffix included), under the same embedding model, from other sessions; prior_n is how many there were. sim_prior_null: the mean similarity to the same kind of centroid for up to 10 other entries, those with the most recent history. The signal is the gap between the two, never sim_prior alone. The same three values are also stored over prior rows answered by the same model_id (*_same) and by a different known one (*_cross); a row with no model_id gets none of those. Axis B is empty on the first ritual: there is no history. The 5 and the 10 are defaults with no data behind them yet.

Rows are scored oldest first, each written before the next one is computed, so one run over many rituals stores what a run after every ritual would have. A skipped row is the exception: rows scored while it was pending are computed without it, and are not recomputed when it scores later.

Authored-phrase vectors are cached in wake_phrase_embedding, keyed by a SHA-256 of the embedding model and the whitespace-normalized phrase, with no phrase text stored, so a later ritual embeds only each row’s guess and title. A cache that cannot be read or written costs only time: the row still scores.

NOTE: Known limitation of sim_content: ritual chunks are cut by byte threshold and embedding chunks by token count. They do not line up, so sim_content is measured against the whole entry, not the specific ritual chunk the guess was made on. For single-chunk entries this makes no difference.

mx memory wake-log report

The Goodhart text, then bucket counts and both axes (medians) for one ritual. Bucket counts are split by phrase source, with an unphrased column for later chunks prompted with no phrase. An authored row past the first chunk, logged before #469, is marked leaked (a leaked field in JSON, after the bucket in text), counted in its own column instead of authored, and left out of sim_phrase. Terminal only; see below.

Flags

Flag Type Description
--wake int Wake to report. Default: the most recent wake with scored rows.
--out path Write to this file instead (JSON if it ends in .json, text otherwise) and print only its path.

Examples

mx memory wake-log report
mx memory wake-log report --wake 7 --out wake-7.json

mx memory wake-log bloom

One entry across rituals, newest first: wake, date, model, position, guess, bucket and the five similarity fields. The view for retuning a phrase. Terminal only.

Flags

Flag Type Description
<ID> string Entry ID (the kn- prefix is optional).
--limit int Maximum rows. Default: 20.
--out path Write to this file instead and print only its path.

Examples

mx memory wake-log bloom kn-abc

mx memory wake-log wake

Every row of one ritual in sequence order. Terminal only.

Flags

Flag Type Description
<N> int Wake number.
--out path Write to this file instead and print only its path.

Examples

mx memory wake-log wake 7

Every report starts with this text, and wake-log report --help carries it:

“Axis A rises by construction when wake phrases are retuned toward logged guesses. It measures how well the phrases fit what the model says. It is a tuning dial for phrase authoring and must never be reported as evidence of identity or memory. Axis B is the only number here that phrase edits cannot raise. Axis B is evidence of stable reaching, not of remembering.”

--begin, --respond and their replays never carry an axis value.

NOTE: The terminal-only rule. The scores must never reach the context of the model that made the guesses: a model that can see its score can play to it, and one that can read its own earlier guesses will repeat them. So report, bloom and wake print to stdout only when stdout is a terminal. Anywhere else they refuse with an error naming the rule, before reading anything, unless --out FILE is given. A model’s shell tool is not a terminal; a human shell is. An --out path that already exists must be a regular file, so /dev/stdout cannot route the output back onto a pipe. An --out that names the process’s own stdout or stderr is refused too, since a model’s shell tool may capture them in a file. In the text layout every control character and bidi control in a stored string is shown escaped (\n, \u{1b}, \u{202e}), so a guess cannot drive or reorder the terminal.

Be plain about what this guard is: it stops accidental and casual leakage, which is the realistic failure. It does not stop a process that deliberately fakes a terminal or reads back a file written with --out. The rest is convention: these three commands are not run during a ritual session, and logged guesses and scores are never quoted into any text a model reads. The tool cannot enforce that.

Wake set exclusion

Entries tagged archive or wake-exclude are kept out of every cascade layer by default, and --begin reports per-tag counts in its excluded field. The match is exact, so a tag that merely starts with archive is unaffected. --include-excluded turns the exclusion off.

The core layer never lets an exclusion cost a kept entry its place: it widens its query until it holds a full set of entries that survive the exclusion. The recent and bridge layers fetch double their quota, which absorbs the ordinary case but is not a guarantee — enough excluded entries in one layer can still leave the wake set short, and batch-archiving is how you would meet that, since a freshly tagged entry counts as recent for seven days.

Every layer counts only as far as its quota is filled, so an entry ranked below the wake set is never counted. The reported figure is an upper bound on the entries the exclusion kept out — an excluded entry displaces everything after it, so one reached only because an earlier exclusion pushed the window down is counted too, although it would not have made the cut untagged.

mx memory wake-fetch

Fetch facts for the wake ritual. Returns entries with resonance >= 3 across all types, sorted by resonance (highest first). Designed as a data source for wake ritual presentation.

Flags

Flag Type Description
--days int Number of days to look back. Default: 15.
--limit int Maximum number of results. Default: 100.
--exclude-tags string Comma-separated list of tag prefixes. Drops any entry that carries at least one tag prefix-matching at least one of these values – useful for excluding whole tag namespaces (e.g. project/ removes every entry tagged project/<anything>). Matching is OR across both the entry’s tags and the supplied prefixes. Empty segments from trailing commas are ignored.

Examples

mx memory wake-fetch
mx memory wake-fetch --days 30 --limit 50
# Exclude an entire tag namespace from the wake set
mx memory wake-fetch --exclude-tags 'project/'
# Exclude multiple namespaces at once
mx memory wake-fetch --exclude-tags 'project/,scratch/'

Embeddings and anchoring

Embeddings enable semantic search and automatic relationship discovery. Each entry can have a vector embedding generated from its title and content. Anchors are connections between entries discovered via embedding similarity.

Chunked embeddings

Entries longer than 400 tokens are automatically split into overlapping chunks before embedding. This ensures semantic search covers the full content of long entries, not just the first 400 tokens.

How it works:

  1. The entry’s embedding text (title + body/summary + tags) is tokenized using the BGE-Base-EN-v1.5 tokenizer.

  2. If the text fits within 400 tokens, a single embedding is generated and stored on the entry — exactly as before. No chunks are created.

  3. If the text exceeds 400 tokens, it is split into overlapping chunks with a sliding window: 400 tokens per chunk, 100-token overlap (stride 300).

  4. Each chunk is embedded separately and stored in the embedding_chunk table.

  5. A normalized mean vector of all chunk embeddings is stored on the entry’s embedding field for auto-anchor compatibility.

  6. The entry’s chunk_count field records how many chunks were created (0 for unchunked entries).

Semantic search with chunks:

When mx memory search --semantic runs, it queries both unchunked entry embeddings and chunk embeddings in parallel. Results are merged by taking the maximum similarity score per entry — if a chunk from entry X scores 0.92 and the entry’s mean vector scores 0.85, the entry’s final score is 0.92. This ensures long entries surface when any section is relevant, not just when the overall average is relevant.

TIP: Short entries (≤400 tokens) behave exactly as before — single embedding, no chunks, no behavior change. Chunking only activates for entries that exceed the 400-token threshold.

NOTE: The embedding_text() method on entries no longer truncates body content. The chunker handles length management, ensuring no content is lost during embedding.

mx memory embed

Generate a vector embedding for one or all entries. Embeddings power semantic search (--semantic flag on search) and automatic anchoring. Long entries (>400 tokens) are automatically split into overlapping chunks, with each chunk embedded separately. Short entries get a single embedding.

Flags

Flag Type Description
-a, --all flag Embed all knowledge entries (instead of a single ID).
--long-only int Only re-embed entries whose embedding_text() exceeds this many tokens. Entries at or below the threshold are skipped entirely. Use with --all. Useful for selectively re-embedding long entries that were previously truncated at a smaller token limit (e.g., 512).

Examples

mx memory embed kn-abc123
mx memory embed --all
# Re-embed only entries that exceed 512 tokens
mx memory embed --all --long-only 512

mx memory auto-anchor

Automatically add anchors between entries based on embedding similarity. Processes a single entry or all entries that have embeddings.

Also re-evaluates existing anchors: any anchor whose cosine similarity has fallen below the threshold (default 0.75) or risen above the near-duplicate ceiling (0.95) is pruned. This keeps the anchor graph self-cleaning – anchors that made sense once but no longer do are removed automatically.

Flags

Flag Type Description
--threshold float Minimum cosine similarity (0.0–1.0). Default: 0.75.
--max-anchors int Maximum anchors to add per entry. Default: 5.
--dry-run flag Preview changes without writing.
--detailed flag Show similarity scores in output.
--fill flag Only process entries with zero existing anchors. Fills gaps in the graph without touching already-anchored entries.

Examples

mx memory auto-anchor
mx memory auto-anchor kn-abc123 --threshold 0.8 --max-anchors 3
mx memory auto-anchor --dry-run --detailed
mx memory auto-anchor --fill

TIP: A typical workflow: run mx memory embed --all to generate embeddings, then mx memory auto-anchor --dry-run --detailed to preview anchor candidates, then mx memory auto-anchor to write them.

NOTE: Anchors are also maintained automatically on every write operation (add, update, edit, append, prepend, restore). After each write, mx re-evaluates anchors and prunes stale ones using the same similarity thresholds. Pass --no-auto-anchor on any of these commands to skip this step – useful for bulk operations or cleanup scripts where the overhead is unwanted.

NOTE: Deferred embedding: Pass --no-embed (or set MX_SKIP_WRITE_EMBED=1) on any write command (add, update, edit, append, prepend, restore, add-batch) to skip synchronous embedding generation on that write. The entry is written and fully durable immediately; only the vector embedding is deferred.

Trade-off: entries written with --no-embed are absent from --semantic (vector) search results until a later mx memory embed --all run fills the gap. Keyword search (mx memory search <query>) and tag-based filters are unaffected.

This flag exists to amortize the  435 MB embedding model cold-load: use it with add-batch (which does its own single hoisted embed pass at the end), or set MX_SKIP_WRITE_EMBED=1 globally in a deployment where a nightly mx memory embed --all (e.g. via Cinder’s embed.nix timer) keeps the graph fresh.

Relationships

Explicit typed edges between entries. While anchors are discovered automatically via embedding similarity, relationships are manually declared semantic connections.

mx memory relationships list

List all relationships for an entry.

Flags

Flag Type Description
--json flag Output as JSON.

Examples

mx memory relationships list kn-abc123

mx memory relationships add

Add a typed relationship between two entries. By default, the target entry (--to) is automatically reinforced by +1 (capped at 10) when the relationship is created – being linked to means the fact proved relevant. The contradicts and supersedes types are excluded from auto-reinforcement because boosting an outdated or contradicted entry works against intent.

Flags

Flag Type Description
--from string Source entry ID.
--to string Target entry ID.
--type string Relationship type: related, supersedes, extends, implements, contradicts.
--no-reinforce flag Skip automatic reinforcement of the target entry.

Examples

mx memory relationships add --from kn-abc --to kn-def --type extends
mx memory relationships add --from kn-abc --to kn-ghi --type supersedes
# Add a relationship without auto-reinforcing the target
mx memory relationships add --from kn-abc --to kn-def --type related --no-reinforce

mx memory relationships delete

Delete a relationship by its ID.

Examples

mx memory relationships delete rel-abc123

Seeding

Seed commands populate the knowledge graph from on-disk artifacts. Used for initial setup and bulk import.

mx memory seed agents

Seed agents from markdown files with YAML frontmatter. Reads from $MX_HOME/memory/seed/agents/ by default.

Flags

Flag Type Description
-p, --path path Path to agents directory. Defaults to $MX_HOME/memory/seed/agents/.

Examples

mx memory seed agents
mx memory seed agents --path /data/agents/

NOTE: Legacy fallback: if $MX_HOME/memory/seed/agents/ does not exist, mx checks $MX_HOME/agents/ and emits a stderr warning. This fallback will be removed in a future release.

mx memory seed knowledge

Seed knowledge from JSONL files. With no path, scans $MX_HOME/memory/seed/knowledge/*.jsonl and imports every file found. With a path, imports just that single file.

Examples

mx memory seed knowledge
mx memory seed knowledge /data/knowledge/bootstrap.jsonl

Health and statistics

mx memory stats

Show index statistics – entry counts, category breakdown, and other aggregate metrics.

Flags

Flag Type Description
--json flag Output as JSON.

Examples

mx memory stats
mx memory stats --json

mx memory health

Show graph health vitality percentages: embedding coverage, anchor coverage, and stale high-resonance entries.

Flags

Flag Type Description
--json flag Output as JSON (default format for dashboard consumers).

Examples

mx memory health
mx memory health --json

mx memory growth

Show per-week entry growth over the last 8 weeks.

Flags

Flag Type Description
--json flag Output as JSON array of 8 integers (oldest to newest).

Examples

mx memory growth
mx memory growth --json

mx memory open-threads

List open threads (category:thread entries with state=\"open\" or no state).

Flags

Flag Type Description
--json flag Output as JSON array (required for dashboard consumers).

Examples

mx memory open-threads
mx memory open-threads --json

Export

mx memory export

Export the entire knowledge database to a file or directory.

Flags

Flag Type Description
-f, --format string Output format: md, jsonl, csv. Default: md.
-o, --output path Output directory for md format (defaults to ./memory-export), or file for jsonl/csv (defaults to stdout).

Examples

mx memory export
mx memory export -f jsonl -o backup.jsonl
mx memory export -f csv -o entries.csv
mx memory export -f md -o /data/export/

Reinforcement

Reinforcement is the mechanism by which the knowledge graph breathes in – entries that are used, referenced, or linked gain resonance, counteracting the natural decay of the exhale. There are three reinforcement paths:

  1. Explicit reinforcement via mx memory reinforce – directly boost an entry’s resonance.

  2. Auto-reinforce on relationship creation – when mx memory relationships add links to a target entry, the target is reinforced by +1 (capped at 10). The contradicts and supersedes types are excluded. Use --no-reinforce to opt out.

  3. Search activation via mx memory search --activate – marks returned results as intentionally consumed, resetting their decay clock and incrementing their activation count.

mx memory reinforce

Reinforce a knowledge entry by incrementing its resonance, updating last_activated, and incrementing activation_count. Used to signal that an entry remains relevant.

Flags

Flag Type Description
--amount int Amount to increase resonance by. Default: 1.
--cap int Maximum resonance cap. Default: 10.
--json flag Output as JSON.

Examples

mx memory reinforce kn-abc123
mx memory reinforce kn-abc123 --amount 2 --cap 8

Metadata management

The knowledge graph has several registries for typed metadata. These commands manage the registries themselves – the types, categories, and agent identities that entries reference.

Agents

mx memory agents list

List all registered agents.

Flags

Flag Type Description
--json flag Output as JSON.

Examples

mx memory agents list

mx memory agents add

Register a new agent.

Flags

Flag Type Description
-d, --description string Agent description.
-D, --domain string Agent domain/responsibility.

Examples

mx memory agents add whistledown -d "Round-trip builder" -D "development"

mx memory agents show

Show details for a specific agent.

Examples

mx memory agents show whistledown

Projects

mx memory projects list

List all registered projects.

Flags

Flag Type Description
--json flag Output as JSON.

Examples

mx memory projects list

mx memory projects add

Register a new project.

Flags

Flag Type Description
--id string Unique project identifier.
--name string Human-readable project name.
--path path Local filesystem path to the project.
--repo-url string Git repository URL (e.g., owner/repo).
--description string Project description.

Examples

mx memory projects add --id mx --name "mx CLI" \
  --repo-url coryzibell/mx --path ~/recipes/coryzibell/mx

Categories

mx memory categories list

List all categories.

Flags

Flag Type Description
--json flag Output as JSON.

Examples

mx memory categories list

mx memory categories add

Add a new category.

Examples

mx memory categories add pitfall "Things that went wrong and why"

mx memory categories remove

Remove a category (only if no entries use it).

Examples

mx memory categories remove pitfall

Applicability

mx memory applicability list

List all applicability types.

Examples

mx memory applicability list

mx memory applicability add

Add a new applicability type.

Flags

Flag Type Description
--id string Unique identifier.
--description string Description of when this applicability applies.
--scope string Scope constraint (e.g., project, global).

Examples

mx memory applicability add --id rust-only \
  --description "Applies only to Rust projects" --scope project

Type registries

These are read-only registries listing the valid values for typed fields. Each supports list with an optional --json flag.

Command Lists valid values for
mx memory tags list Tags used across entries. Supports --category filter.
mx memory source-types list Source types (manual, ram, cache, agent_session).
mx memory entry-types list Entry types (primary, summary, synthesis).
mx memory session-types list Session types (e.g., development, review, exploration).
mx memory relationship-types list Relationship types (related, supersedes, extends, implements, contradicts).
mx memory content-types list Content types (text, code, config, data, binary).

All type registry list commands accept --json for structured output. tags list also accepts --category to filter tags to a specific category.

Session tracking

Sessions group entries created during a work period. Entries can be linked to sessions, and facts can be queried by their source session.

mx memory sessions list

List sessions, optionally filtered by project.

Flags

Flag Type Description
--project string Filter by project ID.
--json flag Output as JSON.

Examples

mx memory sessions list
mx memory sessions list --project mx

mx memory sessions create

Create a new session.

Flags

Flag Type Description
--session-type string Session type (e.g., development, review, exploration).
--project string Associated project ID.

Examples

mx memory sessions create --session-type development --project mx

mx memory sessions close

Close an active session.

Flags

Flag Type Description
--id string Session ID to close.

Examples

mx memory sessions close --id ses-abc123

mx memory for-session

List facts extracted from a specific session. The session ID can be provided with or without the kn- prefix.

Flags

Flag Type Description
--json flag Output as JSON.

Examples

mx memory for-session ses-abc123

mx memory fact-session

Get the session a fact was extracted from. The fact ID can be provided with or without the kn- prefix.

Flags

Flag Type Description
--json flag Output as JSON.

Examples

mx memory fact-session kn-abc123