Persist structured state across sessions
Save JSON checkpoints with exact keys, version checks, and optional expiry.
The state store keeps machine-readable JSON documents across process restarts and surface changes. Use it for progress checkpoints, counters, lookup tables, and coordination records; use memory for natural-language knowledge retrieved by relevance.
Save and resume a checkpoint
Choose a namespace and key
Ask the agent to use a project-specific namespace, such as dashboard-migration, and a stable key such as checkpoint. Namespaces are shared across sessions, not automatically scoped to the current directory.
Save confirmed progress
The agent calls state_put with an input like this after the underlying work succeeds:
{
"namespace": "dashboard-migration",
"key": "checkpoint",
"value": { "phase": "transform", "rows_done": 50000, "last_id": "rec_0xAB4F" }
}A first write returns a version and created: true. Subsequent writes overwrite the document and advance its version.
Read before resuming
{ "namespace": "dashboard-migration", "key": "checkpoint" }Pass this to state_get. It returns the value, version, key, and update timestamp, or null if absent or expired. Confirm the external work still matches the checkpoint before skipping steps.
Available operations
| Tool | Purpose | Result |
|---|---|---|
state_get | Read one namespace/key | Document or null |
state_put | Write without a version condition | version, created |
state_cas | Write only when expected_version matches | matched, and newVersion on success |
state_delete | Remove a document | deleted |
state_query | List a namespace, optionally by prefix | Array of documents sorted by key |
Namespaces and keys must match [A-Za-z0-9_.-]+ and be at most 128 characters. Values can be JSON objects, arrays, strings, numbers, booleans, or null. A null value is different from an absent document: the former is returned inside a document row.
state_put and state_cas accept optional ttl_ms and JSON metadata. Metadata is stored for auditing but is not returned in normal document reads.
Read results
{
"key": "checkpoint",
"value": { "phase": "transform", "rows_done": 50000 },
"version": 3,
"updated_at": 1792287000000
}The update time is milliseconds since the Unix epoch. Treat the returned version as an opaque concurrency token. Do not assume a delete, expiry, or recreation continues the old document's version history.
Avoid lost updates with compare and swap
Two sessions can read the same counter and then overwrite each other's increment if both use state_put. Use state_cas for updates to a shared existing record.
Read the current version
Call state_get for metrics / processed-files. Suppose it returns value 142, version 7.
Write conditionally
{
"namespace": "metrics",
"key": "processed-files",
"expected_version": 7,
"value": 143
}A successful state_cas returns {"matched":true,"newVersion":8}.
Recompute on a conflict
If it returns {"matched":false}, read again and recompute from the new value. The record may have changed, expired, or been deleted. CAS does not create a missing document.
A read followed by an unconditional write is not a lock. In particular, state_get returning null does not prove that another session is not simultaneously starting the same operation. The model-facing tools do not provide an atomic create-if-absent lock operation. Coordinate initial creation and use version checks for existing records.
The store uses WAL-mode SQLite with a 5-second busy timeout. CAS runs in an immediate transaction, so callers racing on the same version cannot both win that update.
Expire temporary state
{
"namespace": "rate-limits",
"key": "github-search",
"value": { "remaining": 9 },
"ttl_ms": 3600000
}Expired documents disappear from state_get and state_query results; expired rows are garbage-collected when the store opens. An update with a new ttl_ms resets expiry from the write time. Omitting ttl_ms on an update removes the previous expiry rather than preserving it.
TTL is useful for cached rate-limit observations or temporary hints. It is not a timer that runs work when a document expires.
List related records
state_query accepts namespace, optional key_prefix, and limit (default 20, maximum 100):
{
"namespace": "dashboard-migration",
"key_prefix": "phase-",
"limit": 50
}Results are sorted by key. The tool has no cursor or offset parameter, so this is bounded listing, not a complete pagination API. Use deliberately partitioned prefixes when you expect many documents. Timestamp-shaped keys can form an audit trail, provided they use only allowed key characters, for example 2026-10-17T21.00.00Z rather than a timestamp containing colons.
Storage and subagents
The database lives at ~/.afk/state/kv/kv.db, or kv/kv.db under AFK_STATE_DIR. It is created on first use with owner-only directory permissions and best-effort owner-only database permissions on platforms that support them. REPL, chat, Telegram, daemon, and scheduled sessions share documents when they use that same state tree.
Top-level sessions can receive all five tools. Ordinary subagents receive read-only state_get and state_query; the coordinator should persist their final checkpoints. Tool permissions can further restrict access.
For coordination within one task, use the shared workspace instead:
| Tool | Use |
|---|---|
workspace_publish | Share a finding, evidence, hypothesis, decision, artifact, or status with siblings |
workspace_query | Look up findings without rereading the same files |
workspace_subscribe | Receive matching workspace updates on child surfaces where subscription support is wired |
workspace_subscribe is not part of the ordinary top-level tool surface. Workspace sharing is session coordination, not a substitute for a durable state checkpoint. See subagents for usage.
Choose state or memory
| Need | Better fit |
|---|---|
| Exact lookup of a JSON checkpoint | State store |
| Concurrent update to an existing counter | State store with CAS |
| A fact recalled by topic | Memory archive |
| Durable user preferences | Memory |
| Findings needed by sibling agents now | Workspace |
| A persistent repository objective | Goal tracking |