createPythonWorkflow function wires everything together automatically.
Import
Host Node JSON Protocol
The bridge between an external process and the Smithers engine is theHostNodeJson type. Every time the engine calls the build function, it passes a serialized context on stdin and expects a HostNodeJson tree on stdout.
HostNodeJson
element node maps 1:1 to a JSX component (Task, Approval, Signal, etc.). The tag field is the component name as a string. rawProps carries the full prop values including non-string types; props carries the string-serialized version used for display.
SerializedCtx
The engine serializes the currentSmithersCtx before invoking the build function:
HostNodeJson tree to stdout.
Agent Reference Resolution
String agent references inrawProps.agent are resolved back to live AgentLike objects before the tree reaches the engine. If a referenced agent name is not in the registry, the engine throws UNKNOWN_AGENT with the available agent names.
createExternalSmithers
The low-level factory. Use this when your build function is already written in TypeScript (e.g., wrapping a non-Python subprocess or a WASM module).ExternalSmithersConfig
| Option | Type | Default | Description |
|---|---|---|---|
schemas | Record<string, ZodObject> | required | Zod schemas for output tables |
agents | Record<string, AgentLike> | required | Agent registry for ref resolution |
buildFn | (ctx: SerializedCtx) => HostNodeJson | required | Synchronous build function |
dbPath | string | ephemeral temp dir | Path for the SQLite database |
Ephemeral SQLite Database
WhendbPath is omitted, createExternalSmithers provisions an ephemeral SQLite database in a temp directory (os.tmpdir()/smithers-ext-*/smithers.db). WAL mode and a 5-second busy timeout are applied automatically. The database is closed on process exit. Pass an explicit dbPath for durable storage across restarts.
serializeCtx
Serialize a liveSmithersCtx to a SerializedCtx for passing to the build function or an external process:
hostNodeToReact
Convert aHostNodeJson tree to React elements, resolving string agent references:
UNKNOWN_AGENT if a referenced agent name is not present in the agents map.
Python Integration
createPythonWorkflow is the recommended entry point for Python-defined workflows. It combines schema auto-discovery, subprocess management, and the host node protocol into a single call.
Setup
Smithers uses uv to run Python scripts. Install uv and ensure it is onPATH:
SerializedCtx JSON from stdin and write a HostNodeJson tree to stdout:
createPythonWorkflow
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
scriptPath | string | required | Path to the Python script (relative to cwd) |
agents | Record<string, AgentLike> | required | Agent registry |
schemas | Record<string, ZodObject> | auto-discovered | Zod schemas; omit to auto-discover from Pydantic |
dbPath | string | ephemeral | SQLite database path |
cwd | string | process.cwd() | Working directory for subprocess |
timeoutMs | number | 30000 | Per-invocation timeout in milliseconds |
env | Record<string, string> | process.env | Additional environment variables |
Build Subprocess
Each time the engine calls the build function, Smithers spawnsuv run <scriptPath> synchronously. The serialized context is passed on stdin; the process must write HostNodeJson to stdout and exit with code 0.
Exit code non-zero, no output, or invalid JSON all throw EXTERNAL_BUILD_FAILED. Timeout throws EXTERNAL_BUILD_FAILED with a timeout message. Stderr is captured and included in the error details.
Build Output Validation
The host node output is validated for akind field before reaching the engine. The minimal valid output is:
Schema Auto-Discovery
Whenschemas is omitted from createPythonWorkflow, Smithers runs the script with --schemas and parses the JSON output as a map of schema names to JSON Schema objects.
In your Python script, handle --schemas to emit Pydantic model schemas:
pydanticSchemaToZod and passed to createExternalSmithers.
Pydantic Schema Conversion
pydanticSchemaToZod converts a Pydantic v2 JSON Schema (from model.model_json_schema()) to a Zod object schema.
Supported Patterns
| Pydantic Pattern | Zod Output |
|---|---|
type: "string" with minLength/maxLength/pattern | z.string().min().max().regex() |
type: "number" / type: "integer" with minimum/maximum | z.number().int().min().max() |
type: "boolean" | z.boolean() |
type: "array" with items | z.array(...) |
type: "object" with properties + required | z.object(...) with optional non-required fields |
enum: [...] on a string field | z.enum([...]) |
anyOf: [T, {type: "null"}] (Optional) | T.nullable() |
allOf: [A, B] | z.intersection(A, B) |
oneOf: [A, B, ...] | z.union([A, B, ...]) |
$ref: "#/$defs/ModelName" | Resolved inline (circular refs become z.any()) |
default: value | .default(value) |
description: "..." | .describe("...") |
$ref Resolution
Pydantic places nested models under$defs. pydanticSchemaToZod resolves #/$defs/ModelName references inline using a JSON Pointer walk. Circular references are detected and collapsed to z.any() to prevent infinite recursion.
nullable anyOf Collapse
Pydantic representsOptional[T] as anyOf: [T, {type: "null"}]. The converter detects this two-variant pattern and collapses it to T.nullable() for clean column mapping in the SQLite schema.
allOf Intersection
allOf with a single entry is unwrapped directly. Multiple entries produce z.intersection(A, z.intersection(B, ...)).