Supplemental memory¶
The Capability Gateway exposes Hindsight through POST /v1/memory and
POST /v1/memory/mcp. Existing harness memories remain authoritative. The
gateway does not ingest transcripts automatically or change model providers.
Configure a principal-bound route¶
Create a device key granting the memory alias and both endpoint paths. Keep the secret in the protected output file; the returned key ID is the principal.
anvil-serving router keys create --name memory-client --model memory.supplemental --path /v1/memory --path /v1/memory/mcp --out /protected/memory-key
In private router configuration, alongside the existing authenticated server and device-key database:
[[router.memory_routes]]
alias = "memory.supplemental"
principal = "key-id-from-key-creation"
backend = "hindsight"
bank = "client-bank"
base_url = "http://127.0.0.1:8888"
auth_env = "HINDSIGHT_GATEWAY_TOKEN"
timeout = 120
The upstream credential belongs only to the router and the operator. Provide it through a protected environment file. Consumers receive their scoped router key, never the upstream administrative credential. Loopback is relative to the router process. The upstream origin must be a declared private address, with no path, query, userinfo, or redirect. The deployment owns verified TLS when the upstream crosses hosts.
Each (alias, principal) has one bank and one backend. Repeating an alias for
different principals is supported. An explicitly shared bank requires an
operator binding for every allowed principal. Unknown aliases return 404;
another principal's alias returns 403. Missing credentials or unavailable
upstreams return 503. Selecting the reserved hermes backend returns 501;
no adapter reads or writes existing Hermes/Pi stores.
Operations¶
The following limits also apply to per-user routes described below.
The REST body contains exactly alias, operation, and arguments:
{"alias":"memory.supplemental","operation":"recall","arguments":{"query":"What is the release checklist?"}}
| Operation | Arguments | Limits |
|---|---|---|
retain |
content, optional context, tags |
32,768 characters; synchronous |
recall |
query, optional budget, max_tokens, tags |
4,096 query characters; 8,192 output tokens |
reflect |
query, optional budget, max_tokens, tags |
4,096 query characters; 4,096 output tokens |
Budgets are low, mid, or high. Tags allow at most 16 identifiers of
128 characters. Fixed principal-bound routes reject caller-selected banks. Endpoints, models, defense policies,
metadata, asynchronous retention, document administration, and extra arguments
are rejected. The response names the selected alias, backend, operation, and
upstream result. A content rejection stays a rejection without echoing the
rejected material in the error.
Recall defaults to 1,024 fact-text tokens. It excludes entity summaries, raw
chunks and source-fact expansion, and returns a ranked prefix of whole facts
within 32 KiB of serialized JSON, preserving each fact's source references.
truncated and omitted_results report facts excluded by this byte cap; an
oversized first fact produces an empty, explicitly truncated result. Narrow
the query when results are truncated. The cap also applies with an explicit
token override because upstream token budgets exclude metadata. MCP returns
one text representation of the result instead of duplicating memory content.
The gateway bounds request bodies to 64 KiB, upstream responses to 2 MiB, concurrent memory requests to four, and total upstream duration to the route timeout (1–300 seconds). Deadline expiration returns 504 and releases the permit, including when an upstream drips bytes. This is separate from model admission and the device key's request budget. No automatic retry or fallback occurs; an interrupted retain has an uncertain outcome.
MCP clients¶
The stateless JSON MCP endpoint exposes only memory_retain, memory_recall,
and memory_reflect. Their schemas contain the aliases authorized for the
authenticated device key. MCP supports protocol versions 2025-11-25 and
2026-07-28; tool notifications never execute writes.
Clients supporting Streamable HTTP can connect directly to the authenticated router endpoint. Stdio clients can reuse the packaged bridge:
anvil-serving mcp serve --controller-url https://router.example.test/v1/memory/mcp --auth-file /protected/memory-key
The bridge and router must have matching package versions. Register this as an additional server; retain existing memory plugins and settings. Provisioning owns protected files and client setup. Neither a browser login nor tailnet membership grants access to the router memory endpoint.
Deployment and recovery¶
Per-user banks¶
A dynamic route resolves the authenticated Connect owner through a protected
bank policy. API keys must still grant the alias and both endpoint paths.
Connect Home lists these dynamic aliases for operator approval. Approve
memory.user, /v1/memory, and /v1/memory/mcp before the user creates a key.
Fixed device-bound memory routes are excluded from that approval catalog.
Unowned device keys cannot use this route; attribution headers and caller-supplied
user IDs do not grant access. Existing fixed principal routes keep their bindings.
[[router.memory_routes]]
alias = "memory.user"
backend = "hindsight"
base_url = "http://127.0.0.1:8888"
auth_env = "HINDSIGHT_GATEWAY_TOKEN"
access_file = "/run/anvil-memory/access.json"
timeout = 120
Mount the root-owned policy directory read-only so atomic replacements become
visible. Connect writes the host policy; the router and browser read it for every
operation. The dedicated directory uses mode 0750 and the configured reader
group; access.json uses mode 0640. Keep bank creation receipts root-only.
Policy subjects are verified Connect human: principal hashes. Each entry has
default_bank, an admin boolean, and explicit banks operation grants. Members
can access only their granted banks; administrators can select and list all banks.
REST accepts optional top-level bank; omission selects the user's default.
operation: "banks" returns the authorized inventory and default bank. It accepts
limit (1–100) and offset (0–1,000,000) for administrator inventory. MCP exposes
memory_banks alongside the existing tools and accepts optional bank on memory
operations. These options are exclusive to Connect-owned dynamic routes.
For administrator-managed key enrollment, REST banks also accepts an optional
arguments.expected_principal with the target Connect human: hash. A key owned
by another account is rejected even when both accounts can access the same banks.
This assertion can only restrict the authenticated key; it never selects a user
or grants access. Resolve the target from the trusted identity provider binding,
never from model input or a display name.
Declare optional memory settings in the private Connect deployment:
base_url, protected root-only auth_file, absolute access_file, numeric
reader_gid, default_banks and shared_banks. The last two map local usernames
to existing shared defaults and explicit shared operation grants. Every shared
default needs a matching grant. No shared bank is created, modified or adopted.
Personal banks keep the legacy retain, recall and reflect grant. To opt an
existing personal bank into native browsing or mutation, include its exact bank
ID under that user's shared_banks entry with the desired additional operations:
list, detail, invalidate and/or restore. Despite the historical field
name, an entry matching the user's protected creation receipt is a personal
operation opt-in, not a shared bank. Provisioning requires that receipt and a
matching live ownership marker; it will not create or adopt a bank for this
opt-in. Other entries retain their exact shared grants. Removing the personal
entry and running users memory USER --confirm restores only the legacy grant.
This uses the existing declaration format, so older account-deletion managers
can still withdraw every mapping for the deleted username.
Use users configure-memory with protected outside-Git installation JSON containing
memory and user_defaults. Optional defaults use
{"email":"owner@example.test","bank":"shared-bank","operations":["recall"]};
the command resolves each email to exactly one enabled account before writing.
Then use users memory USER --confirm for existing accounts. It preserves current
browser grants and rotates existing Connect credentials without restarting sign-in.
Once installed, users create automatically provisions a personal bank, including
accounts created without browser grants. Administrators come from the account's
admin group or its hindsight-ui:admin grant. A configured shared default still
retains the account's personal bank.
Provisioning verifies the bank ownership marker and sensitive-data defense before
publishing access. Interrupted creation retries from its protected receipt.
Provisioning failures leave access closed and preserve created banks. Suspension
and deletion remove the policy entry before native account revocation, retaining
bank contents and receipts. Permanent deletion also withdraws shared/default grants
from the private declaration before removing identity records, so a reused username
receives a fresh personal bank. Resume through users access; retry incomplete memory
setup with users memory. Package installation and managed router/browser
activation are separate steps and require their own authenticated readback.
Infrastructure provisions the digest-pinned upstream service, storage, bank
defense, extraction credentials and backups. Adopt that external Compose
container with the existing host services adopt --external-compose contract;
the Control Plane can then status, log and manage it through the declared
resource owner. This does not introduce a model engine or a new product family.
Keep native Hindsight MCP/admin endpoints private. The upstream UI remains disabled until its authenticated exposure is independently verified. Anvil Connect exposure must retain both its resource authorization and the router device-key check; do not expose the upstream admin origin as a client shortcut.
Connect edge publication preserves the existing tunnel configuration and verifies DNS, ingress and a healthy connected tunnel after applying changes. HTTPS origins use the resource hostname for TLS SNI unless an explicit override is declared.
Managed gateway startup waits for the declared identity provider through the local Caddy listener before starting the gateway. This bounded check uses verified TLS, exact issuer discovery and the declared edge identity. Provided certificates are pinned to the declared leaf; ACME uses system trust. Rollback checks the restored generation before restarting its gateway. The resolved Python interpreter must be executable by the edge service account; private development environments may not meet that requirement. Existing healthy services remain running on a no-op.
Use upstream-consistent backups and verify recovery in a fresh isolated volume. Retain exact image, configuration and archive hashes. Do not restore a snapshot over active memory storage to test recovery. A package rollback also needs the appropriate router key-database backup when crossing schema versions.
Import existing curated memories¶
Preview an explicit import, then apply the same configuration:
anvil-serving harness memory-import --config /protected/memory-import.toml --json
anvil-serving harness memory-import --config /protected/memory-import.toml --confirm --json
The operator configuration lists only the Markdown files to copy:
schema_version = 1
base_url = "http://127.0.0.1:8888"
bank = "imported-memories"
auth_file = "/protected/hindsight-api-key"
[[sources]]
id = "codex-primary"
harness = "codex"
root = "/protected/curated-memory-copy"
files = ["MEMORY.md", "memory_summary.md"]
Supported source labels are codex, pi, hermes, openclaw, and claude.
Select curated memory files explicitly; exclude credentials, session databases,
transcripts, and recovery archives. A configuration permits 32 sources, 128
files, 4 MiB per file and 16 MiB total. Larger migrations use separate configs.
Preview reads no credentials and makes no network requests.
The importer requires the target bank's block_sensitive_data defense, then
retains deterministic chunks with source identity and SHA-256 provenance.
A completion tag is written only after synchronous extraction succeeds.
Identical reruns verify provenance, content hash and that tag before skipping;
unmarked matches resume retention with the same document ID. Changed files create new
versions; neither old Hindsight documents nor original harness files are
deleted or rewritten. A rejected chunk produces a nonzero result with its
document ID, without printing memory content. Investigate failures before
retrying; do not disable the bank defense to force an import.
Import uses an operator credential directly against the private upstream. Bulk retain calls have an 1,800-second deadline because a chunk can require several serialized extraction calls; lookups and completion updates allow 30 seconds. This does not extend the gateway's 300-second interactive limit. Consumers receive only principal-bound router access to the imported bank. Back up and verify recovery after migration before treating it as complete.
Personal API keys during onboarding¶
The optional memory.router object connects bank provisioning to a locally
managed router and protected credential directory:
{"container": "anvil-router", "store_path": "/var/lib/anvil-serving/router-keys/keys.sqlite3", "key_directory": "/etc/anvil-connect/memory/user-keys"}
Install it with the other memory fields through users configure-memory.
The directory must be dedicated, root-owned, mode 0700, and outside Git;
provisioning creates it when absent. Credentials use only memory.user and the
REST/MCP memory paths. They contain no fixed bank ID: the principal's current
bank policy owns the default, shared grants, and administrator all-bank view.
The router and native Connect package must both support users memory-key.
Open WebUI enrollment belongs to the infrastructure provisioner, using its
native encrypted personal settings after the person's first sign-in.