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 whistledownmx 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.mdTIP: 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-embedNOTE: 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 onAdded entry: (kn-\S+)should switch to--jsonrather than assume this line matches that pattern.--jsonmode:{"id": "kn-XXXX", "skipped": true, "duplicate_of": "kn-XXXX", "status": "already_persisted", "note": "..."}, with clean JSON on stdout (no other text) and exit code0.idmirrors every success payload’sidkey so a caller reading.idunconditionally never gets a silentnullon a skip.add-batchper-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 ownK already saved (no action needed)category, separate fromaddedandfailed.add-batchhas no--jsonmode, so this is stdout text only.Pass
--allow-duplicate(single add) orallow_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 whentitleis byte-identical to an existing entry’s), the write’s id is derived frompath_hint:title(generate_id), so a byte-identical forced re-add overwrites the SAME row in place (newupdated_at, same id) instead of creating a second entry –--allow-duplicateonly 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-duplicatenon-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 (
--jsonmode 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
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-abc123mx memory show kn-abc123 --content-only | pbcopymx memory list
List entries, optionally filtered by category, tags, resonance, and other shared filter flags.
Examples
mx memory list -c recipemx memory list -c discovery,decree --min-resonance 5mx 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 search
Search entries by keyword or semantic similarity. Keyword search
is the default; add --semantic to use vector
embeddings.
Flags
| Flag | Type | Description |
|---|---|---|
--semantic |
flag |
Use semantic (vector) search instead of keyword search. |
--activate |
flag |
Activate all returned results: resets
last_activated (decay clock) and increments
activation_count. Marks results as intentionally
consumed rather than just browsed. |
Examples
mx memory search "retry pattern"mx memory search "how to handle timeouts" --semanticmx memory search "agent bootstrap" -c recipe,method --limit 5# Search and activate results (mark as consumed)
mx memory search "retry pattern" --activate# Exclude a whole tag namespace from keyword search
mx memory search "onboarding" --exclude-tags 'tier/'# Exclude a whole tag namespace from semantic search
mx memory search "onboarding" --semantic --exclude-tags 'tier/'NOTE: search accepts all shared
filter flags, including the public-only visibility default and the
hidden-private stderr hint (the hint is suppressed under
--semantic). Semantic search requires entries to have
embeddings generated via mx memory embed.
NOTE: With --semantic,
--exclude-tags exclusion happens in two phases:
unchunked entries are excluded inside the candidate-set query itself
(SQL WHERE), while chunked entries are filtered on the
Rust side after hydration, since embedding_chunk has no
direct tag edge to filter on in SQL. Either way, excluded entries
are dropped before the final sort and truncate, so
--limit returns a full page as long as the search stays
within its scan budget (limit * 50, minimum 1000 chunk
candidates). If that budget is exhausted before enough non-excluded
results are found – which can happen when excluded tags dominate the
ranked candidates – the result set may come back short, and a
warning is logged when that happens.
TIP: By default, search does not activate
results – browsing is not the same as engagement. Use
--activate when you are intentionally consuming the
results (e.g., loading context for a task), not just exploring.
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 recentmx memory recent --days 30 --all-types --sort resonancemx memory recent --resonance-type foundational --limit 10Updating 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 reliabilitymx 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 foundationalmx 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-allmx 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.mdmx 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 --listmx memory restore kn-abc123Deleting 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-abc123Wake 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-xyzNOTE: 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--respondusing 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 scorePer row it stores the guess embedding and:
Axis A (tunable; never identity evidence).
sim_phrase: the best cosine similarity to the row’s ownauthoredphrase snapshot (none forderived,autoorunphrased).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_nis 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, neversim_prioralone. The same three values are also stored over prior rows answered by the samemodel_id(*_same) and by a different known one (*_cross); a row with nomodel_idgets 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 reportmx memory wake-log report --wake 7 --out wake-7.jsonmx 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-abcmx 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 7Every 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-fetchmx 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:
The entry’s embedding text (title + body/summary + tags) is tokenized using the BGE-Base-EN-v1.5 tokenizer.
If the text fits within 400 tokens, a single embedding is generated and stored on the entry — exactly as before. No chunks are created.
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).
Each chunk is embedded separately and stored in the
embedding_chunktable.A normalized mean vector of all chunk embeddings is stored on the entry’s
embeddingfield forauto-anchorcompatibility.The entry’s
chunk_countfield 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-abc123mx memory embed --all# Re-embed only entries that exceed 512 tokens
mx memory embed --all --long-only 512mx 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-anchormx memory auto-anchor kn-abc123 --threshold 0.8 --max-anchors 3mx memory auto-anchor --dry-run --detailedmx memory auto-anchor --fillTIP: 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-abc123mx 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 extendsmx 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-reinforcemx memory relationships delete
Delete a relationship by its ID.
Examples
mx memory relationships delete rel-abc123Seeding
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 agentsmx 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 knowledgemx memory seed knowledge /data/knowledge/bootstrap.jsonlHealth 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 statsmx memory stats --jsonmx 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 healthmx memory health --jsonmx 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 growthmx memory growth --jsonmx 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-threadsmx memory open-threads --jsonExport
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 exportmx memory export -f jsonl -o backup.jsonlmx memory export -f csv -o entries.csvmx 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:
Explicit reinforcement via
mx memory reinforce– directly boost an entry’s resonance.Auto-reinforce on relationship creation – when
mx memory relationships addlinks to a target entry, the target is reinforced by +1 (capped at 10). Thecontradictsandsupersedestypes are excluded. Use--no-reinforceto opt out.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-abc123mx memory reinforce kn-abc123 --amount 2 --cap 8Metadata 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 listmx 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 whistledownProjects
mx memory projects list
List all registered projects.
Flags
| Flag | Type | Description |
|---|---|---|
--json |
flag |
Output as JSON. |
Examples
mx memory projects listmx 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/mxCategories
mx memory categories list
List all categories.
Flags
| Flag | Type | Description |
|---|---|---|
--json |
flag |
Output as JSON. |
Examples
mx memory categories listmx 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 pitfallApplicability
mx memory applicability list
List all applicability types.
Examples
mx memory applicability listmx 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 projectType 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 listmx memory sessions list --project mxmx 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 mxmx memory sessions close
Close an active session.
Flags
| Flag | Type | Description |
|---|---|---|
--id |
string |
Session ID to close. |
Examples
mx memory sessions close --id ses-abc123mx 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-abc123mx 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