State & Persistence

Turn agents are designed to run indefinitely. The runtime provides native, file-backed state persistence without requiring databases, ORMs, or complex state machines.


The Persistence Vector (persist let)

The persist keyword modifies a standard let binding to serialize the variable directly to the .turn_store directory immediately upon assignment.

syntax
persist let x = y;

When the VM boots, before it executes the first line of your script, it scans the .turn_store directory for persisted variables and pre-loads them into the global environment.

stateful_agent.tn
// First run: run_count is 0, then becomes 1
// Second run: run_count pre-loads as 1, then becomes 2
persist let run_count = 0;
persist let run_count = run_count + 1;

call("echo", "This loop has run " + run_count + " times.");

How it works

  1. The compiler emits an Instr::StorePersist(name) bytecode instruction exactly after the standard Instr::Store.
  2. The VM writes the serialized JSON value to .turn_store/persist_<name>.json.
  3. If the script halts, crashes, or is shut down, the value remains on disk.
  4. On the next execution, the initial persist let run_count = 0; acts as a fallback default only. The loaded value from disk shadows the right-hand-side expression.

NOTE

You can persist any valid Turn type: strings, numbers, lists, maps, and even structured JSON objects.


Orthogonal Persistence (suspend)

For workflows that require waiting for external events or human-in-the-loop approval, you can suspend the entire VM execution state:

syntax
let msg = suspend for Any "Waiting for input";

When the VM hits the suspend primitive, it halts execution cleanly. The full state of the process — the instruction pointer, the stack, and the lexical environment — is preserved. The process can be resumed later from exactly where it left off.

payment_flow.tn
call("echo", "Waiting for payment confirmation...");

// Process yields here until the external world wakes it up
let payment = suspend for Any "Awaiting callback data";

call("echo", "Payment received, continuing execution.");
let receipt = infer Receipt { "Generate receipt for payment: " + payment; };

This allows Turn programs to represent business logic naturally, without inverting control flow into complex event-driven callbacks.


State vs. Semantic Memory

Persistence (persist let) and Semantic Memory (remember) serve different purposes:

FeatureBest ForStorage MechanismRetrieval
persist letExact state, counters, config flags.turn_store/persist_x.jsonExact variable name
rememberUnstructured text, facts, learningsHNSW Vector Graph (items.hnsw)Semantic similarity (recall)

Use persist let for exact application state that the script needs to run correctly (e.g., an incrementing sequence ID, or a configuration JSON map). Use remember for knowledge the agent should use to answer questions or make decisions later.


Time-Travel Replay

Every infer call, tool invocation, and state mutation is written to a versioned Write-Ahead Log inside .turn_store/<agent-id>/. If an agent crashes, you do not need to re-run it to understand what happened.

terminal
turn replay my-agent

This opens an interactive session:

interactive_session
Frame 0: infer Report { "Summarize quarterly results" }
[n] next  [p] prev  [q] quit
> n
Frame 1: tool called fs_write { path: "report.txt", content: "..." }

You can step forward and backward through every operation the agent performed. This replaces the need for log scraping, debug print statements, or re-running long LLM workflows just to see what went wrong.


Next Steps