Agentoria

Concepts

The AgentEvent stream, the bounded loop, and the ports that make it all swappable.

The AgentEvent stream

Every turn is an async stream of a typed union, AgentEvent. The same type is produced on the server and consumed in the browser:

type AgentEvent =
  | { type: 'text'; delta: string }
  | { type: 'tool_call'; id: string; name: string; args: unknown; mutates: boolean }
  | { type: 'tool_result'; id: string; name: string; ok: boolean; result?: unknown; error?: string }
  | { type: 'usage'; usage: Usage; costUsd: number | null }
  | { type: 'done'; text: string; usage: Usage; costUsd: number | null }
  | { type: 'error'; error: string }
  | { type: 'custom'; kind: K; data: C[K] };   // generative UI — see below

It's serialized to SSE with encodeAgentEvent and parsed back on the client. The union is open: declare your own custom event kinds → payloads and carry generative UI end-to-end, fully typed.

The bounded loop

runAgent (and the createAgent / createAgentHandler facades over it) drives a bounded loop: call the model, stream text, execute any tool calls, feed results back, repeat until the model stops or a turn cap is hit. Unknown tools, invalid arguments, and thrown handlers all come back as { ok: false, error } so the model can self-correct rather than crash the run.

const agent = createAgent({
  provider,                     // an LlmClient
  tools,                        // a tool registry
  monitor: { operation: 'chat', provider: 'anthropic', model, sink: (m) => telemetry.recordLlmCall(m) },
});

for await (const ev of agent.stream(messages, { ctx }))
  await stream.write(encodeAgentEvent(ev));

Ports & adapters

Everything swappable is an injectable interface. Provide your own, or use the built-ins.

PortBuilt-insSwap for
LlmClientAnthropic, OpenAI-compatible, MockLlmClientany provider / a fake for tests
ToolSourceZod ToolRegistry, MCP clienthand-rolled JSON-Schema tools
ConversationStorein-memoryPostgres, KV, D1
TelemetryStorein-memory, Drizzle/SQLiteyour warehouse
Tracerrecording, noopOpenTelemetry, Langfuse

Because the loop only depends on these interfaces, the same agent code runs against a real provider in production and a scripted MockLlmClient in tests.

On this page