← kvnw.dev

Agent Orchestration System was the first time I tried to build an AI assistant as a runtime instead of a chat session. I wanted one primary agent talking to the user, with supporting agents doing the messy work in the background and picking it back up later without losing context.

The starting idea was simple: the user should only have to deal with one agent. One thread, one voice, one place to hand off intent. Drafting, search, reminders, follow-up work, and long-running tasks could happen behind the scenes through specialized agents built for each kind of job.

One agent in front, many behind

The architecture used an OA/EA split. The Orchestrator Agent was the only part of the system allowed to talk to the user. It held the main conversation, decided what work needed to happen, and either spawned or reused Execution Agents to do that work. Those execution agents were persistent. They kept their own context and could come back to the same thread later instead of starting cold every time.

Agent Orchestration Diagram
Agent Orchestration Diagram

After that, drafting or sending an email was no longer one oversized model response. It became a workflow. The Orchestrator would break the request into jobs, hand them to the right execution agents, wait for those jobs to finish, and then resume once the results were ready.

The point was never to make the model talk more. It was to make it coordinate.

Persistence changed the project

Persistence turned out to be the real hinge. Early versions could complete tasks, but without durable agent identity the work felt disposable. Every job came with the overhead of rebuilding context. The turning point was a roster model that routed work back to the same execution agent when a task matched what it had handled before.

Once that was in place, the agents stopped feeling like thin wrappers around tool calls. They started to feel more like workers with memory. Email drafting, search, follow-ups, and reminders all got better once the system could reuse the right thread instead of reconstructing everything from scratch.

Tools as the action layer

Another important decision was treating tools as the only valid way for the Orchestrator to affect the outside world. Raw model output was not enough. If the system wanted to message the user, draft something, wait, schedule work, or spawn a background job, it had to do it through a structured tool call with validation behind it.

That made the system easier to reason about. Jobs could be inspected, barriers could be tracked, and side effects could be deduplicated. Long-running work could finish asynchronously without turning the whole user experience into one blocking loop.

Email, reminders, and the first real workflows

Email was the first domain where the system felt genuinely useful. It could search for relevant messages, read the surrounding context, draft a reply, ask for approval, and only send once the user confirmed. That approval step mattered. It kept the assistant collaborative instead of reckless.

Orchestration App Demo
Orchestration App Demo

Around that, I built reminder flows and background scheduling. That pushed the project toward the part I actually cared about: an AI system that could hold intent over time instead of answering one message and forgetting the rest.

Email from the agent
A real email from the agent

What it taught me

This was the most ambitious system I had built at that point, and it taught me that orchestration comes down to primitives, not magic. Messages, jobs, waiting, resuming, routing, and memory do not sound special on their own, but once they are composed well, the system stops feeling like a chat loop and starts feeling like infrastructure.

It also made something else obvious: debugging is part of the product. If I could not see how work was being routed, resumed, and completed, the whole thing turned opaque fast. This project was my first serious pass at that problem, and a lot of what came later started here.