Skip to content

How to Isolate State Across Multiple Parallel AI Coding Agent Sessions

Problem

I was running two coding agents in parallel — one on a feature branch, one on a hotfix — and they kept stepping on each other. The hotfix agent “fixed” a file the feature agent had just edited. The feature agent’s context file got overwritten mid-task. Two sessions both replied to the same inbox event. I tried to fix it with file locks, then a shared SQLite, then a queue. Every shared thing became a new failure mode.

The clearest description of the same bug, from someone who had lived through it:

The UI was never the hard part for me, state was. Once I had my own harness I wanted a few sessions going at once, and keeping their context/auth/inbox from stepping on each other got messy fast. What fixed it: a separate state dir per session (own token, own inbox, own log) so nothing shares a poll consumer or a context file. — u/Evening_Classic_9207

Environment

  • A custom harness (or a wrapper around Claude Code, Pi, OpenClaw)
  • Two or more concurrent sessions — different branches, different clients, different cron jobs, or different providers
  • Python 3.11+ for the dispatcher sketch

What happened?

When a single developer runs one session, “state” is just a file path. As soon as a second session starts, every part of that state becomes a contention hazard:

  • Auth tokens shared between sessions mean revoking one revokes all.
  • Context files shared between sessions mean the second session overwrites the first’s working memory mid-task.
  • Inboxes / poll consumers shared between sessions mean two sessions race to reply to the same event.
  • Working copies shared between sessions mean two agents try to edit the same file with conflicting diffs.

Multiple commenters in the thread independently described hitting each of these. The visual difference between a single-context subagent and a properly isolated runtime makes the cost obvious — the subagent pattern floods the main context with every intermediate result, while an isolated runtime keeps the main context small and only returns the final summary.

Architecture comparison of subagent pattern vs isolated runtime

How to solve it?

The pattern that recurs in the thread is a per-session state directory plus a thin dispatcher. Four rules, applied together:

  1. One state directory per session. Layout, roughly:
    Per-session state layout
    ~/.harness/sessions/<session_id>/
    context.json # working memory
    token # auth for this session only
    inbox/ # incoming events
    outbox/ # outgoing events
    log/ # per-session logs
    worktree/ # optional, when the session owns a branch
  2. One process per session. No two sessions share a poll consumer, an event loop, or a context file. The dispatcher is the only thing that talks to all of them.
  3. Filesystem as the boundary. When the dispatcher needs to send a message to a session, it writes to that session’s inbox/. When the session needs to report, it writes to its outbox/. No shared queues, no shared databases for in-flight state.
  4. Per-session worktree. When the session owns a branch, it works in its own worktree/ (or a shallow clone). The dispatcher merges or rebases later.

The five primitives a dispatcher usually grows into are agent, parallel, pipeline, phase, and log. agent runs a single subagent. parallel fans work out to several with a barrier. pipeline runs multi-stage work without a barrier. phase groups progress for the UI. log writes status messages.

Diagram of the five workflow primitives: agent, parallel, pipeline, phase, log

Here is a sketch of the dispatcher. The point is the directory layout and the lack of shared paths — the exact API does not matter.

dispatcher.py
import os, subprocess, uuid
from pathlib import Path
STATE_ROOT = Path.home() / ".harness" / "sessions"
def spawn_session(task: str, branch: str) -> str:
sid = uuid.uuid4().hex[:8]
sdir = STATE_ROOT / sid
(sdir / "inbox").mkdir(parents=True)
(sdir / "outbox").mkdir()
(sdir / "log").mkdir()
(sdir / "token").write_text(issue_token_for(sid))
(sdir / "context.json").write_text({"goal": task, "plan": []})
# Each session is its own process with its own cwd, env, and auth.
subprocess.Popen(
["harness-agent", "--session", sid, "--branch", branch],
cwd=sdir,
env={"HARNESS_STATE": str(sdir)}, # token stays on disk, not env
)
return sid
def send(sid: str, msg: str) -> None:
(STATE_ROOT / sid / "inbox" / f"{uuid.uuid4().hex}.msg").write_text(msg)

The reason

I think the key reason shared state breaks is that every shared path is a coupling point, and the more sessions you run, the more coupling points there are. One state dir per session makes the coupling explicit and pushes the only shared thing — the dispatcher — into a single, small, testable process.

A few related things that came out of the same thread:

  • u/BP041 was running an 18-job OpenClaw stack from a custom dashboard — the same per-session layout, just at a larger scale.
  • The Speedwave team took the same idea further for security: each service keeps its own creds in a separate worker, so even if a session breaks out of its sandbox there is nothing to take.
  • Per-session isolation is what makes it safe to mix providers per session — cheap local models for some jobs, frontier models for others, with no risk of shared tokens.

Summary

In this post, I showed how to isolate state across multiple parallel AI coding agent sessions. The key point is to give every concurrent session its own state directory, its own process, and its own inbox, and to keep the dispatcher as the only thing that talks to all of them. No shared context files, no shared auth, no shared poll consumers.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments