Skip to main content
The Memory API exposes the read surfaces over an agent’s episodic and markdown memory: a health snapshot that scores how usable the memory is, a hybrid recall search that fuses two retrieval engines, and the version audit trail for canonical memory files. The mutating restore endpoint lives on the Admin reference because it requires OWNER/ADMIN.
Every endpoint requires authentication and workspace context. workspace_id is always pulled from the session — never accepted as a query parameter or body field — and crew_id, when set, is validated against the caller’s workspace (cross-tenant lookups return 404).

Endpoints

MethodEndpointPurpose
GET/api/v1/memory/healthMemory health snapshot (five metrics + composite)
POST/api/v1/memory/search/hybridFused FTS5 + episodic memory recall
GET/api/v1/memory/versionsAudit chain for one canonical memory path
GET/api/v1/memory/versions/{sha}Raw blob bytes for one historical version

Health

Read-only snapshot for the memory health dashboard. Backed by internal/consolidate.ComputeHealth. The five aggregate queries it runs are cheap, so the handler recomputes on every request rather than reading the persisted memory_health_snapshots table — those snapshots exist for time-series plots, not real-time reads. See Episodic memory — health scoring for the formula and operator interpretation.

Get health snapshot

GET /api/v1/memory/health
Query parameters:
ParamTypeDescription
crew_idstringOptional. Crew ID (not slug) to scope the score to a single crew. Omit for workspace-aggregate. The ID is validated against the caller’s workspace via crewBelongsToWorkspace; a foreign ID returns 404.
Auth: Every workspace member can read. The response contains only counts and ratios, no raw entry content. Response: 200 OK
{
  "workspace_id": "ws_123",
  "crew_id": "",
  "computed_at": "2026-04-30T11:42:18.018Z",
  "overall": 77.4,
  "metrics": {
    "freshness":    83.1,
    "coverage":     62.0,
    "coherence":    80.2,
    "efficiency":   77.4,
    "reachability": 78.0
  },
  "details": {
    "freshness_recent_24h":     12,
    "freshness_baseline_daily":  9.4,
    "coverage_distinct_types":   5,
    "coherence_relations":       1284,
    "coherence_embeddings":      1390,
    "efficiency_archived":       640,
    "efficiency_live":           1390,
    "reachability_connected":    1084
  }
}
FieldTypeDescription
workspace_idstringEcho of the session’s workspace.
crew_idstringEcho of the request’s crew_id (empty for workspace-aggregate).
computed_atstring (RFC3339)When this response was computed. The handler always recomputes, so this is now.
overallnumberWeighted composite, 0–100. Formula: 0.25·freshness + 0.25·coverage + 0.20·coherence + 0.15·efficiency + 0.15·reachability.
metrics.*numberEach metric, 0–100. See the guide for what each one measures.
detailsobjectFree-form supplementary data — the per-metric inputs each score was derived from (freshness_recent_24h, coverage_distinct_types, coherence_relations/coherence_embeddings, efficiency_archived/efficiency_live, reachability_connected, …). Schema is not stable across versions; consume defensively.
There is no separate band field — clients categorise locally:
BandRange
Redoverall < 50
Yellow50 ≤ overall < 75
Greenoverall ≥ 75
The CLI uses these exact thresholds (internal/cli colour helpers) and the FE follows. Errors:
StatusCondition
401No workspace context in session.
404crew_id set and not in caller’s workspace.
500Compute failure — propagated from ComputeHealth.

POST /api/v1/memory/search/hybrid
Single-shot memory recall that fuses two retrieval engines and merges their results with Reciprocal Rank Fusion (RRF, k = 60):
  • FTS5 / BM25 — the workspace-tier full-text index over markdown memory.
  • Episodic recall — dense-vector + BM25 recall over the crew journal.
The handler degrades gracefully: if only one engine is wired (no embedder, or no FTS engine for the workspace), it returns that engine’s results alone; if neither has matches it returns 200 with an empty hits array and count: 0 — the same shape the sidecar /memory/search uses. Auth: required + workspace context (MEMBER+). Every query is anchored on the caller’s session workspace, so a foreign workspace_id cannot be smuggled through the body. Request body:
{
  "query": "deploy script flakiness",
  "limit": 10,
  "scope": "crew_shared",
  "crew_id": "crew_123"
}
FieldTypeDefaultDescription
querystringRequired. The search text. 400 when empty.
limitinteger10Max hits to return. Non-positive values fall back to the default; capped at 50 server-side.
scopestring""One of "" (no scope filter, MEMBER+ implied), "own" (caller’s own memory only), or "crew_shared" (crew-shared memory). Any other value is 400.
crew_idstringOptional. Binds crew_shared results to a single crew.
Response: 200 OK
{
  "query": "deploy script flakiness",
  "count": 2,
  "hits": [
    {
      "source": "fts",
      "score": 0.0163,
      "fts": { "...": "FTS5 chunk hit" }
    },
    {
      "source": "episodic",
      "score": 0.0159,
      "episodic": { "...": "journal entry hit" }
    }
  ]
}
The unused half of each hit is omitted, not emitted as null (fts / episodic both carry omitempty): an FTS-sourced hit has no episodic key and vice-versa.
FieldTypeDescription
querystringEcho of the request’s query.
countintegerlen(hits).
hits[]arrayFused, RRF-ranked results.
hits[].sourcestring"fts" or "episodic" — which engine produced the hit.
hits[].scorenumberThe RRF score used for ranking (higher = better).
hits[].ftsobject?The FTS5 chunk payload; omitted on episodic-sourced hits.
hits[].episodicobject?The episodic journal-entry payload; omitted on FTS-sourced hits.
StatusCondition
400Malformed JSON, empty query, or an invalid scope.
401No workspace context in the session.
500Underlying memory.HybridSearch failed.

Memory versions audit trail

The HTTP mirror of crewship memory log/show. Workspace is anchored from the session — query strings never carry workspace_id, so a cross-workspace probe can’t smuggle a foreign id through. Restore (POST /api/v1/memory/versions/{sha}/restore) is documented separately on the Admin reference because it requires OWNER/ADMIN and mutates canonical state; the two read endpoints below are MEMBER+.

GET /api/v1/memory/versions

Returns the audit chain for one canonical memory path, newest-first. Auth: required + workspace context. Query parameters:
ParamTypeDefaultDescription
pathstringRequired. Canonical memory path (e.g. agent:martin/AGENT.md). 400 when omitted.
limitinteger20Max rows to return. Positive values are accepted and clamped to a hard ceiling of 1000; non-positive or non-numeric values fall back to the default.
Response: 200 OK
{
  "path": "agent:martin/AGENT.md",
  "count": 2,
  "entries": [
    {
      "id": "mv_01HZA...",
      "path": "agent:martin/AGENT.md",
      "tier": "agent",
      "sha256": "abc...",
      "bytes": 1284,
      "written_at": "2026-05-18T08:30:00Z",
      "written_by": "audit-watcher",
      "parent_sha": "def...",
      "payload_ref": "/var/lib/crewship/memory/versions/ab/abc..."
    },
    {
      "id": "mv_01HZ9...",
      "path": "agent:martin/AGENT.md",
      "tier": "agent",
      "sha256": "def...",
      "bytes": 1180,
      "written_at": "2026-05-17T22:11:48Z",
      "written_by": "audit-watcher",
      "payload_ref": "/var/lib/crewship/memory/versions/de/def..."
    }
  ]
}
FieldTypeDescription
pathstringEcho of the request’s path.
countintegerlen(entries) — handy when paging is bounded by limit.
entries[]arrayNewest-first list of memory_versions rows for this path in the caller’s workspace.
entries[].tierstringagent, crew, workspace, pins, or learned.
entries[].parent_shastring?sha256 of the prior version in the chain. Omitted (not null) on the root version — the field carries omitempty and the column is COALESCE’d to "".
entries[].payload_refstringOn-disk path to the content-addressed blob.
StatusCondition
400Missing path query parameter.
401No workspace context in the session.
500Underlying memory.LogVersions query failed.

GET /api/v1/memory/versions/{sha}

Returns the raw blob bytes for one historical version. Pipe-friendly: the body is the historical content, metadata travels in headers so a CLI pipe to a file produces the exact bytes that were written. Auth: required + workspace context. Path parameters:
ParamDescription
shaContent sha256 from memory_versions.sha256.
Query parameters:
ParamTypeDescription
pathstringRequired. The canonical memory path the sha was written under. The same sha can be paired with different paths if two paths happen to converge on identical content.
Response: 200 OK — raw bytes, Content-Type: application/octet-stream.
HeaderDescription
X-Memory-Version-ShaEcho of the path’s sha.
X-Memory-Version-BytesBody length (matches memory_versions.bytes).
StatusCondition
400Missing sha path segment or missing path query parameter.
401No workspace context.
404No memory_versions row matches (workspace_id, path, sha) — cross-workspace lookups masked as 404 to avoid leaking row existence.
500Read failure from the underlying blob store.
The restore endpoint (POST /api/v1/memory/versions/{sha}/restore) is OWNER/ADMIN-only and applies extra server-side path confinement to refuse canonical targets outside the configured memory root. See the Admin reference for the full mutation surface.

Tenancy

  • workspace_id is always pulled from the session — never accepted as a query parameter or body field.
  • crew_id, when set, is validated via crewBelongsToWorkspace (same helper as the Paymaster API). Cross-tenant lookups return 404.