Skip to content

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.

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.

  1. One project sandbox. ProjectExecutionWorkspace owns exactly one Daytona sandbox for a project. ProjectAgentProfile never owns or forks a sandbox. Isolation admission failure blocks a provider launch; it does not create a hidden per-agent fallback sandbox.

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

  3. Generation-fenced writer lease. RuntimeLease is 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.

  4. Provider-neutral continuity. ContinuityCapsule stores 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.

  5. 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 uses cgroup.freeze, cgroup.kill, and cgroup.events populated=0; PID matching alone is not proof.

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

  7. 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:use against the real profile subject. Agent paths cannot fall back to an admin token.

  8. 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 tool is not relayed as handoff content.

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

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

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.

OutcomeRepository and ownerPublic boundary
Decide profile identity and provider priorityBackend agent-execution domainAgentProfile policy
Admit one active writer generationBackend agent-execution applicationLaunchAgentAttempt command
Persist profiles, leases, and capsulesBackend agent-execution adapterRepository ports owned by the command
Launch or reap a provider processBackend execution-surface adapterDaytona and workspace-tools ports
Mutate Git stateBackend project-delivery contextGeneration-fenced Git broker contract
Authorize MCP useBackend access-control contextSpiceDB permission port using profile subject
Present operator controlsBackend Studio interface and frontend projectionAgent Execution public facade only
Pin, deploy, canary, soak, and recoverAssemblyImmutable 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.

  1. Assembly validates ADR lineage and pins the four repository baselines.
  2. Contract changes first only when a public HTTP, event, or message shape changes.
  3. Backend moves policy into agent-execution, leaving temporary compatibility facades in access with no business decisions.
  4. Backend publishes one signed runtime artifact through Kubernetes BuildKit.
  5. Assembly pins the digest, rolls out canary-first, and records one-sandbox and generation-fence evidence.
  6. Legacy per-agent sandbox creation is removed only after the 24-hour soak and rollback proof pass.

Current partial source evidence:

  • Backend migrations 20260711122100_agent_identity_runtime.sql, 20260711122200_single_project_agent_sandbox.sql, and 20260714113000_agent_profile_mailbox.sql own profile, lease, capsule, shared-sandbox, and profile-mailbox persistence.
  • backend/src/fractalops/adapters/agent_execution/features/identity_runtime/repository.py enforces 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.py proves the workspace service no longer exposes fork_workspace.
  • delivery/images/fractalops-agent-workspace/bin/agent-process-supervisor and agent-process-isolation-launcher own cgroup and Bubblewrap process containment; packages/workspace-tools/src/agent-process-isolation.mjs owns provider-neutral launch enforcement.
  • packages/workspace-tools/src/agent-session.mjs sends Git commits through the root supervisor instead of mutating read-only Git metadata directly. The supervisor git_commit operation 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.py and 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.py enforces 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.py and .github/workflows/agentsquad-identity-evidence.yml gate topology, continuity, authorization, roster, migration, deployment digest, and soak evidence.
  • Signed canary evidence from workflow run 31384984580 proves Backend decf77bbaffad1c95e741663dc60c4e9cf70e622, live Kubernetes BuildKit digest sha256: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.

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

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.