Steps
A Step is the atomic unit in an opentine run graph. It is a frozen dataclass with immutable identity inputs and recorded runtime metadata.
Step Fields
The current Step dataclass is frozen. The artifact stores parent_ids; parent_id exists as a compatibility property that returns the last parent, or None for root steps.
id— full 64-character SHA-256 digest.parent_ids— zero, one, or many parent step IDs.kind—think,tool,model,done, orerror.inputsandoutputs— structured payloads for the step.model_infoandtool_info— model/tool metadata included in the step identity.error— structured error payload included in the step identity.timestamp,duration, andcost— recorded runtime data, outside the identity hash.
1from opentine import Step, StepKind
2
3step = Step(
4 id="f3a7c9e12b04..." * 4,
5 parent_ids=[],
6 kind=StepKind.think,
7 inputs={"text": "I should search for recent papers."},
8 outputs={},
9 model_info="claude-sonnet-4-20250514",
10 tool_info={},
11 error={},
12 timestamp=1717200000.0,
13 duration=0.0,
14 cost=0.0,
15)
Step Kinds
from opentine import StepKind
StepKind.think # Agent reasoning or intermediate text
StepKind.tool # Tool invocation
StepKind.model # Explicit model/harness entry step
StepKind.done # Successful terminal or model text step
StepKind.error # Failure stepContent Addressing
step_id() hashes a canonical immutable payload containing the step kind, parent_ids, inputs, outputs, model/tool metadata, and error payload. Timestamps, duration, and cost are recorded but do not change the ID.
1from opentine import StepKind, step_id
2
3sid = step_id(
4 kind=StepKind.tool,
5 parent_ids=["a1b2c3d4e5f6..."],
6 inputs={"name": "search", "arguments": {"query": "RLHF papers"}},
7 outputs={"result": "Top results..."},
8 model_info="claude-sonnet-4-20250514",
9 tool_info={"name": "search"},
10 error={},
11)
12
13assert len(sid) == 64
The display helpers shorten IDs to 12 characters, but the persisted graph uses full SHA-256 digests as keys.
Creating Steps
Application code usually calls Run.add_step(). It acceptsparent_ids for graph ancestry and also accepts a legacy parent_id argument for simple chains.
1step = run.add_step(
2 kind=StepKind.tool,
3 inputs={"name": "fetch", "arguments": {"url": "https://example.com"}},
4 outputs={"result": "Example Domain..."},
5 parent_ids=[parent_step.id],
6 tool_info={"name": "fetch"},
7 duration=0.8,
8)
9
10print(step.id) # full 64-character SHA-256 digest
11print(step.short_id) # first 12 characters for display
12print(step.parent_ids) # graph ancestry list
13print(step.parent_id) # compatibility accessor for the last parent
Immutability
Steps are frozen so their identity cannot drift after insertion into the content-addressed graph. To correct or retry a decision, add a new child or fork the run from a known-good step.
1step = run.add_step(kind=StepKind.think, inputs={"text": "hello"})
2
3step.outputs = {"new": "data"}
4# dataclasses.FrozenInstanceError: cannot assign to field 'outputs'
5
6corrected = run.add_step(
7 kind=StepKind.think,
8 inputs={"text": "Actually, let me reconsider."},
9 parent_ids=[step.id],
10)