Schema validation
July 5, 2026 · View on GitHub
Every step boundary in a ChainWeaver flow is Pydantic-validated in both directions:
context ─┐ ┌─ context (merged)
├──► tool.input_schema ──► fn ──► tool.output_schema ──┤
(validate) (validate)
If validation fails, the executor raises SchemaValidationError with the failing field
and the step index — no partial state is committed.
Tool boundaries
A tool's input_schema and output_schema are Pydantic BaseModel subclasses. The
executor:
- Builds the input dict from the step's
input_mappingand the accumulated context. - Calls
input_schema.model_validate(input_dict)— raises on type mismatches and missing required fields. Unknown fields follow the schema's Pydantic config: dropped by default, rejected when the schema setsextra="forbid". - Invokes
tool.fn(validated_input); expects adict. - Calls
output_schema.model_validate(output_dict)— same guarantees. - Merges the validated output back into the context.
Flow boundaries (optional)
A Flow can also declare input_schema and output_schema:
from chainweaver import Flow
flow = Flow(
name="etl",
description="...",
steps=[...],
input_schema_ref=Flow.schema_ref_from(InitialInput),
output_schema_ref=Flow.schema_ref_from(FinalOutput),
)
When set, the executor validates initial_input before the first step and the merged
final_output after the last step. Flow-level validation surfaces in the trace as
records with step_index=-1 (input) and step_index=len(steps) (output).
Schema hashing and drift
Every Tool exposes a schema_hash derived from its input schema, output schema, and
schema_version:
tool.schema_hash # SHA-256 of (input_schema, output_schema, schema_version)
tool.input_schema_hash # input only
tool.output_schema_hash # output only
The hash is stable across processes, sorted-keys-canonicalized, and short enough
(16 hex chars) to log. When a tool's schema changes and its schema_hash no longer
matches what a flow has snapshotted in tool_schema_hashes, FlowExecutor surfaces a
DriftInfo entry via get_drift_report(). Call accept_drift(flow_name) after
reviewing a flow's current tool surfaces to create or refresh that baseline.
See Cookbook recipe 5 — Schema drift in CI for the governance pattern.
Compile-time validation
compile_flow(flow, tools) performs all of the above checks statically — before any
tool function runs. It accepts both a linear Flow and a DAGFlow; for a DAG each
step is validated against the union of its transitive depends_on ancestors' outputs
(plus the flow input) rather than list order.
- Every
tool_namein the flow resolves to a registered tool. - Every
input_mappingkey resolves to either a literal, a key in the initial-input schema, or a field in an upstream step's output schema. - Type compatibility between mapped fields (basic Python types + Pydantic models).
on_error="fallback:<tool_name>"targets are validated for input and output compatibility (see Fallback semantics).- Optional warning for shadowed context keys.
from chainweaver import compile_flow
result = compile_flow(flow, tools={"double": double_tool, ...})
if result.errors:
for err in result.errors:
print(err)
The CLI exposes this as chainweaver validate flows/etl.flow.yaml.