Sparse Agent Memory
API docs · No sign-in required

Memory with Sparse Agent Memory

Start with the cloud memory guide and examples. This document covers program integration.

This guide is public at /docs/memory?lang=en. No sign-in is required.
The Markdown edition is available at /docs/memory.md?lang=en. Use the
language selector to switch between Chinese and English.

This guide is for callers: an Auto-Coder Chat account or a program acting
for that account. Memory is isolated by Chat user. All API keys belonging to
one account see the same projects. Other accounts receive “project not found”
even if they guess a project name.

The service does not accept a username. Callers choose project names; the
server derives the namespace from the verified Chat user. Do not call
/v1/...; those are not this gateway's routes.

Below, $GATEWAY is the public origin, such as https://example.com, without
a path. $API_KEY is an ak_… key created in Chat account settings. Send
the key only in the Authorization header. Keep it out of URLs, logs and repositories.

Choose an authentication method

The administrator controls concurrent index tasks: 20 across the service by
default. Additional tasks queue in arrival order, with at most 256 waiting
requests. Regular HTTP requests wait and then return the usual JSON response.
Set timeouts to cover both waiting and execution. After a timed-out write,
read back the result before retrying.

For progress, send Accept: application/x-ndjson on an index request. Each
line is a JSON event. A queued event has state: "queued" and a queue
object containing position, max_concurrency, running and queued.
For example, position 1 means no request is ahead of yours. Execution changes
the state to running. The final event is:

{"state":"completed","http_status":200,"result":{}}

Use the final http_status and result to determine success. HTTP 200 on
the stream itself does not mean the task succeeded. Disconnecting removes a
request that has not started; a running task retains its execution slot until
it finishes.

The administrator identified through Chat (William's account is
allwefantasy@gmail.com) may read GET /api/admin/config and update
PUT /api/admin/config with {"max_concurrency":N}, where N is 1–1000.
The update takes effect immediately and persists across restarts. Cookie
writes require Origin and CSRF verification; ordinary accounts receive 403.
Administrator privileges are bound to the stable Chat account identity,
not a caller-supplied email.

Programs and scripts use Bearer authentication. They do not need cookies,
Origin or X-CSRF-Token.

-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json"

Browser pages use cookies. POST /api/auth/login must include an Origin
matching the deployed gateway origin (the production HTTPS domain, not the
caller's host). Send the login response's csrf_token as
X-CSRF-Token on every subsequent cookie-authenticated write.
GET /api/auth/me also returns the current session's CSRF token.
Bearer callers do not use this flow.

When browser SSO is enabled, users can open GET /api/auth/sso/start and
follow the redirect. Failures return to /?sso_error= with an enumerated
value such as denied, state, failed, unavailable or rate.
Programs should use Bearer authentication rather than depend on browser SSO.

Store a memory

A memory is a complete Computer Index bundle in a project, rather than
a chat transcript. Query ranking depends on the configured runtime. With
the managed runtime, your own JEV may verify leaf evidence inside the
bundle
, or your saved native model ranks index metadata and structured IDs
without reading document bodies. Only older deployments without that runtime
use progressive lexical traversal. In every mode, put searchable descriptions
in node name, description, tags and metadata such as summary,
intent_phrases, aliases and actions. Store document bodies in bundle
files addressed by node path, and read them after retrieving a matching node.

If the user asks to remember something without specifying a project, use
Default. The service creates it idempotently on first use and preserves
existing contents on repeated calls. No manual project creation is necessary.
HTTP routes may omit the project segment: PUT /api/projects/index and
POST /api/projects/query use your own Default project. Explicit
/api/projects/Default/... routes also create it when necessary. With the
existing auto-coder.index.cloud CLI, pass --project Default.
Honor an explicitly chosen project name. Lowercase default and
Default are different projects; old indexes remain intact.

A project stores one bundle at a time. PUT replaces the entire bundle.
Existing files missing from the new bundle are deleted. Before appending a
memory, export the current bundle, merge its documents and index nodes, and
then PUT the merged bundle. Uploading only the new memory would erase the old
contents. Use a different project for an independent bundle.

Project names are case-sensitive and limited to 128 characters. They cannot
have leading or trailing whitespace, /, \, control characters, or be
. or ... A Chinese character counts as one character. URL-encode the
project name as a path segment.

To write:

  1. Use Default when no project is specified: call a route without the project
    segment, or POST {} or an empty body to /api/projects. For a named
    project, first GET /api/projects and create it with
    {"project":"<name>"} if absent. Repeated creation is idempotent;
    created: false does not clear the existing index.
  2. Prepare {"files":{...}} including root.yaml. Paths are relative to
    the bundle and use /. Absolute paths, .., ~, $, empty
    segments and segments starting with . are forbidden. Each node's
    path must also remain inside the bundle. Do not set
    metadata.project_path.
  3. PUT /api/projects/<name>/index. On success, the old bundle has been
    replaced. The response's files is a list of paths, not document bodies.
    Failed validation leaves the old bundle intact; export it to confirm.

A minimal bundle uses type: index for another YAML page and
type: file for a document:

{
  "files": {
    "root.yaml": "apiVersion: autocoder.dream/v1\nkind: ComputerIndex\nmetadata:\n  name: root\n  description: Personal memory\nnodes:\n  - name: Knowledge\n    description: Reusable instructions\n    path: knowledge.yaml\n    type: index\n    metadata:\n      kind: knowledge\n",
    "knowledge.yaml": "apiVersion: autocoder.dream/v1\nkind: ComputerIndex\nmetadata:\n  name: Knowledge\n  description: Instructions\nnodes:\n  - name: Refresh VPN\n    description: Reconnect the VPN on Windows and verify routing\n    path: notes/vpn.md\n    type: file\n    tags: [vpn, windows]\n    metadata:\n      kind: howto\n      knowledge_id: howto.vpn.refresh\n      summary: Check the default route after reconnecting the VPN\n      intent_phrases: [vpn disconnected, refresh vpn]\n",
    "notes/vpn.md": "# Refresh VPN\n\nReconnect the profile, then verify that the default route is restored.\n"
  }
}

Limits: 256 files, 1 MiB of UTF-8 per file, 8 MiB of total contents, and
240 characters per path. The gateway's outer PUT body limit is 12 MiB.
Exceeding an upstream limit is rejected without modifying the old index.

Recall a memory

POST /api/projects/<name>/query. Only these body fields are accepted:

Field Meaning
query String, at most 4000 characters. An empty query requires at least one filter
limit Integer 1–100; default 10
kind String or array of strings matching a node's semantic kind, such as howto
since YYYY-MM-DD or an ISO date-time; retain results on or after that date
status String or array of strings matching an entry's status
project Substring filter over entry names, descriptions, chain and related metadata. This is not the URL project name
path Substring filter over path fields
knowledge_rag Boolean. Knowledge RAG is disabled on this service; even true leaves knowledge_rag_result as {}

mode, model, backend, runtime, index_dir, root, effort
and all other unlisted fields receive 400. The request cannot select a
mode, backend or runtime
. The managed runtime chooses your JEV first, then
your saved model. If both are absent, it returns 409
configuration required. Invalid or damaged credentials produce an explicit
error; the service does not silently change identity or engines. Damaged
personal JEV configuration does not fall back to the model. Only deployments
without the managed runtime use progressive lexical traversal.

Read bundle-relative information from results[]: name, description,
path, target_type, tags, metadata, chain, score and
semantic_kind. For target_type: "file", read the body from the exported
bundle's files[path]. For target_type: "index", open the next YAML page
at that path in the bundle.

The managed runtime adds query_runtime: mode is jev or model,
and source is personal_jev or model. These describe the actual query
route and contain no keys, server paths or backend details.

When root_traversal.strategy is ranked_traversal, read that object's
terminals, status, budget and stats. A non-ok status means
the traversal did not finish successfully, although verified partial
terminals may be present. Report the precise status and those partial
results. Do not describe it as complete success, switch engines silently,
or repeat traversal within the same request. If model_ranking is present,
report its status, candidate coverage and truncation. Only scored entries
are model results; unscored lexical hits are not model output.

index_dir, root_path, resolved_path, document_path, read_next
and yaml_files often contain absolute server paths. Do not open them
on the caller's machine or send them back. To get document bodies, GET
/api/projects/<name>/index and use its relative files map.

Only with root_traversal.strategy == "progressive_root_traversal" should
you follow root_traversal.candidates[].path when results are empty or
root_traversal.required is true. Strategy belongs inside
root_traversal, not at the response's top level. Do not apply progressive
traversal rules to a ranked result: required only means that engine
returned no terminal, and does not authorize rerunning a failed ranked
engine. Do not execute the engine's help_command on the gateway machine.

When you already know a bundle path, use locate rather than search:

{"fs_path":"notes/vpn.md","limit":10}

Provide exactly one of fs_path or xpath. fs_path follows the same
bundle-relative path rules as writes. XPath selects index nodes, for example
//node[contains(@description, 'VPN')].

Maintenance

Endpoints

All routes below except health, public pages, login and SSO require
authentication. Bearer writes need only the key. Cookie writes also need
Origin and X-CSRF-Token. Login itself needs Origin but no CSRF token,
because no session exists yet.

Method Path Purpose
GET /healthz Public. {"ok":true,"checks":{"db":"ok","upstream":"ok"}}; HTTP 503 on failure
GET / Human workspace, with a link to this guide
GET /docs Public redirect to /docs/memory; supports ?lang=en
GET /docs/memory Public HTML guide; ?lang=en selects English, ?lang=zh Chinese
GET /docs/memory.md Public Markdown guide, with the same language parameter
POST /api/auth/login {"api_key":"ak_…"} → cookie, csrf_token and account.email
GET /api/auth/sso/start When enabled, redirects to Chat
GET /api/auth/sso/callback Successful SSO returns 303 to /; failure to /?sso_error=<enum>
POST /api/auth/logout Revokes only this gateway session
GET /api/auth/me Authenticated status and email; cookie sessions include csrf_token. Anonymous responses are also 200 with authenticated:false and sso_enabled. Unreachable identity service returns 503 and identity_status:"unavailable", not an authenticated state
GET /api/projects {"ok":true,"username":"u_<64 hex digits>","projects":["Default",...]}. Username is opaque; do not build URLs from it
POST /api/projects Create a project. Empty body or omitted project uses Default. Response includes project, created, user_created, project_created
GET / POST / PUT /api/projects/<operation> Use your Default when the project name is omitted. Supports status, validate, query, locate, index with the methods below
GET /api/projects/{p}/status Health summary
POST /api/projects/{p}/validate Structural validation
POST /api/projects/{p}/query Recall
POST /api/projects/{p}/locate Retrieve nodes by bundle path or XPath
GET /api/projects/{p}/index Export the entire bundle
PUT /api/projects/{p}/index Replace the entire bundle; response files is an array of paths
GET /api/me/model-config configured:false, or provider, alias, model_label, updated_at. Never returns a key
PUT /api/me/model-config {"provider":"deepseek"|"openrouter","api_key":"..."}. First save or changing provider requires a key; blank keeps the saved key for the same provider. At most 512 characters, without whitespace
DELETE /api/me/model-config Idempotently clear model settings
GET /api/me/jev-config Safe personal JEV status: configured, effective_source (personal_jev/model/unconfigured), TypeSafe provider/provider_label, updated_at. No keys, ciphertext or server paths
PUT /api/me/jev-config {"api_key":"..."} only, ≤8 KiB. First save requires a key; a later blank value keeps it
DELETE /api/me/jev-config Idempotently clear personal JEV only. Effective source becomes your model or unconfigured

Model and JEV settings are not memory contents. Import, export, locate,
validate and status are deterministic and do not call a model or JEV.
Settings apply only to queries: your saved model is the second choice
after your own JEV. Saving a key does not add a file to the index or change
an existing bundle. Do not modify model settings merely to store a memory.

Interpret errors

Gateway errors use a string:

{"ok":false,"error":"not authenticated"}

Upstream index errors retain their response body, with an object:

{"ok":false,"error":{"code":"index_invalid","message":"..."}}
HTTP Meaning
401 Missing, invalid or revoked key; expired session; changed identity. Possible errors include not authenticated, identity credential is invalid or revoked, session expired or revoked, identity changed
403 Untrusted Origin or invalid CSRF for cookie writes; access restrictions also use 403
404 Project absent. Another user's project also returns 404
400 Invalid name, body or bundle. Common codes: invalid_request, invalid_bundle, invalid_name, index_invalid, confinement
409 name_conflict between stored names and namespace keys; or configuration required for managed queries without personal JEV or model. Save a query configuration first
413 Request body exceeds the route's limit
429 Too many login attempts (10 per IP per minute), or more than 32 simultaneous index operations
502 Upstream unavailable or successful response exceeds 33 MiB. Oversized JSON is not returned as a truncated body
503 Chat temporarily unavailable. Index data remains, but identity cannot be verified for this request

Upstream error messages replace the server data directory with
<index-home>. Successful responses are not rewritten and may include
absolute server paths; ignore those paths.

A complete example

curl -s -H "Authorization: Bearer $API_KEY" "$GATEWAY/api/projects"

curl -s -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"project":"memory"}' "$GATEWAY/api/projects"

curl -s -X PUT -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d @bundle.json "$GATEWAY/api/projects/memory/index"

curl -s -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"query":"refresh vpn","kind":"howto","limit":10}' \
  "$GATEWAY/api/projects/memory/query"

After retrieving a node, read its body from the same exported bundle's
files["notes/vpn.md"], rather than request a server path.

Account access and invitations

New ordinary users need a single-use invitation; administrators and existing
members sign in directly. Accounts from before the invitation release retain
their indexes and personal configuration, initially with invitation quota 0.
William's administrator account is allwefantasy@gmail.com. Its role is
bound to the stable Chat account identity; an email change does not transfer it.

New members can sign in with Chat SSO or POST api_key and
invitation_code to the same-origin /api/auth/login. For first-time SSO,
POST invitation_code to the same-origin /api/auth/sso/start, then follow
the returned url. Codes are sent only via POST and invitation-link
fragments, never query parameters. Missing codes return
403 / invite_required; invalid, claimed or expired codes return
403 / invite_invalid; disabled accounts return
403 / account_disabled. Existing members can continue using ordinary
Chat API keys for Bearer requests.

Sparse Agent's independent mem_at_ memory grants are exempt from invitations
only after Chat userinfo verifies aud=auto-coder-index,
client_id=sparse-agent-gateway-memory, expiration and operation scope.
These grants access only the user's indexes and permitted model configuration.
They do not create web members, invitation quota or administrator privileges.
Caller-supplied User-Agent or X-Sparse-Agent headers do not grant an exemption.
Disabled accounts and invalid credentials remain rejected. Chat and Sparse
support went live on 2026-10-06. Server credentials stay on the two servers,
never in a browser or agent. Public login clients cannot issue memory grants;
older grants require reconnecting in the workspace while preserving indexes.

Invitation management supports quota 0–100, depth 0–10 and expiration
1–720 hours (default 168). Member invitations are off by default.
Quota 3 and depth 1 means A can invite a total of 3 people; B cannot invite
others. Pending codes reserve slots; expiration or revocation releases unused
slots. Claimed slots are not refunded. Reducing permissions or disabling
member invitations revokes unclaimed codes that no longer qualify.
Existing descendant accounts and private indexes remain intact.

Method Path Purpose
GET / POST /api/invitations Members view their records or create within quota/depth. Body: {"invite_quota":0,"invite_depth":0,"expires_hours":168}. The raw code is returned only once
DELETE /api/invitations/{id} Creator or administrator revokes a pending code
GET /api/admin/access Administrator reads member permissions and invitation policy
PUT /api/admin/invitation-policy Administrator sends {"members_may_invite":true}
PUT /api/admin/members/{id} Administrator sends complete {"invite_quota":3,"invite_depth":1,"disabled":false}

Cookie writes still require same-origin Origin and CSRF. Memory grants cannot
call these management routes.

Conditional model updates for Sparse cloud memory

PUT /api/me/model-config accepts an optional expected_updated_at string.
Read the current updated_at first; use an empty string only when no model
exists. A changed or deleted configuration returns 409 and is preserved.
Verified Sparse offline grants require memory:model for model access and
remain restricted to their existing memory operation scope.

Markdown source: /docs/memory.md