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:

syntax
let next = StructName { field: new_value, ..current };
epoch_state.tn
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:

syntax
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.

payment_flow.tn
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:

memory.tn
// 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

FeatureBest ForLifetimeRetrieval
Struct epochs (..spread)Structured agent state, OODA loop dataCurrent processField access
suspend checkpointPause across external eventsAcross VM restartsAutomatic resume
remember/recallFacts and values across cyclesPermanent (persists across restarts)Exact key lookup
context.appendWorking input for the current infer callCurrent turnAutomatic (injected into LLM)

Next Steps