Execution units: usage guide¶
Overview¶
This guide shows how to define an execution unit, register it, and wire it into a graph as an ExecutableUnitNode. The subsystem is described in the execution units concept page; it is what lets your graphs call deterministic code — data transforms, fetchers, validators — alongside LLM-powered agents. The moving parts you touch are an ExecutableUnitManifest, an ExecutableUnitRegistry, and an ExecutableUnitRunner that the orchestrator invokes on each visit.
Minimal example¶
import asyncio
from types import SimpleNamespace
from typing import Any
from zeroth.contracts.graph import (
ExecutableUnitNode,
ExecutableUnitNodeData,
)
class EchoRunner:
"""ExecutableUnitRunner-shaped stub that echoes its input payload."""
async def run(self, manifest_ref: str, input_payload: Any) -> SimpleNamespace:
return SimpleNamespace(
output_data=dict(input_payload) if isinstance(input_payload, dict) else {},
audit_record={"manifest_ref": manifest_ref},
)
async def main() -> None:
node = ExecutableUnitNode(
node_id="echo",
graph_version_ref="demo:1",
executable_unit=ExecutableUnitNodeData(
manifest_ref="unit://echo",
execution_mode="native",
),
)
runner = EchoRunner()
result = await runner.run(node.executable_unit.manifest_ref, {"message": "hi"})
print(result.output_data)
asyncio.run(main())
Common patterns¶
- Native Python unit — use
NativeUnitManifestplus aPythonRuntimeAdapterfor in-process calls when you trust the code and want zero-overhead dispatch. - Wrapped command — use
WrappedCommandUnitManifestwithCommandRuntimeAdapterto shell out to an existing CLI (jq,curl, a bespoke binary) without rewriting it. - Project archive — use
ProjectUnitManifestto ship a whole project directory and execute it under the sandbox; useful for "bring your own repo" workflows. - Digest-pinned integrity — compute
compute_manifest_digest()at registration time and check it throughAdmissionControllerso a tampered manifest never runs.
Pitfalls¶
- Skipping validation — always run the manifest through
ExecutableUnitValidatorbefore registering; a malformedRunConfigwill fail deep inside the runner with a much worse error. - Loose sandbox config — the default
SandboxConfigis permissive; set explicitResourceConstraints(CPU, memory, timeout) for anything running untrusted code. - Unchecked output extraction — if you pick
output_extraction_strategy="json_stdout", the unit must print valid JSON on stdout; anything else raisesOutputExtractionError. - Blocking calls in
async def run— wrap synchronous work inasyncio.to_thread()so the orchestrator's event loop stays responsive. - Missing
ExecutableUnitNotFoundErrorhandling — calling amanifest_refthat was never registered raises this; surface it to the user rather than letting the orchestrator log aNodeDispatcherError.
Reference cross-link¶
See the Python API reference for zeroth.integrations.execution.