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_absent or if_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:

ScopeUse it forHow to select it
OrgState shared by the organization.Default for normal API, SDK, and CLI calls.
FunctionState 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.json

REST 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:

FieldLimit
Key512 UTF-8 bytes
Value65,536 UTF-8 bytes after JSON serialization
Search page size1 to 100, default 50
TTL1 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:

FieldMeaning
idStable memory record id.
keyCaller-defined key.
scopeResolved org or function scope.
valueJSON value, omitted when search uses include_value=false.
versionMonotonic version serialized as a string.
created_at / updated_atCreation and last write timestamps.
last_read_atLast read timestamp, or null.
read_count / write_countCounters serialized as strings.
expires_atExpiration timestamp, or null.
created_by / updated_byActor 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-absent

Update only if the current version matches:

primitive memories set state '{"step":3}' --if-version 2

Delete only if the current version matches:

primitive memories delete state --if-version 3

Stale version checks return 409 memory_conflict.

Search

Search lists active memories in lexicographic key order by prefix:

primitive memories search thread: --limit 100 --metadata-only

REST search parameters:

ParameterDescription
prefixKey prefix to match. Omit to list all active memories in scope.
cursorContinuation cursor from meta.cursor.
limitPage size, 1 to 100, default 50.
include_valuetrue or false, default true.
updated_afterOnly include memories updated at or after this timestamp.
updated_beforeOnly include memories updated at or before this timestamp.
scope_type / scope_idOptional 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_day for stored memory size over time.
  • memory.read for get and search operations.
  • memory.write for 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.

Related Pages

  • CLI: primitive memories terminal commands.
  • SDKs: generated client imports and examples.
  • REST API: OpenAPI discovery, auth, and envelope shape.
  • Functions: hosted JavaScript handlers that can use function-scoped Memories.