Work · Agent loop · TypeScript

TypeScript agent harness — planner loop, tools, MCP, traces

A local, single-user agent runtime in TypeScript and Bun. One turn is a thin orchestrator over phases: plan, execute tools, stream the final answer. Planner output is a Zod schema. Tools register dynamically. MCP servers are discovered from the same registry. Traces go out as SSE, with an optional Langfuse observer.

My role Builder of the loop, phase split, tool registry, and trace boundary.

Stack TypeScript · Bun · Zod · MCP · SSE · Langfuse · OpenAI-compatible router

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

Harness

Live answer stream

SSE

Trace observer

Langfuse

What it does

Plans before calling tools

Streams the answer over SSE

Leaves traces for later eval

A bounded planner loop: plan, execute tools, stream the answer, observe.

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

← Halo ITSM MCP Next: AI_devs →