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
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier |
text | string | The memory itself, as stored |
scope | string | Visibility scope: workspace, room:<roomId>, or pair:<humanUserId>:<agentUserId> |
tier | string | core (mirrored from the gateway’s core memory files) or hindsight (long-term facts from the memory provider) |
factType | string | null | Fact classification. Known values: observation, world, experience. Null when the row carries none |
status | string | active, 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 |
provider | string | Memory provider that produced the row, e.g. hindsight or hermes-core |
providerRef | string | The provider’s own stable identifier for this memory |
entityRefs | string[] | Provider refs of entities this memory mentions. Empty array when none |
proofCount | number | null | Number of supporting proofs, when the provider reports one |
firstSeenAt | string | ISO 8601 timestamp of when the memory was first recorded |
lastUsedAt | string | ISO 8601 timestamp of when the memory was last used |
List Memories
GET /api/v1/memoriesReturns 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
| Parameter | Type | Description |
|---|---|---|
tier | string | Filter by tier. Comma-separated for multiple values. Valid: core, hindsight |
factType | string | Filter by fact type. Comma-separated for multiple values. Any string is accepted; the known values are observation, world, experience |
notFactType | string | Exclude fact types. Comma-separated for multiple values. True negation — a memory with no factType survives every notFactType filter |
status | string | Filter by status. Comma-separated for multiple values. Valid: active, archived, deleted. Defaults to active |
scope | string | Filter to one exact scope string, e.g. workspace or room:<roomId> |
provider | string | Filter to one exact provider, e.g. hindsight or hermes-core |
search | string | Case-insensitive substring match against the memory text |
limit | integer | Window size. Default 25, max 100. Values above 100 are silently capped and meta.limitCapped is set in the response |
cursor | string | Pagination 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:
| Field | Type | Description |
|---|---|---|
scannedCount | integer | How many rows the window actually examined before filtering. Lets you tell “nothing matched” from “nothing in range” |
hiddenObservationCount | integer | How many rows the default observation filter suppressed (see below) |
limitCapped | boolean | Present only when the requested limit exceeded 100 and was silently reduced |
maxLimit | integer | Present 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
factTypefilter, and - it survives every
notFactTypefilter.