Memories API
The Memories API is the REST mirror of the Memories page. It returns what your agents have learned: each agent’s core memory plus the long-term facts Cohort extracts from their work, filtered by layer, fact type, scope, source, and free text. To search long-term memory as it is right now, use Search Long-Term Memory.
Reads require the memory:read scope. Every filter, scope check, and bound is the same one the Memories page uses.
Renamed fields
Since 2026-10-01, layer replaces tier, and source replaces provider, in responses and as query filters. The old names keep working, with their old values, until 2026-12-30, and are removed on or after that date. Move to layer and source before then.
| Old | New |
|---|---|
tier: "core" | layer: "core" |
tier: "hindsight" | layer: "long_term" |
provider: "hermes-core" | source: "core" |
provider: "hindsight" | source: "long_term" |
Memory Object
{
"id": "kh7abc123def456",
"text": "The Q3 launch moved to October 14.",
"scope": "workspace",
"layer": "long_term",
"source": "long_term",
"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, or pair:<humanUserId>:<agentUserId> for something one person shared with one agent |
layer | string | core (an agent’s core memory: who it is working with and its standing rules) or long_term (what your agents learned from work and conversations) |
source | string | Which part of Cohort’s memory produced the row: long_term or core |
tier | string | Deprecated, removed on or after 2026-12-30. The old name of layer: core or hindsight |
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 | Deprecated, removed on or after 2026-12-30. The old name of source: hindsight or hermes-core |
providerRef | string | A stable identifier for this memory within its source |
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, such as another member’s pair: memories, are never returned.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
layer | string | Filter by layer. Comma-separated for multiple values. Valid: long_term, core |
tier | string | Deprecated, removed on or after 2026-12-30. Use layer. 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 |
source | string | Filter to one source: long_term or core |
provider | string | Deprecated, removed on or after 2026-12-30. Use source. Filter to one exact old value: 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 layer, tier, source or status values return 400 with the valid values listed, and so does passing a new name together with its deprecated alias (layer with tier, source with provider). factType, notFactType, scope, and the deprecated provider are free-form strings and are never rejected — an unknown value simply matches nothing.
Response
{
"data": [
{
"id": "kh7abc123def456",
"text": "The Q3 launch moved to October 14.",
"scope": "workspace",
"layer": "long_term",
"source": "long_term",
"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",
"layer": "core",
"source": "core",
"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?layer=long_term&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 long-term observations (layer: "long_term" 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 layer — 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.
Search Long-Term Memory
POST /api/v1/memories/recallSearches the workspace’s long-term memory as it is right now and returns what is relevant to query, ranked the way an agent’s own recall ranks it: distilled observations first, with the facts they were built from folded in. GET /api/v1/memories browses a copy that can trail the newest learning by up to a day; this endpoint does not, so a fact learned a minute ago is found. Requires memory:read scope.
Your scope rules still apply: a memory in a scope the key’s user cannot read, or one someone deleted, is never returned. Results are long-term memory only. Each call runs one live search, which uses your workspace’s memory allowance the same way an agent’s recall does.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | What to look for, in plain language. 1 to 400 characters |
limit | integer | No | Most results to return, 1 to 50. Default 10 |
Response
{
"data": [
{
"providerRef": "mem-obs-2210",
"text": "The launch moved to Friday after the legal review.",
"layer": "long_term",
"factType": "observation",
"mentionedAt": "2026-10-01T16:00:00.000Z"
}
],
"meta": { "latencyMs": 812 }
}An empty data means nothing relevant is known. providerRef matches the same memory’s providerRef in GET /api/v1/memories once that copy has caught up.
| Status | Meaning |
|---|---|
| 400 | Missing or too-long query, limit out of range, or an unknown field |
| 409 | Long term agent memory is turned off for this workspace |
| 503 | Memory could not be searched right now (MEMORY_UNAVAILABLE). Retry after Retry-After seconds |
Example
curl -X POST "https://api.cohort.bot/api/v1/memories/recall" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "When is the launch?", "limit": 5}'