An agent can be built from an append-only event log, a synchronous reducer that derives state, and an after-append hook for side effects.
2
Storing streaming chunks, tool calls, errors, and control signals as events makes the system easier to replay, extend, and inspect.
3
A dynamic worker event can contain JavaScript for a processor, allowing any stream to become an AI agent without deploying a separate server.
Summary
Jonas Templestein presents an event-sourced model for building agent harnesses. The system stores everything as events, including model output chunks, tool calls, errors, schedules, and circuit-breaker actions. A synchronous reducer turns those events into current state, while an after-append hook handles side effects such as making an LLM request or appending another event. This split lets a processor catch up after downtime without repeating old requests. Jonas also demonstrates a stream service with HTTP, Server-Sent Events, subscriptions, scheduling, and implicit stream creation. The most unusual feature is a dynamic worker configured by an event whose payload contains JavaScript source. Appending that event makes the stream run as an agent. Jonas connects this design to distributed plugins, sub-agents, safety checkers, and eventually consistent context enrichment. The workshop's live coding fails in places, but the architectural ideas are clear and Jonas is direct about the prototype's missing authentication and operational risks.
Event sourcing would make agent behavior easier to debug
Jonas wants an agent harness in which everything that can happen is recorded in the event log. He points out that existing agents often have an event-like internal history, while some side effects only appear later in traces. Recording all of it would make debugging more direct. He includes streaming chunks as an example: a partial model response can be an ordinary event rather than a special API concept. The same approach can cover tool calls, errors, and other agent actions. His proposed abstraction is deliberately small, so the same event mechanism can support the agent loop and its extensions.
Agent extensions should compose across processes and languages
Jonas wants extensions to be written by people and combined with extensions from other authors. He contrasts this with harnesses that require everything to run in one thread, inside one process, on one computer. In his design, an agent could run on one server while another processor runs elsewhere, with one written in Rust and another in TypeScript. He acknowledges the danger: independent processors can create race conditions or send events in loops. His view is that distributed systems already face these problems, so exposing them in the event model may make them easier to handle and experiment with.
A stream can be treated as the complete primitive for an agent
The service Jonas demonstrates gives each agent path a hierarchy and stores raw events under that path. Events have a type, an optional payload, a stream path, an auto-incrementing offset, and a creation time. Clients can append events with curl and consume them through Server-Sent Events, including a live mode that keeps the connection open. Streams are created implicitly when something is posted to them. Jonas argues that even a simple stream becomes an agent if another program listens and responds to its events. The same interface can also accept loosely structured webhooks and turn invalid input into an error event.
Control operations can be represented as events too
The prototype models several operational actions inside the event system. Invalid input produces an error event, while idempotency keys prevent a webhook from being appended twice. A stream can be paused and resumed by appending control events. Jonas added the pause behavior because processors can accidentally create thousands of events in an infinite loop. The service also has a circuit breaker that pauses a stream after more than 100 events arrive in a second. Scheduling creates an event for future work, and subscriptions can either pull an SSE stream or push matching events to another server. One exception is that a rejected append cannot itself become an error event, since that would create another infinite loop.
Reducers derive state and after-append hooks perform side effects
Jonas defines a stream processor around two pieces of code. The reducer is synchronous: it receives the current state and an event, then returns updated state. It should not perform side effects. The after-append hook is where the processor can append events, make requests, or trigger other work. The separation matters after downtime. If 100 events arrive while the program is asleep, the reducer can replay them to reconstruct state without repeating every old LLM request. Jonas also describes processors as reusable reducers. A UI can reduce raw events into feed items, and one agent processor can import another processor's event types and reducer.
Agent behavior can be built by translating ordinary events into model requests
For a simple LLM processor, Jonas starts with an event called agent input added, whose payload is a string. The reducer adds that input to the agent's history in the shape expected by the OpenAI Responses API. The processor's state contains a model string, a system prompt, and message history. Other configuration changes could also be event-sourced, such as an event that changes the selected model. Responses from the model become events as they arrive, including small streaming fragments. This lets the processor translate between a simple event-oriented view of the agent and the input and output structures required by a model SDK.
A dynamic worker event can deploy a processor onto a stream
Jonas demonstrates an event type called dynamic worker configured. Its payload contains a JavaScript string with a reducer and an after-append hook. Appending the event to a new stream causes the stream to respond to ping with pong, even though the stream initially knows nothing about that behavior. A processor file can also be bundled into an event. Jonas says the same mechanism ran a basic AI agent, although dependencies must be bundled into the string. Secrets such as an OpenAI API key stay outside the stream and are substituted into request headers. In this model, writing roughly 40 lines of agent code and appending them to a stream is enough to turn that stream into an agent.
Distributed processors can add context without blocking the whole agent
Jonas describes processors that react to the same stream from other machines or services. A prompt-injection checker could watch for an impending model request and add information before it is sent. The agent could wait up to 200 milliseconds for that response, then continue whether the checker replied or not. He prefers this eventual-consistency model to widespread before-hooks, because before-hooks can break context caching and cause performance or cost regressions. A retrieval processor could try to add relevant context from a Notion index, while the agent still proceeds if the context does not arrive in time. The design also allows external processors to be offered as services, potentially with payment.
"You can write like the 40 lines of code required for basic AI agent, and then you can append that to any stream, and then that stream becomes an AI agent."52:47
Who should watch
You are building an agent harness and want replayable state rather than hidden side effects in traces.
You are considering plugins or safety services that need to observe and react to an agent's event stream from another process or server.
You want to understand how reducers, event logs, subscriptions, and dynamic workers could fit together, and can tolerate a prototype whose live workshop code does not always run.