API Reference
Query contract. RememberStack exposes exactly four top-level assured operations:
resolve_entity,claims_and_sources_context,facts_context, andcombined_context. The 18 former stock query patterns are discoverableexamples.*saved queries only. There is no legacy compatibility layer or deprecation window.
RememberStack has two deliberately separate truth layers. Claims are
immutable source testimony (“what was asserted, by whom, when”);
facts—relations and observations—are the adjudicated worldview (“what the
system holds or held true”):
supersession-adjudicated, clocked on two time axes (when a fact held in
the world, and when the system learned it), evidence-counted per
distinct source—repetition is not corroboration—and
contradiction-tracked. The fact_claim_evidence association is the
auditable bridge between the layers, recording which claims support or
contradict each fact. Query claims to inspect testimony; query facts to
answer current or historical truth questions, then follow the bridge to see
why the system believes or believed the fact.
(Internally these guarantees are decisions D41 and D54.)
The HTTP API is a thin, typed veneer over one deployment. Open SQL returns
QueryResult/v1. The first three assured operations return a
typed response envelope; combined_context returns ContextBundle/v2 with
the complete claims-and-sources and fact envelopes kept separate. No query endpoint
calls an LLM completion, and
reads never trigger writes. An explicitly composed ingest capability is the
sole client write path and always enters E0.
Four things are true of every deployment's API:
- The shipping query surface is deliberate. Seven open-query entry points
mount under
/query/…;/operationsand/operations/{name}expose exactlyresolve_entity,claims_and_sources_context,facts_context, andcombined_context. - The operation endpoints render from the registry.
GET /operationsis the deployment's active operation rows; the CLI and MCP surfaces render the same set, so the three surfaces are always in lockstep. - The API is the one enforcement point. When a deployment configures an auth perimeter, every endpoint is gated on a valid credential for that deployment before any read runs — one deployment is one trust domain.
- Failures are typed. Open-query failures map to a public error code and
message; envelope endpoints use structured negatives
(
unknown_entity,known_empty,boundary). An unroutable request is an ordinary HTTP status.
The machine-readable schema
The query API is also described by an OpenAPI document, so a client can be
generated from the contract rather than transcribed from prose. It covers
the routes a shipped self-host deployment serves — not the cost-export listener
below, which is a separate process, nor connector management, which no shipped
profile composes. The
document is openapi.json
at the root of the repository. Release v0.17.0 and later attach it as a release
asset of the same name; releases up to and including v0.15.0 predate it. The
repository copy always describes the current main.
A deployment does not serve the schema itself. GET /openapi.json is not a
route, and that is deliberate rather than an oversight: FastAPI's schema
endpoint is not covered by the auth perimeter, so serving it would hand the
complete surface — every path, parameter and response shape — to callers who
have not authenticated. The document is a build artifact of the release
instead, published where anyone entitled to the software can read it and
nowhere else.
Generate a typed client the usual way for your language, for example:
# Pinned to the version you actually run (available from v0.17.0):
curl -fsSL -o openapi.json \
https://github.com/writeitai/remember-stack/releases/download/v0.17.0/openapi.json
# Or, from the repository, describing current main:
curl -fsSL -o openapi.json \
https://raw.githubusercontent.com/writeitai/remember-stack/main/openapi.json
# TypeScript
npx openapi-typescript openapi.json -o src/engine-schema.ts
# Python
uvx openapi-python-client generate --path openapi.jsonPin to the release you run rather than to main for anything you ship: the
document is the contract of one version, and main describes whatever is
newest.
The document is stamped with the package version it describes, and declares
the bearer scheme, so a generated client knows to send Authorization: Bearer …. Whether a credential is actually required is a deployment's own
configuration: a host with no perimeter configured serves openly and ignores
one, which is why the quickstart works without any. Sending a credential is
always safe; a client that cannot send one cannot talk to a guarded host.
It describes the surface of a fully configured self-host deployment — what the shipped stack serves with its capabilities in place — not every route the code could theoretically mount. Two things follow, and they are worth keeping straight:
- Capabilities nobody ships are absent. A route the code can mount but no deployment composes would be advertised and then answer 404 everywhere, so CI fails if the export composes a capability the self-host profile never passes.
- Capabilities you have not configured are still listed. Whether a perimeter or a spend lease is composed is decided at run time from your settings, and no check on the published document can know your configuration. Treat the schema as the contract of a fully configured deployment, and your own configuration as what your host actually enforces.
If you build a deployment programmatically with a different set of capabilities, its surface is the subset you composed.
Regenerate it after changing any route:
uv run python scripts/export_openapi.py -o openapi.jsonCI compares the committed file against a fresh export and fails when they differ, so the published contract cannot quietly fall behind the code.
Cost export (ops listener, not this API)
Content-free spend export is not a route on the query API. When
REMEMBERSTACK_COST_EXPORT_BIND is set, the API process starts a second
listener that serves only:
| Method | Path | Contract |
|---|---|---|
GET | /ops/cost-export/v1 | rememberstack.cost_export.v1 page |
Auth is Authorization: Bearer <REMEMBERSTACK_COST_EXPORT_TOKEN> (minimum 32
bytes). A customer perimeter token is rejected. The path is frozen; a later
contract is /ops/cost-export/v2. Pages carry allowlisted receipts only — no
query text, prompts, or memory content. Local operators can print the same
page with remember ops cost-export. See Deployment.
Open query space
Choose among live/evidence-composable SQL, typed live graph methods, and the
four assured operations (resolve_entity, claims_and_sources_context,
facts_context, combined_context).
| Method | Path | Contract |
|---|---|---|
POST | /query/sql | One sandboxed statement; QueryResult/v1 |
POST | /query/sql/explain | EXPLAIN without execution |
GET | /query/space | Manifest-backed schema discovery (full first-call payload) |
GET | /query/space/search | Search checked-in manifest text only |
GET | /query/saved | Saved-query registry metadata |
GET | /query/saved/{namespace}/{name} | One immutable version |
POST | /query/saved/{namespace}/{name}/run | Execute active saved SQL |
GET /query/space opens with the two-layer headline, the three retrieval
choices, honesty warnings, and worked examples (contrast pair, predicate
vocabulary, full audit trail, two-layer divergence, live graph helpers, and
semantic-to-relational joins). It
also exposes the full authoritative discovery members (views, function
signatures, core operation descriptors, SQL grammar, and tier limits). Docs
copy the bound example text; they do not
import Python modules.
The four design-bound facts-layer examples ship verbatim:
Contrast pair. WRONG current-truth (claim validity windows are testimony, not what the system currently believes):
SELECT claim_id, claim_text, claim_valid_from, claim_valid_until
FROM claims_live
WHERE claim_valid_from <= $1::timestamptz
AND (claim_valid_until IS NULL
OR claim_valid_until >= $1::timestamptz);RIGHT replacement — start from adjudicated current facts and join testimony:
SELECT f.*, e.claim_id, e.stance, e.source_handle
FROM facts_current AS f
JOIN fact_claim_evidence_live AS e
USING (deployment_id, fact_kind, fact_id)
ORDER BY f.fact_kind, f.fact_id, e.stance, e.claim_id;Predicate-vocabulary discovery. Discover the deployed fact vocabulary before writing predicate filters:
SELECT predicate, count(*) FROM facts_current GROUP BY 1 ORDER BY 2 DESC;Full audit trail. Walk fact → live evidence association → immutable claim → live source lineage:
SELECT f.fact_kind, f.fact_id, f.predicate,
e.stance, e.source_handle,
c.claim_id, c.claim_text, c.asserted_at,
d.doc_id
FROM facts_current AS f
JOIN fact_claim_evidence_live AS e
USING (deployment_id, fact_kind, fact_id)
JOIN claims_live AS c
USING (deployment_id, claim_id)
JOIN documents_live AS d
ON d.deployment_id = c.deployment_id
AND d.doc_id = c.doc_id
WHERE f.fact_id = $1::uuid
ORDER BY e.stance, c.asserted_at DESC, d.doc_id, c.claim_id;Two-layer divergence. A current adjudicated fact whose newest current testimony contradicts it:
WITH ranked_testimony AS (
SELECT e.deployment_id, e.fact_kind, e.fact_id,
e.claim_id, e.stance, c.claim_text, c.asserted_at,
row_number() OVER (
PARTITION BY e.deployment_id, e.fact_kind, e.fact_id
ORDER BY c.asserted_at DESC NULLS LAST, c.claim_id
) AS testimony_rank
FROM fact_claim_evidence_live AS e
JOIN claims_live AS c
USING (deployment_id, claim_id)
)
SELECT f.*, r.claim_id, r.claim_text, r.asserted_at, r.stance
FROM facts_current AS f
JOIN ranked_testimony AS r
USING (deployment_id, fact_kind, fact_id)
WHERE r.testimony_rank = 1
AND r.stance = 'contradicts';Shipped examples.* identities run only through
POST /query/saved/examples/{name}/run — never as top-level tools.
Operations — the registry, rendered
The registry contains exactly four assured, named operations. They keep the
typed response envelope where that contract adds value; reusable query patterns
belong under examples.*, not in a second compatibility catalog.
GET /operations
Returns the deployment's active operations as tool descriptors — the same list an MCP client discovers:
[
{
"name": "resolve_entity",
"description": "Resolve a name to ranked current entity candidates …",
"input_schema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
},
"output_grain": "fact",
"answer_intent": "orientation"
}
]result_schema, result_contract, output_grain, and answer_intent travel
with each descriptor. output_grain is null only for combined_context, whose
contract is the explicitly two-grain ContextBundle/v2.
POST /operations/{name}
Runs one operation by name over a JSON body of arguments and returns its declared result contract. Arguments are coerced to the types the primitives need (a UUID string to a UUID, an ISO-8601 string to an instant):
curl -X POST "$REMEMBERSTACK_API_URL/operations/resolve_entity" \
-H "content-type: application/json" \
-d '{"name": "Alice"}'An unknown operation is 404; a missing required argument is 422.
On self-host upgrades, run the new image's setup entrypoint before starting
API or worker processes. Setup atomically reconciles the four stored canonical
descriptors, including their implementation-plan hashes. When the surface
manifest changes, setup publishes the new hash, suspends customer saved queries
for explicit revalidation, and reseeds platform examples.* against the new
hash. The stock Compose file enforces that order by making API and workers wait
for the setup service to complete successfully.
The complete operation catalog is:
| Operation | Arguments | What it returns |
|---|---|---|
resolve_entity | name | ranked current entity candidates; exact-name ambiguity is returned, never guessed away |
claims_and_sources_context | query; optional entity_ids (1–20 unique UUIDs), k (1–100, default 50), candidate_k (1–400, default 200) | current testimony only: confirmed claims and source chunks, never facts or entity candidates |
facts_context | query; optional entity_ids (1–19 unique UUIDs), hops (1–2, default 1), free predicate (1–200 chars), k (1–30, default 15), evidence_per_fact (1–5, default 3), time | neighborhood-scoped relations and observations for current / at; anchor-scoped overlap / history; supporting and contradicting testimony |
combined_context | query; optional entity_ids (1–19 unique UUIDs), hops (1–2, default 1), free predicate (1–200 chars), time | ContextBundle/v2: complete claims_and_sources_context and neighborhood-aware facts_context child envelopes, never a blended result list |
entity_ids is optional. Omit it for deployment-wide semantic retrieval; pass
confirmed survivor IDs when identity materially narrows the question. Supplying
any unknown, retired, forgotten, or foreign ID yields one opaque
unknown_entity result with no partial answer. Use resolve_entity first when
a name is ambiguous. Entity and fact-time eligibility are applied before the
bounded ranking cut.
For current and at, supplying IDs invokes the default D97 recipe: the live graph walks
from each anchor with an empty predicate list, the anchor-plus-neighbor union is
capped at 20 IDs, and fact-text search is constrained to that union. Empty
predicates mean every stored relation, including other:*. Supplying
predicate narrows both traversal and PostgreSQL-confirmed relations to that
exact dynamic value; because observations have no relation predicate, that
filtered result contains relations only. Unfiltered observations are searched
and returned as facts, but they are never graph nodes. Unavailable live-graph
authority or a current anchor absent from it is a boundary. A neighbor that
becomes stale across the bounded graph and fact reads is omitted and counted in
dropped_by_hydration, so current anchor facts can still return. Caps are
reported in truncation.
With no IDs, fact search is deployment-wide and may use matching entity profile
prose to nominate an additional bounded fact scope—so “list banks” can match an
“is a bank” observation without any type parameter. overlap and history
keep their explicit anchors without graph expansion because a neighborhood
requires one world-time instant.
The time object is a closed union: {"mode":"current"} (default),
{"mode":"at","at":"…"}, {"mode":"overlap","from":"…","to":"…"},
or {"mode":"history"}. These modes select world time under current system
belief; the separate two-axis facts_as_of/open-SQL path answers what the
system believed at a past instant. Every fact envelope echoes the applied mode
in its required temporal_scope.
The 18 former stock query patterns are shipped only as discoverable
examples.* saved queries. They are not operation tools and have no deprecation
or migration surface.
Document inventory
GET /documents
Lists the document lineages this deployment holds, newest first by when the lineage was first seen. Note what that is not: re-ingesting a document does not move it up. See the ordering note below for why the key is immutable. Composed like the other optional capabilities: absent means an ordinary 404, never a fallback.
| Parameter | Meaning |
|---|---|
limit | page size, 1–200 (default 50) |
cursor | opaque position from the previous page; absent on the last page |
status | filter on the newest version's state: ingesting, converting, structuring, ready, failed |
Each row carries the lineage (doc_id, title, source_kind, source_uri,
first_seen_at), the newest version observed (latest: version_id,
version_no, status, ingested_at, and error when it failed), and
serving.
latest is the newest version, not the current one — and that distinction is
the point of this endpoint. A lineage's current version is the snapshot
that finished processing and is being served. The two differ exactly when
something is unfinished or broken: a document uploaded a minute ago and still
converting has no current version at all, and a document whose newest upload
failed still points at the older working one. Listing by the current pointer
would hide precisely the documents somebody is looking for, so this reports
the highest version_no in the lineage.
serving is the separate question — whether any ready snapshot exists to
answer queries from. That keeps "the newest upload failed" and "there is
nothing here to search" apart, which are different problems with different
fixes.
A version the deployment has been asked to delete is not the document's
current state, so latest falls back to the newest version that still
exists. Deleting a version sets its tombstone and leaves status alone — a
deleted snapshot still reads ready — so a listing that simply took the
highest version number would show somebody a document they had removed,
labelled as fine. A lineage with no surviving version is not listed at all.
Lineages the deployment has been asked to forget are not listed. The tombstone remains in the spine so an audit can tell "forgotten" from "never existed", but a customer's inventory is what they still hold.
Order. Newest document first, by first_seen_at — when the lineage was
created — with doc_id breaking ties. A document ingested a moment ago is at
the top; one re-ingested a moment ago keeps its place, because it is the
same document.
That is deliberate, and the alternative is broken rather than merely different. Ordering by most-recent-activity gives a key that moves: a document below the cursor that gains a version jumps above it and is returned on no page at all, and one already returned whose newest version is deleted falls back below the cursor and is returned twice. Neither is visible to the reader. An immutable key has neither failure, and it is servable by an index, so a page costs the page size rather than a scan of the corpus.
A malformed cursor is a 400 rather than a silent restart, because restarting would re-serve a page already seen and read as duplicated documents. Note what that does and does not check: the cursor is verified to parse, not to have been issued by this deployment. It is not a capability — the query is scoped to the caller's own deployment either way — so the worst a hand-written but well-formed cursor can do is start somebody at an odd place in their own corpus.
One cost worth knowing: status filters on the newest version, which is
resolved per lineage after the index has chosen the candidate. Paging without
a filter is an index seek — the cursor predicate is pushed into the index
condition, so a page costs the page size whatever the corpus holds. Filtering
on a rare status walks further, because the engine must resolve each
candidate's newest version before it can tell whether it matches. That is
inherent to filtering on a joined value, not a defect, and it is bounded by
the corpus rather than by the page.
deleted is not an accepted status filter. The listing only reports
versions that are not tombstoned, and a hard forget sets the tombstone
alongside the status, so filtering on it could only ever return nothing —
which the reader would take as "no deleted documents" rather than "this
question cannot be asked here".
A read-only credential may call this endpoint.
Client writes
These endpoints exist only when the deployment composes the corresponding service. An absent capability is an ordinary 404, never a client-side fallback that bypasses the deployment.
POST /ingest
Sends raw bytes with filename and mime query parameters. The optional
source_kind and source_ref parameters form one stable lineage identity and
must be supplied together. source_modified_at, source_version_ref, and
versioning_mode=snapshot|living carry the feeder's version metadata. Changed
bytes under the same identity append a document version; identical bytes are
an idempotent no-op. On that no-op, source_version_ref may advance as the
connector cursor, but the existing version's source_modified_at never changes
or clears because it already fed extraction. A supplied source timestamp must
be timezone-aware UTC; omitting it preserves an unknown source time.
POST /readiness
When composed, accepts document-version UUIDs plus an exhaustive require
object with the Boolean keys pipeline, p1, live_graph, and p3. The
response reports each capability's requested/readiness state and safe reason.
live_graph proves PostgreSQL 19 catalog and bounded query health; it has no
generation. p3 separately requires a fresh published CorpusFS snapshot and
includes its version/publication evidence. The response also includes
non-secret model IDs plus document_binding_generation. The value
document-t0-v1 means exact same-document T0 replay is built and enabled;
null means the resolver safely uses the ordinary global cascade. This
endpoint inspects work; it does not wait, retry, or trigger a build.
Connector management
| Method & path | Operation |
|---|---|
GET /connectors | list deployment-side connectors |
POST /connectors | add typed connector configuration |
GET /connectors/{connector_id} | read current status |
POST /connectors/{connector_id}/pause | pause execution |
Connector credentials remain deployment-side. The client may send a
credential_ref naming an existing secret, but connector execution never
moves into the client process. The deployment profile supplies the persistent
manager for this typed composition port.
Python SDK
The base wheel exposes the synchronous typed client without PostgreSQL, worker, projection, or adapter dependencies:
from pathlib import Path
from remember import MemoryClient, ReadinessRequirements
with MemoryClient.from_settings() as memory:
tools = memory.list_operations()
answer = memory.run_operation(
name="claims_and_sources_context",
arguments={"query": "What did Alice say about the launch?"},
)
landed = memory.ingest(
Path("project.md"),
source_kind="custom-feeder",
source_ref="workspace/project",
source_version_ref="revision-17",
)
ready = memory.pipeline_readiness(
version_ids=(landed.version_id,),
require=ReadinessRequirements(
pipeline=True, p1=True, live_graph=True, p3=False
),
)The SDK also exposes typed resolve, lookup_relations,
transcript_relation, lookup_observations, search_claims, search_chunks,
hydrate_relation, and connector-management methods. Network and non-success
HTTP responses raise MemoryApiError with the status, optional public error
code, and deployment-provided detail.
Primitives
The primitives are available directly for callers that compose their own plans. Each returns the envelope.
| Method & path | What it answers |
|---|---|
GET /resolve?name=&context_entity_ids= | resolve a name to ranked current entities; repeat context_entity_ids up to eight times to rank ambiguous names by their current relation adjacency (never a silent guess) |
GET /lookup/relations?subject_entity_id=&predicate=&object_entity_id=&valid_at= | relations matching an (s, p, o) pattern — current, or as-of a world-time instant |
GET /lookup/observations?entity_id=&property_query=&k= | live observations on one entity, semantic over statements |
GET /search/claims?query=&k=&channel=semantic|bm25 | semantic or token/BM25-ranked claim search — evidence grain, never a current-fact answer; k is 1–400 |
GET /search/chunks?query=&k=&channel=semantic|bm25 | semantic or token/BM25-ranked search over live source passages; returns chunks[] as separately typed evidence, never claims or facts; k is 1–400 |
POST /search/claims | the same claim search with {query, k, channel} in the body, so the terms never reach the request line |
POST /search/chunks | the same chunk search with {query, k, channel} in the body |
GET /hydrate/relation/{relation_id} | the audit chain: a relation → its evidence claims → source documents |
GET /transcript/relation/{relation_id} | the decision history: why the system believes what it believes |
Searching from a browser
Both searches also accept a POST with {query, k, channel} in the body.
It is the same search — same ranking, same spend accounting, same read scope —
and the only difference is where the terms travel.
A query is usually the most sensitive string in the exchange: a person's name,
a diagnosis, an unannounced deal. A URL is not a private place. It is written
to access logs, kept by proxies, retained in browser history and attached to
outgoing referrers. The GET forms are fine for a client on a private path to
its own deployment, and they are unchanged; anything reaching a deployment
from a browser should use the body form (D59).
The body is closed: an unknown key is a 422 rather than a silently ignored
parameter, so passing limit instead of k tells you rather than quietly
returning ten results.
Evidence rows returned by claim search and relation hydration include
asserted_at, claim_valid_from, claim_valid_until,
claim_valid_precision, and claim_valid_kind. asserted_at is when the
source made this statement (the message was sent, the page was published).
claim_valid_from / claim_valid_until are when the claim says it happened or
was true, as the source asserted it; claim_valid_kind names which
(event_time = when the claimed event happened, effective_period or
proposition_validity = when the claimed state was true, measurement_period
= the period a claimed figure covers), and claim_valid_precision says how
exact the bounds are. A date the extractor could resolve is already written
into claim_text and repeated in the bounds; a relative phrase still present in
claim_text is relative to asserted_at. These are evidence, not the fact
layer's current-validity verdict. The OpenAPI document carries the same
descriptions on each field. Chunk results disclose an optional deterministic context_prefix
(location header under the embedding-input policy) separately from body-only
chunk_text and carry the current document, version,
representation, offsets, and source timestamps confirmed by the live spine.
facts_context is a flat fact-grain envelope: facts[] contains the selected
verdicts, evidence[] contains the backing testimony, fact_evidence[]
explicitly maps each fact to each claim and stance, and evidence_totals[]
reports exact returned/total counts for both stances. Both association arrays
carry the complete (fact_kind, fact_id) identity; relation and observation
UUIDs must not be grouped by bare fact_id. It accepts only explicit
entity IDs, not names or a hidden resolution step. Current and point-in-time
ID-scoped calls add the separately returned one-hop nodes[] described above;
observations remain only in facts[]. A fact with no current
evidence returns only when D54 identifies historically backed support as
withdrawn; a fact with no qualifying provenance is never returned.
Choose the authority explicitly: claims_and_sources_context for what sources said,
facts_context for what the system holds or held true, and combined_context when
the answer-producing agent needs both complete views. ID-scoped current and
point-in-time fact operations add a bounded live-graph neighborhood by default.
Use the typed graph HTTP/SDK operations or live SQL graph helpers when you need
paths, more than two hops, paging, or shapes beyond that assured default.
The default question channel independently fuses semantic and BM25 claim nominations and source-chunk
nominations, confirms the complete already-fetched pools, and takes the final
bound only after live confirmation. A stale head result is therefore refilled
from the deterministic ranked tail without another index search. Exact claim
texts are grouped after NFKC, case-folding, whitespace collapse, and
leading/trailing punctuation removal. The highest-ranked confirmed claim
represents each group; its corroboration_count counts distinct document
lineages and grouped_claim_ids lists only live PostgreSQL-confirmed members.
The result keeps those atomic claims in evidence[] and verbatim source
passages in chunks[]; neither payload is promoted to current-fact truth.
The saved examples.claims_hybrid_rrf and examples.chunks_hybrid_rrf
patterns expose either half when a caller needs only one evidence type; they
run through /query/saved/examples/{name}/run and are not assured operations.
The four operation descriptors expose their integer version, so clients can
pin the behavior that produced an answer. The current catalog is
resolve_entity@1, claims_and_sources_context@1, facts_context@2, and
combined_context@3; the composite advanced again for its
ContextBundle/v2 field names after D97's default neighborhood change.
resolve always returns every exact-name candidate. Supplying focal entities only
reorders those candidates: a candidate related to more of the supplied entities
ranks first, and each candidate discloses its context_hits. Context therefore
helps with questions such as “which John from this project?” without converting a
ranking hint into a hidden identity decision.
A relation returned by lookup or hydrate that sits in a live
contradiction group always carries the other sides with it — a contradiction
is surfaced, never silently resolved. A fact whose supporting testimony was
withdrawn is returned flagged, not hidden.
Live graph surface
PostgreSQL 19 serves graph reads directly from the same authority database. Server-owned one-hop neighborhoods execute fixed SQL/PGQ patterns; deeper neighborhoods and shortest-tier paths execute bounded recursive SQL helpers. There is no graph export, generation, local database, or Cypher API.
The public SQL helpers are memory_v1.graph_neighborhood,
memory_v1.graph_path, and memory_v1.graph_citation_path. Entity helpers
apply both half-open time axes during expansion. Every helper clamps depth,
results, frontier, expansion, and time budgets and emits one terminal status
row that reports truncation and effective bounds.
Typed GraphQueries methods hydrate the returned node and edge identifiers
from evidence-rich memory_v1 authority views inside the same read-only
repeatable-read transaction. A committed relation is therefore immediately
eligible for graph reads; no publication step or snapshot-age warning exists.
The typed HTTP/SDK surface is:
| HTTP | MemoryClient | Bound |
|---|---|---|
POST /graph/neighborhood | graph_neighborhood(...) | 1–4 hops, at most 500 returned neighbors |
POST /graph/path | graph_path(...) | shortest tier within 1–6 hops, at most 10 equal-length paths |
POST /graph/citation-path | graph_citation_path(...) | directed citation chain within 1–6 hops |
Neighborhood and entity-path requests accept valid_at and believed_at
only as a pair. Callers provide identifiers, filters, clocks, and bounds—not
SQL or PGQ text. Invalid bounds and partial clock coordinates fail validation
before traversal.
The auth perimeter
A deployment that configures an auth-perimeter provider gates the whole API on
a credential passed in the Authorization header:
Authorization: <scheme> <value>
The credential is handed to the configured provider. A missing or failing credential is 401; a credential authenticated to a different deployment is 403. Inside the perimeter it is one trust domain — there is no per-request, content-level authorization (isolation is achieved by running separate deployments). Without a configured provider the API is open, and the perimeter is the deployment's infrastructure (network, IAM) to enforce.
Configuration
REMEMBERSTACK_API_URL— where the SDK, CLI, and MCP client reach the API.REMEMBERSTACK_API_AUTHORIZATION— optional completeAuthorizationheader value.REMEMBERSTACK_API_TIMEOUT_SECONDS— client request timeout (default 30 seconds).- The API process composes the query engine (search index, model provider) — the surface itself carries no provider adapters.