Skip to content

Latest commit

 

History

History
71 lines (56 loc) · 3.03 KB

File metadata and controls

71 lines (56 loc) · 3.03 KB

Architecture

Shape

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.

The 8 tools

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

Retry classifier

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.

Provider seam

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.

OTel tracing

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.

What is deliberately not here

  • Real MCP wire (this is MCP-shaped, not MCP-bit-identical; the docs/mcp-comparison.md enumerates the deltas)
  • Streaming tool outputs (request-response only)
  • Tool composition or sub-agents (single linear plan)
  • Parallel tool calls (sequential; SAY-5/agentic-runner v4 covers that study)
  • Auth on tool servers (subprocess trust model)
  • A real LLM in CI (FakeProvider only)