RememberStackremember.dev/docs

API Reference

Query contract. RememberStack exposes exactly four top-level assured operations: resolve_entity, claims_and_sources_context, facts_context, and combined_context. The 18 former stock query patterns are discoverable examples.* 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/…; /operations and /operations/{name} expose exactly resolve_entity, claims_and_sources_context, facts_context, and combined_context.
  • The operation endpoints render from the registry. GET /operations is 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.json

Pin 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.json

CI 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:

MethodPathContract
GET/ops/cost-export/v1rememberstack.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).

MethodPathContract
POST/query/sqlOne sandboxed statement; QueryResult/v1
POST/query/sql/explainEXPLAIN without execution
GET/query/spaceManifest-backed schema discovery (full first-call payload)
GET/query/space/searchSearch checked-in manifest text only
GET/query/savedSaved-query registry metadata
GET/query/saved/{namespace}/{name}One immutable version
POST/query/saved/{namespace}/{name}/runExecute 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:

OperationArgumentsWhat it returns
resolve_entitynameranked current entity candidates; exact-name ambiguity is returned, never guessed away
claims_and_sources_contextquery; 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_contextquery; 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), timeneighborhood-scoped relations and observations for current / at; anchor-scoped overlap / history; supporting and contradicting testimony
combined_contextquery; optional entity_ids (1–19 unique UUIDs), hops (1–2, default 1), free predicate (1–200 chars), timeContextBundle/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.

ParameterMeaning
limitpage size, 1–200 (default 50)
cursoropaque position from the previous page; absent on the last page
statusfilter 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 & pathOperation
GET /connectorslist deployment-side connectors
POST /connectorsadd typed connector configuration
GET /connectors/{connector_id}read current status
POST /connectors/{connector_id}/pausepause 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 & pathWhat 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|bm25semantic or token/BM25-ranked claim search — evidence grain, never a current-fact answer; k is 1–400
GET /search/chunks?query=&k=&channel=semantic|bm25semantic 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/claimsthe same claim search with {query, k, channel} in the body, so the terms never reach the request line
POST /search/chunksthe 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:

HTTPMemoryClientBound
POST /graph/neighborhoodgraph_neighborhood(...)1–4 hops, at most 500 returned neighbors
POST /graph/pathgraph_path(...)shortest tier within 1–6 hops, at most 10 equal-length paths
POST /graph/citation-pathgraph_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 complete Authorization header 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.