Serialization
Every opentine run is fully serializable. The .tine file format captures the complete run tree — every step, its inputs, outputs, costs, and timing — in a single JSON file powered by msgspec.
Save and Load
Use Run.save(path) to write a run to disk and Run.load(path) to read it back. The loaded run is identical to the original — you can inspect it, fork from any step, or continue execution.
1from opentine import Agent, Run
2from opentine.models.anthropic import Anthropic
3from opentine.tools.web import search, fetch
4
5agent = Agent(
6 model=Anthropic("claude-sonnet-4-20250514"),
7 tools=[search, fetch],
8)
9
10# Run the agent
11run = agent.run_sync("Research distributed systems consensus algorithms")
12
13# Save to a .tine file
14run.save("consensus_research.tine")
15
16# Load it back on any machine
17loaded = Run.load("consensus_research.tine")
18print(f"Steps: {len(loaded.root_steps())}")
19print(f"Cost: {loaded.total_cost:.4f}")
Pause and Resume
For long-running agents, you can pause execution mid-run and resume later. This is useful for agents that run for minutes or hours, or when you need to move execution to a different machine.
1from opentine import Run
2
3# Pause a running agent — saves state and stops execution
4run.pause("long_research.tine")
5
6# Later (even on a different machine): resume from where it stopped
7resumed = Run.resume("long_research.tine")
Run.pause(path) saves the current run state (including therunning status) to disk and stops execution. Run.resume(path) is a classmethod that loads the paused run and continues from where it left off.
Content-Addressed Step IDs
Every step in a run tree has a unique ID computed as the SHA-256 hash of its kind, inputs, and parent ID. This is generated by the step_id() function.
1from opentine import step_id, StepKind
2
3# step_id computes a SHA-256 hash of the step's content
4sid = step_id(
5 kind=StepKind.tool,
6 inputs={"name": "search", "args": {"query": "quantum computing"}},
7 parent_id="parent_abc123",
8)
9
10print(sid) # e.g. "a7f3b2c1..."
Content addressing gives you two important properties:
- Deterministic: The same step content always produces the same ID, regardless of when or where it runs. This makes runs diffable and comparable.
- Deduplication: Identical steps are automatically recognized. If two runs take the same path, their shared steps have the same IDs.
1# Identical inputs always produce the same ID
2sid_a = step_id(kind=StepKind.tool, inputs={"name": "search", "args": {"query": "hello"}})
3sid_b = step_id(kind=StepKind.tool, inputs={"name": "search", "args": {"query": "hello"}})
4assert sid_a == sid_b # Content-addressed: same content = same ID
5
6# Different inputs produce different IDs
7sid_c = step_id(kind=StepKind.tool, inputs={"name": "search", "args": {"query": "world"}})
8assert sid_a != sid_c
The .tine File Format
A .tine file is JSON serialized via msgspec. It contains the run ID, status, and a flat array of steps. The tree structure is encoded throughparent_id references.
1{
2 "run_id": "consensus_research",
3 "status": "completed",
4 "steps": [
5 {
6 "id": "a7f3b2c1...",
7 "parent_id": null,
8 "kind": "think",
9 "inputs": {
10 "content": "I need to research consensus algorithms..."
11 },
12 "outputs": {
13 "content": "I'll start by searching for the main types..."
14 },
15 "model_info": {
16 "model": "claude-sonnet-4-20250514",
17 "provider": "anthropic"
18 },
19 "timestamp": "2026-04-09T14:23:01.234Z",
20 "duration": 1.23,
21 "cost": 0.0012
22 },
23 {
24 "id": "b8e4c3d2...",
25 "parent_id": "a7f3b2c1...",
26 "kind": "tool",
27 "inputs": {
28 "name": "search",
29 "args": {
30 "query": "distributed systems consensus algorithms Raft Paxos PBFT"
31 }
32 },
33 "outputs": {
34 "results": ["..."]
35 },
36 "model_info": null,
37 "timestamp": "2026-04-09T14:23:02.464Z",
38 "duration": 0.87,
39 "cost": 0.0
40 }
41 ]
42}
Each step in the array contains these fields:
id— Content-addressed SHA-256 hashparent_id— ID of the parent step, ornullfor root stepskind— One ofthink,tool,model,done, orerrorinputs— What went into this step (prompt, tool name and args, etc.)outputs— What came out (model response, tool result, etc.)model_info— Model name and provider (null for tool steps)timestamp— ISO 8601 timestamp of when the step startedduration— How long the step took in secondscost— Cost in USD for this step
Inspecting Step Fields
After loading a run, you can access all step fields directly as Python attributes.
1from opentine import Run
2
3run = Run.load("consensus_research.tine")
4
5for step in run.root_steps():
6 print(f"ID: {step.id}")
7 print(f"Parent: {step.parent_id}")
8 print(f"Kind: {step.kind.value}")
9 print(f"Inputs: {step.inputs}")
10 print(f"Outputs: {step.outputs}")
11 print(f"Model: {step.model_info}")
12 print(f"Timestamp: {step.timestamp}")
13 print(f"Duration: {step.duration:.2f}s")
14 print(f"Cost: {step.cost:.4f}")
15 print()
Forking from Saved Runs
Because .tine files capture the complete tree, you can load a run and fork from any step. The forked run includes all steps up to the fork point, ready for a new execution path.
1from opentine import Run
2
3# Load a previously saved run
4run = Run.load("consensus_research.tine")
5
6# Inspect the steps to find a good fork point
7for step in run.root_steps():
8 print(f"[{step.kind.value}] {step.id[:12]}... — {step.duration:.1f}s")
9
10# Fork from a specific step
11forked = run.fork(from_step_id="b8e4c3d2...", new_run_id="consensus_v2")
12
13# Save the forked run
14forked.save("consensus_v2.tine")
Design Notes
- msgspec over json:opentine uses msgspec for serialization because it's faster and produces more compact output than the standard library
jsonmodule. Files are still valid JSON and can be read by any JSON parser. - Flat array, not nested tree: Steps are stored in a flat array with
parent_idreferences rather than a nested structure. This makes serialization simpler, diffs cleaner, and forking straightforward — just truncate the array and append new steps. - Human-readable:
.tinefiles are plain JSON. You can open them in any editor, pipe them throughjq, or process them with standard JSON tools. - Content addressing enables diffing: Because step IDs are derived from content, you can compare two runs by comparing their step ID sequences. Shared prefixes mean shared execution paths.