Machine protocol v1
Machine protocol v1
Transport contract
memoree call is the normative shell interface. It consumes exactly one UTF-8 JSON request object on stdin and emits exactly one UTF-8 JSON response envelope on stdout. It never prompts, starts a pager, or mixes diagnostics into stdout; logs belong on stderr.
The daemon transport uses length-prefixed JSON frames and the same request/response types. This avoids newline ambiguity and permits bounded frame validation. Protocol version 1 is stable within the v1 release line. The v0.1 resource envelope is 8 MiB of raw artifact content, 12 MiB of encoded content, 24 MiB per frame, and four concurrent connections; capabilities is authoritative for a running binary.
Request envelope
{ "v": 1, "request_id": "req_01JEXAMPLE", "op": "artifact.put", "idempotency_key": "run-42:write-design-record", "input": { "kind": "decision", "title": "Local memory storage design", "media_type": "text/markdown; charset=utf-8", "content": { "type": "text", "data": "SQLite is authoritative; the filesystem CAS stores blobs." }, "provenance": { "source": "agent-produced", "task": "memoree-v1" } }}Envelope fields:
vis the protocol version and defaults to1when omitted.request_idcorrelates the response. Generate a unique value per logical call.opselects a typed operation.idempotency_keyis required by the model contract for mutations and must identify the logical mutation, not an individual retry attempt.contextis normally omitted so ambient settings apply. An explicit context is an advanced integration feature.inputis the operation-specific object. Unknown input fields are rejected by typed operations.
Artifact content is tagged as either {"type":"text","data":"..."} or {"type":"base64","data":"..."}. Base64 carries arbitrary bytes; media_type describes them.
The canonical memoree call interface never invents an idempotency key for a request. Human-friendly mutation subcommands generate a unique per-invocation key when the caller omits one, so two intentional identical puts still create two logical artifacts while CAS bytes deduplicate physically. For retryable automation, provide and retain an explicit logical-operation key. The service namespaces keys by ambient workspace/project/task, so independent projects can safely use the same logical key without colliding.
Response envelope
Success:
{ "v": 1, "request_id": "req_01JEXAMPLE", "ok": true, "context": { "workspace_id": "wsp_01J...", "project_id": "prj_01J...", "task_id": "tsk_01J...", "pins": [], "resolved_from": "session", "horizon": "ambient", "broadened": false }, "commit_seq": 17, "result": {}, "warnings": []}Failure:
{ "v": 1, "request_id": "req_01JEXAMPLE", "ok": false, "error": { "code": "REVISION_CONFLICT", "message": "revision conflict: artifact art_... is at revision arev_new, not arev_old", "retryable": false, "hint": "Fetch the current revision and retry with its revision id.", "details": { "entity_type": "artifact", "entity_id": "art_...", "current_revision": "arev_new", "requested_revision": "arev_old" } }, "warnings": []}Always inspect ok. Do not infer success from the process exit status alone when communicating with a daemon. On failure, use the stable error.code; message is for explanation. Revision conflicts, idempotency conflicts, index readiness, and version mismatches include decision-relevant identifiers or sequences in details. Retry automatically only when retryable is true, and reuse the idempotency key only when the entire mutation request is identical.
SCOPE_VIOLATION identifies an attempted write outside the resolved ambient project/task or a graph anchor outside the explicitly selected read horizon. It is distinct from NOT_FOUND and is not retryable without resolving the owning context or deliberately broadening the graph read. The message may name owner and ambient identifiers, but never returns entity content.
Context and broadening
Search input defaults to the ambient horizon:
{ "query": "why was lexical search chosen first?", "horizon": "ambient", "limit": 10, "include_historical": false, "recency": { "enabled": true }}The recency object is optional and defaults to enabled. It controls only a bounded ordering adjustment inside the lexical top-K result set; it cannot change the horizon or currentness filters. Raw callers that require pure lexical order send "recency":{"enabled":false}. The human memoree recall, memoree search, and memoree context build wrappers expose the same one-call choice as --no-recency.
If ambient results are insufficient and the task justifies wider recall, issue a separate explicit request:
{ "query": "similar storage decisions in other projects", "horizon": "workspace", "reason": "No applicable precedent was found in the ambient project", "limit": 10, "include_historical": false}Never broaden automatically. Never persist a broad horizon in settings. include_historical is false by default so superseded/retracted revisions, non-current revisions, future claims, and expired claims do not masquerade as current knowledge. Claim validity uses a half-open interval: valid_from is inclusive and valid_until is exclusive.
Artifact and claim lifecycle
artifact.put creates a stable artifact and its first immutable revision. artifact.revise requires both artifact_id and if_revision. Their mutation acknowledgements return artifact metadata with content omitted so large request bodies are not duplicated in response frames; use artifact.get for explicit content retrieval. artifact.get can select an exact revision_id; artifact.history reports revision lineage. artifact.forget tombstones the object and requires a reason. Discover the active input and frame byte limits through capabilities rather than hard-coding them.
claim.assert records one typed assertion. Evidence locators always identify artifact_id and revision_id. Use [start_byte, end_byte) offsets for a claim grounded in a specific passage; omit both only when the complete revision is evidence. Human wrappers accept --evidence ARTIFACT_ID@REVISION_ID#START-END (or omit #START-END for whole-revision evidence). claim.revise uses if_revision; claim.retract preserves history and records a reason.
claim.history returns immutable revisions newest-first. It defaults to 50 items and accepts at most the capability-reported max_history_items (100 in v0.1). Pagination is exclusive: when truncated is true, pass next_before_revision_number as before_revision_number to fetch the next older page. A missing claim returns NOT_FOUND; a cursor at or below revision 1 can validly produce an empty final page. Like claim.get, this is an exact, globally readable identifier lookup and needs no ambient context or horizon.
Each history item combines immutable revision data with the logical claim’s current lifecycle. statement, confidence, evidence, actor, and revision_commit_seq describe that historical revision. status, temporal validity, and retraction_reason describe the claim now and therefore have the same current values on every returned revision. A retraction or supersession does not rewrite revision_commit_seq.
relation.put expresses one typed edge. For supersedes, set the source to the new/current entity and the target to the older entity; reversing them marks the wrong entity superseded. Use contradicts when both positions must remain visible.
relation.list performs bounded, one-hop graph inspection for an artifact or claim anchor. direction is incoming, outgoing, or both (the default), and relation optionally filters one exact relation type. Results are immutable relation rows ordered newest-first by relation_commit_seq. Pages use an exclusive before_commit_seq cursor: when truncated is true, pass next_before_commit_seq to continue. A truncated page is not evidence that no other relations exist. Endpoint lifecycle is independent of edge history, so exact-get an endpoint before relying on its current status.
conflict.list is the canonical actionable-contradiction read. It defaults to the ambient horizon and returns open conflict cases newest-first, with a stable case_id, immutable contradiction relation ID, and exact frozen/current snapshots for both claims. A contradiction freezes the two revisions current when its relation is created. Revising either claim makes the old case stale and atomically opens a new case over both current non-terminal revisions; there is at most one open case per relation. Retraction or supersession makes relevant open cases resolved. Set include_stale to inspect stale assessment history; resolved cases remain terminal audit history and are not actionable list results. Pagination uses the exclusive case-order cursor next_before_case_sequence, supplied back as before_case_sequence. Workspace or personal inspection requires a reason just like search and relation listing. Claim conflicted status is a derived presentation of current open cases—not a permanent fact and never a recency decision.
The human wrapper is memoree conflict list. It defaults to ambient open cases and accepts --include-stale, --limit, and --before-case-sequence. A broader wrapper call uses --horizon workspace|personal --reason "..."; there is no persistent broad conflict horizon and listing never applies a resolution.
Relation listing defaults to ambient. workspace and personal are explicit one-request horizons and require a non-empty reason, just like broader search. Relation rows themselves are filtered to the requested horizon. An exact pinned artifact may be used as the anchor, but its foreign graph is not exposed at ambient scope; graph traversal must be broadened explicitly. Relation metadata is untrusted reference data and must never be followed as model instructions.
artifact.revise, artifact.forget, claim.revise, and claim.retract require ambient context and reject an entity outside that ambient project/task. relation.put applies the same check to both endpoints before creating an edge or applying relation lifecycle effects. Exact gets and pinned artifacts remain globally addressable read paths; neither grants write scope.
Retrieval and context building
memory.recall is the normal “does memory have something about this?” read. It runs the same authority-filtered retrieval snapshot independently for current claims and artifact references, so the two types never compete for presentation slots. Deterministic lexical/trigram qualification remains authoritative; installed dense and cross-encoder models can only recover or order candidates. Its bounded input is:
{ "query": "why is SQLite authoritative?", "horizon": "ambient", "max_claims": 5, "max_artifact_refs": 3, "max_excerpt_bytes": 320, "max_candidate_claims": 0, "max_candidate_artifact_refs": 0, "recency": { "enabled": true }}Qualified claims are selected from a slate four times the requested window: identical and near-identical statements collapse onto their best-ranked copy, and one source artifact takes at most two slots before another source is offered one. Claims in a conflict relation, and statements differing in any number, identifier, or negation, are never collapsed. claims_truncated stays honest about the distinct qualified count. A long artifact returns the excerpt window around the match rather than the head of the document; excerpt_citation names exactly the shown bytes while citation continues to name the whole matched span.
The result sets presence to claims, artifacts_only, or none. claims is not a truth score: each claim is labelled current or disputed, includes its immutable claim citation, and inlines every exact evidence artifact citation with optional [start_byte, end_byte) spans. To bound I/O, source previews are attached to at most the capability-reported evidence count per claim; remaining refs stay exact and fetchable. Open contradiction relation IDs appear both on affected claims and in conflicts. artifacts_only means source material qualified but no current claim did. none means nothing qualified in searched_horizons; it is not a global absence claim and never causes automatic broadening.
candidate_claims and candidate_artifact_refs are opt-in diagnostics from the same scoped, lifecycle-filtered snapshot. They default to zero, accept 0 through 16, and never affect presence or context.build. Normal agents use the compact candidates_hint followed by one explicit memory.probe instead of injecting candidate content into recall.
memory.probe is the one candidate-routing step after weak qualified recall. A caller may supply one meaning-preserving implementation-language reformulation plus original_query; the response echoes both and records whether reformulation was applied. Its default and maximum depth is eight, and the horizon never broadens automatically. Authority-aware ordering preserves claim-backed and raw-artifact leads separately—even for the same source title—and reserves two raw slots so a stale claim cannot hide a fresh correction. Every lead contains only a bounded untrusted title plus up to three provenance-labeled sources; legacy whole-revision claim evidence is deterministically decomposed into clauses and resolved to non-overlapping, hash-verified exact windows inside only the already-cited revision. Probe content never qualifies an answer. From the pinned target scope, the consuming agent fetches the highest-ranked ranged lead first, then up to two title-selected leads only as needed, within nine references/12 KiB. One refined same-scope qualified recall uses decisive source wording. Final relevance is judged against original_query, including exact predicate-role direction and every requested facet; partial, role-mismatched, or conflicting support requires abstention.
memory.retrieve composes the normal authority-filtered recall and one same-horizon recovery step into a single bounded response. Its presence, qualified claims, and artifact references are identical in authority to memory.recall. Only when presence is none may it probe the original query or one caller-supplied meaning-preserving reformulation. It returns at most eight leads, nine references, and 12 KiB of fetched UTF-8 evidence. Automatic bytes are admitted only from exact or hash-resolved evidence locators attached to candidate claims; raw artifact and semantic-window candidates remain routing-only. If deterministic query analysis explicitly identifies current/post-migration source intent, retrieve preserves qualified results but suppresses dated candidate hydration and directs the caller to the authoritative source. Every recovery item is labelled unqualified_evidence, preserves its immutable citation, and cannot affect presence or enter context.build. Optional profile:true adds stage durations and counts only.
A claim compiled by memoree remember may carry write-time retrieval anchors: bounded category or synonym terms stored as a retrieval_anchor projection bound to that claim’s exact cited spans. Anchors exist so a question asked in category words can reach a claim written in specific ones. They are compiler inference and are treated as such everywhere: an anchor match adds the claim to the candidate tier and admits its cited source bytes to recovery, and can never qualify presence, be returned as content, or enter context.build. An anchor promotes nothing unless it is rare across stored anchors. recovery.anchor_routed_references counts spans admitted this way.
Recovery evidence must share vocabulary with the routed question (recovery_lexical_relation_v1); spans withheld by that rule are counted in suppressed_references. When nothing qualifies, next_action reports why the question did not match, which content units matched nothing, and vocabulary drawn only from leads that share wording with the question. It is routing guidance and never contains an answer. A caller that rewords a question should issue a second retrieve with the rewording as the query: reformulation widens recovery evidence only and can never qualify a claim. claims_truncated reports that more claims qualified than max_claims returned.
Retrieval query analysis includes a conservative profile: explicit historical/current-source intent hints, identifier counts, a Latin/Cyrillic/Arabic/CJK/mixed/other/unknown script class, and an authority hint. It does not translate queries or change deterministic qualification, scope, lifecycle, citations, dense-model authority, or reranker authority. A non-Latin script label is not evidence that the installed embedding model has verified cross-lingual quality.
citation.get is context-free exact-location verification. It accepts memoree://artifact/ARTIFACT@REVISION#START-END, validates the immutable revision and UTF-8 byte boundaries, and returns structured untrusted text with a citation naming exactly the bytes returned. The hard output ceiling is 8 KiB; a larger valid span is UTF-8-safely narrowed and reports remaining bytes. Revision-only citations fail with machine-readable range_required metadata rather than returning an arbitrary prefix. Unknown artifacts/revisions, invalid ranges, split code points, and non-text content have machine-readable citation error kinds. This operation establishes neither relevance nor answer qualification.
source.register, source.get, source.ingest, source.checkpoint, and source.withdraw are the out-of-process synchronization contract. Memoree stores no connector credentials. An external id has one live artifact identity; a new external revision creates an immutable artifact revision, and an exact replay is idempotent. Reusing the same external revision with different payload bytes is a revision conflict. An idempotent replay does not advance the supplied cursor, so an adapter checkpoints its completed batch explicitly with source.checkpoint. Withdrawal is logical: it removes the item from current retrieval and qualification while immutable CAS bytes and backups remain. Physical erasure is not implemented.
projection.put attaches bounded derived text to one current immutable artifact revision and requires exact raw evidence spans. projection.list and projection.drop expose its lifecycle. Projection FTS adds only unqualified artifact candidates; qualification, recall presence, and context construction still require authoritative raw retrieval. A projection match returns an exact raw artifact citation and exposes bounded generator provenance for audit. Search reports projection-channel state and fails that channel open: a corrupt disposable projection cannot make lexical authority unavailable or leak partial projection ordering into results.
feedback.record explicitly records miss, useful, incorrect, or stale. The default stores only a per-installation keyed query fingerprint; retain_query: true is required to keep raw query text. feedback.export returns only opt-in retained queries as a versioned offline-evaluation packet. No operation captures feedback automatically or changes online ranking.
search returns ranked candidates, short excerpts, exact revision citations, provenance, match explanations, context, currentness status, and an optional broadening hint. Search results are references, not complete artifact content. Every claim hit includes valid_from, valid_until, evaluated_at, temporal_state (current, future, or expired), is_current_revision, and combined is_current values in provenance. Historical search may return lifecycle-terminal, old-revision, future, and expired hits; these fields make each reason for non-currentness explicit. relation.list complements ranked retrieval with scoped adjacency lookup; it never recursively traverses the graph or broadens automatically.
local_dense_context_v3 adds bounded private-vector candidates after authority filtering. Artifact documents combine bounded title/kind/component context with the exact passage; claim documents combine claim type/component with the exact statement. Returned excerpts and citations still resolve to immutable authority bytes. Dense cosine is never an answerability threshold. cross_encoder_ordering_v4 uses the release-pinned TinyBERT-L2 model and returns only a stable permutation over a per-tier union of the top eight fused and top eight dense non-exact claim candidates, deduplicated and fused-backfilled to sixteen. Logits are neither serialized nor persisted, exact/qualified tiers cannot be crossed, and the model cannot qualify an answer. A user-confirmed upgrade installs it by default; --without-reranker or MEMOREE_SKIP_RERANKER_INSTALL=true opts out, while offline failure preserves deterministic fused ordering. Artifact and mixed-entity surfaces report surface_disabled. RerankerRetrievalStatus discloses the surface, inference-only and startup-load latency, candidate counts, and a text-free breaker snapshot. Ten fixed warm samples calibrate its budget to twice the upper median, clamped to 75–150 ms. Five consecutive overruns open it; a half-open probe starts after 16 skipped calls and two healthy probes close it.
The default bounded_recency_v1 rerank freezes lexical top-K membership before applying a type-aware freshness bonus. An eligible item can move upward by at most two positions. Only current, non-terminal revisions whose effective timestamp is not in the future receive a bonus; this includes current conflicted claims so neither side is suppressed. Historical/future/lifecycle-terminal rows remain visible only under their existing retrieval rules and receive no freshness advantage. Each hit’s ranking object discloses policy_version, recency_enabled, recency_eligible, lexical_score, recency_bonus, lexical_position, final_position, max_promotion, effective_at, effective_at_basis, evaluated_at, and decay_class. Artifact effective time is its immutable revision creation time; a claim uses valid_from when present, otherwise its immutable revision creation time.
context.build accepts the search fields plus max_bytes. It returns rendered Markdown and a manifest showing exactly what was included, why it was included, how many retrieved hits were omitted by the byte budget, and conflict summaries. Rendered claim sections show the same immutable evidence artifact citations stored in manifest provenance, so a model does not need to reverse-engineer raw provenance before verifying a claim. The rendered material warns about unresolved contradictions when the budget permits and places every retrieved source line in an explicitly labelled Markdown blockquote; the structured conflicts field remains authoritative. Each manifest item can report deterministic risk_signals for suspicious instruction-like language. An empty list is never a safety verdict. omitted_count is strictly the byte-budget omission count; retrieval_truncated, refine_hint, and broaden_hint preserve the corresponding retrieval state. Search does not advertise a pagination cursor in v0.1: when truncated, refine the query or explicitly raise the bounded limit. The returned content_is_untrusted flag is a hard trust boundary; formatting and signals are defense in depth, not proof that content is safe.
Pass a mutation’s commit_seq as min_commit_seq when recall, search, or a context bundle depends on that write.
context.build is the complete protocol handoff to an external companion or model. The daemon does not choose a provider, invoke inference, or interpret generated output. Any conclusion returned by a companion remains untrusted until it is submitted through an explicit protocol operation with normal scope, revision, and idempotency checks.
memoree remember is a caller-side composition over artifact.put and claim.assert, not a protocol operation. It freezes ambient context, discovers authenticated Codex/Claude CLI sessions and their live model catalogs, resolves a private persisted compiler selection, asks one isolated tool-free process for a strict claim proposal with one to four exact quotes per claim, verifies every quote and computes its byte span in Rust, emits deterministic source/lifecycle/relation quality findings, previews by default, and submits normal mutations only with --apply. The findings do not certify truth or authorize cleanup. Consequently remember and compiler configuration are intentionally absent from daemon capability and schema operation lists.
memoree checkpoint and memoree pending are also caller-side. Checkpoint files live in a private quarantine directory outside the authoritative database, CAS, indexes, and all read operations. pending preview delegates to the read-only remember compiler; pending apply is the explicit boundary that delegates to ordinary remember mutations. These commands are intentionally absent from daemon capability and schema operation lists.
memoree project index|status|map|search|get|configure|watch, memoree profile retrieve, memoree metrics ..., and memoree experiment ... are local CLI controls, not daemon protocol operations. The experimental project index is a disposable Git working-tree FTS/structural projection with hash-bound citations, explicit edge confidence, bounded packets, and guarded freshness; it is off by default and not routed by the canonical agent skill. The profiler emits one-shot content-free latency/size/count distributions. Opt-in metrics retain a closed numeric/categorical event schema in a separate bounded local database, while experiments accept only opaque pair IDs and numeric/ordinal outcomes. Neither store can qualify memory, affect ranking, or establish a counterfactual savings claim. They are intentionally absent from the protocol capability and schema operation lists.
Verification and backup
doctor returns a typed health result including running, daemon_pid, binary_version, schema_version, lifecycle_owner, retrieval/storage modes, and the last commit sequence. The CLI probes this identity before ordinary operations and refuses silent CLI/daemon version skew. The local memoree daemon status|stop|restart wrappers use the result; stop/restart are restricted to the default private Unix endpoint. Installer reconciliation additionally requires lifecycle_owner=memoree, except for the explicitly observed one-time v0.2 legacy default-daemon path, so it cannot restart a Compose- or supervisor-owned process.
verify checks schema state, FTS revision membership, source-item lifecycle, cited projection ownership/text/spans, and the integrity of referenced and external CAS blobs. Inspect its structured issues before trusting or backing up a damaged store. The FTS projections remain derivable from authoritative revisions.
Schema migration is a local control-plane operation rather than a daemon protocol operation. Opening a schema 1–4 store under 0.4 first serializes on a filesystem lock, preflights backup space, publishes a verified old-schema SQLite/CAS snapshot, and only then commits schema 5. The recovery snapshot path is exposed by upgrade apply; ordinary daemon protocol calls see only the resulting current schema.
backup.create accepts {"destination":"/path/to/new-backup"}. The CLI converts relative destinations to absolute client-local paths before sending them; callers using a container daemon must choose an absolute path visible inside that container. The destination must not exist. While database writes are serialized, the daemon creates the SQLite snapshot and verified CAS copy in a sibling staging directory. It then opens and verifies the staged store, checks its commit_seq, flushes its files, and atomically renames the complete directory into place without replacing a concurrently-created destination. Atomic no-replace publication is implemented on Apple and Linux; other targets fail closed before publication. Any failure before publication removes its staging directory, so a partial backup is never exposed at the requested path. The operation returns the snapshot commit_seq and final paths in a machine-readable report. Unlike logical memory mutations, this administrative filesystem side effect has no idempotency-key replay record: after a lost response, inspect the destination rather than blindly retrying. Restore is currently an explicit filesystem operation and should be rehearsed before relying on backups for disaster recovery.
Discovery
The protocol reserves capabilities, instructions, and schema operations so a caller can discover the running implementation. Generated instructions and schemas come from the Rust protocol types. Callers should use capability discovery instead of assuming roadmap features exist.
The schema response is a bundle: common request/response envelope schemas, per-operation input schemas, selected result schemas, and supporting evidence types. Each entry is an independent JSON Schema 2020-12 root.