Skip to Content
FeaturesHTTP source fidelity

HTTP source fidelity and recovery

The running server publishes its complete schema at /openapi.json and interactive reference at /docs. Configure per-user bearer credentials as described in the HTTP API reference, which also covers running the server and when it refuses a network bind. An authenticated owner can omit user_id; selecting another owner returns 403. Source/node lookups belonging to another user return 404.

Review identity proposals

GET /v1/alias-proposals?status=pending lists the authenticated owner’s decoded unverified alias proposals. Optional scope, status, and limit filters apply after owner isolation. Each item includes the complete proposal journal and its accepted or rejected review when present.

POST /v1/alias-proposals/{proposal_operation_id}/review accepts one decision:

{ "decision": "accepted", "reviewer_id": "human:catalog-owner", "reason": "The source catalog confirms one licensed product." }

Acceptance creates a verified traversable alias link while leaving both entity nodes active. Rejection requires a reason and creates no link. Identical retries return the first decision; conflicting decisions and stale assessed nodes return HTTP 409. The API never turns Jev advice into an automatic merge.

Store supplied memory

POST /v1/store accepts the Python store() fields: content, retrieval_content, value_bindings, role, speaker, user_id, session_id, node_type, scope, metadata, epistemic_type, source_type, confidence, event_time and ttl_days.

{ "content": "The telescope recorded the observation successfully.", "retrieval_content": "Telescope observation: success.", "role": "tool", "source_type": "tool_output", "epistemic_type": "observed", "session_id": "observation-42", "scope": "project", "event_time": "2024-03-10T07:30:00Z", "confidence": 0.81, "ttl_days": null, "metadata": {"instrument": "cobalt"} }

event_time requires a timezone offset. confidence must be finite and between zero and one. Omitting ttl_days uses the configured default for the node type; JSON null disables TTL, and a nonnegative integer overrides it. TTL is measured from node creation, not the historical event time. Omitted source/epistemic fields use the engine’s inference rules. Source classification describes provenance; it does not independently establish truth.

When retrieval_content is present, PRME retains content verbatim in the immutable event and uses the compact value for the graph node, vector and lexical indexes, retrieval results and packed model context. Omit it for the historical one-string behavior. The projection is recovered from the checksummed direct store journal and never regenerated after restart.

role is user (the memory’s owner, the default), participant (another human in the conversation), assistant, tool or system. Without an explicit source_type, user and participant sources are user_stated, assistant and system sources are system_inferred, and tool sources are tool_output. speaker optionally names who said it, such as each person in a conversation between two people:

{ "content": "I went to the support group yesterday.", "role": "participant", "speaker": "Caroline", "session_id": "chat-7", "event_time": "2023-06-09T19:55:00Z" }

The speaker is kept with the source event and its node under the reserved metadata.prme_speaker_v1 key. A request that sets that key in metadata returns HTTP 422, so remove it when copying metadata from a stored memory and pass speaker instead. The reader context format prints the name before the record text unless the text already begins with it, and the auditable format adds a speaker key. Leading and trailing whitespace is removed; an empty name, a name longer than 200 characters, or one with control characters, line breaks or bidirectional controls returns HTTP 422. The speaker is an unverified caller assertion, not an identity. It does not change the role or source type, and it is not a separate search field: a name written only in speaker is not matched by lexical search.

value_bindings can attach source-backed presentation values and distinct, caller-supplied lookup forms to a direct memory. Every presentation must occur verbatim in both content and the indexed text. Retrieval responses expose only bindings whose presentation survived context packing. See typed presentation and lookup values for the exact-only resolution rule and audit fields.

The response contains the immutable source event_id, its node_id when available, and processing_status. A completed status means the direct node and indexes were materialized. It is separate from the assertion’s epistemic classification and memory lifecycle. An index outage can leave a saved node with pending work; the response exposes that state.

Node reads retain session, event time, validity dates and TTL. Retrieval result items retain source classification, scope, session, event/validity dates and evidence_refs. Resolve those event UUIDs with GET /v1/events/{event_id} to read the original source, or /nodes below that event to inspect its derived nodes.

Extract memory from a message

POST /v1/ingest accepts content, role, speaker, user_id, session_id, metadata, scope, event_time and wait_for_extraction. role and speaker work as for POST /v1/store; the speaker stays with the source event and its raw note, not yet with the claims extracted from it. Extraction is asynchronous by default. Set wait_for_extraction: true to wait for the configured extraction pipeline. Inspect saved extraction work with GET /v1/events/{event_id}/extraction-status. For historical messages, provide event_time with a timezone offset, such as 2024-03-10T01:30:00-06:00. A phrase such as “yesterday” is resolved relative to that source time. The immutable ingestion timestamp still records when PRME received the message. Omit event_time to use ingestion time as the relative-date anchor. This does not change previously saved extraction plans or infer an unknown source date.

Both write endpoints reject unknown fields with HTTP 422. Earlier HTTP versions silently discarded extra fields; clients relying on that behavior must correct their payloads. The former namespace field was never applied and is now rejected. Scope and authenticated owner controls retain their documented meanings; metadata and session labels are not additional authorization boundaries.

Aggregate grounded quantities

POST /v1/quantities/aggregate accepts an owner and a structured QuantityAggregationQuery. It performs a complete owner-scoped scan for an unchanged store, revalidates every decimal against its claim and evidence, and never converts units. Optional predicate_prefixes match only the normalized predicate itself or an underscore-delimited suffix.

POST /v1/quantities/aggregate-text accepts {"user_id": "alice", "question": "How much did I raise?"}. This separate convenience route supports only a fixed set of complete, qualifier-free amount/count shapes. Its response always exposes the structured plan and assumptions and preserves the exact I or we subject. Unsupported wording returns plan.status="unsupported" and aggregation=null without scanning memory. Ordinary retrieval never auto-routes to either operation.

Recover an accepted request

If graph materialization or blocking extraction fails after source admission, the server returns HTTP 503 with a receipt such as:

{ "detail": "Source saved; processing did not complete. Inspect its status and retry the saved work.", "event_id": "11111111-1111-1111-1111-111111111111", "reason_code": "TimeoutError", "accepted": true }

The reason is a sanitized category; provider messages are not returned. Inspect the referenced source and work status instead of resubmitting the content, which would create another event. A receipt-read failure can occur after processing already completed, so check status before deciding whether work needs retrying. A general service-unavailable response without accepted: true is not this receipt. Network failures can still leave admission uncertain; these routes do not yet support client idempotency keys.

WorkInspectProcess or retry
Supplied node or raw-source indexesGET /v1/events/{event_id}/processing-statusPOST /v1/materializations/process
LLM extraction and its derived artifactsGET /v1/events/{event_id}/extraction-statusPOST /v1/events/{event_id}/retry-extraction, then POST /v1/extractions/process

For raw imports, POST /v1/ingest/fast accepts {"user_id": "...", "items": [...]}. Each item can set content, role, speaker, session_id, scope, metadata, and a timezone-aware event_time. The resolved owner applies to the complete ordered list, which is validated before I/O and committed with its repair jobs as one transaction. The response returns event_ids in input order and an accepted count. A UUID Idempotency-Key header binds the exact ordered inputs to the resolved owner. Retrying it returns the original IDs after restart; changed inputs return 409. Call the materialization processor until pending is zero; do not resubmit IDs whose admission already succeeded.

Materialization processing accepts {"budget_ms": 5000} and returns processed, pending and failed counts. With zero budget it only inspects counts. The budget is checked between operations; a started operation can exceed it. It processes one bounded batch for the authenticated owner, computes missing embeddings when needed, and never invokes LLM extraction. Repeat passes for remaining work after resolving persistent failures. Operator-mode callers must specify user_id.

Malformed UUIDs and invalid request fields produce HTTP 422 with structured validation details. Valid unknown or foreign identities return 404.

Correct an outdated claim

Store the corrected source first, resolve both event IDs to node IDs, then call POST /v1/supersedences:

{ "old_node_id": "11111111-1111-4111-8111-111111111111", "new_node_id": "22222222-2222-4222-8222-222222222222", "evidence_id": "33333333-3333-4333-8333-333333333333" }

The response returns the superseded old node followed by the active replacement. All nodes and optional evidence must belong to the authenticated owner and one scope. The state, edge, and checksummed audit record commit atomically. Sending the exact body again is safe after a timeout or restart; changing its evidence after publication returns 422.

PUT /v1/nodes/{node_id}/promote and PUT /v1/nodes/{node_id}/archive accept a UUID Idempotency-Key header. Reuse the same key after an ambiguous response; a matching retry returns the current node and a key reused for different lifecycle inputs returns 409. Without the header, repeated transitions keep the strict state-machine error behavior.

Save relevance judgments for future evaluated learning

Retrieval metrics include request_id and receipt_persisted. If the latter is true, GET /v1/retrievals/{request_id} reads the saved candidate score traces, content hashes, configuration and context membership through the authenticated owner. A logging failure does not fail retrieval; it sets the flag to false. Legacy requests without a receipt return 404.

POST /v1/retrieve accepts max_per_source as an optional positive integer. Candidates with the same exact nonempty evidence set and byte-identical content share that cap; later source groups fill the requested limit. Equal text from distinct events remains distinct. The response metrics report the applied value, and excluded candidates use reason source_limit.

max_per_evidence is the broader optional bound. Candidates with the same exact nonempty evidence set share this cap even when their text differs. Nodes without evidence are not grouped. Response metrics and receipts retain the applied value, and excluded candidates use reason evidence_limit.

Pipeline receipts explicitly retain packing ordering, context guidance, context format, episode-routing settings, and the configured current-update multiplier. With the default settings (rank fusion with a current-state recency boost and an event-time tie-break, the reader format, balanced ordering, a rank fusion session decay of 0.6 and folded repeated text) they use schema version 22, or version 19 with PRME_PACKING__FOLD_REPEATED_TEXT=false. Weighted retrievals (PRME_SCORING__FUSION=weighted) use version 12, or version 14 in the reader format (context_format="reader"), which also records context_citations; the bundle’s context_references maps any m3 style references to node IDs. Weighted retrievals with event-time recency (PRME_SCORING__RECENCY_TIME=event_time) use version 20 in any context format, which records that setting. Retrievals with session context packing (PRME_PACKING__SESSION_CONTEXT_PACKING, issue #86) use version 21 under either scoring formula, which records that setting. Retrievals that fold repeated text (PRME_PACKING__FOLD_REPEATED_TEXT=true) use version 22 under either scoring formula, which records that setting. Retrievals with rank fusion (PRME_SCORING__FUSION=rrf, the default) use version 16, which records fusion and rrf_k, formula version 2 score provenance, and each candidate’s semantic_relevance. Version 15 rank fusion receipts saved before semantic_relevance existed stay valid. With the rank fusion session decay (PRME_PACKING__SESSION_CONTEXT_RANK_FUSION_SCORE_DECAY, 0.6 by default) set, they use version 17, which also records that decay. Under rank fusion, POST /v1/retrieve returns 422 for non-neutral ranking_multipliers. A result’s score is then the rank-based fused score, which says where the result ranks among the candidates rather than how relevant it is, so min_score compares against each result’s semantic_relevance instead: the semantic cosine similarity of the memory behind it (for added session, episode or evidence context, the memory that pulled it in). Each result carries that value; weighted scoring omits it. A floor tuned on weighted scores does not carry over, and when a floor is set, a result with a low cosine is filtered out even if it is an exact keyword match. When vector search fails or detects an embedding model mismatch (VECTOR in metrics.backend_failures) and no result has a cosine, the floor is skipped instead of returning nothing: results and cross-scope hints keep their fused order, metrics.min_score_skipped is true, and the receipt uses version 18, which records min_score_skipped. With the rank fusion recency boost or event-time tie-break (PRME_SCORING__RRF_RECENCY_BOOST, 0.25 by default, and PRME_SCORING__RRF_TIE_BREAK, event_time by default) set, rank fusion receipts use version 19 instead, which records those settings as well as any session decay or skipped floor. min_score=0 keeps everything in both modes. Version 9 score provenance records any applied current_update operation and its exact coefficient. Version 10 retains the evidence-projection policy and replayable evidence_projection operations; versions 1–9 mean projection was disabled. Version 11 retains the mutually exclusive evidence-augmentation policy and its replayable score operations; versions 1–10 mean augmentation was disabled. Version 12 records the augmentation anchor policy; versions 1–11 mean all. Historical schema versions 4 and 5 introduced density/score and balanced ordering; version 6 introduced guidance, and version 7 introduced context format. Their score_provenance map contains applied weights, base features and ordered neural/session adjustments for each returned candidate; ranking_policy records the actual sorting rule. Python’s RetrievalReceipt.model_validate_json(...) and replay_ranking() validate and replay this saved ranking without the current graph. Version 1 and 2 receipt JSON and checksums remain unchanged and still support labels; version 1 cannot replay scores. These snapshots cover returned candidates only, not unseen retrieval candidates. Versions 1–7 mean episode routing was disabled and omit its three configuration fields when serialized. Versions 1–8 mean the separate current-update multiplier was disabled and omit it from configured and applied scoring weights. Versions 3 and later also include execution.parameters and execution.features, recording request filters/adjustments and reported model/environment identity. These fields do not establish that a remote model is pinned. The explicit ranking-adjustment trial argument is currently available through Python; HTTP reads its saved receipts.

POST /v1/relevance accepts this body after a user explicitly judges a result:

{ "feedback_id": "11111111-1111-4111-8111-111111111111", "request_id": "22222222-2222-4222-8222-222222222222", "labels": {"33333333-3333-4333-8333-333333333333": true}, "surface": "results", "method": "explicit_user" }

Use actual IDs from retrieval. Reusing feedback_id with the same judgment returns the original record and timestamp. Conflicting reuse or an unknown candidate returns 400. A missing or foreign receipt returns 404. Owner mismatches return 403. Non-boolean labels, empty labels and malformed UUIDs return 422. If the caller omits feedback_id, the server generates one; callers needing safe retries after a lost response should choose it before sending.

surface is results (returned candidates) or context (included bundle entries). Positive context labels require source content; reference-only entries do not qualify. method is explicit_user or structured_evaluation. These are supplied classifications, not verified identities. Unlabelled candidates are not negatives.

Read a record with GET /v1/relevance/{feedback_id} or list with GET /v1/relevance?limit=100&after_id=.... Pages sort by feedback UUID, not creation time; a concurrent insert can precede the cursor. Records retain the original exposure even after graph changes. Collection does not modify facts, change weights, or establish learned quality. The legacy global feedback tuner does not consume these records; evaluated scoped learning remains pending in RFC-0017.

Record answer-time memory citations

POST /v1/answer-citations records which content-bearing entries from one saved retrieval context supported an application answer:

{ "citation_id": "44444444-4444-4444-8444-444444444444", "request_id": "22222222-2222-4222-8222-222222222222", "answer_id": "assistant-message-42", "cited_node_ids": ["33333333-3333-4333-8333-333333333333"], "answer_sha256": "1f3c2b1a00000000000000000000000000000000000000000000000000000000", "method": "application_verified" }

answer_id is the caller’s stable answer reference. The optional digest binds the record to answer bytes without storing answer text. method is model_reported, application_verified, or human_verified. These names report how the citation set was obtained; the service does not independently inspect the answer. An empty cited_node_ids list explicitly records an answer with no memory citations and remains distinct from missing telemetry.

Every cited node must be a content-bearing entry in the saved context. A result that was omitted during packing, a reference-only entry, an unknown candidate, or a foreign receipt is rejected. Reusing citation_id with identical input returns the original record and timestamp; changed input returns 409. Missing or foreign receipts return 404, and owner mismatches return 403.

Read one record with GET /v1/answer-citations/{citation_id} or page owned records with GET /v1/answer-citations?limit=100&after_id=.... Records bind to the receipt and rendered-context checksums, and remain valid if a cited memory is later changed or archived. Citation telemetry does not mutate memories or ranking and does not establish that an uncited memory has zero causal value.

Confirming a memory

PUT /v1/nodes/{node_id}/reinforce accepts an optional UUID Idempotency-Key header and optional body {"evidence_id": "<event UUID>"}. Keep the same key, node and evidence for retries; changing the request under a used key returns 409. A key is scoped to the node’s owner within its memory namespace. The response contains the current node, including any changes made since the original call. Without a key, each call is a separate confirmation. See confirmation and retry semantics.

Consumer node and provenance filters

POST /v1/retrieve accepts filters.exclude_node_types and filters.source_types as enum lists. For example:

{ "query": "What should I know about my coverage?", "user_id": "member", "filters": { "exclude_node_types": ["note", "entity"], "source_types": ["user_stated"] } }

MCP memory_retrieve accepts the same lists as direct tool arguments. Filters apply in both retrieval modes, before result limiting and context packing, and to supplementary hints and expanded context. Omitting source_types allows all sources; [] returns none. An unknown enum is rejected. The filters do not classify question sentences or prove that pasted user text describes its owner; see the Python contract and limitations.


Source: docs/HTTP-API.md  in PRME at 58caddb1.