sandbox-agent/foundry/packages/backend/CLAUDE.md
Nathan Flurry 57a07f6a0a
wip (#256)
2026-03-14 23:47:43 -07:00

5.7 KiB
Raw Blame History

Backend Notes

Actor Hierarchy

Keep the backend actor tree aligned with this shape unless we explicitly decide to change it:

OrganizationActor
├─ HistoryActor(organization-scoped global feed)
├─ GithubDataActor
├─ RepositoryActor(repo)
│  └─ TaskActor(task)
│     ├─ TaskSessionActor(session) × N
│     │  └─ SessionStatusSyncActor(session) × 0..1
│     └─ Task-local workbench state
└─ SandboxInstanceActor(sandboxProviderId, sandboxId) × N

Coordinator Pattern

Actors follow a coordinator pattern where each coordinator is responsible for:

  1. Index tables — keeping a local SQLite index/summary of its child actors' data
  2. Create/destroy — handling lifecycle of child actors
  3. Routing — resolving lookups to the correct child actor

Children push updates up to their direct coordinator only. Coordinators broadcast changes to connected clients. This keeps the read path local (no fan-out to children).

Coordinator hierarchy and index tables

OrganizationActor (coordinator for repos + auth users)
│
│  Index tables:
│  ├─ repos              → RepositoryActor index (repo catalog)
│  ├─ taskLookup         → TaskActor index (taskId → repoId routing)
│  ├─ taskSummaries      → TaskActor index (materialized sidebar projection)
│  ├─ authSessionIndex   → AuthUserActor index (session token → userId)
│  ├─ authEmailIndex     → AuthUserActor index (email → userId)
│  └─ authAccountIndex   → AuthUserActor index (OAuth account → userId)
│
├─ RepositoryActor (coordinator for tasks)
│  │
│  │  Index tables:
│  │  └─ taskIndex       → TaskActor index (taskId → branchName)
│  │
│  └─ TaskActor (coordinator for sessions + sandboxes)
│     │
│     │  Index tables:
│     │  ├─ taskWorkbenchSessions → Session index (session metadata, transcript, draft)
│     │  └─ taskSandboxes         → SandboxInstanceActor index (sandbox history)
│     │
│     └─ SandboxInstanceActor (leaf)
│
├─ HistoryActor (organization-scoped audit log, not a coordinator)
└─ GithubDataActor (GitHub API cache, not a coordinator)

When adding a new index table, annotate it in the schema file with a doc comment identifying it as a coordinator index and which child actor it indexes (see existing examples).

Ownership Rules

  • OrganizationActor is the organization coordinator and lookup/index owner.
  • HistoryActor is organization-scoped. There is one organization-level history feed.
  • RepositoryActor is the repo coordinator and owns repo-local caches/indexes.
  • TaskActor is one branch. Treat 1 task = 1 branch once branch assignment is finalized.
  • TaskActor can have many sessions.
  • TaskActor can reference many sandbox instances historically, but should have only one active sandbox/session at a time.
  • Session unread state and draft prompts are backend-owned workbench state, not frontend-local state.
  • Branch rename is a real git operation, not just metadata.
  • SandboxInstanceActor stays separate from TaskActor; tasks/sessions reference it by identity.
  • The backend stores no local git state. No clones, no refs, no working trees, and no git-spice. Repository metadata comes from GitHub API data and webhook events. Any working-tree git operation runs inside a sandbox via executeInSandbox().
  • When a backend request path must aggregate multiple independent actor calls or reads, prefer bounded parallelism over sequential fan-out when correctness permits. Do not serialize independent work by default.
  • Only a coordinator creates/destroys its children. Do not create child actors from outside the coordinator.
  • Children push state changes up to their direct coordinator only — never skip levels (e.g., task pushes to repo, not directly to org, unless org is the direct coordinator for that index).
  • Read paths must use the coordinator's local index tables. Do not fan out to child actors on the hot read path.
  • Never build "enriched" read actions that chain through multiple actors (e.g., coordinator → child actor → sibling actor). If data from multiple actors is needed for a read, it should already be materialized in the coordinator's index tables via push updates. If it's not there, fix the write path to push it — do not add a fan-out read path.

Multiplayer Correctness

Per-user UI state must live on the user actor, not on shared task/session actors. This is critical for multiplayer — multiple users may view the same task simultaneously with different active sessions, unread states, and in-progress drafts.

Per-user state (user actor): active session tab, unread counts, draft text, draft attachments. Keyed by (userId, taskId, sessionId).

Task-global state (task actor): session transcript, session model, session runtime status, sandbox identity, task status, branch name, PR state. These are shared across all users viewing the task — that is correct behavior.

Do not store per-user preferences, selections, or ephemeral UI state on shared actors. If a field's value should differ between two users looking at the same task, it belongs on the user actor.

Maintenance

  • Keep this file up to date whenever actor ownership, hierarchy, or lifecycle responsibilities change.
  • If the real actor tree diverges from this document, update this document in the same change.
  • When adding, removing, or renaming coordinator index tables, update the hierarchy diagram above in the same change.
  • When adding a new coordinator index table in a schema file, add a doc comment identifying which child actor it indexes (pattern: /** Coordinator index of {ChildActor} instances. ... */).