Skip to content

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 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 --> Run

reload 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:

LayerBoundaryRole
Product / dataTenant (shown in the console as the workspace) plus ownerSessions, messages, approvals, and audit isolate by tenant and owner; Postgres row-level security backs this up
User filesFile sandboxProduction keeps user files in object storage with the tenant in the key prefix and never exposes server paths
Kernel runtime contextdirectory (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.

ConceptMeaning
directoryRuntime platform root: the path root for tool execution and config discovery; console requests without a directory use the process startup directory
worktreeRepo 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.

One Runner per session:

StateMeaning
IdleIdle
RunningrunLoop in progress
ShellShell exclusive
ShellThenRunShell in progress with a queued run
  • Another ensureRunning while 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 ends

Client 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.

SymptomCheck first
Config change has no effectWhether instance reloaded; wrong directory
One tenant sees another’s dataWhether tenant and owner are bound in the request context; whether row isolation is active
External path rejectedcontainsPath and external_directory permission
prompt reports busyWhether the same session already has run/shell
Two writers interleaveConcurrent loop calls that bypass Runner (should not happen)

Related reading: