When I started working on parallel execution for Agent Graph, the first question was not how many agents I could start. The real question was where parallelism should live. If that boundary is wrong, more agents only create faster confusion.
From my experience in HPC and distributed systems, performance work always becomes an ownership question. Which unit owns state? Which unit can retry? Which unit can be observed? Which unit is allowed to finish? Agent systems have the same problem. The model can reason, but the software around the model must decide the concurrency contract.
The important decision is where to place parallelism
Inside one engineering issue, later work often depends on earlier work. Architecture shapes implementation. Review needs a concrete result. Testing needs the result that was actually reviewed. If all of these agents run together in one shared conversation, the system may look busy, but the state becomes hard to trust.
So in Agent Graph 0.2.0, I chose the issue as the concurrency unit. A broad request can be split into narrow outcomes: one backend change, one UI change, one migration, one investigation. Each issue has its own selected team, dynamic route, evidence, and completion decision. Several issues can move at the same time, but one issue remains a controlled graph.
One Dispatcher, several issue state machines
This is the architecture I wanted: one global runtime, not many small schedulers fighting each other. The Dispatcher owns durable queue transitions, agent leases, worker launch, error recovery, cancellation, and final integration. Each issue is a logical state machine inside that service.
This distinction matters. The system is allowed to run multiple issue graphs, but it is not allowed to lose the single source of truth. The Dispatcher can say that issue 21 is using the backend agent, issue 22 is using the frontend agent, and issue 23 must wait because the backend lease is busy. That is the difference between parallel execution and uncontrolled execution.
Every issue receives a durable number and an independent queue. Starts are slightly staggered to avoid a provider and container startup burst.
An agent identity may work on only one issue at a time. Another issue requesting that agent waits without blocking unrelated specialists.
Each worker may suggest allowed next specialists, including a bounded loop. Required-agent coverage ensures the selected team still participates.
The optional final agent reviews the actual integration candidate. Only the Dispatcher performs an approved Git merge.
Worktrees make the boundary real
It is easy to draw boxes called "issues" on a diagram. The harder part is making those boxes real at runtime. For a Git-backed project, every issue owns one branch and one worktree for its whole lifetime. All workers inside that issue mount the same worktree, so implementation, review, and correction see one continuous state. Different issues mount different worktrees, so they do not overwrite one another.
This also defines the safety rule. If a project is not under Git, Agent Graph serializes work in the registered directory. If the target checkout is dirty, isolated issue work may continue, but final integration waits. I prefer a slower system that preserves human changes over a faster system that silently mixes states.
Parallel where independent
Different issues, different worktrees, different available specialists.
Sequential where state converges
One issue graph, one agent identity, and one project merge at a time.
The control plane should outlive the editor
Another lesson in 0.2.0 is that the editor should not be the runtime. Earlier versions kept too much orchestration ownership inside the extension host. That is fragile for long-running work. A browser refresh, an extension reload, or a closed window should not become a scheduling event.
The new design moves ownership into a detached per-user daemon. The daemon owns the Manager Agent, authenticated MCP server, Dispatcher, and durable recovery. Visual Studio Code and Code Server become clients. The built-in Manager Agent and trusted external agents use the same typed MCP tools to inspect the roster, create issues, monitor progress, cancel work, and read results.
The daemon also publishes its version and build identity through health status. The extension package, daemon, MCP runtime, and Docker image must all report the same full build UUID before the runtime is considered ready. I added this because local development needs traceability too. A package filename is not enough when several moving parts must agree on one build.
Completion must be an event
Detached containers are useful only if the scheduler knows exactly when they finish. Reading logs and guessing completion is not a control plane. In 0.2.0, a small monitor inside the worker forwards bounded, sequenced output and one terminal event to a private runtime endpoint. The Dispatcher persists that event before it changes the issue queue or releases the agent lease.
Docker logs still matter, but they are diagnostic evidence, not scheduler state. After a daemon restart, persisted issue state and worker identity drive reattachment and recovery without launching the same step twice.
The graph still has to stay dynamic
Parallel issues do not turn each issue into a fixed pipeline. A worker still receives the intersection of its global allowed routes and the issue’s selected team. It may suggest zero, one, or several next workers with focused assignments. The Dispatcher validates and queues those suggestions, and completed agents may be revisited within explicit limits.
This is important to me because I do not want an agent workflow to become a hard-coded assembly line. The required-agent list is a participation pool, not a linear plan. Dynamic work runs first. When the graph settles, the Dispatcher promotes any required specialist that has not yet received a real turn. The system keeps flexible routing, but team coverage remains deterministic.
What 0.2.0 changes for the user
For the user, the change should feel simple: ask for broader work, let the Manager split it into issues, and watch independent tickets progress without losing the ability to inspect what happened.
- Several narrow tickets can progress at the same time.
- Git-backed issues work in isolated branches and worktrees.
- Busy specialists are shared safely through global leases.
- Final review and merge are explicit and optional.
- Manager chat, MCP, scheduling, and recovery survive editor reloads.
- Chat remains human-facing while issue history preserves the deeper agent discussion and audit evidence.
Release confidence is part of the architecture
I also treat the release flow as part of the design. The 0.2.0 release builds and reinstalls the extension, reloads the exact standalone daemon, rebuilds the Docker image tagged with the same version and UUID, discovers the live MCP contract as an external client, and sends a system issue through every configured agent.
Then it creates two independent issues, observes distinct graph workers running concurrently, waits for serialized final review, and requires both tickets to complete. The release is not accepted because the diagram looks plausible. It is accepted because the packaged control plane executes the behavior end to end.
For me, Agent Graph 0.2.0 is another example of the software design shift I have been thinking about: we are moving from using agents as assistants outside the project to building agent runtime into the project itself. Once the agent is inside the software, architecture becomes more important, not less. Models can choose useful engineering steps, but reliable software must own state, permissions, evidence, recovery, and the boundaries of concurrency.