Skip to main content

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