Read a context in one memory snapshot
POST /api/v1/memory/records/batch reads 1–100 distinct memory IDs in one
authorized scope. All requested records and their transitive sources use the
same storage snapshot and server evaluation time. This feature is available in
the batch-read feature build; discover the route in the server's /openapi.json
before using it. Earlier 0.10.0 images do not provide it.
Use this endpoint when constructing or revalidating a context made from several memories. It replaces one HTTP read per memory with one request for the group. It does not search, generate embeddings, create a cache, or advance consumer progress. Ranking versions and existing individual reads remain unchanged.
Request current records#
Use a current bearer credential with memory_read and an exact resource grant.
Tenant and the subject of private memory come from the credential. A batch cannot
mix projects, missions, agents, tenants, or private subjects.
curl --fail-with-body 'http://localhost:7474/api/v1/memory/records/batch' \
--header "Authorization: Bearer $QILBEE_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"contract_version": 1,
"scope": {
"project_id": "research",
"mission_id": "mission-42",
"agent_id": "researcher",
"visibility": "private"
},
"record_ids": [
"15d5c731-4e35-443c-9309-06a6561314e9",
"31cb94eb-9233-41cf-82f1-ea42dfc7d260"
]
}'
Set mission_id to null for missionless memory. IDs are parsed as UUIDs;
different textual spellings of the same UUID still count as duplicates. Unknown
request fields, duplicate IDs, an empty list, or more than 100 IDs are rejected.
Interpret every entry#
HTTP 200 contains contract_version, the requested scope, and batch:
| Field | Meaning |
|---|---|
evaluated_at_millis |
One server timestamp, in Unix milliseconds, used for eligibility throughout the batch |
entries |
Exactly one entry per requested UUID, preserving request order |
entries[].record_id |
The requested UUID, including when unavailable |
entries[].record |
The complete current MemoryRecord, or null |
record_bytes |
Serialized root-record bytes examined, including unavailable roots; excludes indexes and dependency reads |
dependency_work |
Aggregate distinct dependency reads and serialized bytes across this request |
A non-null record has the same ID as its entry and satisfies the same eligibility
rules as an individual current read.
Deleted, expired, rejected, missing, or otherwise ineligible records return null.
Invalid transitive dependencies also make a record unavailable. A UUID from
another authorized namespace is never looked up outside the requested namespace.
The response gives no per-record unavailability reason; work counters describe
aggregate work, including ineligible roots. Review explanations still require
their separate authorization.
For example, two unavailable IDs produce two entries, not an empty success list:
{
"contract_version": 1,
"scope": {
"project_id": "research",
"mission_id": "mission-42",
"agent_id": "researcher",
"visibility": "private"
},
"batch": {
"evaluated_at_millis": 1700000000000,
"entries": [
{"record_id": "15d5c731-4e35-443c-9309-06a6561314e9", "record": null},
{"record_id": "31cb94eb-9233-41cf-82f1-ea42dfc7d260", "record": null}
],
"record_bytes": 0,
"dependency_work": {"records_examined": 0, "bytes_examined": 0}
}
}
The zero byte counts in this example describe absent IDs. Stored tombstones or ineligible records can incur work even when all entries are null.
Revalidate a retained context#
- Bring the application's invalidation consumer to its fixed catch-up fence.
- Read the context's exact IDs using this endpoint. Verify the returned scope, entry count, order and IDs before using any content.
- Discard the retained context if a required entry is null or its revision differs from the revision used to construct that context. Compare revisions as integers without floating-point conversion.
- Apply the application's existing before/after feed synchronization and invalidation-epoch checks. A batch does not commit or acknowledge a checkpoint.
- If transport, authentication, contract validation or server evaluation fails, treat the context as unvalidated. Preserve durable consumer progress.
Expiry can occur without any change-feed event, including in an ancestor whose own expiry is not copied into the derived record. Reaching the feed watermark therefore does not replace current eligibility checks. Historical checkpoint receipts also do not replace reading current checkpoint state.
evaluated_at_millis is an observation, not a validity lease. It is not a
change cursor, cannot be passed to a checkpoint, and does not prevent subsequent
mutations, revocation, or expiry. The endpoint does not promise that the context
remains valid throughout an agent task or authorize an external action. Multiple
batches are separate snapshots; splitting more than 100 IDs does not produce an
atomic context across the requests.
Limits and failure handling#
| Limit | Behavior |
|---|---|
| 1–100 distinct IDs | Fixed contract limit; no pagination or silent truncation |
| 64 KiB request body | HTTP 413 if the JSON transport exceeds the limit |
| 8 MiB root-record bytes | HTTP 400 if exceeded, including stored unavailable roots |
| 4,096 distinct dependency lookups / 16 MiB dependency bytes | Shared across all roots; HTTP 400 on exhaustion |
| Existing derivation graph bounds | An ineligible graph yields a null record, as with individual reads |
| Shared retrieval execution slots | HTTP 503 retrieval_busy if no slot is available after authorization |
The 8 MiB root budget is fixed and independent of lexical/hybrid scan settings. Neither byte budget is an RSS or exact response-size limit: indexes, wrappers, temporary objects and the already-fetched value that crosses a budget are additional work. Dependencies shared between roots reuse one request-local validated record cache. It never persists across snapshots or scopes.
Admission shares QILBEE_MAX_CONCURRENT_RETRIEVALS with lexical, cosine and hybrid
search. The permit covers the blocking read and response serialization, and stays
held if the caller disconnects while the operation is running. Single-record
GETs retain their existing behavior. Configure client deadlines, bounded backoff
for 503, and appropriate container limits.
The server returns no successful prefix when a budget, dependency read or
integrity check fails. HTTP 500 signals an encountered internal/integrity failure;
it must not be interpreted as an unavailable record. HTTP 401/403 apply to the
whole request. Successful responses and errors use Cache-Control: no-store.
See the context-consumer qualification for the integration evidence motivating this contract and its remaining limits.