Replay & Resume

opentine runs are fully serializable. You can pause a run mid-flight, save it to a .tine file, transfer it to another machine, and resume exactly where you left off. Combined with forking, this gives you complete control over long-running agent workflows.

Pausing a Run

Call run.pause(path) to save the run to disk and mark its status as paused. This is a single atomic operation — the status change and file write happen together.

pause.py
1from opentine import Run
2
3# Create and execute a run
4run = Run(id="long-task", model_info="claude-sonnet-4-20250514",
5          system_prompt="Research assistant", user_prompt="Analyze 100 papers")
6
7step_1 = run.add_step(kind="think", inputs={"thought": "Start with batch 1"})
8step_2 = run.add_step(kind="tool",
9                       inputs={"tool": "search", "query": "batch 1"},
10                       outputs={"results": ["paper_1", "paper_2"]},
11                       parent_id=step_1.id, duration=2.0)
12
13# Pause the run — saves to disk and sets status to "paused"
14path = run.pause("research_checkpoint.tine")
15# => Path("research_checkpoint.tine")
16print(run.status)  # => RunStatus.paused

Resuming a Run

Run.resume(path) is a classmethod that loads a paused run from disk and sets its status to running. You get back a fully hydrated Run object with all its steps intact, ready to continue.

resume.py
1# Later (or on a different machine) — resume from where we left off
2from opentine import Run
3
4resumed = Run.resume("research_checkpoint.tine")
5print(resumed.status)     # => RunStatus.running
6print(len(resumed.steps)) # => 2  (the steps we had before pausing)
7
8# Continue adding steps
9step_3 = resumed.add_step(
10    kind="model",
11    inputs={"prompt": "Summarize batch 1 results..."},
12    outputs={"text": "Papers 1 and 2 cover..."},
13    parent_id=resumed.steps[-1].id,
14    duration=3.1,
15    cost=0.003,
16)

Save and Load

Under the hood, pause() and resume() are built on the lower-level save() and load() methods. The difference is that save() and load()preserve the run's status as-is, while pause() and resume() explicitly set it to paused and running respectively.

save_load.py
1# save() and load() are the low-level primitives
2# They serialize/deserialize without changing status
3
4# Save the run as-is
5path = run.save("my_run.tine")
6
7# Load it back — status is preserved exactly
8loaded = Run.load("my_run.tine")
9print(loaded.status)  # => whatever it was when saved
10
11# pause() and resume() are convenience wrappers:
12#   pause(path)  = set status to "paused",  then save(path)
13#   resume(path) = load(path), then set status to "running"

Cross-Machine Resume

Because .tine files are plain JSON (serialized via msgspec), they can be transferred between machines, stored in version control, or uploaded to cloud storage. This enables workflows like starting a run on a laptop and finishing it on a GPU server.

cross_machine.py
1# Machine A: developer laptop
2run = agent.run_sync("Expensive analysis task")
3# Agent processes 50 steps, then we want to move it
4
5run.pause("analysis.tine")
6# Upload analysis.tine to shared storage (S3, GCS, etc.)
7
8# --- transfer the .tine file ---
9
10# Machine B: GPU server
11from opentine import Run
12
13run = Run.resume("analysis.tine")
14# All 50 steps are restored — continue from where we left off
15result = agent.continue_run(run)

Replay: Re-Executing from a Step

Replay is the concept of re-executing a run (or part of a run) from a specific step. This isn't a separate API — it's a combination of fork() and continue_run(). Fork from the step you want to replay from, and the agent picks up from that point.

You can also use a loaded run purely for inspection — walk the tree, examine inputs and outputs, compute costs — without re-executing anything.

replay.py
1from opentine import Run
2
3# Load a completed run
4run = Run.load("finished_research.tine")
5
6# Walk the tree to replay / inspect what happened
7for step in run.root_steps():
8    print(f"Root: {step.kind} -> {step.inputs}")
9    for child in run.children(step.id):
10        print(f"  Child: {child.kind} -> {child.inputs}")
11        for grandchild in run.children(child.id):
12            print(f"    Grandchild: {grandchild.kind}")
13
14# Replay from a specific step: fork and re-execute
15fork_point = run.steps[10]  # step 10
16replayed = run.fork(from_step_id=fork_point.id)
17
18# Now re-run from step 10 with the same or different agent
19result = agent.continue_run(replayed)

Putting It All Together

The full power of opentine comes from combining these primitives: run an agent, fork from a failure point, pause the fork, transfer it, resume on a new machine, and finish the job. Each step in this workflow is a single method call.

full_workflow.py
1# Full workflow: run -> fail -> fork -> pause -> transfer -> resume
2from opentine import Run
3
4# 1. Initial run fails at step 15
5run = agent.run_sync("Complex multi-step task")
6# run.status => RunStatus.failed
7
8# 2. Fork from the last good step
9last_good = [s for s in run.steps if s.kind != "error"][-1]
10forked = run.fork(from_step_id=last_good.id)
11
12# 3. Pause and transfer to a different environment
13forked.pause("retry.tine")
14# scp retry.tine gpu-server:/workspace/
15
16# 4. Resume on the target machine and finish
17# (on gpu-server)
18run = Run.resume("/workspace/retry.tine")
19result = agent.continue_run(run)
20run.save("/workspace/completed.tine")