6.8 KiB
Storage Revamp Plan
Status: active rollout, phases 1 and 2 complete
This document now tracks the storage revamp as it exists today, not as a speculative design exercise. The DB foundation and the project/workspace identity cutover have landed. What remains is the explicit creation/archive surface cleanup, timeline durability cutover, and final removal of legacy paths.
Goals
- make structured records durable in Drizzle + SQLite
- make projects and workspaces explicit first-class records
- stop deriving project/workspace identity from agent
cwd - keep agent snapshot persistence behind clear ownership
- move committed timeline history to storage-owned rows
- remove legacy JSON and in-memory authority once the DB path is proven
Out of scope
- moving config, keypairs, push tokens, or server identity into the DB
- persisting raw provider deltas or transport-only chunk streams
- designing a hosted/remote database story beyond keeping the schema portable
- durable reasoning history unless product explicitly asks for it later
Current state
The storage revamp is no longer hypothetical.
Completed:
- Drizzle + SQLite database bootstrap is in place
projects,workspaces, andagent_snapshotsuse integer primary keysworkspaces.project_idandagent_snapshots.workspace_idcascade on deleteagent_snapshots.workspace_idisNOT NULL- legacy JSON import feeds the DB-backed structured records
- project/workspace records use explicit
directoryfields instead of path-as-identity - session read paths now use persisted workspace/project rows instead of cwd/git derivation
workspace-reconciliation-service.tsis deletedworkspace-registry-bootstrap.tsis deletedworkspace-registry-model.tsis reduced tonormalizeWorkspaceId
Still pending:
- explicit
create_project/create_workspaceAPI cleanup - final archive cascade behavior for descendants and live agents
- committed timeline storage cutover
- removal of remaining legacy JSON and in-memory committed-history authority
Converged decisions
Structured record authority
Projects, workspaces, and agent snapshots are DB-backed structured records. The server should not recreate project/workspace identity from:
- git remotes
- worktree main-repo roots
- normalized cwd strings
Temporary exception:
- agent creation may still find-or-create a workspace by directory if the UI has not yet provided
workspaceIdexplicitly
That fallback is transitional and should be deleted once the client always sends the workspace id.
Storage seams
The useful seams remain concrete and domain-shaped:
ProjectRegistryWorkspaceRegistryAgentSnapshotStoreAgentTimelineStore
There is no reason to reintroduce a reconciliation service layer for project/workspace identity.
Timeline contract
The long-term timeline contract remains:
- committed rows are durable, canonical history
- provisional live updates are transient subscription state
- committed history is fetched by seq
- provider history replay is not the durability mechanism
The structured-record cutover is complete before the timeline cutover so timeline rows can rely on stable DB-backed agent and workspace identity.
Remaining phases
Phase 3: Explicit creation and archive cleanup
Goal: Remove the last transitional write paths that still infer state from directories.
Required work:
- add explicit
create_projecthandling - add explicit
create_workspacehandling - make agent creation require
workspaceIdonce the UI is ready - finish archive semantics for workspaces/projects and any descendant agent state
- remove the temporary find-or-create-by-directory fallback from agent creation
Exit gate:
- project/workspace creation is explicit end to end
- no normal creation path infers identity from cwd or git metadata
- archive flows behave consistently for structured records and live runtime state
Phase 4: Timeline storage cutover
Goal: Make committed history durable and storage-owned.
Required work:
- make
AgentTimelineStoreauthoritative for committed history - write one committed row per finalized logical item
- support tail, before-seq, and after-seq queries from storage
- stop treating provider history hydration as the normal refresh/load path
- keep provisional live updates in memory only
Exit gate:
- committed history survives daemon restart
- reconnect uses committed catch-up plus future live events without gaps or duplicates
- unloaded agents can serve committed history from storage alone
Phase 5: Legacy cleanup
Goal: Remove compatibility paths after the DB-backed model is fully authoritative.
Required work:
- remove legacy JSON authority for structured records
- remove in-memory committed-history ownership
- remove provider-history rehydrate compatibility paths
- trim dead protocol and reducer logic from the pre-storage model
- update architecture docs to match the final model
Exit gate:
- there is one durable storage path for structured records
- there is one durable storage path for committed timeline history
- the runtime no longer depends on the removed JSON/in-memory model
Data model summary
Projects
- integer primary key
directoryis uniquedisplay_namekind:git | directory- optional
git_remote - timestamps and archive state
Workspaces
- integer primary key
- belongs to a project by
project_id directoryis uniquedisplay_namekind:checkout | worktree- timestamps and archive state
Agent snapshots
agent_idremains the primary key- belongs to a workspace by integer
workspace_id workspace_idis required- timestamps, lifecycle state, persistence metadata, attention metadata, archive state
Timeline rows
Target shape once Phase 4 lands:
agent_id- committed
seq - committed timestamp
- canonical finalized item payload
Not part of durable history:
- raw streaming chunks
- provisional assistant text
- provisional reasoning text
Verification requirements
Every remaining phase should keep the same bar:
npm run typecheck- targeted tests for the touched storage/session/runtime paths
- migration/import coverage when storage authority changes
- reconnect and catch-up scenario coverage when timeline behavior changes
At minimum, timeline cutover must explicitly prove:
fetch-after-seqfetch-before-seq- restart durability
- no-gap/no-duplicate reconnect behavior
Main risks
- timeline work reintroduces provider-history replay as hidden authority
- archive behavior diverges between stored records and live in-memory agents
- explicit creation work leaves the transitional cwd fallback in place too long
- cleanup stalls after compatibility paths stop being exercised
Rule of thumb
If a new change needs to ask "what can we infer from this cwd?" for project or workspace identity, it is probably moving in the wrong direction.