85 KiB
Foundry Planned Changes
How to use this document
Work through items checking boxes as you go. Some items have dependencies — do not start an item until its dependencies are checked off. After each item, run pnpm -w typecheck && pnpm -w build && pnpm -w test to validate. If an item includes a "CLAUDE.md update" section, apply it in the same change. Commit after each item passes validation.
Progress Log
- 2026-03-14 10: Initial architecture mapping complete.
- Confirmed the current hot spots match the spec:
auth-useris still mutation-by-action,historyis still a separate actor with anappendaction wrapper, organization still ownstaskLookup/taskSummaries, and theWorkbench*surface is still shared across backend/client/frontend. - Started foundational rename and migration planning for items
1,6, and25because they drive most of the later fallout.
- Confirmed the current hot spots match the spec:
- 2026-03-14 11: Audit-log rename slice landed.
- Renamed the backend actor from
historytoaudit-log, switched the queue name toauditLog.command.append, and removed theappendaction wrapper. - Updated task/repository/organization call sites to send directly to the audit-log queue or read through the renamed audit-log handle.
- Renamed the backend actor from
- 2026-03-14 12: Foundational naming and dead-surface cleanup landed.
- Renamed the backend auth actor surface from
authUsertouser, including actor registration, key helpers, handles, and Better Auth service routing. - Deleted the dead
getTaskEnriched/enrichTaskRecordfan-out path and changed organization task reads to go straight to the task actor. - Renamed admin-only GitHub rebuild/reload actions with the
admin*prefix across backend, client, and frontend. - Collapsed organization realtime to full-snapshot
organizationUpdatedevents and aligned task events totype: "taskUpdated".
- Renamed the backend auth actor surface from
- 2026-03-14 13: Task schema migration cleanup landed.
- Removed the task actor's runtime
CREATE TABLE IF NOT EXISTS/ALTER TABLEhelpers fromtask/workbench.tsandtask/workflow/init.ts. - Updated the checked-in task migration artifacts so the schema-defined task/session/runtime columns are created directly by migrations.
- Removed the task actor's runtime
- 2026-03-14 14: Item 3 blocker documented.
- The spec's requested literal singleton
CHECK (id = 1)on the Better Authusertable conflicts with the existing Better Auth adapter contract, which relies on external stringuser.id. - Proceeding safely will require a design adjustment for that table rather than a straight mechanical migration.
- The spec's requested literal singleton
- 2026-03-14 15: Better Auth mapping comments landed.
- Added Better Auth vs custom Foundry table/action comments in the user and organization actor schema/action surfaces so the adapter-constrained paths are explicit.
- 2026-03-15 09: Branch rename surface deleted and stale organization subscription fixed.
- Removed the remaining branch-rename surface from the client, mock backend, frontend UI, and repository action layer. There are no remaining
renameBranch/renameWorkbenchBranchreferences in Foundry. - Fixed the remote backend client to listen for
organizationUpdatedon the organization connection instead of the deadworkspaceUpdatedevent name.
- Removed the remaining branch-rename surface from the client, mock backend, frontend UI, and repository action layer. There are no remaining
- 2026-03-15 10: Backend workspace rename landed.
- Renamed the backend task UI/workflow surface from
workbenchtoworkspace, including the task actor file, queue topic family, organization proxy actions, and the task session table name (task_workspace_sessions). - Backend actor code no longer contains
Workbench/workbenchreferences, so the remaining shared/client/frontend rename can align to a stable backend target.
- Renamed the backend task UI/workflow surface from
- 2026-03-15 11: Default model moved to user-scoped app state.
- Removed
defaultModelfrom the organization schema/snapshot and stored it on the user profile instead, exposed through the app snapshot as a user preference. - Wired
setAppDefaultModelthrough the backend/app clients and changed the model picker to persist the starred/default model instead of resetting local React state on reload.
- Removed
- 2026-03-15 11: Workspace surface completed across Foundry packages.
- Renamed the shared/client/frontend surface from
WorkbenchtoWorkspace, includingworkspace.ts, workspace client/model files, DTO/type names, backend-client method names, frontend view-model imports, and the affected e2e/test files. - Verified that Foundry backend/shared/client/frontend packages no longer contain
Workbench/workbenchreferences.
- Renamed the shared/client/frontend surface from
- 2026-03-15 11: Singleton constraints tightened where safe.
- Added
CHECK (id = 1)enforcement forgithub_meta,repo_meta,organization_profile, anduser_profiles, and updated the affected code paths/migrations to use row id1. - The Better Auth
usertable remains blocked by the adapter contract, so item3is still open overall.
- Added
- 2026-03-14 12: Confirmed blocker for later user-table singleton work.
- Item
3conflicts with the current Better Auth adapter contract for theusertable: the adapter depends on the external stringuser.id, while the spec also asks for a literal singletonCHECK (id = 1)on that same table. - That cannot be applied mechanically without redesigning the Better Auth adapter contract or introducing a separate surrogate identity column. I have not forced that change yet.
- Item
No backwards compatibility — delete old code, don't deprecate. If something is removed, remove it everywhere (backend, client, shared types, frontend, tests, mocks).
Suggested execution order (respects dependencies)
Wave 1 — no dependencies, can be done in any order: 1, 2, 3, 4, 5, 6, 13, 16, 20, 21, 23, 25
Wave 2 — depends on wave 1: 7 (after 1), 9 (after 13), 10 (after 1+6), 11 (after 4), 22 (after 1), 24 (after 21), 26 (after 25)
Wave 3 — depends on wave 2: 8 (after 7+25), 12 (after 10), 15 (after 9+13), 19 (after 21+24)
Wave 4 — depends on wave 3: 14 (after 15)
Final: 17 (deferred), 18 (after everything), final audit pass (after everything)
Index
- 1. Rename Auth User actor → User actor
- 2. Add Better Auth mapping comments to user/org actor tables
- 3. Enforce
id = 1CHECK constraint on single-row tables - 4. Move all mutation actions to queue messages
- 5. Migrate task actor raw SQL to Drizzle migrations
- 6. Rename History actor → Audit Log actor
- 7. Move starred/default model to user actor settings (depends on: 1)
- 8. Replace hardcoded model/agent lists with sandbox-agent API data (depends on: 7, 25)
- 9. Flatten
taskLookup+taskSummariesinto singletaskstable (depends on: 13) - 10. Reorganize user and org actor actions into
actions/folders (depends on: 1, 6) - 11. Standardize workflow file structure across all actors (depends on: 4)
- 12. Audit and remove dead code in organization actor (depends on: 10)
- 13. Enforce coordinator pattern and fix ownership violations
- 14. Standardize one event per subscription topic (depends on: 15)
- 15. Unify tasks and pull requests — PRs are just task data (depends on: 9, 13)
- 16. Chunk GitHub data sync and publish progress
- 17. Type all actor context parameters — remove
c: any(DEFERRED — do last) - 18. Final pass: remove all dead code (depends on: all other items)
- 19. Remove duplicate data between
c.stateand SQLite (depends on: 21, 24) - 20. Prefix admin/recovery actions with
admin - 21. Remove legacy/session-scoped fields from task table
- 22. Move per-user UI state from task actor to user actor (depends on: 1)
- 23. Delete
getTaskEnrichedandenrichTaskRecord(dead code) - 24. Clean up task status tracking (depends on: 21)
- 25. Remove "Workbench" prefix from all types, functions, files, tables
- 26. Delete branch rename (branches immutable after creation) (depends on: 25)
- Final audit pass: dead events scan (depends on: all other items)
[ ] 1. Rename Auth User actor → User actor
Rationale: The actor is already a single per-user actor storing all user data. The "Auth" prefix is unnecessary.
Files to change
foundry/packages/backend/src/actors/auth-user/→ rename directory touser/index.ts— rename exportauthUser→user, display name"Auth User"→"User"db/schema.ts,db/db.ts,db/migrations.ts,db/drizzle.config.ts— update any auth-prefixed references
foundry/packages/backend/src/actors/keys.ts—authUserKey()→userKey()foundry/packages/backend/src/actors/handles.ts—getOrCreateAuthUser→getOrCreateUser,getAuthUser→getUser,selfAuthUser→selfUserfoundry/packages/backend/src/actors/index.ts— update import path and registrationfoundry/packages/backend/src/services/better-auth.ts— update allauthUserreferences- Action names — consider dropping "Auth" prefix from
createAuthRecord,findOneAuthRecord,updateAuthRecord,deleteAuthRecord,countAuthRecords, etc.
[ ] 2. Add Better Auth mapping comments to user/org actor tables, actions, and queues
Rationale: The user and organization actors contain a mix of Better Auth-driven and custom Foundry code. Tables, actions, and queues that exist to serve Better Auth's adapter need comments so developers know which pieces are constrained by Better Auth's schema/contract and which are ours to change freely.
Table mapping
| Actor | Table | Better Auth? | Notes |
|---|---|---|---|
| user | user |
Yes — 1:1 user model |
All fields from Better Auth |
| user | session |
Yes — 1:1 session model |
All fields from Better Auth |
| user | account |
Yes — 1:1 account model |
All fields from Better Auth |
| user | user_profiles |
No — custom Foundry | GitHub login, role, eligible orgs, starter repo status |
| user | session_state |
No — custom Foundry | Active organization per session |
| org | auth_verification |
Yes — Better Auth verification model |
Lives on org actor because verification happens before user exists |
| org | auth_session_index |
No — custom routing index | Maps session tokens → user actor IDs for Better Auth adapter routing |
| org | auth_email_index |
No — custom routing index | Maps emails → user actor IDs for Better Auth adapter routing |
| org | auth_account_index |
No — custom routing index | Maps OAuth accounts → user actor IDs for Better Auth adapter routing |
Action/queue mapping (user actor)
| Action/Queue | Better Auth? | Notes |
|---|---|---|
createAuthRecord |
Yes — Better Auth adapter | Called by Better Auth adapter to create user/session/account records |
findOneAuthRecord |
Yes — Better Auth adapter | Called by Better Auth adapter for single-record lookups with joins |
findManyAuthRecords |
Yes — Better Auth adapter | Called by Better Auth adapter for multi-record queries |
updateAuthRecord |
Yes — Better Auth adapter | Called by Better Auth adapter to update records |
updateManyAuthRecords |
Yes — Better Auth adapter | Called by Better Auth adapter for bulk updates |
deleteAuthRecord |
Yes — Better Auth adapter | Called by Better Auth adapter to delete records |
deleteManyAuthRecords |
Yes — Better Auth adapter | Called by Better Auth adapter for bulk deletes |
countAuthRecords |
Yes — Better Auth adapter | Called by Better Auth adapter for count queries |
getAppAuthState |
No — custom Foundry | Aggregates auth state for frontend consumption |
upsertUserProfile |
No — custom Foundry | Manages Foundry-specific user profile data |
upsertSessionState |
No — custom Foundry | Manages Foundry-specific session state |
Action/queue mapping (organization actor app-shell)
| Action/Queue | Better Auth? | Notes |
|---|---|---|
| App-shell auth index CRUD actions | Yes — Better Auth adapter routing | Maintain lookup indexes so the adapter can route by session/email/account to the correct user actor |
auth_verification CRUD |
Yes — Better Auth verification model |
Used for email verification and password resets |
Files to change
foundry/packages/backend/src/actors/auth-user/db/schema.ts— add doc comments to each table:user,session,account: "Better Auth core model — schema defined at https://better-auth.com/docs/concepts/database"user_profiles,session_state: "Custom Foundry table — not part of Better Auth"
foundry/packages/backend/src/actors/auth-user/index.ts— add doc comments to each action/queue:- Better Auth adapter actions: "Better Auth adapter — called by the Better Auth adapter in better-auth.ts. Schema constrained by Better Auth."
- Custom actions: "Custom Foundry action — not part of Better Auth"
foundry/packages/backend/src/actors/organization/db/schema.ts— add doc comments toauth_verification(Better Auth core), and the three index tables (Better Auth adapter routing)foundry/packages/backend/src/actors/organization/app-shell.ts— add doc comments to auth index actions marking them as Better Auth adapter routing infrastructure
[ ] 3. Enforce id = 1 CHECK constraint on all single-row actor tables
Rationale: When an actor instance represents a single entity, tables that hold exactly one row should enforce this at the DB level with a CHECK (id = 1) constraint. The task actor already does this correctly; other actors don't.
Tables needing the constraint
| Actor | Table | Current enforcement | Fix needed |
|---|---|---|---|
| auth-user (→ user) | user |
None | Add CHECK (id = 1), use integer PK |
| auth-user (→ user) | user_profiles |
None | Add CHECK (id = 1), use integer PK |
| github-data | github_meta |
Hardcoded id=1 in code only |
Add CHECK (id = 1) in schema |
| organization | organization_profile |
None | Add CHECK (id = 1), use integer PK |
| repository | repo_meta |
Hardcoded id=1 in code only |
Add CHECK (id = 1) in schema |
| task | task |
CHECK constraint | Already correct |
| task | task_runtime |
CHECK constraint | Already correct |
Files to change
foundry/packages/backend/src/actors/auth-user/db/schema.ts— changeuseranduser_profilestables to integer PK with CHECK constraintfoundry/packages/backend/src/actors/auth-user/index.ts— update queries to useid = 1patternfoundry/packages/backend/src/services/better-auth.ts— update adapter to use fixedid = 1foundry/packages/backend/src/actors/github-data/db/schema.ts— add CHECK constraint togithub_meta(already usesid=1in code)foundry/packages/backend/src/actors/organization/db/schema.ts— changeorganization_profileto integer PK with CHECK constraintfoundry/packages/backend/src/actors/organization/actions.ts— update queries to useid = 1foundry/packages/backend/src/actors/repository/db/schema.ts— add CHECK constraint torepo_meta(already usesid=1in code)- All affected actors — regenerate
db/migrations.ts
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "Single-row tables (tables that hold exactly one record per actor instance, e.g. metadata or profile tables) must use an integer primary key with aCHECK (id = 1)constraint to enforce the singleton invariant at the database level. Follow the pattern established in the task actor'staskandtask_runtimetables."
[ ] 4. Move all mutation actions to queue messages
Rationale: Actions should be read-only (queries). All mutations (INSERT/UPDATE/DELETE) should go through queue messages processed by workflow handlers. This ensures single-writer consistency and aligns with the actor model. No actor currently does this correctly — the history actor has the mutation in the workflow handler, but the append action wraps a wait: true queue send, which is the same anti-pattern (callers should send to the queue directly).
Violations by actor
User actor (auth-user) — auth-user/index.ts — 7 mutation actions:
createAuthRecord(INSERT, line 164)updateAuthRecord(UPDATE, line 205)updateManyAuthRecords(UPDATE, line 219)deleteAuthRecord(DELETE, line 234)deleteManyAuthRecords(DELETE, line 243)upsertUserProfile(UPSERT, line 283)upsertSessionState(UPSERT, line 331)
GitHub Data actor — github-data/index.ts — 7 mutation actions:
fullSync(batch INSERT/DELETE/UPDATE, line 686)reloadOrganization(batch, line 690)reloadAllPullRequests(batch, line 694)reloadRepository(INSERT/UPDATE, line 698)reloadPullRequest(INSERT/DELETE/UPDATE, line 763)clearState(batch DELETE, line 851)handlePullRequestWebhook(INSERT/UPDATE/DELETE, line 879)
Organization actor — actions.ts — 5 mutation actions:
applyTaskSummaryUpdate(UPSERT, line 464)removeTaskSummary(DELETE, line 476)applyGithubRepositoryProjection(UPSERT, line 521)applyGithubDataProjection(INSERT/UPDATE/DELETE, line 547)recordGithubWebhookReceipt(UPDATE, line 620)
Organization actor — app-shell.ts — 38 mutation actions:
Better Auth index mutations (11):
authUpsertSessionIndex(UPSERT)authDeleteSessionIndex(DELETE)authUpsertEmailIndex(UPSERT)authDeleteEmailIndex(DELETE)authUpsertAccountIndex(UPSERT)authDeleteAccountIndex(DELETE)authCreateVerification(INSERT)authUpdateVerification(UPDATE)authUpdateManyVerification(UPDATE)authDeleteVerification(DELETE)authDeleteManyVerification(DELETE)
Organization profile/state mutations (13):
updateOrganizationShellProfile(UPDATE on organizationProfile)markOrganizationSyncStarted(UPDATE on organizationProfile)applyOrganizationSyncCompleted(UPDATE on organizationProfile)markOrganizationSyncFailed(UPDATE on organizationProfile)applyOrganizationStripeCustomer(UPDATE on organizationProfile)applyOrganizationStripeSubscription(UPSERT on organizationProfile)applyOrganizationFreePlan(UPDATE on organizationProfile)setOrganizationBillingPaymentMethod(UPDATE on organizationProfile)setOrganizationBillingStatus(UPDATE on organizationProfile)upsertOrganizationInvoice(UPSERT on invoices)recordOrganizationSeatUsage(UPSERT on seatAssignments)applyGithubInstallationCreated(UPDATE on organizationProfile)applyGithubInstallationRemoved(UPDATE on organizationProfile)
App-level mutations that delegate + mutate (8):
skipAppStarterRepo(calls upsertUserProfile)starAppStarterRepo(calls upsertUserProfile + child mutation)selectAppOrganization(calls setActiveOrganization)triggerAppRepoImport(calls markOrganizationSyncStarted)createAppCheckoutSession(calls applyOrganizationFreePlan + applyOrganizationStripeCustomer)finalizeAppCheckoutSession(calls applyOrganizationStripeCustomer)cancelAppScheduledRenewal(calls setOrganizationBillingStatus)resumeAppSubscription(calls setOrganizationBillingStatus)recordAppSeatUsage(calls recordOrganizationSeatUsage)handleAppStripeWebhook(calls multiple org mutations)handleAppGithubWebhook(calls org mutations + github-data mutations)syncOrganizationShellFromGithub(multiple DB operations)applyGithubRepositoryChanges(calls applyGithubRepositoryProjection)
Task actor workbench — task/workbench.ts — 14 mutation actions:
renameWorkbenchTask(UPDATE, line 970)renameWorkbenchBranch(UPDATE, line 988)createWorkbenchSession(INSERT, line 1039)renameWorkbenchSession(UPDATE, line 1125)setWorkbenchSessionUnread(UPDATE, line 1136)updateWorkbenchDraft(UPDATE, line 1143)changeWorkbenchModel(UPDATE, line 1152)sendWorkbenchMessage(UPDATE, line 1205)stopWorkbenchSession(UPDATE, line 1255)syncWorkbenchSessionStatus(UPDATE, line 1265)closeWorkbenchSession(UPDATE, line 1331)markWorkbenchUnread(UPDATE, line 1363)publishWorkbenchPr(UPDATE, line 1375)revertWorkbenchFile(UPDATE, line 1403)
Repository actor — repository/actions.ts — 5 mutation actions/helpers:
createTask→ callscreateTaskMutation()(INSERT on taskIndex + creates task actor)registerTaskBranch→ callsregisterTaskBranchMutation()(INSERT/UPDATE on taskIndex)reinsertTaskIndexRow()(INSERT/UPDATE, called fromgetTaskEnriched)deleteStaleTaskIndexRow()(DELETE)persistRemoteUrl()(INSERT/UPDATE on repoMeta, called fromgetRepoOverview)
History (audit log) actor — append action must also be removed
The history actor's workflow handler is correct (mutation in queue handler), but the append action (line 77) is a wait: true wrapper around the queue send — same anti-pattern. Delete the append action. Callers (the appendHistory() helper in task/workflow/common.ts) should send directly to the auditLog.command.append queue with wait: false (audit log writes are fire-and-forget, no need to block the caller).
Reference patterns (queue handlers only, no action wrappers)
- Task actor core — initialize, attach, push, sync, merge, archive, kill all use queue messages directly
Migration approach
This is NOT about wrapping queue sends inside actions. The mutation actions must be removed entirely and replaced with queue messages that callers (including packages/client) send directly.
Each actor needs:
- Define queue message types for each mutation
- Move mutation logic from action handlers into workflow/queue handlers
- Delete the mutation actions — do not wrap them
- Update
packages/clientto send queue messages directly to the actor instead of calling the old action - Update any inter-actor callers (e.g.
better-auth.ts,app-shell.ts, other actors) to send queue messages instead of calling actions
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "Actions must be read-only. All database mutations (INSERT, UPDATE, DELETE, UPSERT) must be queue messages processed by workflow handlers. Callers (client, other actors, services) send messages directly to the queue — do not wrap queue sends inside actions. Follow the pattern established in the task workflow actor's queue handlers."
[ ] 5. Migrate task actor raw SQL to Drizzle migrations
Rationale: The task actor uses raw db.execute() with ALTER TABLE ... ADD COLUMN in workbench.ts and workflow/init.ts instead of proper Drizzle migrations. All actor DBs should use the standard Drizzle migration pattern.
Files to change
foundry/packages/backend/src/actors/task/workbench.ts(lines 24-56) — removeALTER TABLEraw SQL, add columns todb/schema.tsand generate a proper migrationfoundry/packages/backend/src/actors/task/workflow/init.ts(lines 12-15) — same treatmentfoundry/packages/backend/src/actors/task/db/schema.ts— add the missing columns that are currently added viaALTER TABLEfoundry/packages/backend/src/actors/task/db/migrations.ts— regenerate with new migration
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "All actor databases must use Drizzle ORM with proper schema definitions and generated migrations. No raw SQL (db.execute(),ALTER TABLE, etc.). Schema changes must go throughschema.ts+ migration generation."
[ ] 6. Rename History actor → Audit Log actor
Rationale: The actor functions as a comprehensive audit log tracking task lifecycle events. "Audit Log" better describes its purpose.
Files to change
foundry/packages/backend/src/actors/history/→ rename directory toaudit-log/index.ts— rename exporthistory→auditLog, display name"History"→"Audit Log", queuehistory.command.append→auditLog.command.append- Internal types:
HistoryInput→AuditLogInput,AppendHistoryCommand→AppendAuditLogCommand,ListHistoryParams→ListAuditLogParams
foundry/packages/backend/src/actors/keys.ts—historyKey()→auditLogKey()foundry/packages/backend/src/actors/handles.ts—getOrCreateHistory→getOrCreateAuditLog,selfHistory→selfAuditLogfoundry/packages/backend/src/actors/index.ts— update import path and registrationfoundry/packages/shared/src/contracts.ts—HistoryEvent→AuditLogEventfoundry/packages/backend/src/actors/organization/actions.ts—history()action →auditLog(), update importsfoundry/packages/backend/src/actors/repository/actions.ts— updategetOrCreateHistorycallsfoundry/packages/backend/src/actors/task/workflow/common.ts—appendHistory()→appendAuditLog()foundry/packages/backend/src/actors/task/workflow/init.ts— update imports and callsfoundry/packages/backend/src/actors/task/workflow/commands.ts— update imports and callsfoundry/packages/backend/src/actors/task/workflow/push.ts— update imports and calls
Coverage gaps to fix
The audit log only covers 9 of ~24 significant events (37.5%). The entire task/workbench.ts file has zero logging. Add audit log calls for:
High priority (missing lifecycle events):
task.switch— intask/workflow/index.tshandleSwitchActivitytask.session.created— intask/workbench.tscreateWorkbenchSessiontask.session.closed— intask/workbench.tscloseWorkbenchSessiontask.session.stopped— intask/workbench.tsstopWorkbenchSession
Medium priority (missing user actions):
task.session.renamed— renameWorkbenchSessiontask.message.sent— sendWorkbenchMessagetask.model.changed— changeWorkbenchModeltask.title.changed— renameWorkbenchTasktask.branch.renamed— renameWorkbenchBranchtask.pr.published— publishWorkbenchPrtask.file.reverted— revertWorkbenchFile
Low priority / debatable:
task.draft.updated,task.session.unread,task.derived.refreshed,task.transcript.refreshed
CLAUDE.md updates needed
foundry/packages/backend/CLAUDE.md— renameHistoryActor→AuditLogActorin actor hierarchy, add maintenance rule: "Every new action or command handler that represents a user-visible or workflow-significant event must append to the audit log actor. The audit log must remain a comprehensive record of all significant operations."foundry/CLAUDE.md— rename "History Events" section → "Audit Log Events", update the list to include all events above, add note: "When adding new task/workbench commands, always add a corresponding audit log event."
[ ] 7. Move starred/default model to user actor settings
Dependencies: item 1
Rationale: The starred/default model preference is currently broken — the frontend stores it in local React state that resets on reload. The org actor's organizationProfile table has a defaultModel column but there's no action to update it and it's the wrong scope anyway. This is a per-user preference, not an org setting.
Current state (broken)
- Frontend (
mock-layout.tsxline 313) —useState<ModelId>("claude-sonnet-4")— local state, lost on reload - Model picker UI (
model-picker.tsx) — has star icons +onSetDefaultcallback, but it only updates local state - Org actor (
organization/db/schema.tsline 43) —defaultModelcolumn exists but nothing writes to it - No backend persistence — starred model is not saved anywhere
Changes needed
-
Add
user_settingstable to user actor (or adddefaultModelcolumn touser_profiles):defaultModel(text) — the user's starred/preferred model- File:
foundry/packages/backend/src/actors/auth-user/db/schema.ts
-
Add queue message to user actor to update the default model:
- File:
foundry/packages/backend/src/actors/auth-user/index.ts
- File:
-
Remove
defaultModelfrom org actororganizationProfiletable (wrong scope):- File:
foundry/packages/backend/src/actors/organization/db/schema.ts
- File:
-
Update frontend to read starred model from user settings (via
appsubscription) and send queue message on star click:- File:
foundry/packages/frontend/src/components/mock-layout/model-picker.tsx - File:
foundry/packages/frontend/src/components/mock-layout.tsx
- File:
-
Update shared types — move
defaultModelfromFoundryOrganizationSettingsto user settings type:- File:
foundry/packages/shared/src/app-shell.ts
- File:
-
Update client to send the queue message to user actor:
- File:
foundry/packages/client/
- File:
[ ] 8. Replace hardcoded model/agent lists with sandbox-agent API data
Dependencies: items 7, 25
Rationale: The frontend hardcodes 8 models in a static list and ignores the sandbox-agent API's GET /v1/agents endpoint which already exposes the full agent config — models, modes, and reasoning/thought levels per agent. The frontend should consume this API 1:1 instead of maintaining its own stale copy.
Current state (hardcoded)
foundry/packages/frontend/src/components/mock-layout/view-model.ts(lines 20-39) — hardcodedMODEL_GROUPSwith 8 modelsfoundry/packages/client/src/workbench-model.ts(lines 18-37) — identical hardcodedMODEL_GROUPScopyfoundry/packages/shared/src/workbench.ts(lines 5-13) —WorkbenchModelIdhardcoded union type- No modes or thought/reasoning levels exposed in UI at all
- No API calls to discover available models
What the sandbox-agent API already provides (GET /v1/agents)
Per agent, the API returns:
- models — full list with display names (Claude: 4, Codex: 6, Cursor: 35+, OpenCode: 239)
- modes — execution modes (Claude: 5, Codex: 3, OpenCode: 2)
- thought_level — reasoning levels (Codex: low/medium/high/xhigh, Mock: low/medium/high)
- capabilities — plan_mode, reasoning, status support
- credentialsAvailable / installed — agent availability
Changes needed
-
Remove hardcoded model lists from:
foundry/packages/frontend/src/components/mock-layout/view-model.ts— deleteMODEL_GROUPSfoundry/packages/client/src/workbench-model.ts— deleteMODEL_GROUPSfoundry/packages/shared/src/workbench.ts— replaceWorkbenchModelIdunion type withstring(dynamic from API)
-
Backend: fetch and cache agent config from sandbox-agent API
- Add an action or startup flow that calls
GET /v1/agents?config=trueon the sandbox-agent API - Cache the result (agent list + models + modes + thought levels) in the appropriate actor
- Expose it to the frontend via the existing subscription/event system
- Add an action or startup flow that calls
-
Frontend: consume API-driven config
- Model picker reads available models from backend-provided agent config, not hardcoded list
- Expose modes selector per agent
- Expose thought/reasoning level selector for agents that support it (Codex, Mock)
- Group models by agent as the API does (not by arbitrary provider grouping)
-
Update shared types — make model/mode/thought_level types dynamic strings rather than hardcoded unions:
foundry/packages/shared/src/workbench.ts
-
No backwards compatibility needed — we're cleaning up, not preserving old behavior
[ ] 9. Flatten taskLookup + taskSummaries into single tasks table on org actor
Dependencies: item 13
Rationale: taskLookup (taskId → repoId) is a strict subset of taskSummaries (which also has repoId + title, status, branch, PR, sessions). There's no reason for two tables with the same primary key. Flatten into one tasks table.
Current state
taskLookup—taskId(PK),repoId— used only for taskId → repoId resolutiontaskSummaries—taskId(PK),repoId,title,status,repoName,updatedAtMs,branch,pullRequestJson,sessionsSummaryJson— materialized sidebar data
Changes needed
-
Merge into single
taskstable infoundry/packages/backend/src/actors/organization/db/schema.ts:- Drop
taskLookuptable - Rename
taskSummaries→tasks - Keep all columns from
taskSummaries(already includesrepoId)
- Drop
-
Update all references:
foundry/packages/backend/src/actors/organization/actions.ts— replacetaskLookupqueries withtaskstable lookupsfoundry/packages/backend/src/actors/organization/app-shell.ts— if it references either table- Any imports of the old table names from schema
-
Regenerate migrations —
foundry/packages/backend/src/actors/organization/db/migrations.ts
[ ] 10. Reorganize user and organization actor actions into actions/ folders
Dependencies: items 1, 6
Rationale: Both actors cram too many concerns into single files. The organization actor has app-shell.ts (1,947 lines) + actions.ts mixing Better Auth, Stripe, GitHub, onboarding, workbench proxying, and org state. The user actor mixes Better Auth adapter CRUD with custom Foundry actions. Split into actions/ folders grouped by domain, with betterAuth prefix on all Better Auth actions.
User actor → user/actions/
| File | Actions | Source |
|---|---|---|
actions/better-auth.ts |
betterAuthCreateRecord, betterAuthFindOneRecord, betterAuthFindManyRecords, betterAuthUpdateRecord, betterAuthUpdateManyRecords, betterAuthDeleteRecord, betterAuthDeleteManyRecords, betterAuthCountRecords + all helper functions (tableFor, columnFor, normalizeValue, clauseToExpr, buildWhere, applyJoinToRow, applyJoinToRows) |
Currently in index.ts |
actions/user.ts |
getAppAuthState, upsertUserProfile, upsertSessionState |
Currently in index.ts |
Organization actor → organization/actions/
Delete app-shell.ts — split its ~50 actions + helpers across these files:
| File | Actions | Source |
|---|---|---|
actions/better-auth.ts |
betterAuthFindSessionIndex, betterAuthUpsertSessionIndex, betterAuthDeleteSessionIndex, betterAuthFindEmailIndex, betterAuthUpsertEmailIndex, betterAuthDeleteEmailIndex, betterAuthFindAccountIndex, betterAuthUpsertAccountIndex, betterAuthDeleteAccountIndex, betterAuthCreateVerification, betterAuthFindOneVerification, betterAuthFindManyVerification, betterAuthUpdateVerification, betterAuthUpdateManyVerification, betterAuthDeleteVerification, betterAuthDeleteManyVerification, betterAuthCountVerification + auth clause builder helpers |
Currently in app-shell.ts |
actions/stripe.ts |
createAppCheckoutSession, finalizeAppCheckoutSession, createAppBillingPortalSession, cancelAppScheduledRenewal, resumeAppSubscription, recordAppSeatUsage, handleAppStripeWebhook, applyOrganizationStripeCustomer, applyOrganizationStripeSubscription, applyOrganizationFreePlan, setOrganizationBillingPaymentMethod, setOrganizationBillingStatus, upsertOrganizationInvoice, recordOrganizationSeatUsage |
Currently in app-shell.ts |
actions/github.ts |
resolveAppGithubToken, beginAppGithubInstall, triggerAppRepoImport, handleAppGithubWebhook, syncOrganizationShellFromGithub, syncGithubOrganizations, applyGithubInstallationCreated, applyGithubInstallationRemoved, applyGithubRepositoryChanges, reloadGithubOrganization, reloadGithubPullRequests, reloadGithubRepository, reloadGithubPullRequest, applyGithubRepositoryProjection, applyGithubDataProjection, recordGithubWebhookReceipt, refreshTaskSummaryForGithubBranch |
Currently split across app-shell.ts and actions.ts |
actions/onboarding.ts |
skipAppStarterRepo, starAppStarterRepo, starSandboxAgentRepo, selectAppOrganization |
Currently in app-shell.ts |
actions/organization.ts |
getAppSnapshot, getOrganizationShellState, getOrganizationShellStateIfInitialized, updateOrganizationShellProfile, updateAppOrganizationProfile, markOrganizationSyncStarted, applyOrganizationSyncCompleted, markOrganizationSyncFailed, useOrganization, getOrganizationSummary, reconcileWorkbenchState |
Currently split across app-shell.ts and actions.ts |
actions/tasks.ts |
createTask, createWorkbenchTask, listTasks, getTask, switchTask, applyTaskSummaryUpdate, removeTaskSummary, findTaskForGithubBranch, applyOpenPullRequestUpdate, removeOpenPullRequest, attachTask, pushTask, syncTask, mergeTask, archiveTask, killTask |
Currently in actions.ts |
actions/workbench.ts |
markWorkbenchUnread, renameWorkbenchTask, renameWorkbenchBranch, createWorkbenchSession, renameWorkbenchSession, setWorkbenchSessionUnread, updateWorkbenchDraft, changeWorkbenchModel, sendWorkbenchMessage, stopWorkbenchSession, closeWorkbenchSession, publishWorkbenchPr, revertWorkbenchFile |
Currently in actions.ts (proxy calls to task actor) |
actions/repos.ts |
listRepos, getRepoOverview |
Currently in actions.ts |
actions/history.ts |
history (→ auditLog after rename) |
Currently in actions.ts |
Also move:
APP_SHELL_ORGANIZATION_IDconstant →organization/constants.tsrunOrganizationWorkflow→organization/workflow.ts- Private helpers (
buildAppSnapshot,assertAppOrganization,collectAllTaskSummaries, etc.) → colocate with the action file that uses them
Files to update
foundry/packages/backend/src/services/better-auth.ts— update all action name references to usebetterAuthprefixfoundry/packages/backend/src/actors/organization/index.ts— import and spread action objects fromactions/files instead ofapp-shell.ts+actions.tsfoundry/packages/backend/src/actors/auth-user/index.ts(oruser/index.ts) — import actions fromactions/files
[ ] 11. Standardize workflow file structure across all actors
Dependencies: item 4
Rationale: Workflow logic is inconsistently placed — inline in index.ts, in actions.ts, or in a workflow/ directory. Standardize: every actor with a workflow gets a workflow.ts file. If the workflow is large, use workflow/{index,...}.ts.
Changes per actor
| Actor | Current location | New location | Notes |
|---|---|---|---|
| user (auth-user) | None | workflow.ts (new) |
Needs a workflow for mutations (item 4) |
| github-data | Inline in index.ts (~57 lines) |
workflow.ts |
Extract runGithubDataWorkflow + handler |
| history (→ audit-log) | Inline in index.ts (~18 lines) |
workflow.ts |
Extract runHistoryWorkflow + appendHistoryRow |
| organization | In actions.ts (~51 lines) |
workflow.ts |
Extract runOrganizationWorkflow + queue handlers |
| repository | In actions.ts (~42 lines) |
workflow.ts |
Extract runRepositoryWorkflow + queue handlers |
| task | workflow/ directory (926 lines) |
workflow/ directory — already correct |
Keep as-is: workflow/index.ts, workflow/queue.ts, workflow/common.ts, workflow/init.ts, workflow/commands.ts, workflow/push.ts |
| sandbox | None (wrapper) | N/A | No custom workflow needed |
Pattern
- Small workflows (< ~200 lines): single
workflow.tsfile - Large workflows (> ~200 lines):
workflow/index.tsholds the main loop, other files hold step groups:workflow/index.ts— main loop + handler dispatchworkflow/queue.ts— queue name definitions (if many)workflow/{group}.ts— step/activity functions grouped by domain
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "Every actor with a message queue must have its workflow logic in a dedicatedworkflow.tsfile (orworkflow/index.tsfor complex actors). Do not inline workflow logic inindex.tsoractions.ts. Actions are read-only handlers; workflow handlers process queue messages and perform mutations."
[ ] 12. Audit and remove dead code in organization actor
Dependencies: item 10
Rationale: The organization actor has ~50+ actions across app-shell.ts and actions.ts. Likely some are unused or vestigial. Audit all actions and queues for dead code and remove anything that has no callers.
Scope
- All actions in
organization/actions.tsandorganization/app-shell.ts - All queue message types and their handlers
- Helper functions that may no longer be called
- Shared types in
packages/sharedthat only served removed actions
Approach
- Trace each action/queue from caller → handler to confirm it's live
- Remove any action with no callers (client, other actors, services, HTTP endpoints)
- Remove any queue handler with no senders
- Remove associated types and helpers
[ ] 13. Enforce coordinator pattern and fix ownership violations
Rationale: The actor hierarchy follows a coordinator pattern: org → repo → task → session. The coordinator owns the index/summary of its children, handles create/destroy, and children push updates up to their coordinator. Several violations exist where levels are skipped.
Coordinator hierarchy (add to CLAUDE.md)
Organization (coordinator for repos)
├── Repository (coordinator for tasks)
│ └── Task (coordinator for sessions)
│ └── Session
Rules:
- The coordinator owns the index/summary table for its direct children
- The coordinator handles create/destroy of its direct children
- Children push summary updates UP to their direct coordinator (not skipping levels)
- Read paths go through the coordinator, not direct cross-level access
- No backwards compatibility needed — we're cleaning up
Violations to fix
V1: Task index tables on wrong actor (HIGH)
taskLookup and taskSummaries (item 9 merges these into tasks) are on the organization actor but should be on the repository actor, since repo is the coordinator for tasks.
Fix:
- Move the merged
taskstable (from item 9) torepository/db/schema.ts - Repository owns task summaries, not organization
- Organization gets a
repoSummariestable instead (repo count, latest activity, etc.) — the repo pushes its summary up to org
V2: Tasks push summaries directly to org, skipping repo (HIGH)
Task actors call organization.applyTaskSummaryUpdate() directly (line 464 in actions.ts), bypassing the repository coordinator.
Fix:
- Task pushes summary to
repository.applyTaskSummaryUpdate()instead - Repository updates its
taskstable, then pushes a repo summary up to organization - Organization never receives task-level updates directly
V3: Org resolves taskId → repoId from its own table (MEDIUM)
resolveRepoId(c, taskId) in organization/actions.ts queries taskLookup directly. Used by switchTask, attachTask, pushTask, syncTask, mergeTask, archiveTask, killTask (7 actions).
Fix:
- Remove
resolveRepoId()from org actor - Org must know the
repoIdfrom the caller (frontend already knows which repo a task belongs to) or query the repo actor - Update all 7 proxy actions to require
repoIdin their input instead of looking it up
V4: Duplicate task creation bookkeeping at org level (MEDIUM)
createTaskMutation in org actor calls repository.createTask(), then independently inserts taskLookup and seeds taskSummaries. Repository already inserts its own taskIndex row.
Fix:
- Org calls
repository.createTask()— that's it - Repository handles all task index bookkeeping internally
- Repository pushes the new task summary back up to org as part of its repo summary update
Files to change
foundry/packages/backend/src/actors/organization/db/schema.ts— removetaskLookupandtaskSummaries, addrepoSummariesif neededfoundry/packages/backend/src/actors/repository/db/schema.ts— add mergedtaskstable (task summaries)foundry/packages/backend/src/actors/organization/actions.ts— removeresolveRepoId(),applyTaskSummaryUpdate,removeTaskSummary,findTaskForGithubBranch,refreshTaskSummaryForGithubBranch; update proxy actions to requirerepoIdin inputfoundry/packages/backend/src/actors/repository/actions.ts— addapplyTaskSummaryUpdateaction (receives from task), push repo summary to orgfoundry/packages/backend/src/actors/task/workflow/common.ts— change summary push target from org → repofoundry/packages/shared/src/contracts.ts— update input types to includerepoIdwhere neededfoundry/packages/client/— update calls to passrepoId
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add coordinator pattern rules:## Coordinator Pattern The actor hierarchy follows a strict coordinator pattern: - Organization = coordinator for repositories - Repository = coordinator for tasks - Task = coordinator for sessions Rules: - Each coordinator owns the index/summary table for its direct children. - Only the coordinator handles create/destroy of its direct children. - Children push summary updates to their direct coordinator only (never skip levels). - Cross-level access (e.g. org directly querying task state) is not allowed — go through the coordinator. - Proxy actions at higher levels (e.g. org.pushTask) must delegate to the correct coordinator, not bypass it.
[ ] 14. Standardize one event per subscription topic across all actors
Dependencies: item 15
Rationale: Each subscription topic should have exactly one event type carrying the full replacement snapshot. The organization topic currently violates this with 7 subtypes. Additionally, event naming is inconsistent across actors. Standardize all of them.
Current state
| Topic | Wire event name | Event type field | Subtypes | Issue |
|---|---|---|---|---|
app |
appUpdated |
type: "appUpdated" |
1 | Name is fine |
organization |
organizationUpdated |
7 variants | 7 | Needs consolidation |
task |
taskUpdated |
type: "taskDetailUpdated" |
1 | Wire name ≠ type name |
session |
sessionUpdated |
type: "sessionUpdated" |
1 | Fine |
sandboxProcesses |
processesUpdated |
type: "processesUpdated" |
1 | Fine |
Target state
Every topic gets exactly one event. Wire event name = type field = {topic}Updated. Each carries the full snapshot for that topic.
| Topic | Event name | Payload |
|---|---|---|
app |
appUpdated |
FoundryAppSnapshot |
organization |
organizationUpdated |
OrganizationSummarySnapshot |
task |
taskUpdated |
WorkbenchTaskDetail |
session |
sessionUpdated |
WorkbenchSessionDetail |
sandboxProcesses |
processesUpdated |
SandboxProcessSnapshot[] |
Organization — consolidate 7 subtypes into 1
Remove the discriminated union. Replace all 7 subtypes:
taskSummaryUpdated,taskRemoved,repoAdded,repoUpdated,repoRemoved,pullRequestUpdated,pullRequestRemoved
With a single organizationUpdated event carrying the full OrganizationSummarySnapshot. The client replaces its cached state — same pattern as every other topic.
Task — fix event type name mismatch
Wire event is taskUpdated but the type field says taskDetailUpdated. Rename to taskUpdated everywhere for consistency.
Files to change
foundry/packages/shared/src/realtime-events.ts— replaceOrganizationEventunion with single event type; renameTaskEvent.typefromtaskDetailUpdated→taskUpdatedfoundry/packages/backend/src/actors/organization/actions.ts— update all 7c.broadcast("organizationUpdated", { type: "taskSummaryUpdated", ... })calls to emit single event with full snapshotfoundry/packages/backend/src/actors/organization/app-shell.ts— same for any broadcasts herefoundry/packages/backend/src/actors/task/workbench.ts— renametaskDetailUpdated→taskUpdatedin broadcast callsfoundry/packages/client/src/subscription/topics.ts— simplifyapplyEventfor organization topic (no more discriminated union handling); update task event type namefoundry/packages/client/src/subscription/mock-manager.ts— update mock event handlingfoundry/packages/frontend/— update any direct references to event type names
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "Each subscription topic must have exactly one event type. The event carries the full replacement snapshot for that topic — no discriminated unions, no partial patches, no subtypes. Event name must match the pattern{topic}Updated(e.g.organizationUpdated,taskUpdated). When state changes, broadcast the full snapshot; the client replaces its cached state."
[ ] 15. Unify tasks and pull requests — PRs are just task data
Dependencies: items 9, 13
Rationale: From the client's perspective, tasks and PRs are the same thing — a branch with work on it. The frontend already merges them into one sorted list, converting PRs to synthetic task objects with pr:{prId} IDs. The distinction is artificial. A "task" should represent any branch, and the task actor lazily wraps it. PR metadata is just data the task holds.
Current state (separate entities)
- Tasks: stored in task actor SQLite, surfaced via
WorkbenchTaskSummary, events viataskSummaryUpdated - PRs: stored in GitHub data actor (
githubPullRequeststable), surfaced viaWorkbenchOpenPrSummary, events viapullRequestUpdated/pullRequestRemoved - Frontend hack: converts PRs to fake task objects with
pr:{prId}IDs, merges into one list - Filtering logic: org actor silently swallows
pullRequestUpdatedif a task claims the same branch — fragile coupling - Two separate types:
WorkbenchTaskSummaryandWorkbenchOpenPrSummarywith overlapping fields
Target state (unified)
- One entity: a "task" represents a branch. Task actors are lazily created when needed (user creates one, or a PR arrives for an unclaimed branch).
- PR data lives on the task: the task actor stores PR metadata (number, title, state, url, isDraft, authorLogin, etc.) as part of its state, not as a separate entity
- One type:
WorkbenchTaskSummaryincludes full PR fields (nullable). No separateWorkbenchOpenPrSummary. - One event:
organizationUpdatedcarries task summaries that include PR data. No separate PR events. - No synthetic IDs: every item in the sidebar is a real task with a real taskId
Changes needed
- Remove
WorkbenchOpenPrSummarytype frompackages/shared/src/workbench.ts— merge its fields intoWorkbenchTaskSummary - Expand task's
pullRequestfield from{ number, status }to full PR metadata (number, title, state, url, headRefName, baseRefName, isDraft, authorLogin, updatedAtMs) - Remove
openPullRequestsfromOrganizationSummarySnapshot— all items are tasks now - Remove PR-specific events from
realtime-events.ts:pullRequestUpdated,pullRequestRemoved - Remove PR-specific actions from organization actor:
applyOpenPullRequestUpdate,removeOpenPullRequest - Remove branch-claiming filter logic in org actor (the
if task claims branch, skip PRcheck) - GitHub data actor PR sync: when PRs arrive (webhook or sync), create/update a task for that branch lazily via the repository coordinator
- Task actor: store PR metadata in its DB (new columns or table), update when GitHub data pushes changes
- Frontend: remove
toOpenPrTaskModelconversion, removepr:ID prefix hack, remove separateopenPullRequestsstate — sidebar is just tasks - Repository actor: when a PR arrives for a branch with no task, lazily create a task actor for it (lightweight, no sandbox needed)
Implications for coordinator pattern (item 13)
This reinforces: repo is the coordinator for tasks. When GitHub data detects a new PR for a branch, it tells the repo coordinator, which creates/updates the task. The task holds the PR data and pushes its summary to the repo coordinator.
No backwards compatibility needed
The authSessionIndex, authEmailIndex, authAccountIndex, and authVerification tables stay on the org actor. They're routing indexes needed by the Better Auth adapter to resolve user identity before the user actor can be accessed (e.g. session token → userId lookup). Already covered in item 2 for adding comments explaining this.
[ ] 16. Chunk GitHub data sync and publish progress
Rationale: runFullSync in the github-data actor fetches everything at once (all repos, branches, members, PRs), replaces all tables atomically, and has a 5-minute timeout. For large orgs this will timeout or lose all data mid-sync (replace pattern deletes everything first). Needs to be chunked with incremental progress.
Current state (broken for large orgs)
runFullSync()(github-data/index.tsline 486-538):- Fetches ALL repos, branches, members, PRs in 4 sequential calls
replaceRepositories/Branches/Members/PullRequests— deletes all rows then inserts all new rows- Single 5-minute timeout wraps the entire operation
- No progress reporting to the client — just "Syncing GitHub data..." → "Synced N repositories"
- If it fails mid-sync, data is partially deleted with no recovery
Changes needed
-
Chunk the sync by repository — sync repos first (paginated from GitHub API), then for each repo chunk, sync its branches and PRs. Members can be a separate chunk.
-
Incremental upsert, not replace — don't delete-then-insert. Use upsert per row so partial sync doesn't lose data. Mark rows with a sync generation ID; after full sync completes, delete rows from previous generations.
-
Run in a loop, not a single step — each chunk is a separate workflow step with its own timeout. If one chunk fails, previous chunks are persisted.
-
Publish progress per chunk — after each chunk completes:
- Update
github_metawith progress (e.g.syncedRepos: 15/42) - Push progress to the organization actor
- Organization broadcasts to clients so the UI shows progress (e.g. "Syncing repositories... 15/42")
- Update
-
Initial sync uses the same chunked approach —
github-data-initial-syncstep should kick off the chunked loop, not callrunFullSyncdirectly
Files to change
foundry/packages/backend/src/actors/github-data/index.ts:- Refactor
runFullSyncinto chunked loop - Replace
replaceRepositories/Branches/Members/PullRequestswith upsert + generation sweep - Add progress metadata to
github_metatable - Publish progress to org actor after each chunk
- Refactor
foundry/packages/backend/src/actors/github-data/db/schema.ts— add sync generation column to all tables, add progress fields togithub_metafoundry/packages/backend/src/actors/organization/actions.ts(orapp-shell.ts) — handle sync progress updates and broadcast to clientsfoundry/packages/shared/src/app-shell.ts— add sync progress fields toFoundryGithubState(e.g.syncProgress: { current: number; total: number } | null)foundry/packages/frontend/— show sync progress in UI (e.g. "Syncing repositories... 15/42")
Deferred — tackle later
[ ] 17. Type all actor context parameters — remove c: any (DEFERRED — do last)
Rationale: 272+ instances of c: any, ctx: any, loopCtx: any across all actor code. This eliminates type safety for DB access, state access, broadcasts, and queue operations. All context parameters should use RivetKit's proper context types.
Scope (by file, approximate count)
| File | any contexts |
|---|---|
organization/app-shell.ts |
~108 |
organization/actions.ts |
~56 |
task/workbench.ts |
~53 |
github-data/index.ts |
~23 |
repository/actions.ts |
~22 |
sandbox/index.ts |
~21 |
handles.ts |
~19 |
task/workflow/commands.ts |
~10 |
task/workflow/init.ts |
~4 |
auth-user/index.ts |
~2 |
history/index.ts |
~2 |
task/workflow/index.ts |
~2 |
task/workflow/common.ts |
~2 |
task/workflow/push.ts |
~1 |
polling.ts |
~1 |
Changes needed
-
Determine correct RivetKit context types — check RivetKit exports for
ActionContext,ActorContextOf,WorkflowContext,LoopContext, or equivalent. Referencepolling.tswhich already defines typed contexts (PollingActorContext<TState>,WorkflowPollingActorContext<TState>). -
Define per-actor context types — each actor has its own state shape and DB schema, so the context type should be specific (e.g.
ActionContext<typeof organization>or similar). -
Replace all
c: anywith the proper typed context across every file listed above. -
Type workflow/loop contexts —
ctx: anyin workflow functions andloopCtx: anyin loop callbacks need proper types too.
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "All actor context parameters (c,ctx,loopCtx) must be properly typed using RivetKit's context types. Never useanyfor actor contexts. Each actor should define or derive its context type from the actor definition."
[ ] 18. Final pass: remove all dead code
Dependencies: all other items (do this last, after 17)
Rationale: After completing all changes above, many actions, queues, SQLite tables, workflow steps, shared types, and helper functions will be orphaned. Do a full scan to find and remove everything that's dead.
Scope
Scan the entire foundry codebase for:
- Dead actions — actions with no callers (client, other actors, services, HTTP endpoints)
- Dead queues — queue message types with no senders
- Dead SQLite tables — tables with no reads or writes
- Dead workflow steps — step names that are no longer referenced
- Dead shared types — types in
packages/sharedthat are no longer imported - Dead helper functions — private functions with no callers
- Dead imports — unused imports across all files
When to do this
After all items 1–17 are complete. Not before — removing code while other items are in progress will create conflicts.
[ ] 19. Remove duplicate data between c.state and SQLite
Dependencies: items 21, 24
Rationale: Several actors store the same data in both c.state (RivetKit durable state) and their SQLite tables. Mutable fields that exist in both can silently diverge — c.state becomes stale when the SQLite copy is updated. Per the existing CLAUDE.md rule, c.state should hold only small scalars/identifiers; anything queryable or mutable belongs in SQLite.
Duplicates found
Task actor — c.state (createState in task/index.ts lines 124-139) vs task/taskRuntime tables:
| Field | In SQLite? | Mutable? | Verdict |
|---|---|---|---|
organizationId |
No | No | KEEP — identity field |
repoId |
No | No | KEEP — identity field |
taskId |
No | No | KEEP — identity field |
repoRemote |
No (but org repos table has it) |
No | DELETE — not needed on task, read from repo/org |
branchName |
Yes (task.branch_name) |
Yes | REMOVE from c.state — HIGH risk, goes stale on rename |
title |
Yes (task.title) |
Yes | REMOVE from c.state — HIGH risk, goes stale on rename |
task (description) |
Yes (task.task) |
No | REMOVE from c.state — redundant |
sandboxProviderId |
Yes (task.sandbox_provider_id) |
No | REMOVE from c.state — redundant |
agentType |
Yes (task.agent_type) |
Yes | DELETE entirely — session-specific (item 21) |
explicitTitle |
No | No | MOVE to SQLite — creation metadata |
explicitBranchName |
No | No | MOVE to SQLite — creation metadata |
initialPrompt |
No | No | DELETE entirely — dead code, session-specific (item 21) |
initialized |
No | Yes | DELETE entirely — dead code, status already tracks init progress |
previousStatus |
No | No | DELETE entirely — never set, never read |
Repository actor — c.state (createState in repository/index.ts) vs repoMeta table:
| Field | Mutable? | Risk |
|---|---|---|
remoteUrl |
No | Low — redundant but safe |
Fix
Remove all duplicated fields from c.state. Keep only identity fields needed for actor key resolution (e.g. organizationId, repoId, taskId). Read mutable data from SQLite.
Task actor c.state should become:
createState: (_c, input) => ({
organizationId: input.organizationId,
repoId: input.repoId,
taskId: input.taskId,
})
Fields already in SQLite (branchName, title, task, sandboxProviderId) — remove from c.state, read from SQLite only. Fields not yet in SQLite (explicitTitle, explicitBranchName) — add to task table, remove from c.state. Dead code to delete entirely: agentType, initialPrompt (item 21), initialized, previousStatus, repoRemote.
Repository actor c.state should become:
createState: (_c, input) => ({
organizationId: input.organizationId,
repoId: input.repoId,
})
remoteUrl is removed from repo actor c.state entirely. The repo actor reads remoteUrl from its own repoMeta SQLite table when needed. The org actor already stores remoteUrl in its repos table (source of truth from GitHub data). The getOrCreateRepository() helper in handles.ts currently requires remoteUrl as a parameter and passes it as createWithInput — this parameter must be removed. Every call site in organization/actions.ts and organization/app-shell.ts currently does a DB lookup for remoteUrl just to pass it to getOrCreateRepository() — all of those lookups go away. On actor creation, the repo actor should populate its repoMeta.remoteUrl by querying the org actor or github-data actor, not by receiving it as a create input.
Files to change
foundry/packages/backend/src/actors/task/index.ts— trimcreateState, update allc.state.*reads for removed fields to read from SQLite insteadfoundry/packages/backend/src/actors/task/workbench.ts— updatec.state.*readsfoundry/packages/backend/src/actors/task/workflow/*.ts— updatec.state.*readsfoundry/packages/backend/src/actors/repository/index.ts— trimcreateState, removeremoteUrlfrom input typefoundry/packages/backend/src/actors/repository/actions.ts— update allc.state.remoteUrlreads to queryrepoMetatable; removepersistRemoteUrl()helperfoundry/packages/backend/src/actors/handles.ts— removeremoteUrlparameter fromgetOrCreateRepository()foundry/packages/backend/src/actors/organization/actions.ts— remove allremoteUrllookups done solely to pass togetOrCreateRepository()(~10 call sites)foundry/packages/backend/src/actors/organization/app-shell.ts— same cleanup for app-shell call sites
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "Never duplicate data betweenc.stateand SQLite.c.stateholds only immutable identity fields needed for actor key resolution (e.g.organizationId,repoId,taskId). All mutable data and anything queryable must live exclusively in SQLite. If a field can change after actor creation, it must not be inc.state."
[ ] 20. Prefix all admin/recovery actions with admin
Rationale: Several actions are admin-only recovery/rebuild operations but their names don't distinguish them from normal product flows. Prefix with admin so it's immediately clear these are not part of regular user flows.
Actions to rename
Organization actor:
| Current name | New name | Why it's admin |
|---|---|---|
reconcileWorkbenchState |
adminReconcileWorkbenchState |
Full fan-out rebuild of task summary projection |
reloadGithubOrganization |
adminReloadGithubOrganization |
Manual trigger to refetch all org GitHub data |
reloadGithubPullRequests |
adminReloadGithubPullRequests |
Manual trigger to refetch all PR data |
reloadGithubRepository |
adminReloadGithubRepository |
Manual trigger to refetch single repo |
reloadGithubPullRequest |
adminReloadGithubPullRequest |
Manual trigger to refetch single PR |
GitHub Data actor:
| Current name | New name | Why it's admin |
|---|---|---|
fullSync |
adminFullSync |
Full replace of all GitHub data — recovery operation |
reloadOrganization |
adminReloadOrganization |
Triggers full sync manually |
reloadAllPullRequests |
adminReloadAllPullRequests |
Triggers full sync manually |
clearState |
adminClearState |
Deletes all GitHub data — recovery from lost access |
NOT renamed (these are triggered by webhooks/normal flows, not manual admin actions):
reloadRepository— called by push/create/delete webhooks (incremental, normal flow)reloadPullRequest— called by PR webhooks (incremental, normal flow)handlePullRequestWebhook— webhook handler (normal flow)syncGithubOrganizations— called during OAuth callback (normal flow, though also used for repair)
Files to change
foundry/packages/backend/src/actors/github-data/index.ts— rename actionsfoundry/packages/backend/src/actors/organization/actions.ts— rename actionsfoundry/packages/client/src/backend-client.ts— update method namesfoundry/packages/frontend/— update any references to renamed actions
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "Admin-only actions (recovery, rebuild, manual resync, state reset) must be prefixed withadmin(e.g.adminReconcileState,adminClearState). This makes it clear they are not part of normal product flows and should not be called from regular client code paths."
[ ] 21. Remove legacy/session-scoped fields from task table
Rationale: The task table has fields that either belong on the session, are redundant with data from other actors, or are dead code from the removed local git clone. These should be cleaned up.
Fields to remove from task table and c.state
agentType — Legacy from when task = 1 session. Only used for defaultModelForAgent(c.state.agentType) to pick the default model when creating a new session. Sessions already have their own model column in taskWorkbenchSessions. The default model for new sessions should come from user settings (see item 16 — starred model stored in user actor). Remove agentType from task table, c.state, createState, TaskRecord, and all defaultModelForAgent() call sites. Replace with user settings lookup.
initialPrompt — Stored on c.state at task creation but never read anywhere. Completely dead code. This is also session-specific, not task-specific — the initial prompt belongs on the first session, not the task. Remove from c.state, createState input type, and CreateTaskCommand/CreateTaskInput types. Remove from repository/actions.ts create flow.
prSubmitted — Redundant boolean set when submitPullRequest runs. PR state already flows from GitHub webhooks → github-data actor → branch name lookup. This boolean can go stale (PR closed and reopened, PR deleted, etc.). Remove entirely — PR existence is derivable from github-data by branch name (already how enrichTaskRecord and buildTaskSummary work).
Dead fields on taskRuntime table
provisionStage — Values: "queued", "ready", "error". Redundant with status — init_complete implies ready, error implies error. Never read in business logic. Delete.
provisionStageUpdatedAt — Timestamp for provisionStage changes. Never read anywhere. Delete.
Dead fields on TaskRecord (in workflow/common.ts)
These are always hardcoded to null — remnants of the removed local git clone:
diffStat— was populated frombranchestable (deleted)hasUnpushed— was populated frombranchestable (deleted)conflictsWithMain— was populated frombranchestable (deleted)parentBranch— was populated frombranchestable (deleted)
Remove from TaskRecord type, getCurrentRecord(), and all consumers (contracts, mock client, tests, frontend).
Files to change
foundry/packages/backend/src/actors/task/db/schema.ts— removeagentTypeandprSubmittedcolumns fromtasktable; removeprovisionStageandprovisionStageUpdatedAtfromtaskRuntimetablefoundry/packages/backend/src/actors/task/index.ts— removeagentType,initialPrompt,initialized,previousStatus,repoRemotefromcreateStateand input typefoundry/packages/backend/src/actors/task/workbench.ts— removedefaultModelForAgent(),agentTypeForModel(), update session creation to use user settings for default model; removeprSubmittedset insubmitPullRequestfoundry/packages/backend/src/actors/task/workflow/common.ts— removeagentType,prSubmitted,diffStat,hasUnpushed,conflictsWithMain,parentBranchfromgetCurrentRecord()andTaskRecordconstructionfoundry/packages/backend/src/actors/task/workflow/init.ts— removeagentTypefrom task row insertsfoundry/packages/shared/src/contracts.ts— removeagentType,prSubmitted,diffStat,prUrl,hasUnpushed,conflictsWithMain,parentBranchfromTaskRecordschema (note:prUrlandprAuthorshould stay if still populated byenrichTaskRecord, or move to the unified task/PR model from item 15)foundry/packages/client/src/mock/backend-client.ts— update mock to remove dead fieldsfoundry/packages/client/test/view-model.test.ts— update test fixturesfoundry/packages/frontend/src/features/tasks/model.test.ts— update test fixturesfoundry/packages/backend/src/actors/organization/actions.ts— remove any references toagentTypein task creation inputfoundry/packages/backend/src/actors/repository/actions.ts— updateenrichTaskRecord()to stop setting dead fields
[ ] 22. Move per-user UI state from task actor to user actor
Dependencies: item 1
Rationale: The task actor stores UI-facing state that is user-specific, not task-global. With multiplayer (multiple users viewing the same task), this breaks — each user has their own active session, their own unread state, their own drafts. These must live on the user actor, keyed by (taskId, sessionId), not on the shared task actor.
Per-user state currently on the task actor (wrong)
taskRuntime.activeSessionId — Which session the user is "looking at." Used to:
- Determine which session's status drives the task-level status (running/idle) — this is wrong, the task status should reflect ALL sessions, not one user's active tab
- Return a "current" session in
attachTaskresponses — this is per-user - Migration path for legacy single-session tasks in
ensureWorkbenchSeeded
This should move to the user actor as activeSessionId per (userId, taskId).
taskWorkbenchSessions.unread — Per-user unread state stored globally on the session. If user A reads a session, user B's unread state is also cleared. Move to user actor keyed by (userId, taskId, sessionId).
taskWorkbenchSessions.draftText / draftAttachmentsJson / draftUpdatedAt — Per-user draft state stored globally. If user A starts typing a draft, it overwrites user B's draft. Move to user actor keyed by (userId, taskId, sessionId).
What stays on the task actor (correct — task-global state)
taskRuntime.activeSandboxId— which sandbox is running (global to the task)taskRuntime.activeSwitchTarget/activeCwd— sandbox connection state (global)taskRuntime.statusMessage— provisioning/runtime status (global)taskWorkbenchSessions.model— which model the session uses (global)taskWorkbenchSessions.status— session runtime status (global)taskWorkbenchSessions.transcriptJson— session transcript (global)
Fix
Add a userTaskState table to the user actor:
export const userTaskState = sqliteTable("user_task_state", {
taskId: text("task_id").notNull(),
sessionId: text("session_id").notNull(),
activeSessionId: text("active_session_id"), // per-user active tab
unread: integer("unread").notNull().default(0),
draftText: text("draft_text").notNull().default(""),
draftAttachmentsJson: text("draft_attachments_json").notNull().default("[]"),
draftUpdatedAt: integer("draft_updated_at"),
updatedAt: integer("updated_at").notNull(),
}, (table) => ({
pk: primaryKey(table.taskId, table.sessionId),
}));
Remove activeSessionId from taskRuntime. Remove unread, draftText, draftAttachmentsJson, draftUpdatedAt from taskWorkbenchSessions.
The task-level status should be derived from ALL sessions (e.g., task is "running" if ANY session is running), not from one user's activeSessionId.
Files to change
foundry/packages/backend/src/actors/auth-user/db/schema.ts— adduserTaskStatetablefoundry/packages/backend/src/actors/task/db/schema.ts— removeactiveSessionIdfromtaskRuntime; removeunread,draftText,draftAttachmentsJson,draftUpdatedAtfromtaskWorkbenchSessionsfoundry/packages/backend/src/actors/task/workbench.ts— remove allactiveSessionIdreads/writes; remove draft/unread mutation functions; task status derivation should check all sessionsfoundry/packages/backend/src/actors/task/workflow/common.ts— removeactiveSessionIdfromgetCurrentRecord()foundry/packages/backend/src/actors/task/workflow/commands.ts— removeactiveSessionIdreferences inattachTaskfoundry/packages/backend/src/actors/task/workflow/init.ts— removeactiveSessionIdinitializationfoundry/packages/client/— draft/unread/activeSession operations route to user actor instead of task actorfoundry/packages/frontend/— update subscription to fetch per-user state from user actor
CLAUDE.md update
foundry/packages/backend/CLAUDE.md— add constraint: "Per-user UI state (active session tab, unread counts, draft text, draft attachments) must live on the user actor, not on shared task/session actors. Task actors hold only task-global state visible to all users. This is critical for multiplayer correctness — multiple users may view the same task simultaneously with different active sessions, unread states, and in-progress drafts."
[ ] 23. Delete getTaskEnriched and enrichTaskRecord (dead code)
Rationale: getTaskEnriched is dead code with zero callers from the client. It's also the worst fan-out pattern in the codebase: org → repo actor → task actor (.get()) → github-data actor (listPullRequestsForRepository fetches ALL PRs, then .find()s by branch name). This is exactly the pattern the coordinator model eliminates — task detail comes from getTaskDetail on the task actor, sidebar data comes from materialized taskSummaries on the org actor.
What to delete
enrichTaskRecord()—repository/actions.ts:117-143. Fetches all PRs for a repo to find one by branch name. Dead code.getTaskEnrichedaction —repository/actions.ts:432-450. Only caller ofenrichTaskRecord. Dead code.getTaskEnrichedorg proxy —organization/actions.ts:838-849. Only caller of the repo action. Dead code.GetTaskEnrichedCommandtype — wherever defined.
Files to change
foundry/packages/backend/src/actors/repository/actions.ts— deleteenrichTaskRecord()andgetTaskEnrichedactionfoundry/packages/backend/src/actors/organization/actions.ts— deletegetTaskEnrichedproxy action
[ ] 24. Clean up task status tracking
Dependencies: item 21
Rationale: Task status tracking is spread across c.state, the task SQLite table, and the taskRuntime table with redundant and dead fields. Consolidate to a single status enum on the task table. Remove statusMessage — human-readable status text should be derived on the client from the status enum, not stored on the backend.
Fields to delete
| Field | Location | Why |
|---|---|---|
initialized |
c.state |
Dead code — never read. status already tracks init progress. |
previousStatus |
c.state |
Dead code — never set, never read. |
statusMessage |
taskRuntime table |
Client concern — the client should derive display text from the status enum. The backend should not store UI copy. |
provisionStage |
taskRuntime table |
Redundant — status already encodes provision progress (init_bootstrap_db → init_enqueue_provision → init_complete). |
provisionStageUpdatedAt |
taskRuntime table |
Dead — never read. |
What remains
statuson thetasktable — the single canonical state machine enum. Values:init_bootstrap_db,init_enqueue_provision,init_complete,running,idle,error,archive_*,kill_*,archived,killed.
Files to change
foundry/packages/backend/src/actors/task/db/schema.ts— removestatusMessage,provisionStage,provisionStageUpdatedAtfromtaskRuntimetablefoundry/packages/backend/src/actors/task/index.ts— removeinitialized,previousStatusfromcreateStatefoundry/packages/backend/src/actors/task/workflow/common.ts— removestatusMessageparameter fromsetTaskState(), remove it fromgetCurrentRecord()queryfoundry/packages/backend/src/actors/task/workflow/init.ts— removestatusMessage,provisionStage,provisionStageUpdatedAtfrom taskRuntime inserts/updates; removeensureTaskRuntimeCacheColumns()raw ALTER TABLE for these columnsfoundry/packages/backend/src/actors/task/workflow/commands.ts— removestatusMessagefrom handler updatesfoundry/packages/backend/src/actors/task/workflow/push.ts— removestatusMessageupdatesfoundry/packages/backend/src/actors/task/workbench.ts— removestatusMessagefrombuildTaskDetail(), removeensureTaskRuntimeCacheColumns()for these columnsfoundry/packages/shared/src/workbench.ts— removestatusMessagefromWorkbenchTaskDetailfoundry/packages/frontend/— derive display text fromstatusenum instead of readingstatusMessage
[ ] 25. Remove "Workbench" prefix from all types, functions, files, and tables
Rationale: "Workbench" is not a real concept in the system. It's a namespace prefix applied to every type, function, file, and table name. The actual entities are Task, Session, Repository, Sandbox, Transcript, Draft, etc. — "Workbench" adds zero information and obscures what things actually are.
Rename strategy
Drop "Workbench" everywhere. If the result collides with an existing name (e.g., auth Session), use the domain prefix (e.g., TaskSession vs auth Session).
Type renames (shared/src/workbench.ts)
| Before | After |
|---|---|
WorkbenchTaskStatus |
TaskStatus (already exists as base, merge) |
WorkbenchAgentKind |
AgentKind |
WorkbenchModelId |
ModelId |
WorkbenchSessionStatus |
SessionStatus |
WorkbenchTranscriptEvent |
TranscriptEvent |
WorkbenchComposerDraft |
ComposerDraft |
WorkbenchSessionSummary |
SessionSummary |
WorkbenchSessionDetail |
SessionDetail |
WorkbenchFileChange |
FileChange |
WorkbenchFileTreeNode |
FileTreeNode |
WorkbenchLineAttachment |
LineAttachment |
WorkbenchHistoryEvent |
HistoryEvent |
WorkbenchDiffLineKind |
DiffLineKind |
WorkbenchParsedDiffLine |
ParsedDiffLine |
WorkbenchPullRequestSummary |
PullRequestSummary |
WorkbenchOpenPrSummary |
OpenPrSummary |
WorkbenchSandboxSummary |
SandboxSummary |
WorkbenchTaskSummary |
TaskSummary |
WorkbenchTaskDetail |
TaskDetail |
WorkbenchRepositorySummary |
RepositorySummary |
WorkbenchSession |
TaskSession (avoids auth Session collision) |
WorkbenchTask |
TaskSnapshot (avoids task table collision) |
WorkbenchRepo |
RepoSnapshot |
WorkbenchRepositorySection |
RepositorySection |
TaskWorkbenchSnapshot |
DashboardSnapshot |
WorkbenchModelOption |
ModelOption |
WorkbenchModelGroup |
ModelGroup |
TaskWorkbenchSelectInput |
SelectTaskInput |
TaskWorkbenchCreateTaskInput |
CreateTaskInput |
TaskWorkbenchRenameInput |
RenameTaskInput |
TaskWorkbenchSendMessageInput |
SendMessageInput |
TaskWorkbenchSessionInput |
SessionInput |
TaskWorkbenchRenameSessionInput |
RenameSessionInput |
TaskWorkbenchChangeModelInput |
ChangeModelInput |
TaskWorkbenchUpdateDraftInput |
UpdateDraftInput |
TaskWorkbenchSetSessionUnreadInput |
SetSessionUnreadInput |
TaskWorkbenchDiffInput |
DiffInput |
TaskWorkbenchCreateTaskResponse |
CreateTaskResponse |
TaskWorkbenchAddSessionResponse |
AddSessionResponse |
File renames
| Before | After |
|---|---|
shared/src/workbench.ts |
shared/src/types.ts (or split into task.ts, session.ts, etc.) |
backend/src/actors/task/workbench.ts |
backend/src/actors/task/sessions.ts (already planned in item 7) |
client/src/workbench-client.ts |
client/src/task-client.ts |
client/src/workbench-model.ts |
client/src/model.ts |
client/src/remote/workbench-client.ts |
client/src/remote/task-client.ts |
client/src/mock/workbench-client.ts |
client/src/mock/task-client.ts |
Table rename
| Before | After |
|---|---|
task_workbench_sessions |
task_sessions |
Function renames (backend — drop "Workbench" infix)
All functions in backend/src/actors/task/workbench.ts:
createWorkbenchSession→createSessioncloseWorkbenchSession→closeSessionchangeWorkbenchModel→changeModelsendWorkbenchMessage→sendMessagestopWorkbenchSession→stopSessionrenameWorkbenchBranch→ deleted (see item 26)renameWorkbenchTask→renameTaskrenameWorkbenchSession→renameSessionrevertWorkbenchFile→revertFilepublishWorkbenchPr→publishPrupdateWorkbenchDraft→updateDraftsetWorkbenchSessionUnread→setSessionUnreadmarkWorkbenchUnread→markUnreadsyncWorkbenchSessionStatus→syncSessionStatusensureWorkbenchSeeded→ensureSessionSeeded
Queue/command type renames (backend)
TaskWorkbenchValueCommand→TaskValueCommandTaskWorkbenchSessionTitleCommand→SessionTitleCommandTaskWorkbenchSessionUnreadCommand→SessionUnreadCommand
Scope
~420 occurrences across shared (35+ types), backend (200+ refs), client (324 refs), frontend (96 refs). Mechanical find-and-replace once the rename map is settled.
Files to change
foundry/packages/shared/src/workbench.ts— rename file, rename all exported typesfoundry/packages/shared/src/index.ts— update re-export pathfoundry/packages/shared/src/app-shell.ts— updateWorkbenchModelId→ModelIdimportfoundry/packages/shared/src/realtime-events.ts— update allWorkbench*type importsfoundry/packages/backend/src/actors/task/workbench.ts— rename file + all functionsfoundry/packages/backend/src/actors/task/index.ts— update imports and action registrationsfoundry/packages/backend/src/actors/task/db/schema.ts— renametaskWorkbenchSessions→taskSessionsfoundry/packages/backend/src/actors/task/workflow/— update all workbench referencesfoundry/packages/backend/src/actors/organization/— update type imports and action namesfoundry/packages/backend/src/actors/repository/— update type importsfoundry/packages/client/src/— rename files + update all type/function referencesfoundry/packages/frontend/src/— update all type imports
CLAUDE.md update
Update foundry/packages/backend/CLAUDE.md coordinator hierarchy diagram: taskWorkbenchSessions → taskSessions.
[ ] 26. Delete branch rename (branches immutable after creation)
Dependencies: item 25
Rationale: Branch name is assigned once at task creation and never changes. Branch rename is unused in the frontend UI and SDK, adds ~80 lines of code, and creates a transactional consistency risk (git rename succeeds but index update fails).
Delete
task/workbench.ts— deleterenameWorkbenchBranch()(~50 lines)task/index.ts— deleterenameWorkbenchBranchactiontask/workflow/queue.ts— remove"task.command.workbench.rename_branch"queue typetask/workflow/index.ts— remove"task.command.workbench.rename_branch"handlerorganization/actions.ts— deleterenameWorkbenchBranchproxy actionrepository/actions.ts— deleteregisterTaskBranchaction (only caller was rename flow)client/src/workbench-client.ts— removerenameBranchfrom interfaceclient/src/remote/workbench-client.ts— deleterenameBranch()methodclient/src/mock/workbench-client.ts— deleterenameBranch()methodclient/src/backend-client.ts— deleterenameWorkbenchBranchfrom interface + implementationclient/src/mock/backend-client.ts— deleterenameWorkbenchBranchimplementationfrontend/src/components/mock-layout.tsx— removerenameBranchfrom client interface, deleteonRenameBranchcallbacks and allrenameBranchwiring (~8 refs)shared/src/workbench.ts— deleteTaskWorkbenchRenameInput(if only used by branch rename; check if task title rename shares it)
Keep
deriveFallbackTitle()+sanitizeBranchName()+resolveCreateFlowDecision()— initial branch derivation at creationregisterTaskBranchMutation()— used during task creation foronBranchpathrenameWorkbenchTask()— title rename is independent, staystaskIndextable — still the coordinator index for branch→task mapping
[ ] Final audit pass (run after all items above are complete)
Dead code scan
Already tracked in item 18: once all changes are complete, do a full scan to find dead actions, queues, SQLite tables, and workflow steps that need to be removed.
Dead events audit
Scan all event types emitted by actors (in packages/shared/src/realtime-events.ts and anywhere actors call c.broadcast() or similar). Cross-reference against all client subscribers (in packages/client/ and packages/frontend/). Remove any events that are emitted but never subscribed to by any client. This includes events that may have been superseded by the consolidated single-topic-per-actor pattern (item 14).