velesdb-memory
August 17, 2026 · View on GitHub
Every tool the
velesdb-memoryMCP 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.
- Getting the server running and wired to a client → MCP server setup
- The context compiler in depth (budgets, risk, handles, hooks) → Context compiler
- The embedded, language-native
AgentMemoryAPI (a different path, same engine) → Agent Memory SDK - Online and offline embedding migration → Migrating embedding models
Which tools exist in which build
The tool surface is feature-gated at build time:
| Feature | Default? | Tools it adds |
|---|---|---|
mcp | yes | remember, 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 |
context | yes | compile_context, compile_transcript, explain_compilation, retrieve_context_source, context_savings, save_working_context, load_working_context, list_working_contexts, suggest_budget |
embedder-http | yes | none — it enables the Ollama and OpenAI-compatible embedding backends |
extractor-http | yes | none — it enables the HTTP backends remember_extracted needs (see that tool) |
ollama | no | compatibility alias for embedder-http |
extract | no | compatibility 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 , 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'sfrom/to,forget's andfeedback'sid,remember'slinks[].target,explain_compilation'sfragment_id,save_working_context's nestedfragment_id/memory_id) accepts a JSON number or a decimal string. Its input schema advertises only the string: a client harness flattens a two-formtypeinto "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: truerewrites every id field of that response (fragment_id,content_hash,memory_id,fragment_ids) into decimal strings. Defaultfalse, so existing clients see today's numeric response.load_working_contextis 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
fact | string | yes | The 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.) |
links | array of {target, relation} | no | Typed edges created at write time; target accepts a number or a decimal string. |
metadata | object | no | Free-form structured metadata for later filtering. Capped at 64 KiB serialized (MAX_METADATA_BYTES). |
ttl_seconds | integer | no | Durable 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
query | string | yes | Natural-language query. |
k | integer | no | Default 10, capped at 1000 (MAX_RECALL_LIMIT). |
filter | object | no | Exact-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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
query | string | yes | Natural-language query. |
k | integer | no | Default 10, capped at 1000. |
filters | array of {field, op, value} | yes | op ∈ eq, 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).
| Parameter | Type | Required | Notes |
|---|---|---|---|
query | string | yes | Natural-language query. |
k | integer | no | Default 10, capped at 1000. Multi-hop questions benefit from ~32–64; simple and temporal recall saturate early. |
filter | object | no | Exact-match metadata filter. |
hops | integer | no | Graph hops walked from the top vector hit (default 2, capped at 10). |
graph_boost | number | no | Weight added to a graph-reached fact's normalised vector score (default 0.15). |
pool | integer | no | Depth 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_field | string | no | Metadata 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
from | integer or decimal string | yes | The memory the link points from. |
to | integer or decimal string | yes | The memory the link points to. |
relation | string | yes | Label, 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
from | integer or decimal string | yes | The side the link points from, exactly as given to relate. |
to | integer or decimal string | yes | The side the link points to. |
relation | string | yes | The 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
id | integer or decimal string | yes | As 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Matched 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
decision | string | yes | The decision or fact to explain. |
max_hops | integer | no | Default 2 (DEFAULT_WHY_HOPS), capped at 10 (MAX_WHY_HOPS). |
filter | object | no | Exact-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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
id | integer or decimal string | yes | The recalled memory to reinforce. |
success | boolean | yes | true = 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
text | string | yes | Raw text. Capped at 1 MiB (MAX_FACT_BYTES). |
metadata | object | no | Applied to every extracted fact. |
extractor | string | no | Per-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_key | string | no | Retry 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
request_id | string | yes | The 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: falsemeans the offlinehashdefault — recall matches surface form, not meaning, and switching to a semantic embedder is an env-var change, never a rebuild. All three arenullwhen 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: falseon a store predating the record; the mismatch check then degrades to dimension alone.extraction—{ configured, autograph_active, autograph_dropped }:configuredsays whetherremember_extractedmay omit its per-callextractor; explicitoutlineremains available when it isfalse. The two autograph fields report the background enrichment worker and its counted drops.memory—{ facts, edges }: corpus size.edges: 0is the meaningful reading — nothing ever wired the graph, sowhyhas nothing to walk and degrades to plain search.edges: nullmeans the backend cannot count without materializing (not the same statement as0).
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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
cursor | integer or decimal string | no | The previous page's next_cursor. Omit to start the walk. |
limit | integer | no | Page size (default 50, clamped server-side). |
filter | object | no | Keep only facts whose metadata equals every given key. A filtered page may come back sparse — keep following next_cursor; the walk stays exhaustive. |
include_internal | boolean | no | Also 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
— 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, durablephase,runningflag, target backend/model/ dimension and generated destination path; cancellation_requested,last_error, and the mandatoryrecovery_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 }.
| Parameter | Type | Required | Notes |
|---|---|---|---|
target_backend | string | yes | A backend configured in this daemon's environment, for example ollama, openai, or hash. |
pause_budget_ms | integer | yes | Maximum admitted and enforced request pause during cutover. |
journal_max_bytes | integer | no | Bounded journal capacity; default 64 MiB. A full journal refuses a source write before it becomes untracked. |
fact_batch | integer | no | Base-copy batch size; default 256. |
replay_batch | integer | no | Maximum dirty records replayed per batch; default 256. |
edge_cap | integer | no | Maximum outgoing edges synchronized per source id; default 4096. |
observation_window | integer | no | Consecutive samples used for convergence admission; default 3. |
verification_reserve_ms | integer | no | Cutover 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
query | string | yes | What the agent is working on — drives relevance scoring. |
fragments | array of ContextFragment | yes | At most 1024 (MAX_FRAGMENTS). |
token_budget | integer | yes | Hard ceiling on the assembled content. Clamped to 10,000,000 (MAX_TOKEN_BUDGET). |
project | string | no | Facet recorded in provenance and used by the memory bridge. |
target_model | string | no | Selects the pricing row for cost insights. |
memory_scope | {project?, k?, hops?, graph_boost?} | no | Pulls relevant stored memories into the compilation. |
policy | CompilePolicy | no | Per-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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
query | string | yes | Same role as compile_context's query. |
transcript | string | one of | Inline transcript text. |
path | string | one of | Absolute path, same VELESDB_MEMORY_INGEST_ROOTS allowlist as a fragment path, capped at 8 MiB (MAX_TRANSCRIPT_BYTES). |
token_budget | integer | yes | As compile_context. |
project / target_model / policy | no | As compile_context. | |
segmentation | {format?, min_segment_bytes?, cache_system_turn?} | no | format ∈ auto (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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
request | CompileRequest | yes | The exact request to explain. |
fragment_id | string (or integer) | yes | The 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_index | integer | no | 0-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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
handle | string | yes | A 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
project | string | no | Restrict 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
target_model | string | yes | Matched case-insensitively against a static, committed table. |
reserve_tokens | integer | no | Room 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
project | string | yes | Same facet convention as remember's project metadata. |
session | string | yes | A stable id for the agent run you want to resume (e.g. a conversation id). |
working | WorkingContext | yes | Capped 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
project | string | yes | The facet it was saved under. |
session | string | yes | The 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 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.
| Parameter | Type | Required | Notes |
|---|---|---|---|
project | string | yes | The 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.
Building the graph: ids, links, and the agent pattern
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:
| Language | Entry point |
|---|---|
| Rust | MemoryService::remember / recall / relate / forget / why — docs.rs |
| Python | from velesdb import MemoryService |
| Node.js | npm 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':
| Category | JSON-RPC code | Typical causes |
|---|---|---|
InvalidInput | -32602 INVALID_PARAMS | oversized fact/text, malformed filter, IngestDisabled, IngestOutsideRoots, IngestPath, ContextOverLimit |
NotFound | -32602 INVALID_PARAMS | a 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_ERROR | store 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