How Child Session Spawning and Coordination Works in Background-Agents
Child session spawning and coordination in Background-Agents relies on a three-layer architecture spanning the control plane, Cloudflare Durable Objects, and web client, enforcing hierarchical limits through parentSessionId and spawnDepth tracking while propagating state changes via WebSocket events.
Background-Agents architect every interactive coding job as a session backed by a Cloudflare Durable Object (DO) and persisted in a D1 SQLite table. When a session needs to delegate work to a sandboxed sub-task, it triggers child session spawning and coordination through a strictly enforced pipeline that maintains provenance, depth limits, and real-time UI synchronization.
The Three-Layer Architecture
The system decomposes child session management into distinct backend, compute, and frontend layers, each with specific responsibilities for creation, execution, and observation.
Control Plane (Backend)
The control plane handles the authoritative creation and validation of child sessions. In packages/control-plane/src/routes/sessions/[parentId]/children.ts, the POST /sessions/:parentId/children endpoint validates the request, checks the parent's current child count, and inserts a new row into the sessions table.
The database schema, defined in terraform/d1/migrations/0012_add_parent_session.sql, stores parentSessionId, spawnDepth, spawnSource, and copies the parent's environmentId to ensure environmental provenance. The endpoint enforces hard limits: 429 Too Many Requests is returned if a parent already has ≥ 5 active children or ≥ 15 total children. The handler also propagates userId from parent to child, preserving null values when the parent lacks an authenticated user.
After database insertion, the control plane instantiates the child DO using env.SESSION.idFromName(newSessionId) to obtain a stub, then calls /internal/initialize to seed the sandbox authentication token and link the child to its parent.
Durable Object (Session DO)
Each session runs inside a Cloudflare Durable Object defined in packages/control-plane/src/durableObjects/session.ts. The DO maintains its own internal state at /internal/state and tracks its hierarchical position via the parentSessionId and spawnDepth properties.
When the child's lifecycle changes (e.g., spawned, running, completed), the DO emits a server-message with type: "child_session_update" to notify connected clients. This message conforms to the shared schema used across the platform, ensuring consistent state representation between the sandbox and the control plane.
Web Client (Frontend)
The frontend coordinates UI updates through WebSocket listeners and SWR cache revalidation. In packages/web/src/lib/session-socket/reducer.ts, incoming messages are routed based on their type. When a child_session_update arrives, the reducer invokes the revalidation logic in packages/web/src/lib/session-socket/swr-revalidation.ts.
This layer triggers immediate refetching of two critical cache keys: the parent's child list (/api/sessions/:id/children) and the global unarchived-session list. The useChildSessions React hook in the frontend consumes these endpoints via useSWR, ensuring the hierarchy displays real-time state without manual refresh.
The Child Session Lifecycle
The spawning workflow follows a strict sequence to maintain consistency across the distributed system:
- API Invocation – A client or parent session POSTs to
/sessions/:parentId/childrenwith a JSON body containingtitle,prompt, andmodelparameters. - Validation & Persistence – The control plane validates the request against concurrency limits, increments
spawnDepth(parent depth + 1), and inserts the child record with inheritedenvironmentIdanduserId. - DO Instantiation – The control plane generates a fresh sandbox auth token, resolves the child DO stub via
idFromName, and invokes/internal/initializeto activate the sandbox. - State Publication – The child DO publishes
child_session_updatemessages as its status transitions, including metadata about its parent relationship. - Client Revalidation – The web client receives the WebSocket event, triggers SWR revalidation of the child list and global sessions, and renders the updated hierarchy.
Concurrency Limits and Safety Guarantees
The implementation enforces several invariants to prevent resource exhaustion and maintain data integrity:
- Depth Tracking – The
spawnDepthcolumn records generational distance from the root session; tests verify grandchildren receivespawnDepth = 2. - Environment Provenance – Children immutable inherit the parent's
environmentId, ensuring sandboxed tools execute in the same context as their orchestrator. - Identity Propagation – Authentication state flows downward; if a parent has
userId: null, the child explicitly receivesuserId: nullrather than defaulting to a system value. - Rate Limiting – Hard caps of 5 active and 15 total children per parent prevent runaway spawning, returning HTTP 429 when exceeded.
Implementation Examples
Spawning a Child Session via API
curl -X POST https://api.example.com/sessions/parent-abc/children \
-H "Authorization: Bearer $PARENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Run Linter",
"prompt": "lint the repo",
"model": "gpt-4o-mini"
}'
# Response: { "sessionId": "child-xyz" }
Listening for Child Updates via WebSocket
socket.onmessage = (msg) => {
const data = JSON.parse(msg.data);
if (data.type === "child_session_update") {
// Triggers SWR revalidation of child list and global sessions
swrRevalidate(data.sessionId);
}
};
Fetching Child Sessions in React
import useSWR from "swr";
export const useChildSessions = (parentId: string) =>
useSWR(`/api/sessions/${parentId}/children`, fetcher);
Summary
- Child session spawning and coordination operates across three layers: the control plane (
packages/control-plane/src/routes/sessions/[parentId]/children.ts), the Durable Object (packages/control-plane/src/durableObjects/session.ts), and the web client (packages/web/src/lib/session-socket/reducer.ts). - The system enforces hierarchical limits of 5 active and 15 total children per parent, returning HTTP 429 when thresholds are exceeded.
- State propagation relies on WebSocket messages with type
child_session_update, triggering SWR revalidation of/api/sessions/:id/childrenand the global session list. - Children inherit
environmentIdanduserIdfrom parents, withspawnDepthtracking generational distance in the SQLite schema defined interraform/d1/migrations/0012_add_parent_session.sql.
Frequently Asked Questions
What are the limits on child session spawning?
Background-Agents enforces two hard limits: 5 active children and 15 total children per parent session. When a POST request to /sessions/:parentId/children would violate these constraints, the control plane returns HTTP 429 without creating a database record or Durable Object instance.
How does environment inheritance work across parent-child relationships?
When a child session spawns, the control plane copies the parent's environmentId into the child's record during the INSERT operation in packages/control-plane/src/routes/sessions/[parentId]/children.ts. This ensures the child executes within the same sandboxed environment as its parent, maintaining consistent tool access and filesystem context.
What happens when a child session's status changes?
The child Durable Object emits a WebSocket message with type: "child_session_update" whenever its lifecycle state changes (e.g., from "spawning" to "running" or "completed"). The client's reducer.ts catches this event and triggers SWR revalidation of the parent's child list and the global session cache, updating the UI without manual refresh.
How is the parent-child hierarchy tracked in the database?
The sessions table includes parentSessionId (foreign key reference), spawnDepth (integer representing generational distance from root), and spawnSource (describing the origin tool or action). These columns, added in terraform/d1/migrations/0012_add_parent_session.sql, enable recursive queries and enforce depth-based constraints while preserving the full ancestry chain.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →