velesdb-memory

August 17, 2026 · View on GitHub

Every tool the velesdb-memory MCP server advertises, one section each.

This page is generated by hand from the server source, not from memory. The authoritative definitions live in crates/velesdb-memory/src/mcp.rs (memory tools) and crates/velesdb-memory/src/mcp/migration_tools.rs (online-migration tools), crates/velesdb-memory/src/mcp/context_tools.rs (context-compiler tools); the parameter shapes are in src/mcp/dto.rs and src/context/model.rs.

Which tools exist in which build

The tool surface is feature-gated at build time:

FeatureDefault?Tools it adds
mcpyesremember, recall, recall_where, recall_fused, feedback, relate, unrelate, forget, entity, why, remember_extracted, extraction_status, memory_status, list_memories, migration_start, migration_status, migration_cancel, migration_recover
contextyescompile_context, compile_transcript, explain_compilation, retrieve_context_source, context_savings, save_working_context, load_working_context, list_working_contexts, suggest_budget
embedder-httpyesnone — it enables the Ollama and OpenAI-compatible embedding backends
extractor-httpyesnone — it enables the HTTP backends remember_extracted needs (see that tool)
ollamanocompatibility alias for embedder-http
extractnocompatibility alias for extractor-http

default = ["mcp", "persistence", "context", "embedder-http", "extractor-http"] (the last two carry the HTTP backends for embedding and extraction — runtime-switched, off until their env vars opt in), so a plain cargo install velesdb-memory advertises all 27 tools. They are served by one MCP server: the context router is combined into the memory router in McpServer::new, never a second server.

By design the server exposes memory semantics only. It never exposes raw database capabilities (query, create_collection, upsert, traverse) — that boundary is what keeps local, embedded use inside the VelesDB Core License.

Scalar inputs on the wire

Every input slot advertises exactly one scalar type. Input unions are not published because degraded MCP harnesses flatten them into an untyped {}; the single advertised type is the form a schema-driven caller should send.

Some harnesses nevertheless JSON-encode a non-string scalar inside a string. For a lenient slot, the server accepts that string only when decoding it as JSON produces the same advertised scalar value: an integer slot may accept both 6 and "6", but still rejects "abc", "1.5", arrays, objects and booleans. Leniency is never applied to a genuine string slot, whose content must not be reinterpreted as JSON.

Invariant: same field name + same advertised scalar type means identical tolerance for the JSON-encoded string form. The live MCP schema test same_named_scalar_input_slots_share_wire_tolerance enforces this rule without maintaining a list of field names.

Rejection invariant: leniency never accepts an invalid JSON string or a value of another scalar or container type. The live test every_non_string_scalar_input_rejects_incompatible_forms checks every boolean, integer and number slot, including uniquely named fields.

Ids on the wire (read this before relaying any id)

Memory ids and fragment ids are u64 and routinely exceed 2532^{53}, where a JSON number silently loses precision in any JavaScript-based client.

  • Every tool that returns an id also returns a decimal-string twin: id_str, edge_id_str, ids_str, from_str/to_str, target_id_str. Relay the string form, not the number.
  • Every tool that accepts an id (relate/unrelate's from/to, forget's and feedback's id, remember's links[].target, explain_compilation's fragment_id, save_working_context's nested fragment_id/memory_id) accepts a JSON number or a decimal string. Its input schema advertises only the string: a client harness flattens a two-form type into "anything", which destroys the contract instead of publishing it, so the schema names the one form that survives a float-lossy client. The server still accepts both.
  • The compiler tools additionally take a per-request switch: policy.ids_as_strings: true rewrites every id field of that response (fragment_id, content_hash, memory_id, fragment_ids) into decimal strings. Default false, so existing clients see today's numeric response. load_working_context is the exception that needs no switch: it always answers in decimal strings, because it is the reading half of a round trip whose writing half (save_working_context) advertises only that form.

Memory tools

remember

Store a fact in durable local memory.

ParameterTypeRequiredNotes
factstringyesThe text to store. Capped at 2048 bytes (MAX_EMBEDDABLE_TEXT_BYTES) — roughly what the embedding model's context window holds; a longer fact is refused with its size, never silently truncated. (The wider 1 MiB MAX_FACT_BYTES allocation cap applies to remember_extracted's raw text and to stored context-compiler sources, not to a single fact.)
linksarray of {target, relation}noTyped edges created at write time; target accepts a number or a decimal string.
metadataobjectnoFree-form structured metadata for later filtering. Capped at 64 KiB serialized (MAX_METADATA_BYTES).
ttl_secondsintegernoDurable expiry that survives a restart. Omit for a permanent fact; falls back to the server's VELESDB_MEMORY_DEFAULT_TTL.

Returns { id, id_str }. The id is derived from the fact's content, so re-remembering identical text is idempotent — same id, updated in place.

When the server runs autograph through its async worker, edges derived from a remember land asynchronously: an entity or why read immediately after may not see them yet — the fact itself is always immediately readable.

remember { "fact": "we chose parking_lot to avoid lock poisoning",
           "metadata": { "project": "checkout" },
           "links":   [ { "target": "1234", "relation": "decided_in" } ],
           "ttl_seconds": 604800 }
→ { "id": 9876543210, "id_str": "9876543210" }

Automatic dating (_veles_date)

Every remember / remember_extracted call auto-stamps the fact's metadata with _veles_date — today's date read from the system clock, as a YYYYMMDD integer — unless the caller already set that key, in which case it is never overwritten. Temporal recall therefore works with zero setup:

// no metadata at all — still gets a date
remember { "fact": "we chose parking_lot to avoid lock poisoning" }
// stored metadata: { "_veles_date": 20260725 }   (today, auto-stamped)

recall_fused { "query": "why parking_lot", "date_field": "_veles_date" }
// → dated_context: "- [2026-07-25] we chose parking_lot ..."
//   now: "2026-07-25"

To date a fact retroactively (an incident that happened last month, not today), set _veles_date explicitly — an explicit value is always respected:

remember { "fact": "payment provider timeout set to 8s",
           "metadata": { "_veles_date": 20260610 } }

_veles_date behaves like ordinary metadata everywhere else: it round-trips through recall / recall_where / recall_fused results and works as a recall_where range filter ({ "field": "_veles_date", "op": "ge", "value": 20260101 }). It is the one key under the reserved _veles_ prefix a caller may set and read; every other _veles_* key is rejected on write and stripped from every read.

recall

Semantic (vector) retrieval with an optional exact-match metadata filter served by the ColumnStore.

ParameterTypeRequiredNotes
querystringyesNatural-language query.
kintegernoDefault 10, capped at 1000 (MAX_RECALL_LIMIT).
filterobjectnoExact-match metadata, e.g. {"project":"veles","status":"resolved"}.

Returns { memories: [ { id, id_str, score, content, metadata } ] }. Ranking blends similarity with each fact's learned confidence, so feedback changes future recall order; the returned score stays the raw similarity, never the blended value.

recall { "query": "billing retries", "k": 5, "filter": { "project": "checkout" } }
→ { "memories": [ { "id": 9876543210, "id_str": "9876543210", "score": 0.59, "content": "…" } ] }

recall_where

Semantic recall constrained by typed column predicates — ranges and comparisons, not just equality.

ParameterTypeRequiredNotes
querystringyesNatural-language query.
kintegernoDefault 10, capped at 1000.
filtersarray of {field, op, value}yesopeq, ne, lt, le, gt, ge. All predicates are ANDed.

recall_where returns { memories }, most similar first — the same envelope as recall, each memory carrying its id (id_str for float-lossy clients), content, score and metadata.

Comparisons are type-strict, with no runtime coercion. A filter value of 20230601 (a JSON number) never matches a fact stored as {"ts": "20230601"} (a JSON string) — same value, different JSON type, no match and no error. Store comparable values numerically at remember time.

Your own memories only. The store also holds internal scaffolding in the same collection: the entity hubs behind entity/why, and the context compiler's artefacts (stored sources, compilation events, working contexts and their per-project index). None of it is ever returned here, whatever the predicate. This is enforced by the engine, not implied by those facts being unfilterable: ne matches a fact that has no such field at all, and scaffolding carries none of your columns, so every ne predicate used to sweep all of it in.

recall_where { "query": "incidents",
               "filters": [ { "field": "_veles_date", "op": "ge", "value": 20260101 },
                            { "field": "_veles_date", "op": "le", "value": 20260131 } ] }

recall_fused

recall, plus a graph walk from the top vector hit that folds any connected fact into the ranking — the tri-engine path (vector similarity + ColumnStore filter + graph reach).

ParameterTypeRequiredNotes
querystringyesNatural-language query.
kintegernoDefault 10, capped at 1000. Multi-hop questions benefit from ~32–64; simple and temporal recall saturate early.
filterobjectnoExact-match metadata filter.
hopsintegernoGraph hops walked from the top vector hit (default 2, capped at 10).
graph_boostnumbernoWeight added to a graph-reached fact's normalised vector score (default 0.15).
poolintegernoDepth of the oversampled vector candidate pool fusion re-ranks before the k cutoff (default max(k × 8, 64), floored at 1, capped at 1000). Same knob as the Node/WASM pool and the Python options={"pool": …}.
date_fieldstringnoMetadata key holding each fact's YYYYMMDD date (e.g. the automatic _veles_date).

Returns { memories, dated_context?, now? }. dated_context (a chronological - [YYYY-MM-DD] content rendering, oldest first, undated facts last) and the now anchor appear only when date_field was set and at least one fact carries a date.

For recall, recall_where, and recall_fused, k is the advertised result-count parameter. The former limit spelling remains accepted as a deprecated wire alias for one compatibility version, but it is absent from the generated tool schemas. Sending both names is rejected.

relate

Create a typed, directional edge between two existing memories.

ParameterTypeRequiredNotes
frominteger or decimal stringyesThe memory the link points from.
tointeger or decimal stringyesThe memory the link points to.
relationstringyesLabel, read as from relation to (caused_by, depends_on, authored_by, supersedes, …).

Returns { edge_id, edge_id_str }. Idempotent per (from, relation, to): the id is derived from the triple, so a repeated call answers the edge already there rather than adding a parallel one.

Direction matters. Traversal follows OUTGOING edges only: point from at the memory you will later ask why about, and to at its evidence (decision → cause, fact → source). An edge pointing into a memory is invisible to why(that memory).

relate { "from": "9876543210", "to": "1234", "relation": "depends_on" }
→ { "edge_id": 42, "edge_id_str": "42" }

unrelate

Remove a typed edge — relate's exact undo, so a mistaken edge no longer costs the facts at its endpoints.

ParameterTypeRequiredNotes
frominteger or decimal stringyesThe side the link points from, exactly as given to relate.
tointeger or decimal stringyesThe side the link points to.
relationstringyesThe label of the link to remove.

Returns { found, removed }. Idempotent: removing an absent edge answers found: false instead of erroring, so a cleanup can be replayed safely. Only the edge is removed — the two memories, and any entity, are untouched (collecting an orphaned entity stays forget's job). It refuses exactly what relate refuses: an empty relation, and from equal to to.

Scope. The store does not distinguish a link created with relate from one auto-derived from a passage (remember_extracted, autograph), so unrelate removes both alike. To correct an auto-derived link, prefer forget + remember of the source fact — otherwise remembering the same passage again can rebuild the edge removed here.

unrelate { "from": "9876543210", "to": "1234", "relation": "depends_on" }
→ { "found": true, "removed": 1 }

forget

Permanently delete a memory by id, removing the fact and its graph links.

ParameterTypeRequiredNotes
idinteger or decimal stringyesAs returned by remember or recall.

Returns { id, id_str, found }. found: false means nothing was stored under that id — a stale id or a typo. That is a no-op, not an error, but it is reported distinctly from a real deletion. The deletion itself cannot be undone; for time-based expiry use remember's ttl_seconds instead.

entity

Look up everything the auto-built graph knows about one named entity: the attributes merged onto its node and the typed edges leaving it.

ParameterTypeRequiredNotes
namestringyesMatched case-insensitively (trimmed, lowercased).

Returns { found, id, id_str, name, attributes, relations, relations_in, relations_truncated, relations_in_truncated }. found: false means nothing has ever mentioned that entity; name always echoes the canonicalized queried name so parallel lookups stay pairable. relations are the typed edges leaving the entity; relations_in those pointing at it, each naming its source. Both matter, and reading only one loses half the graph: the store holds camille --soeur de--> theo, so "who is Theo's sister?" is answered by his relations_in, never by his relations. The bipartite mentions scaffolding is excluded from both.

Each direction is budget-bounded: at most 64 resolved edges (MAX_ENTITY_RELATIONS) found within a scan window of 4096 raw edges (MAX_ENTITY_SCAN_EDGES) — an entity mentioned by thousands of facts would otherwise be a constructible multi-megabyte response. A cut is REPORTED, not silent: relations_truncated / relations_in_truncated say when the matching list is a partial view, since a list holding exactly the cap is otherwise indistinguishable from a cut one.

With the async autograph worker active, edges derived from a remember land asynchronously, so an entity read immediately after that remember may not see them yet — the fact itself is always immediately readable.

entity { "name": "Theo Durand" }
→ { "found": true, "name": "theo durand", "attributes": { "age": 15 },
    "relations": [ { "predicate": "frere de", … } ],
    "relations_in": [ { "predicate": "soeur de", "source": "camille", … } ] }

why

The differentiator: find the best-matching memory and return the connected subgraph reachable from it through typed links.

ParameterTypeRequiredNotes
decisionstringyesThe decision or fact to explain.
max_hopsintegernoDefault 2 (DEFAULT_WHY_HOPS), capped at 10 (MAX_WHY_HOPS).
filterobjectnoExact-match metadata filter scoping the seed, e.g. {"project":"veles"}.

Returns { nodes: [ { id, id_str, content, hop } ], edges: [ { from, from_str, to, to_str, relation } ], truncated } — the seed is hop: 0.

The walk is width-bounded as well as depth-bounded: at most 64 outgoing edges are followed from any one node (MAX_WHY_NODE_DEGREE), at most 500 nodes (MAX_WHY_NODES) and 2000 edges (MAX_WHY_EDGES) are returned per walk. A walk that hits a budget SAYS so: truncated: true means a cap cut the walk before it exhausted the reachable graph — the signal counts alone cannot carry, a subgraph sitting exactly at a cap being indistinguishable from a complete one. The same budgets bound the graph half of recall_fused.

This is what a pure vector search cannot do: it surfaces the PR, the ticket, or the benchmark reachable through typed links even when they share no words with the question.

why { "decision": "why did we choose parking_lot", "max_hops": 2,
      "filter": { "project": "checkout" } }

feedback

Reinforce a recalled memory with an outcome.

ParameterTypeRequiredNotes
idinteger or decimal stringyesThe recalled memory to reinforce.
successbooleanyestrue = the fact was useful, false = it was noise.

Returns { id, id_str, confidence } — the fact's new learned confidence in [0, 1]. recall re-ranks by this confidence, so repeated feedback drifts useful facts up and noise down with no retraining. The compiler's policy.importance.confidence weight reads the same signal (see Context compiler).

remember_extracted

Durably accept a passage for background extraction and return before model generation. The worker extracts atomic facts and auto-builds the fact↔topic graph, so why can connect them with no manual relate.

ParameterTypeRequiredNotes
textstringyesRaw text. Capped at 1 MiB (MAX_FACT_BYTES).
metadataobjectnoApplied to every extracted fact.
extractorstringnoPer-call backend: outline, ollama, or openai. Omit to use the daemon default from VELESDB_MEMORY_EXTRACTOR. outline is always available; a remote name must match the backend configured when the daemon started.
idempotency_keystringnoRetry key, at most 256 bytes. The same key and payload reuse one durable job; the same key with a changed payload is rejected.

Returns an immediate { request_id, state, reused } receipt. A newly accepted job reports state: "accepted"; an identical retry may report the later persisted state and sets reused: true. Without an explicit key, an exact normalized request is still content-addressed and deduplicated.

Acceptance is durable: accepted and running records are recovered after a restart. The generated extraction is itself persisted before the first memory write, so a restart during graph storage replays stable facts and edges rather than asking the model for a second interpretation. If the process dies while the model is still generating—before an output exists to persist—that generation is retried on restart. At most 64 non-terminal jobs are admitted.

extraction_status

Read the durable result of remember_extracted.

ParameterTypeRequiredNotes
request_idstringyesThe 64-character lowercase hexadecimal id from the receipt.

Returns { request_id, state, ids, ids_str, skipped_over_cap, error }. state is one of accepted, running, committed, or failed. While work is pending, both id arrays are empty and the terminal fields are null. On committed, ids are in extraction order, ids_str are their u64-safe decimal twins, and skipped_over_cap counts facts the extractor produced but the store dropped for exceeding the 2048-byte embeddable-text cap. On failed, error explains the terminal failure.

Terminal snapshots retain the request fingerprint and result, but discard the source text, metadata, and generated extraction. This preserves retry idempotence without retaining an extra copy of the passage indefinitely.

memory_status

Report the server's health and configuration — the answers a user otherwise discovers only through degraded recall. Takes no parameters.

Returns { embedder, provenance, extraction, memory }:

  • embedder{ model, dimension, semantic }: what is RUNNING. semantic: false means the offline hash default — recall matches surface form, not meaning, and switching to a semantic embedder is an env-var change, never a rebuild. All three are null when the host embedded the server without declaring an identity.
  • provenance{ recorded, model, dimension }: what the store was FILLED by, per its on-disk record (#1751). recorded: false on a store predating the record; the mismatch check then degrades to dimension alone.
  • extraction{ configured, autograph_active, autograph_dropped }: configured says whether remember_extracted may omit its per-call extractor; explicit outline remains available when it is false. The two autograph fields report the background enrichment worker and its counted drops.
  • memory{ facts, edges }: corpus size. edges: 0 is the meaningful reading — nothing ever wired the graph, so why has nothing to walk and degrades to plain search. edges: null means the backend cannot count without materializing (not the same statement as 0).

Call it at session start, and whenever recall quality or why's evidence trails surprise you.

list_memories

Audit the store: walk every stored fact, page by page — the question recall structurally cannot answer, because recall ranks by resemblance to a query and what resembles nothing you thought to ask stays invisible.

ParameterTypeRequiredNotes
cursorinteger or decimal stringnoThe previous page's next_cursor. Omit to start the walk.
limitintegernoPage size (default 50, clamped server-side).
filterobjectnoKeep only facts whose metadata equals every given key. A filtered page may come back sparse — keep following next_cursor; the walk stays exhaustive.
include_internalbooleannoAlso list graph scaffolding and reserved _veles_* keys, verbatim. Default false.

Returns { memories, next_cursor }: memories entries carry { id, id_str, content, metadata }, ids ascending — two audits of the same store see the same order — with metadata under recall's visibility rule (business keys plus the auto-stamped _veles_date). next_cursor is a decimal string to pass back as cursor; null ends the walk. Ids exceed 2532^{53} — relay id_str.

For a full-store backup in one command — including a store whose configured embedder no longer matches, which the daemon refuses to SERVE but which stays yours to READ — use the CLI instead (stop the daemon first; it holds the store's single-writer lock):

velesdb-memory export --output memories.jsonl   # --include-internal for a verbatim backup

Default is opt-in at runtime. The backends are compiled into the default build. Set VELESDB_MEMORY_EXTRACTOR to choose the backend used when a call omits extractor; without that default, an explicit extractor: "outline" still works, while an omitted or unconfigured remote choice returns an actionable error rather than silently doing nothing. Configuration: MCP server setup → auto-extraction.


Online embedding migration tools

These four tools control one background migration owned by the daemon that already has the source open. They reuse the existing MCP transport and its authorization boundary. Credentials stay in environment-backed backend configuration and are never persisted. The full operator and recovery procedure is in Migrating embedding models.

Every response has { configured, job }. configured: false and job: null mean that the embedding host did not attach the daemon control plane. When a job exists, it contains:

  • its epoch_id, durable phase, running flag, target backend/model/ dimension and generated destination path;
  • cancellation_requested, last_error, and the mandatory recovery_action, when present;
  • progress: base fact/edge/batch counts, input/output watermarks, distinct dirty fact and edge-source counts, pending journal bytes, estimated pause, and measured cutover in milliseconds.

migration_start

Start one online embedding migration and return after its job, epoch identity, journal, and destination preparation are durable. The daemon continues serving the source in the background. migration_start returns { configured, job }.

ParameterTypeRequiredNotes
target_backendstringyesA backend configured in this daemon's environment, for example ollama, openai, or hash.
pause_budget_msintegeryesMaximum admitted and enforced request pause during cutover.
journal_max_bytesintegernoBounded journal capacity; default 64 MiB. A full journal refuses a source write before it becomes untracked.
fact_batchintegernoBase-copy batch size; default 256.
replay_batchintegernoMaximum dirty records replayed per batch; default 256.
edge_capintegernoMaximum outgoing edges synchronized per source id; default 4096.
observation_windowintegernoConsecutive samples used for convergence admission; default 3.
verification_reserve_msintegernoCutover budget reserved for final verification; default 100 ms.

Refuses an unsupported backend, invalid work limit, pre-existing generated destination, or another non-terminal migration. Poll migration_status after acceptance.

migration_status

Take no parameters. Read the durable job state and progress without performing migration work. phase is one of prepared, capturing, base_copied, catching_up, non_converging, cutover_ready, quiescing, activated, committed, or cancelled. migration_status returns { configured, job }.

migration_cancel

Take no parameters. Durably request cancellation while the source is still authoritative. A running worker observes it at the next bounded batch; a stopped job is cancelled immediately. Epoch identity and target provenance are verified before generated artifacts are removed. From quiescing onward the call refuses and reports the required recovery action. migration_cancel returns { configured, job }.

migration_recover

Take no parameters. Resume a stopped prepared, capturing, base_copied, catching_up, non_converging, or cutover_ready job after revalidating the target model, dimension, and vector witness. A quiescing or activated job must first follow startup's crash-safe cutover recovery; terminal jobs refuse. migration_recover returns { configured, job }.


Context-compiler tools

Full semantics — budgets, preservation rules, risk, retrieval handles, determinism — are in the Context compiler guide. The sections below are the wire reference.

compile_context

Compile context fragments into a token-budgeted, provenance-audited prompt context, deterministically and with no LLM call.

ParameterTypeRequiredNotes
querystringyesWhat the agent is working on — drives relevance scoring.
fragmentsarray of ContextFragmentyesAt most 1024 (MAX_FRAGMENTS).
token_budgetintegeryesHard ceiling on the assembled content. Clamped to 10,000,000 (MAX_TOKEN_BUDGET).
projectstringnoFacet recorded in provenance and used by the memory bridge.
target_modelstringnoSelects the pricing row for cost insights.
memory_scope{project?, k?, hops?, graph_boost?}noPulls relevant stored memories into the compilation.
policyCompilePolicynoPer-request policy override.

Each ContextFragment is { id?, content?, path?, kind?, priority?, metadata?, media? } — exactly one of path, non-empty content, or media per fragment. metadata is capped at 64 KiB serialized; metadata.verbatim: true forces preservation and metadata.cache: true puts the fragment in the stable cache prefix.

Returns { content, sections, decisions, sources, retrieval_handles, insights, risk, warnings }. risk is "low" / "medium" / "high"; "high" means preserve-classified content could not be packed — check it before using the output.

compile_context { "query": "state of the canary deploy",
                  "token_budget": 500,
                  "fragments": [
                    { "content": "The canary is green: 2% traffic, zero errors in the last 10 minutes." },
                    { "content": "Rollback runbook: kubectl rollout undo deployment/canary." } ] }
→ { "content": "…both fragments packed…", "decisions": [ /* 2 entries, "action": "preserve" */ ],
    "insights": { "tokens_in": 44, "tokens_out": 45, "tokens_saved": 0 }, "risk": "low" }

compile_transcript

One-call shortcut over compile_context for a raw agent-session transcript: it segments the transcript into turns and sub-turns first, so the caller does not have to hand-split it into fragments.

ParameterTypeRequiredNotes
querystringyesSame role as compile_context's query.
transcriptstringone ofInline transcript text.
pathstringone ofAbsolute path, same VELESDB_MEMORY_INGEST_ROOTS allowlist as a fragment path, capped at 8 MiB (MAX_TRANSCRIPT_BYTES).
token_budgetintegeryesAs compile_context.
project / target_model / policynoAs compile_context.
segmentation{format?, min_segment_bytes?, cache_system_turn?}noformatauto (default), plain, jsonl; min_segment_bytes default 256; cache_system_turn default true.

Exactly one of transcript or path must be set. Returns { context, segmentation } where context is byte-compatible with compile_context's output and segmentation is the audit trail (format_detected, one entry per segment with index, turn, role, kind, byte_start, byte_end, fragment_id, plus merged_segments).

Segmentation details (marker table, JSONL rules, edge cases) → Context compiler → transcripts.

explain_compilation

Answer "why was this fragment dropped, shortened, cached, or externalized?". Stateless: compilation is deterministic, so the request is simply re-compiled with event and source recording off.

ParameterTypeRequiredNotes
requestCompileRequestyesThe exact request to explain.
fragment_idstring (or integer)yesThe fragment whose decision to return. Relay the value compile_context handed you — under policy.ids_as_strings that is a decimal string, and this tool accepts it unchanged.
fragment_indexintegerno0-based position in request.fragments. Takes priority over fragment_id when given.

Returns one ContextDecision: { fragment_id, memory_id, action, rule_id, reason, relevance, risk, content_hash, handle? }. fragment_id is the content-derived id the decision was recorded under, and memory_id is set only when the fragment was pulled from memory rather than supplied inline.

Pass fragment_index when fragments may be byte-identical: they share a content-addressed fragment_id, so a plain id lookup always resolves to the deduplication survivor's decision, never a dropped twin's.

Two caveats. With a memory_scope, the re-compile recalls from current memory, so decisions about pulled memories reflect the store as it is now. A path fragment is re-read from disk, so the decision reflects the file's current content, not necessarily what the original call saw.

retrieve_context_source

Fetch back the exact original bytes behind a ctx://source/<hash> handle.

ParameterTypeRequiredNotes
handlestringyesA ctx://source/<hash> handle from a compiled context.

Returns { handle, content, media? }media is present whenever the original fragment carried an inline image, byte-identical to the submitted payload.

This is what makes "over budget" mean set aside, not lost: sources are cached for every distinct fragment, not just the externalized ones.

context_savings

Aggregate the recorded savings of past compile_context calls.

ParameterTypeRequiredNotes
projectstringnoRestrict the aggregation to one project facet.

Returns { events, tokens_in, tokens_out, tokens_saved, cost_saved_micros_by_currency, truncated }. cost_saved_micros_by_currency totals the saving per currency, in millionths of a unit, so no rounding happens before you read it. truncated: true means the sweep hit the recall cap. Figures are local estimates recorded per compilation — metadata only, never content — not a provider's billed count.

suggest_budget

Suggest a starting token_budget for a named target model.

ParameterTypeRequiredNotes
target_modelstringyesMatched case-insensitively against a static, committed table.
reserve_tokensintegernoRoom reserved for the response, subtracted from the window (default 0).

Returns { window, suggested_budget, source }. source always names the static table and its "as of" date — this never makes a network call. An unlisted model returns window: null and suggested_budget: null: an honest "unknown", never a guess.


Working-context tools (cross-session resumption)

save_working_context

Persist the session's distilled working state so a later session can pick it up instead of re-deriving it.

ParameterTypeRequiredNotes
projectstringyesSame facet convention as remember's project metadata.
sessionstringyesA stable id for the agent run you want to resume (e.g. a conversation id).
workingWorkingContextyesCapped at 1 MiB serialized.

WorkingContext is { goal?, active_constraints[], verified_facts[], open_hypotheses[], decisions[], exact_evidence[], pending_actions[] }.

Returns { id, id_str } — the stored system fact backing this context; relay id_str if you intend to forget it. Saving again under the same project + session replaces the previous state (idempotent upsert), and refreshes the entry in the project index rather than duplicating it.

load_working_context

Resume a session.

ParameterTypeRequiredNotes
projectstringyesThe facet it was saved under.
sessionstringyesThe session id it was saved under.

Returns { found, working, other_sessions }. found: false with working: null means nothing was ever saved under that exact pair — not an error, but check other_sessions: a similarly-named entry there usually means session was a typo rather than a genuinely fresh start.

Ids inside working (fragment_id, memory_id) come back as decimal strings, unconditionally — the exact bytes save_working_context accepts, so an agent can enrich what it loaded and save it back without converting anything. Relaying them as numbers would round every id past 2532^{53} on a float-lossy client, silently breaking the provenance trail of the very tool that exists to survive a lost session.

list_working_contexts

Discover what is resumable before guessing a session id.

ParameterTypeRequiredNotes
projectstringyesThe facet to list.

Returns { sessions: [ { session, saved_at } ] }, most-recently-saved first; saved_at is Unix seconds. Empty (not an error) when the project never saved anything.


remember returns a stable id derived from the fact's content. Pass it to relate or forget, or as a links[].target on a later remember — that is how the graph gets built, and what why traverses. There is no automatic link inference in the default build; remember_extracted is the opt-in way to have a local model wire edges for you.

The pattern this is designed for: at the end of a task, remember the decision with metadata (project, author, status) and a link to the PR or ticket. Days later, why("…") recovers not just the decision but the PR, ticket, and benchmark linked to it — where recall alone returns only look-alike text.

Forgetting and expiry. Facts are permanent by default. Delete one explicitly with forget. To make a fact self-expire, pass ttl_seconds to remember — a durable TTL persisted with the fact, so it survives a restart; expired facts stop being recalled. Set VELESDB_MEMORY_DEFAULT_TTL (seconds) to apply a default expiry to every fact that does not set its own. To wipe everything, delete the store directory at VELESDB_MEMORY_PATH.

Using the engine without MCP

The same tools are available as a library, with no MCP server in the path:

LanguageEntry point
RustMemoryService::remember / recall / relate / forget / whydocs.rs
Pythonfrom velesdb import MemoryService
Node.jsnpm install @wiscale/velesdb-memory-node

Per-language coverage of the context-compiler half is tabulated in Context compiler → where the compiler is available. A different, lower-level API — explicit semantic / episodic / procedural stores, with no why() graph walk and no MCP surface — is documented in the Agent Memory SDK guide.

Error model

Domain errors are mapped onto JSON-RPC through a transport-neutral category, so the MCP taxonomy cannot drift from the bindings':

CategoryJSON-RPC codeTypical causes
InvalidInput-32602 INVALID_PARAMSoversized fact/text, malformed filter, IngestDisabled, IngestOutsideRoots, IngestPath, ContextOverLimit
NotFound-32602 INVALID_PARAMSa missing id — JSON-RPC defines no "not found" code, and from the protocol's view a bad id is a bad parameter
Internal-32603 INTERNAL_ERRORstore faults, a panicked or cancelled tool task, remember_extracted without a configured backend

Last updated: 2026-08-09 · Applies to: velesdb-memory 0.13.0