Primitive Memories
Primitive Memories are durable JSON key-value records for agent state. Use them when an agent or Function needs to remember compact structured state across turns, emails, retries, or process restarts without bringing its own database.
Common uses:
- Remember the last processed email or thread id.
- Persist workflow checkpoints such as
{ "step": 2 }. - Share small org-scoped state across tools or agents.
- Keep Function-local state scoped by immutable Function id.
- Coordinate retries with
if_absentorif_version.
Memories are not a replacement for bulk storage or document search. Values are JSON, keys are caller-defined strings, and search is by key prefix.
Scopes
Every memory belongs to exactly one organization. Within that organization it is resolved under one of two scopes:
| Scope | Use it for | How to select it |
|---|---|---|
| Org | State shared by the organization. | Default for normal API, SDK, and CLI calls. |
| Function | State private to one Primitive Function. | Pass a Function id UUID, not a function name. |
When a request is already authenticated as a Function, Primitive uses that Function id automatically. API and SDK callers can also pass the x-primitive-function-id header or explicit function scope. CLI users can pass --function <function-id>.
Function names are mutable and are never accepted as scope identifiers.
CLI
Use the primitive memories commands for terminal and agent workflows:
primitive memories set thread:latest '{"email_id":"em_123"}' primitive memories get thread:latest primitive memories search thread: --metadata-only primitive memories delete thread:latest
Values must be valid JSON. Strings must be quoted as JSON strings:
primitive memories set greeting '"hello"'Function-scoped memories:
primitive memories set state '{"step":2}' --function <function-id> primitive memories get state --function <function-id> primitive memories search state: --function <function-id> primitive memories delete state --function <function-id>
Read a value from a file:
primitive memories set agent:state --value-file ./state.jsonREST API
All endpoints live under https://api.primitive.dev/v1 and use the standard Primitive bearer auth header.
Set a memory:
curl -X PUT https://api.primitive.dev/v1/memories \ -H "Authorization: Bearer $PRIMITIVE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"key":"thread:latest","value":{"email_id":"em_123"}}'
Get a memory:
curl "https://api.primitive.dev/v1/memories?key=thread:latest" \ -H "Authorization: Bearer $PRIMITIVE_API_KEY"
Search by key prefix:
curl "https://api.primitive.dev/v1/memories/search?prefix=thread:&include_value=false" \ -H "Authorization: Bearer $PRIMITIVE_API_KEY"
Delete a memory:
curl -X DELETE "https://api.primitive.dev/v1/memories?key=thread:latest" \ -H "Authorization: Bearer $PRIMITIVE_API_KEY"
Function scope over REST:
curl -X PUT https://api.primitive.dev/v1/memories \ -H "Authorization: Bearer $PRIMITIVE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "state", "value": { "step": 2 }, "scope": { "type": "function", "id": "<function-id>" } }'
For get, delete, and search, use query scope:
curl "https://api.primitive.dev/v1/memories?key=state&scope_type=function&scope_id=<function-id>" \ -H "Authorization: Bearer $PRIMITIVE_API_KEY"
SDKs
In Node.js, use the high-level client:
import { createPrimitiveClient } from '@primitivedotdev/sdk/api'; const client = createPrimitiveClient({ apiKey: process.env.PRIMITIVE_API_KEY, }); await client.memories.set({ key: 'thread:latest', value: { email_id: 'em_123' }, }); const memory = await client.memories.get('thread:latest'); const page = await client.memories.search({ prefix: 'thread:', includeValue: false, }); await client.memories.delete('thread:latest');
Function-scoped writes pass scope: { type: 'function', id: '<function-id>' }. Function-scoped reads, deletes, and searches pass scope_type: 'function' and scope_id: '<function-id>'.
client.memories.search is key prefix search, not free-text or semantic search. Use client.semanticSearch(...) for mail search. The generated SDK clients still expose lower-level OpenAPI operation ids setMemory, getMemory, deleteMemory, and searchMemories for callers that need the exact REST operation shape.
JSON and Limits
Values can be any valid JSON value:
- object
- array
- string
- number
- boolean
null
Limits:
| Field | Limit |
|---|---|
| Key | 512 UTF-8 bytes |
| Value | 65,536 UTF-8 bytes after JSON serialization |
| Search page size | 1 to 100, default 50 |
| TTL | 1 second to 31,536,000 seconds |
The API rejects values that do not serialize as JSON.
Metadata and Versions
Memory records include metadata agents can use for coordination:
| Field | Meaning |
|---|---|
id | Stable memory record id. |
key | Caller-defined key. |
scope | Resolved org or function scope. |
value | JSON value, omitted when search uses include_value=false. |
version | Monotonic version serialized as a string. |
created_at / updated_at | Creation and last write timestamps. |
last_read_at | Last read timestamp, or null. |
read_count / write_count | Counters serialized as strings. |
expires_at | Expiration timestamp, or null. |
created_by / updated_by | Actor that created or last updated the record. |
Use version with compare-and-set when multiple workers may update the same key.
TTLs
Set an expiration with ttl_seconds or expires_at:
curl -X PUT https://api.primitive.dev/v1/memories \ -H "Authorization: Bearer $PRIMITIVE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"key":"lock:agent","value":{"owner":"agent-1"},"ttl_seconds":300}'
On update, existing TTL is preserved unless you pass ttl_seconds, expires_at, or clear_ttl.
Version Checks
Create only if the key is absent:
primitive memories set lock '{"owner":"agent-1"}' --if-absentUpdate only if the current version matches:
primitive memories set state '{"step":3}' --if-version 2Delete only if the current version matches:
primitive memories delete state --if-version 3Stale version checks return 409 memory_conflict.
Search
Search lists active memories in lexicographic key order by prefix:
primitive memories search thread: --limit 100 --metadata-onlyREST search parameters:
| Parameter | Description |
|---|---|
prefix | Key prefix to match. Omit to list all active memories in scope. |
cursor | Continuation cursor from meta.cursor. |
limit | Page size, 1 to 100, default 50. |
include_value | true or false, default true. |
updated_after | Only include memories updated at or after this timestamp. |
updated_before | Only include memories updated at or before this timestamp. |
scope_type / scope_id | Optional scope selection. |
Use include_value=false when an agent only needs keys and metadata.
Billing
Primitive Memories are metered through the same usage billing system as the rest of Primitive:
memory.storage.gb_dayfor stored memory size over time.memory.readfor get and search operations.memory.writefor set and delete operations.
On Developer, billable actions draw from the monthly usage credit grant and can return 402 developer_usage_credit_exhausted when credits are exhausted. Paid plans accrue usage overage instead of stopping at monthly credits. See Pricing and Limits & Quotas.