ADR-0011 — Identity-centric AgentSquad runtime in one project sandbox
ADR-0011 — Identity-centric AgentSquad runtime in one project sandbox
Section titled “ADR-0011 — Identity-centric AgentSquad runtime in one project sandbox”Status: Accepted (2026-07-11) Implementation: Partial; source ownership and signed live canary proof are current. The same-run 24-hour leak-free soak remains open.
Context
Section titled “Context”Current AgentSquad treats a run/session/slot as practical agent identity. Normal slot provisioning creates a Daytona fork per agent, so agent count multiplies sandboxes. Worktree topology locks serialize setup but do not fence active writers. Provider switching restarts a process without a durable identity lease or provider-neutral continuity record. Mailbox, context, and continuity state remain run-scoped. Process caps count sandbox-like resources instead of active provider processes and can fail open when provider inventory is unavailable.
The required invariant is different: eight durable agent profiles may exist while one project sandbox hosts at most four active provider processes. Codex is preferred, but Claude may take over the same identity after graceful handoff or crash recovery.
Decision
Section titled “Decision”-
One project sandbox.
ProjectExecutionWorkspaceowns exactly one Daytona sandbox for a project.ProjectAgentProfilenever owns or forks a sandbox. Isolation admission failure blocks a provider launch; it does not create a hidden per-agent fallback sandbox. -
Persistent agent identity. Add
ProjectAgentProfile, unique by tenant, project, and profile key. It owns stable worktree, mailbox, context, subject, Armory manifest, provider priority, and current runtime generation. Sessions and runs reference the profile; they do not define it. -
Generation-fenced writer lease.
RuntimeLeaseis the sole active-process and write authority. Acquisition increments profile generation with compare-and-swap. Heartbeat, release, mailbox mutation, Git mutation, capsule commit, and takeover require matching profile, lease ID, generation, and active state. A project-level transaction admits at most four unexpired active leases and fails closed when the count cannot be proved. -
Provider-neutral continuity.
ContinuityCapsulestores canonical task state, decisions, pending work, repository revision, mailbox cursor, context reference, previous digest, sequence, schema version, and integrity digest. Graceful takeover commits and verifies a capsule before fencing the old lease. Crash takeover waits for lease expiry, fences it atomically, proves old cgroup empty, then starts the next generation. Codex-to-Claude changes runtime, not identity or owned state. -
Per-process containment. Each active provider process runs inside a generation-owned cgroup v2 leaf and Bubblewrap boundary. Only the owned worktree and profile state are writable. Observation roots, Git common metadata, peer profiles, runtime binaries, and shared sources are read-only or hidden. Environment variables use an allowlist; capabilities are dropped; private
/tmp, PID namespace, and no-new-privileges are required. Reaping usescgroup.freeze,cgroup.kill, andcgroup.events populated=0; PID matching alone is not proof. -
Generation-fenced Git broker. Provider processes cannot write Git metadata directly. Mutating operations carry profile ID, lease ID, generation, expected revision/OID, and idempotency key. Existing profile worktrees are never reset until matching lease quiescence is proved.
-
Identity capability plane. Armory compiles one versioned capability manifest per profile. Missing required skill or MCP server denies launch. ContextForge owns stable subject authentication, gateway routing, and telemetry only; it cannot write profile business state. SpiceDB enforces
mcp_server:useagainst the real profile subject. Agent paths cannot fall back to an admin token. -
Compact collaboration. Launch receives a compact roster snapshot. Later roster changes use revisioned deltas addressed to profile mailboxes. Lifecycle-only text such as
waiting for first toolis not relayed as handoff content. -
Migration and proof. Backfill profiles first, dual-write profile references, canary the single-sandbox scheduler, fence and reap legacy forks, then remove fork creation. Release requires immutable build/deploy digest equality and signed evidence proving eight profiles, four-process peak, one sandbox, both takeover kinds, zero dual writers, unauthorized MCP HTTP 403, full roster delivery, and a leak-free 24-hour soak.
Boundaries
Section titled “Boundaries”- Database migrations own durable schema; runtime
create_all()is not migration truth. - Bubblewrap is containment, not identity, authorization, or writer fencing.
- ContextForge projections cannot become profile or continuity write authority.
- Build and image publication use Kubernetes BuildKit only; no host-local image build.
- No takeover proceeds when cgroup absence, lease generation, or artifact provenance is uncertain.
Pinned OSS behavior and upgrade traps are recorded in the Daytona runtime matrix, SpiceDB 1.40.0, Bubblewrap process boundary, and ContextForge 1.0.5.
Vibe Carving ownership map
Section titled “Vibe Carving ownership map”The durable outcome owner is the backend agent-execution bounded context. Physical
placement follows outcome slices, not the Studio screen or the Daytona vendor API.
| Outcome | Repository and owner | Public boundary |
|---|---|---|
| Decide profile identity and provider priority | Backend agent-execution domain | AgentProfile policy |
| Admit one active writer generation | Backend agent-execution application | LaunchAgentAttempt command |
| Persist profiles, leases, and capsules | Backend agent-execution adapter | Repository ports owned by the command |
| Launch or reap a provider process | Backend execution-surface adapter | Daytona and workspace-tools ports |
| Mutate Git state | Backend project-delivery context | Generation-fenced Git broker contract |
| Authorize MCP use | Backend access-control context | SpiceDB permission port using profile subject |
| Present operator controls | Backend Studio interface and frontend projection | Agent Execution public facade only |
| Pin, deploy, canary, soak, and recover | Assembly | Immutable artifacts and black-box scenarios |
Studio may translate operator intent and render projections. It cannot own profile, lease, capsule, takeover, containment, or provider-routing rules. Workspace-tools is a provider adapter artifact: it verifies a launch capability and enforces containment; it cannot issue identity, choose a provider, or mutate durable Agent Execution state.
Target backend slices:
domain/agent_execution/features/├── agent_profile/├── runtime_lease/└── continuity_capsule/application/agent_execution/features/├── launch_agent_attempt/└── takeover_agent_attempt/adapters/agent_execution/features/├── identity_runtime/└── daytona_process_sessions/interfaces/http/agent_execution/features/Tests remain beside each behavior owner. Cross-artifact one-sandbox, takeover, authorization, rollback, and 24-hour soak proof belongs to assembly scenarios.
Release choreography
Section titled “Release choreography”- Assembly validates ADR lineage and pins the four repository baselines.
- Contract changes first only when a public HTTP, event, or message shape changes.
- Backend moves policy into
agent-execution, leaving temporary compatibility facades inaccesswith no business decisions. - Backend publishes one signed runtime artifact through Kubernetes BuildKit.
- Assembly pins the digest, rolls out canary-first, and records one-sandbox and generation-fence evidence.
- Legacy per-agent sandbox creation is removed only after the 24-hour soak and rollback proof pass.
Implementation evidence
Section titled “Implementation evidence”Current partial source evidence:
- Backend migrations
20260711122100_agent_identity_runtime.sql,20260711122200_single_project_agent_sandbox.sql, and20260714113000_agent_profile_mailbox.sqlown profile, lease, capsule, shared-sandbox, and profile-mailbox persistence. backend/src/fractalops/adapters/agent_execution/features/identity_runtime/repository.pyenforces generation, project process capacity, capsule chain integrity, and reap evidence.backend/src/fractalops/application/agent_execution/features/takeover_agent_attempt/owns graceful and crash takeover.resolve_agent_provider_profile/owns provider priority.backend/src/fractalops/application/access/features/daytona_workspace/daytona_workspace_lifecycle_test.pyproves the workspace service no longer exposesfork_workspace.delivery/images/fractalops-agent-workspace/bin/agent-process-supervisorandagent-process-isolation-launcherown cgroup and Bubblewrap process containment;packages/workspace-tools/src/agent-process-isolation.mjsowns provider-neutral launch enforcement.packages/workspace-tools/src/agent-session.mjssends Git commits through the root supervisor instead of mutating read-only Git metadata directly. The supervisorgit_commitoperation verifies the signed profile, lease generation, cgroup reference, expected HEAD, bounded path set, and idempotency key before committing.backend/src/fractalops/application/access/features/studio/team/roster_broadcast.pyand the profile mailbox repository own compact roster delivery and durable mailbox state.- Studio runtime coordination calls the public Agent Execution feature entrypoints for launch, observation reconciliation, takeover, stop, and reap decisions. Studio retains operator-intent translation and session projection; it does not call identity-runtime repository methods directly. Backend Tach dependency and interface checks validate the published feature boundaries.
backend/src/fractalops/adapters/policy/features/authorize_mcp_server/spicedb_permissions.pyenforces profile-subject MCP authorization. Agent process configuration mints a non-admin ContextForge identity; the admin token remains confined to catalog health and user provisioning control paths.- Assembly
scenarios/agentsquad/identity_evidence_collector.pyand.github/workflows/agentsquad-identity-evidence.ymlgate topology, continuity, authorization, roster, migration, deployment digest, and soak evidence. - Signed canary evidence from
workflow run 31384984580
proves Backend
decf77bbaffad1c95e741663dc60c4e9cf70e622, live Kubernetes BuildKit digestsha256:2675050e539ec909c265291c6d0e3e6af7228fdfad436d860159d80c297792a0, eight profiles, four active processes, one sandbox, graceful and crash takeover, zero forks, zero dual writers, full roster delivery, zero lifecycle-noise forwarding, zero credential residue, and unauthorized MCP HTTP 403. The checkpoint observed 1,081 seconds and its Sigstore bundle verifies against this workflow identity. - AgentSquad identity evidence records immutable pins, checkpoint claims, independent verification, and the still-running same-state soak without treating the canary as final proof.
These files prove source behavior and denial contracts. They do not replace deployed proof.
Known gaps
Section titled “Known gaps”- Complete the linked signed 24-hour soak and verify the final Contract surface, Sigstore bundles, cleanup evidence, and uploaded artifact.
- Retain zero legacy sandboxes, unreaped leases, credential residue, and dual writers through the final observation rather than inferring leak freedom from the canary.
Consequences
Section titled “Consequences”- Resource use scales with active processes, not configured agent count.
- Identity survives provider crash, session retry, and runtime replacement.
- Recovery may block instead of guessing when lease or cgroup proof is missing.
- Migration touches the highest-blast-radius execution path and must remain canary-first.
Rollback
Section titled “Rollback”Before legacy fork removal, rollback disables single-sandbox admission and returns new runs to the legacy scheduler while preserving profile/capsule rows. After removal, rollback uses the pinned prior image and migration downgrade only after active leases are drained and profile state is archived. Evidence must record rollback digest and legacy-fork reap status.