- Context
- Course tasks and local missions needed a loop that could be inspected, not a hidden agent SDK. The harness is the reference implementation for that loop: abort and max-steps, a mutable turn state, and traces you can replay. It is a local runtime, not a multi-tenant product.
- Intervention
-
Split the turn into composable phases and kept the orchestrator thin so each phase can be tested and traced on its own.
Phase 1 — Planner step
runAgent wires the turn. The planner phase builds the prompt from the native tool catalog, calls the model router, and parses JSON against a Zod schema. Failures stay on the step instead of becoming a silent tool call.
Phase 2 — Tool execution and MCP
ToolRegistry loads native modules and can register or unregister tools at runtime. The tool phase executes the planner call, writes a trace record, and can discover MCP tools into the same registry.
Phase 3 — Answer and traces
The final phase streams the answer. AgentObserver wraps Langfuse without scattering try/catch through the loop. SSE events and a session replay JSON are the record of the turn.
- Risks & how they were handled
-
- Unbounded tool loops → max-steps and abort on the orchestrator.
- Planner JSON that is not a tool call → Zod parse before execution.
- Tracing that swallows the turn → observer is optional and degrades when Langfuse is absent.
- What I'd do differently
-
- Point the same loop at the Halo tool set once resolve-ticket exists.
- Promote a few mission traces into a standing eval suite.
- Outcome
- A turn runs as plan, tool execution, and a streamed answer, with MCP tools able to join the registry and with a Langfuse/SSE record of what the planner asked for. This is the loop the Halo server is being shaped to sit under.
System map
User turn
chat / SSE
Planner step
Zod JSON
Tool registry
native + MCP
Live answer stream
SSE
Trace observer
Langfuse
Plans before calling tools
Streams the answer over SSE
Leaves traces for later eval
What shipped
- Phase-split loop Plans, runs tools, then answers — Zod-shaped phases with step limits.
- Dynamic tool registry Native tools plus MCP discovery in one registry.
- Traces Langfuse observer and SSE turn traces for replay and evals.
Stack TypeScript Bun Zod MCP SSE Langfuse