Overview
Step is the atomic unit of computation in Coevolved. A Step wraps a callable and adds:
- Optional Pydantic input/output validation
- Automatic tracing events (start/end/error)
- Stable step identity (
step_id) for grouping and observability
- annotations that describe what the step is (LLM/tool/agent/etc.)
The Step primitive
At its core, a step is “a function of state”:
from coevolved.base.step import Step
def enrich(state: dict) -> dict:
return {**state, "enriched": True}
step = Step(enrich, name="enrich")
out = step({"enriched": False})
Coevolved passes the output of one step as the input to the next (when you compose steps).
Validation (Pydantic schemas)
If you provide input_schema and/or output_schema, Coevolved validates at runtime.
from pydantic import BaseModel
from coevolved.base.step import Step
class In(BaseModel):
x: int
class Out(BaseModel):
y: int
def fn(state: In) -> Out:
return Out(y=state.x + 1)
validated = Step(fn, input_schema=In, output_schema=Out, name="validated")
validated({"x": 1})
Validation is a boundary tool. Use it at the edges of modules (or before an LLM/tool step) where state contracts matter most.
Tracing hooks
Every step execution emits lifecycle events. You can plug in sinks (console, file, etc.) via the tracer.
By default, Coevolved uses a global tracer. Steps accept an optional tracer=... argument, but most users configure the default tracer once.
Annotations
Annotations are a free-form dictionary attached to a step. Coevolved uses a few conventional keys:
kind: "llm", "tool", "agent", or "step"
tool_schema: a Pydantic model used to generate tool specs
Prebuilt constructors like llm_step(...) and tool_step(...) set these automatically.
Next steps