State & Persistence
Turn agents are designed to run indefinitely across restarts, network partitions, and crashes. The runtime provides native, file-backed state persistence without requiring databases, ORMs, or external state machines. The foundation is strict immutability: state is never mutated, it evolves.
State as Immutable Epochs
Turn enforces strict immutability. An agent's state is never overwritten. Every OODA cycle produces a new, complete state snapshot called an epoch. The struct spread operator (..base) makes this concise and safe:
let next = StructName { field: new_value, ..current };struct AgentState {
cycle: Num,
last_action: Str,
confidence_floor: Num
};
let state = AgentState { cycle: 1, last_action: "init", confidence_floor: 0.85 };
// Create a new epoch without touching untouched fields
let next_state = AgentState {
cycle: state.cycle + 1,
last_action: "analysis_complete",
..state
};
call("echo", "Epoch: " + next_state.cycle);Because every epoch is a complete, independent value, the VM can checkpoint them automatically. If the process crashes, the VM re-loads the last epoch and resumes exactly where it left off.
Orthogonal Persistence (suspend)
For workflows that require waiting for external events, human-in-the-loop approvals, or long pauses between cycles, use suspend to checkpoint the entire VM execution state:
suspend;When the VM hits suspend, it halts execution cleanly. The full state of the process (the instruction pointer, the stack, and the lexical environment) is written to .turn_store/. The process can be resumed later from exactly where it left off.
struct PaymentConfirmation { transaction_id: Str, amount: Num, status: Str };
call("echo", "Waiting for payment confirmation...");
// Checkpoint: VM state is persisted to disk here.
// External system can wake this process when payment arrives.
suspend;
// Execution resumes here after the VM is restarted externally
let payment = recall("pending_payment");
call("echo", "Payment received, continuing execution.");
let receipt = infer PaymentConfirmation {
"Generate a receipt for this payment: " + payment;
};NOTE
suspend is a synchronization primitive for the external world. It does not return a value. Use remember/recall to pass data across suspension boundaries.
Working Memory (remember / recall)
For state that must survive across suspend boundaries and process restarts, use the built-in key-value memory store:
// Write to the process's durable key-value store
remember("last_summary", "Q4 results: revenue up 18%, churn up 3%");
remember("cycle_count", 7);
// Read it back later, even after a restart or suspend
let summary = recall("last_summary");
let count = recall("cycle_count");
call("echo", "Cycle " + count + ": " + summary);remember writes to the process's isolated key-value store. recall retrieves by exact key. Returns null if the key does not exist. Both persist automatically across suspend boundaries and VM restarts.
The .turn_store Directory
The Turn VM writes all durable state to a .turn_store/ directory in the working directory. This includes:
- Process memory (from
remember) - Suspension checkpoints (from
suspend) - Struct definitions loaded at runtime
You do not need to configure or manage this. It is created automatically on first run.
TIP
Add .turn_store/ to your .gitignore for development. Commit it to version control only if you need to snapshot an agent's state for deployment.
State vs. Context vs. Memory
| Feature | Best For | Lifetime | Retrieval |
|---|---|---|---|
Struct epochs (..spread) | Structured agent state, OODA loop data | Current process | Field access |
suspend checkpoint | Pause across external events | Across VM restarts | Automatic resume |
remember/recall | Facts and values across cycles | Permanent (persists across restarts) | Exact key lookup |
context.append | Working input for the current infer call | Current turn | Automatic (injected into LLM) |