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.

save_load.py
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.

pause_resume.py
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.

step_ids.py
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.
step_ids.py
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.

consensus_research.tine
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 hash
  • parent_id — ID of the parent step, or null for root steps
  • kind — One of think, tool,model, done, or error
  • inputs — 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 started
  • duration — How long the step took in seconds
  • cost — Cost in USD for this step

Inspecting Step Fields

After loading a run, you can access all step fields directly as Python attributes.

inspect.py
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.

fork_saved.py
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 libraryjson module. 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 withparent_id references rather than a nested structure. This makes serialization simpler, diffs cleaner, and forking straightforward — just truncate the array and append new steps.
  • Human-readable: .tine files are plain JSON. You can open them in any editor, pipe them through jq, 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.

Next Steps

  • Learn how Forking uses serialized runs to branch execution
  • Explore Run Trees to understand the tree structure
  • Use the CLI to inspect and manipulate .tine files