Two processes per agent run.
Go orchestrator (one process)
|
| spawn + JSON-RPC over stdin/stdout
v
Python tool server (one process per tool, 8 total)
The orchestrator runs the agent loop: ask the provider what tool to call
next, dispatch to the matching tool server, validate the response against
the tool's declared JSON Schema, classify any error as transient or
permanent, retry with backoff if transient, attribute an OTel span to the
step, repeat until finish is called or the step budget is exhausted.
Tool servers are subprocesses speaking line-delimited JSON-RPC 2.0 on
stdio. The first call to a tool is initialize; the second is
tools/list, which returns the schemas the tool exposes. Subsequent calls
are tools/call with the input payload. This is MCP-style: not exactly
the Model Context Protocol on the wire, but the shape and semantics are
the same. The wire bytes are documented in docs/jsonrpc-wire.md and the
deltas vs MCP are in docs/mcp-comparison.md.
| Tool | Purpose |
|---|---|
| file_read | Read a UTF-8 text file from a sandbox root |
| file_write | Write a UTF-8 text file under a sandbox root |
| http_get | GET a URL (mocked in tests; allowlisted in prod) |
| calculate | Safe arithmetic evaluator (parser, not eval) |
| query_db | Read a bundled SQLite |
| summarize | Deterministic first-N-sentences summary |
| extract_json | Extract + validate a JSON object against a Pydantic schema |
| finish | Terminate the loop with a final answer |
A failure is transient if any of: timeout, JSON-RPC code -32603, network
error. Anything else (schema validation, bad input, tool not found) is
permanent and does not retry. The retry budget is per-step (default 3)
with backoff 100ms / 400ms / 1600ms.
FakeProvider reads a scripted plan from YAML and emits tool calls in
order, fully deterministic. ClaudeProvider is stubbed and env-gated; CI
never invokes it. All tests run through FakeProvider.
Every step writes one span with attributes for tool_name, step_idx,
attempt, latency_ms, success, and truncated input/output payloads.
Default exporter is in-memory (used by the span-tree-shape tests). The
--otel-mode otlp flag swaps in a real OTLP exporter.
- Real MCP wire (this is MCP-shaped, not MCP-bit-identical; the
docs/mcp-comparison.mdenumerates the deltas) - Streaming tool outputs (request-response only)
- Tool composition or sub-agents (single linear plan)
- Parallel tool calls (sequential;
SAY-5/agentic-runnerv4 covers that study) - Auth on tool servers (subprocess trust model)
- A real LLM in CI (FakeProvider only)