Skip to Content

Memories API

The Memories API is the REST mirror of the Memory browse UI. It returns what your agents have learned — core memory synced from the gateway plus long-term facts extracted by the memory provider — filtered by tier, fact type, scope, provider, and free text.

Reads require the memory:read scope. Every filter, scope check, and bound is the same code path the in-app Memory browser uses; only the identity source differs (API key rather than session).

Memory Object

{ "id": "kh7abc123def456", "text": "Cohort runs on Convex.", "scope": "workspace", "tier": "hindsight", "factType": "world", "status": "active", "provider": "hindsight", "providerRef": "mem-world-8891", "entityRefs": ["entity:cohort"], "proofCount": 3, "firstSeenAt": "2025-01-10T12:00:00.000Z", "lastUsedAt": "2025-01-16T09:30:00.000Z" }

Field Reference

FieldTypeDescription
idstringUnique identifier
textstringThe memory itself, as stored
scopestringVisibility scope: workspace, room:<roomId>, or pair:<humanUserId>:<agentUserId>
tierstringcore (mirrored from the gateway’s core memory files) or hindsight (long-term facts from the memory provider)
factTypestring | nullFact classification. Known values: observation, world, experience. Null when the row carries none
statusstringactive, archived, or deleted. Archived rows were weeded by the retention policy (expired or transitory, with no recent recall) and are recoverable; deleted rows are retained for sync reconciliation
providerstringMemory provider that produced the row, e.g. hindsight or hermes-core
providerRefstringThe provider’s own stable identifier for this memory
entityRefsstring[]Provider refs of entities this memory mentions. Empty array when none
proofCountnumber | nullNumber of supporting proofs, when the provider reports one
firstSeenAtstringISO 8601 timestamp of when the memory was first recorded
lastUsedAtstringISO 8601 timestamp of when the memory was last used

List Memories

GET /api/v1/memories

Returns memories readable by the calling key’s user in the current workspace. Requires memory:read scope.

The endpoint scans a bounded recency window ordered by creation time, newest first, then returns the surviving rows sorted by lastUsedAt, newest first. Rows in scopes you cannot read — another member’s pair: memories, a room you do not belong to — are never returned.

Query Parameters

ParameterTypeDescription
tierstringFilter by tier. Comma-separated for multiple values. Valid: core, hindsight
factTypestringFilter by fact type. Comma-separated for multiple values. Any string is accepted; the known values are observation, world, experience
notFactTypestringExclude fact types. Comma-separated for multiple values. True negation — a memory with no factType survives every notFactType filter
statusstringFilter by status. Comma-separated for multiple values. Valid: active, archived, deleted. Defaults to active
scopestringFilter to one exact scope string, e.g. workspace or room:<roomId>
providerstringFilter to one exact provider, e.g. hindsight or hermes-core
searchstringCase-insensitive substring match against the memory text
limitintegerWindow size. Default 25, max 100. Values above 100 are silently capped and meta.limitCapped is set in the response
cursorstringPagination cursor from a previous response’s cursor field. Opaque — do not construct one. A malformed cursor is ignored and the scan starts from the top

Invalid tier or status values return 400 with the valid values listed. factType, notFactType, scope, and provider are free-form strings and are never rejected — an unknown value simply matches nothing.

Response

{ "data": [ { "id": "kh7abc123def456", "text": "Cohort runs on Convex.", "scope": "workspace", "tier": "hindsight", "factType": "world", "status": "active", "provider": "hindsight", "providerRef": "mem-world-8891", "entityRefs": ["entity:cohort"], "proofCount": 3, "firstSeenAt": "2025-01-10T12:00:00.000Z", "lastUsedAt": "2025-01-16T09:30:00.000Z" }, { "id": "kh7ghi789jkl012", "text": "Dave prefers squash merges.", "scope": "workspace", "tier": "core", "factType": null, "status": "active", "provider": "hermes-core", "providerRef": "core:PREFERENCES.md#4", "entityRefs": [], "proofCount": null, "firstSeenAt": "2025-01-08T08:15:00.000Z", "lastUsedAt": "2025-01-15T22:04:00.000Z" } ], "cursor": "1736510400000", "hasMore": true, "meta": { "scannedCount": 25, "hiddenObservationCount": 4 } }

Example

curl "https://api.cohort.bot/api/v1/memories?tier=hindsight&factType=world&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY"

No total, by design

This endpoint deliberately returns no total — unlike the other paginated list endpoints. Memory browse is a bounded, escapable recency window: limit bounds the slice of the bank that is scanned, and filters are applied to that slice. A true count would mean scanning the whole memory bank on every page, which is exactly what the bounded-window design refuses.

Read meta instead:

FieldTypeDescription
scannedCountintegerHow many rows the window actually examined before filtering. Lets you tell “nothing matched” from “nothing in range”
hiddenObservationCountintegerHow many rows the default observation filter suppressed (see below)
limitCappedbooleanPresent only when the requested limit exceeded 100 and was silently reduced
maxLimitintegerPresent alongside limitCapped. Currently 100

Because filtering happens after the window is taken, data can hold fewer than limit items while hasMore is still true. Paginate on hasMore and cursor, never on data.length.

Auto-extracted observations are hidden by default

With no fact-type filter supplied, auto-extracted hindsight observations (tier: "hindsight" with factType: "observation") are omitted from data and counted in meta.hiddenObservationCount. That default exists so an unfiltered browse is not swamped by raw extraction noise.

Supplying any fact-type filter turns the default off — factType or notFactType, inclusive or negated. Asking for a fact type is you stating what you want, and it wins outright. To see observations, pass factType=observation.

Fact-type filter semantics

Fact-type filters apply uniformly to every tier — core rows are not exempt. A memory with no factType is not a member of any fact type, so:

  • it matches no factType filter, and
  • it survives every notFactType filter.