Instance boundaries and concurrency
A session always lands on a directory instance: config, permission boundaries, state cache, and the scheduling Runner hang on that scope. The same directory reuses one instance; one session runs only one primary task at a time (prompt or shell), so two runLoops cannot scramble messages.
Instance scope
Section titled “Instance scope”Instance context at least includes: current directory, worktree, project. That is the scope root key, not three optional fields.
flowchart TD Call[Business call] --> Prov[Instance.provide directory] Prov --> Cache{Cache hit?} Cache -->|yes| Reuse[Reuse instance] Cache -->|no| Boot[boot + write cache] Reuse --> Run[Run in context] Boot --> Runreload releases the old same-directory instance then boots again: directory unchanged, but project, VCS, or config may have changed, so cached state must be dropped.
User-facing boundaries: workspace (tenant) and directory
Section titled “User-facing boundaries: workspace (tenant) and directory”The kernel organizes instances by directory, while the product no longer makes users pick one. Two boundary layers must be read separately:
| Layer | Boundary | Role |
|---|---|---|
| Product / data | Tenant (shown in the console as the workspace) plus owner | Sessions, messages, approvals, and audit isolate by tenant and owner; Postgres row-level security backs this up |
| User files | File sandbox | Production keeps user files in object storage with the tenant in the key prefix and never exposes server paths |
| Kernel runtime context | directory (platform root) | Decides which path and which config the tools run against |
Three consequences:
- directory still decides relative paths for file tools and the root of config and capability discovery, but it maps to an operations-fixed platform root rather than a folder the user opened. Console requests usually carry no directory, so the runtime uses its startup directory.
- Cross-tenant invisibility does not come from directory separation; it comes from tenant and owner in the request context, row isolation in storage, and the sandbox prefix.
- A session carries an optional field recording the control-plane execution target (a local directory or a remote proxy). Local development usually leaves it empty; it is not the tenant boundary.
When data leaks across tenants, first separate the symptom: different tenants under one directory, or tenant context never bound so row isolation never applied.
directory, worktree, and path bounds
Section titled “directory, worktree, and path bounds”| Concept | Meaning |
|---|---|
| directory | Runtime platform root: the path root for tool execution and config discovery; console requests without a directory use the process startup directory |
| worktree | Repo or project root boundary |
| sandbox (resolved) | Checkout top path, often used as worktree |
A path counts as “inside” if it falls under directory, or under worktree (skip the second check when non-git and worktree degenerates to root). Otherwise it may trigger external-directory style permissions. Separately, a git worktree list supports multi-branch parallel workspaces; that is not the same concept as the runtime sandbox string above.
Runner: serialize per session
Section titled “Runner: serialize per session”One Runner per session:
| State | Meaning |
|---|---|
| Idle | Idle |
| Running | runLoop in progress |
| Shell | Shell exclusive |
| ShellThenRun | Shell in progress with a queued run |
- Another
ensureRunningwhile a run exists: wait for the same result; do not start another outer loop - Shell starts only from Idle; busy returns a busy error
- cancel can interrupt the current run or shell
stateDiagram-v2 [*] --> Idle Idle --> Running: prompt Idle --> Shell: shell Running --> Idle: complete / cancel Shell --> Idle: complete / cancel Shell --> ShellThenRun: queue prompt ShellThenRun --> Running: shell endsClient side: sync HTTP waits until that session task finishes; SSE can observe busy and Part deltas in parallel. Waiting inside the Effect runtime does not block the whole process; other sessions and event connections still progress.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Check first |
|---|---|
| Config change has no effect | Whether instance reloaded; wrong directory |
| One tenant sees another’s data | Whether tenant and owner are bound in the request context; whether row isolation is active |
| External path rejected | containsPath and external_directory permission |
| prompt reports busy | Whether the same session already has run/shell |
| Two writers interleave | Concurrent loop calls that bypass Runner (should not happen) |
Related reading: