Product architecture
We’re Building a Program Database
Record every thread. Propose only what should land.
PDB separates captured agent Threads, explicit Proposals, immutable Implementations, deterministic Commits, optional Git recovery, and production telemetry that can inform—but never authorize—the next change.
Six coding agents had isolated workspaces, clean commits, and passing checks. They still stopped shipping. At the end of the development loop, every task became a part-time release coordinator: exchanging source SHAs, provider versions, fencing tokens, and permission to advance main.
The first response was operational. We stopped letting feature agents deploy, then designed a Routed Monorepo so unrelated work would not wait behind one global release queue. That fixed the immediate authority problem. It also exposed the deeper one: the program was spread across source control, coding-agent conversations, checks, artifacts, deployment records, and observations from the live product. Every downstream agent had to reconstruct those relationships from scratch.
We are now building a Program Database, or PDB: a local-first, transactional system of record for a program and the work that changes it.
The shortest explanation is:
Record every thread. Propose only what should land. Preserve every implementation attempt. Let one deterministic kernel decide what becomes
main.
That sentence is more important than the database technology. PDB is not a Git server with extra tables, a graph database for source code, or a warehouse containing agent transcripts. It changes the unit of collaboration. A pull request starts with a branch and a diff. PDB starts with an explicit Proposal assembled from any useful combination of conversation, source, constraints, checks, and artifacts. Code may accompany the Proposal, but it is not the Proposal.
This remains an architecture direction and an early local prototype, not a shipped replacement for Git or Forge. The format is intentionally unstable while the bootstrap proves exact source recovery, crash safety, agent isolation, serializable commits, and self-hosting.
Record everything. Propose almost nothing.
Coding agents produce far more history than code.
A thread may contain a useful diagnosis but no patch. It may explore three approaches and reject all of them. It may discover that the requested change is already present. It may fail because the repository was broken before the agent arrived. Or it may produce a working change that a human does not want to integrate.
Deleting those histories makes future agents repeat the work. Treating every history as proposed work floods the merge system with noise. PDB therefore separates passive capture from explicit intent.
A Thread is an exact, append-only conversation imported from Codex, Claude Code, OpenCode, Devin, or another adapter. User, assistant, developer, system, and tool messages keep their roles. Tool calls, failures, subthreads, timestamps, and provider payloads remain available. Installing PDB hooks means the repository remembers the thread whether or not anyone commits code or declares the task successful.
A Thread has no merge authority. Ten Threads can produce no Proposals. Five Threads can inform one Proposal. One especially productive Thread can inform several Proposals. The relationship is many-to-many because the real work is often many-to-few.
A Proposal is the explicit request to change the program. It has one stable identity and immutable numbered versions. It can reference selected messages, files, source objects, checks, artifacts, or analysis, but it does not need a large schema before it becomes useful. At minimum it can be the exact request and exact base state that a human or agent chose to submit.
An Implementation is an immutable program result that claims to satisfy one or more exact Proposal versions. It may be code supplied by the proposer, a fresh result from a coding agent, a repair of a failed attempt, or one cohesive change produced by a merge agent for several compatible Proposals.
A CheckRun records what was actually checked against which exact Implementation: the command or verifier, source and dependency identities, environment, producer, result, and logs. “Tests passed” is not a floating green badge. It is a durable result tied to exact inputs.
A Commit is the canonical transition accepted by the deterministic transaction kernel. It advances the protected main Environment only after conflict ranges, invariants, grants, and required CheckRuns validate.
Consider an authentication incident. One Thread notices that server-rendered pages can read sessions. A second discovers that the public edge caches those pages. A third experiments with cache headers but never reaches a safe result. A human can stage the decisive messages from the first two Threads, the failed experiment from the third, and a short constraint—“private sessions must never enter a shared cache”—into one Proposal. The failed Thread remains valuable context without becoming a change request of its own.
This is the first major conceptual correction to the original PDB design. We no longer make every captured conversation begin life as a draft PromptRevision. There is no canonical draft Proposal. Drafting happens in a private agent session. A Proposal exists only when someone explicitly submits it.
What comes after the pull request?
The pull request is carrying too many meanings at once.
It is a request, a branch comparison, a discussion, a code-review surface, a CI trigger, an integration candidate, a merge queue entry, and often an implied release request. Those meanings coincide when one human writes one patch against a mostly stable branch. They separate under agent-scale development.
An agent may have the right intent and a weak implementation. Several requests may need one coherent implementation. A submitted patch may work in isolation but conflict semantically with a change ahead of it. A reviewer may approve the desired behavior without authorizing arbitrary source edits or production access. PDB gives each fact its own durable identity instead of stretching “PR” further.
The ordinary command-line path remains intentionally small:
pdb hooks install --agent all
pdb add --thread THREAD_ID
pdb add --message MESSAGE_ID
pdb add src/session/cache.rs
pdb status
pdb unadd src/session/cache.rs
pdb propose --title "Keep authenticated SSR out of shared caches"
pdb add borrows the useful muscle memory of git add: select what belongs in the next durable unit. But PDB staging can select conversation messages, source, checks, artifacts, and typed context—not only file content. Staging belongs to the current private AgentSession and does not create a public draft object for every intermediate selection.
pdb propose freezes the selection as an immutable Proposal version and submits it to the train immediately. The title is optional. There is no second “mark ready” operation and no ambiguity about whether a draft should enter the queue.
This is where the Git analogy ends. pdb propose does not create the canonical program Commit. It creates the explicit request that the integration system is allowed to pursue. A later Implementation plus its exact CheckRuns may become a Commit.
| GitHub-shaped concept | PDB concept | Why it is separate |
|---|---|---|
| Issue or agent chat | Thread | Preserve all investigation without implying integration intent. |
| Local index | AgentSession staging | Select messages, source, checks, and other exact context privately. |
| Pull request | Proposal | State the exact request to integrate, independent of any one code attempt. |
| PR branch or patchset | Implementation | Preserve each immutable attempt and its provenance. |
| CI status | CheckRun | Bind a result to exact source, dependencies, policy, and producer. |
| Merge commit | Commit | Advance canonical state only through deterministic validation. |
Proposal management is similarly small:
pdb proposal list --output json
pdb proposal show PROPOSAL_ID
pdb proposal withdraw PROPOSAL_ID --expected-version VERSION
Withdrawal is an event, not deletion. A returned Proposal keeps its stable identity. The proposer can stage repaired context and submit a new immutable version under that identity. History stays comprehensible without creating a new object every time the merge agent asks for a correction.
The minimal lifecycle is submitted → landed, submitted → returned, or submitted → withdrawn. “Running checks,” “batched,” “materializing,” and “waiting behind a dependency” describe train execution, not new Proposal states.
One implementation can satisfy several proposals
“Merge a prompt” is memorable, but it can suggest that every prompt should produce its own patch and then be text-merged. That is not the design.
Imagine three submitted Proposals:
- make authenticated sessions available during server rendering;
- prevent private responses from entering the shared edge cache; and
- preserve anonymous-page caching performance.
Three independent patches could fight over the same renderer, session boundary, and cache policy. A merge agent can instead read all three exact Proposal versions and produce one Implementation: establish a request-scoped session interface, mark private render paths uncacheable, retain the anonymous cache path, and add checks for both privacy and cache-hit behavior.
That Implementation does not rewrite the Proposals or pretend their original code was different. It references all three requests, the source state it read, the exact result Snapshot it produced, and every predecessor attempt it used or rejected. If it lands, one Commit can satisfy all three Proposals. If it fails, the attempt remains inspectable and the Proposals can be returned independently.
This is the agentic part of the train. The train orders Proposals by declared dependencies and priority, then age and deterministic arrival order. It can ask an external coding or merge agent to build against the predicted future state containing compatible work ahead. It can batch compatible Proposals, bisect a failed batch, and return a semantic conflict to the source context. It cannot silently edit a Proposal, grant itself authority, waive a required CheckRun, or advance main.
The mechanics borrow useful ideas from Trunk Merge Queue: test against predicted future state, avoid retesting compatible work unnecessarily, and bisect failures. The fundamental difference is the queued object. PDB queues an explicit request and permits a new cohesive Implementation; it is not restricted to replaying a submitted branch commit.
Parallel agents need private Environments
Git worktrees solve an important problem: two processes can edit different directories without overwriting one checkout. They do not solve dependency isolation, generated-file collisions, semantic conflicts, or the cognitive tax of teaching every agent which worktree is “real.”
PDB instead exposes two related concepts.
An Environment is a named program lineage with an immutable head history. main is the authoritative Environment because policy protects that identity, not because it uses a different record type. A side Environment forks from an exact main Commit in the first version. It can live for minutes or months, produce previews, or receive a bounded production experiment without becoming canonical.
An AgentSession is one private writable view pinned to an Environment head. It looks like an ordinary project directory to the coding agent. PDB may implement it with Linux OverlayFS, APFS clone operations on macOS, ReFS block cloning on Windows, or a copy fallback. That machinery is deliberately hidden. Codex, Claude Code, OpenCode, or another tool should believe it is alone in a normal directory.
Multiple agents may attach to the same Environment, but they do not share a writable upper layer. Each Session retains its own staging, generated output, process state, and attempt checkpoints. Publishing uses an expected Environment head, so a stale Session receives a typed conflict instead of overwriting newer work.
<figure class="forge-blog-figure" aria-labelledby="program-database-environments-caption"> <a href="/blog/program-database/environments.svg" aria-label="Open the PDB Environments and AgentSessions diagram at full size"> <img src="/blog/program-database/environments.svg" alt="The protected main Environment produces side Environments for authentication and rendering work. Each side Environment can have multiple private AgentSessions with separate writable layers and generated output. Immutable dependency resolutions can be shared by digest. Sessions submit Proposals and Implementations to one train. Versioned Domains for identity, edge, and migrations describe routed conflict scopes but are not branches or independent histories." width="1200" height="820" loading="lazy" decoding="async" /> </a> <figcaption id="program-database-environments-caption"><strong>Environment is lineage; AgentSession is isolation; Domain is routing.</strong> Agents get normal private directories. Shared immutable dependencies save work, while generated output and writable source overlays stay Session-local.</figcaption> </figure>Dependencies deserve their own treatment. Most Sessions can share a digest-addressed, immutable dependency resolution from main. If one experiment needs a different compiler or package version, that resolution belongs to the Environment or Session and becomes an exact Implementation input. Mutable outputs such as node_modules/.cache, target/, .next/, coverage data, and local databases stay private and never enter the program Snapshot accidentally.
A Domain is not another word for Environment or lane. It is a versioned routed-monorepo concern: identity, edge-cache, database-migrations, or billing-contracts. Domains describe source ranges, semantic facts, invariants, dependencies, resources, and required checks that must be considered together. An Implementation can touch several Domains. The train can temporarily compose them for one run, but every Commit still enters the same canonical order.
The authentication example makes the distinction useful:
auth-experimentis an Environment: a program lineage based on an exactmainCommit.session-42is an AgentSession: one agent’s private writable directory for that Environment.identity + edge-cacheis a Domain composition: the conflict and validation scope the resulting Implementation must satisfy.
The main Environment remains authoritative, but side Environments are not second-class. They can produce stable previews and, under explicit expiring policy, serve a bounded audience for a canary or A/B experiment. Traffic assignment never changes canonical source.
The conflict Git cannot see
The original authentication and server-rendering work supplied the decisive example. One task made sessions readable on the server. Another changed how public routes rendered. Their final file edits could become disjoint after ordinary refactoring, yet together they could render private authenticated state into a cacheable response.
Snapshot isolation can accept that form of write skew: each transaction reads an old rule, writes a different key, and leaves the combined system violating the rule. A text merge may be clean for the same reason. Serializable validation rejects one transaction because both read or constrain the authenticated-page-rendering invariant and one changed its truth.
Migration numbering is the smaller example. Two isolated agents both see 0009 as the next available prefix and create different migrations. Neither agent is behaving irrationally. The missing record is a predicate read and write over database/migrations/next. PDB treats that namespace as a conflict range, commits one allocation, and returns the other attempt against the new state.
At commit time, the kernel receives:
- a base Commit version;
- exact object and fact reads;
- predicate or range reads;
- proposed writes and Environment-ref preconditions;
- named invariant versions;
- required CheckRun identities; and
- a bounded capability grant.
It compares those claims with Commits since the base version. If a write intersects a declared read or conflict range, a ref changed, a CheckRun is stale, or an invariant fails, the kernel returns a typed conflict. Otherwise it assigns a monotonic repository version, appends the Commit records, and advances the protected Environment ref atomically.
Only that short transition serializes. Conversation capture, planning, code generation, builds, previews, review, analytics, and long agent reasoning remain parallel. No agent holds an open-ended repository lease while it thinks.
A Program Database is a ledger, not a graph database
The portable database is deliberately boring:
.pdb/
manifest.json
ledger.sqlite
objects/
git/ # optional exact-source projection
objects/ stores immutable content-addressed source, assets, transcript payloads, logs, and larger record bodies. Deterministic bounded CBOR gives hashed internal envelopes an unambiguous byte representation. Users do not need to inspect CBOR: manifest.json, pdb inspect --json, the HTTP API, and the embedded web app provide ordinary interfaces.
ledger.sqlite is the canonical append-only record store and the sole home of correctness-critical indexes: current refs, Commit versions, record and object existence, conflict-range writes, grants, invariants, idempotency keys, Proposal fences, and external-effect fences. SQLite also directly serves normal local product and administration queries.
There is no mandatory graph database, vector database, search service, or second query database. Program structure is graph-shaped, but ordinary relational edge tables and recursive SQL are enough until measured workloads prove otherwise. Adding a disposable copy of the ledger would create another schema, checkpoint, migration path, freshness model, file footprint, and crash-recovery story without making canonical commits safer.
This is the second major correction to the earlier design: DuckDB is not the PDB query layer. A healthy PDB can browse Threads, Proposals, Implementations, Environments, Domains, Commits, artifacts, deployments, and observations using SQLite alone.
<figure class="forge-blog-figure" aria-labelledby="program-database-storage-caption"> <a href="/blog/program-database/storage-boundaries.svg" aria-label="Open the PDB storage and authority boundary diagram at full size"> <img src="/blog/program-database/storage-boundaries.svg" alt="Inside the portable PDB boundary are manifest.json, canonical ledger.sqlite, immutable objects, and an optional Git projection. The single local pdb host owns writes and serves the CLI, embedded admin web app, and versioned API. External coding agents, builds, previews, cloud synchronization, production effects, and an optional DuckDB telemetry attachment operate outside the canonical store through bounded capabilities and immutable receipts." width="1200" height="840" loading="lazy" decoding="async" /> </a> <figcaption id="program-database-storage-caption"><strong>The portable truth is small.</strong> SQLite and immutable objects are canonical. Git is an optional escape path. Agents, builds, production effects, and analytical attachments remain outside the state-owning boundary.</figcaption> </figure>One native process owns each .pdb directory. Users install one executable, pdb; there is no second pdbd product to understand. pdb serve runs or manages the internal host and its self-bundled administration web app. The useful core must work offline with no account, Docker, Node.js, Rust toolchain, Cloudflare account, or system Git after installation.
The web interface is not a terminal UI translated into a browser. It is the administration surface for the PDB being viewed: dense Thread history with explicit human, assistant, system, and tool roles; Proposal composition and lineage; Implementation attempts and diffs; CheckRuns; Environment and Session state; Domain conflicts; Commits; exports; effects; and recovery status.
Rust owns the boundaries where determinism and hostile input matter most: canonical serialization and hashing, bounded Git parsing, immutable-object validation, the append-only ledger, MVCC/OCC validation, crash recovery, and deterministic import/export. The embedded interface and generated SDK can remain TypeScript. External coding agents, builds, sandboxes, previews, and production adapters remain replaceable processes. Rust is a boundary choice, not an excuse to move every I/O-heavy product concern into the kernel.
Git is an escape hatch, not the substrate
Making Git optional does not make exact source optional.
When PDB imports a Git repository, it must retain blob bytes; raw path bytes and tree modes; symlinks; parent order; author and committer names, emails, times, and offsets; messages; extra headers; signatures; tags; refs; and SHA-1 or SHA-256 object format. Those objects must export with their original identities. Git AI notes and related conversation metadata must also survive import and export losslessly.
For a native PDB Commit, the Git projector deterministically renders the selected Environment history, records the resulting Git object IDs, verifies the repository, and then advances exported refs through compare-and-swap. A read-only recovery mirror can therefore outlive PDB itself.
System Git is optional. The native Git boundary must support import, export, and ordinary smart HTTP clone, fetch, and push without shelling out to git. When native Git is installed, PDB can still use git index-pack --fsck-objects and differential fixtures as defense-in-depth and conformance checks.
This relationship is intentionally asymmetric:
- PDB must be able to reconstruct the exact source represented in Git.
- Git is not expected to contain every Thread, Proposal, Domain, CheckRun, conflict read, capability grant, rollout, or Observation.
A project can begin as Git history imported into PDB. During bootstrap, Git can remain the familiar editing and recovery surface. Eventually a PDB-native project can operate with no Git projection at all. Compatibility is a feature; permanent dependence is not.
The Program Database closes the loop
Software development does not end when source reaches main.
A Commit produces an immutable Artifact. A Deployment attempts to activate that Artifact. A Rollout records who received it, under which policy, for how long, and with what rollback target. An Observation records what a provider or public probe actually saw. Product events describe what users did with the running program.
Those records let PDB connect a production outcome back through Artifact, Commit, Implementation, Proposal, and Thread. They do not make raw analytics canonical program state.
This is the narrow, useful role for DuckDB: an optional host-local attachment for real production and product telemetry. It may download scoped event data from a deployed application, retain high-volume raw rows, join them to bounded PDB dimensions, and support exploratory SQL or Parquet export. It is not rebuilt from ledger.sqlite, does not mirror the admin UI, and is not required to open or recover a PDB.
Suppose a side Environment receives five percent of checkout traffic. Its Artifact improves median latency but increases payment failures for one browser family. The telemetry attachment can join those events to the exact Rollout and Artifact, trace them to the Commit and Implementation, and show which Proposals the Implementation intended to satisfy. An analyst or agent can stage that finding and propose a repair.
The analytics result cannot directly roll back production, change a capability, or advance main. A deterministic monitor may append a signed Observation or CheckRun under an explicit policy. Production authority may then act on that canonical record. Exploratory SQL, model-generated recommendations, and dashboards remain context.
Privacy defaults follow the same boundary. Structured allowlisted metadata is the default. Raw prompts, source, logs, and user content remain content-addressed and require explicit repository policy for analysis. Private per-PDB analytics can exist for hosted projects. Cross-PDB aggregation and benchmarking require a separate opt-in.
This also clarifies the hosted product. Forge Cloud should not merely sell Git hosting or a dashboard over local files. Its value is managed PDB authority: verified synchronization, collaborative Proposal history, the agentic train, managed agent compute, trusted checks, previews, production effects, retention, audit, private organizational analytics, and optional cross-PDB intelligence. The format, local host, event schemas, Git projection, telemetry attachment, exports, and dashboards stay open source.
What the first proof must demonstrate
The first useful proof is not “replace Forge.” It is one local PDB completing a coherent loop:
- Initialize a persistent PDB and capture complete repository Threads from supported coding agents.
- Import an existing Git repository without requiring system Git, then verify exact export and recovery.
- Create two private AgentSessions from one Environment and let both agents work as though they own the directory.
- Use
pdb addto select exact messages, source, and checks, then usepdb proposeto submit only intended work. - Produce one Implementation for one Proposal and another cohesive Implementation satisfying several Proposals.
- Record successful and failed CheckRuns against exact inputs.
- Commit disjoint valid work concurrently and reject a real semantic write-skew or migration-range conflict.
- Crash between durable boundaries, restart, and recover either the old or new valid state—never a hybrid.
- Browse the result through the embedded web app and inspect the optional Git recovery projection while offline.
- Deploy an immutable Artifact in a bounded experiment, attach real telemetry, and turn a finding into an explicit new Proposal.
- Import PDB’s own source and begin recording subsequent PDB Threads, Proposals, Implementations, and Commits inside PDB.
The conformance suite must include deterministic codec vectors, Git round trips, differential checks against native Git, malformed-pack fuzzing, bounded-memory tests, process-kill crash matrices, idempotent replay, stale-ref races, MVCC conflicts, write-skew fixtures, AgentSession isolation, offline startup, backup and restore, and native macOS, Linux, and Windows behavior. Performance must be measured against named corpora before any Rust speed claim becomes part of the product story.
The format can remain unstable through this bootstrap. Correctness cannot. Every stage should leave an atomic implementation history, including failed attempts, so self-hosting begins with an honest record rather than a cleaned-up myth.
A repository answers where the files are. A pull request asks whether one branch should merge. A Program Database should answer more durable questions:
- Which conversations led to this Proposal?
- Which exact Implementation satisfied it, and which attempts failed?
- What did that Implementation read and assume?
- Which CheckRuns still apply?
- Which semantic invariants and production resources can it affect?
- What is canonical now?
- Which Artifact are users actually running?
- What did the running product teach us to propose next?
Git made source collaboration universal. PDB’s wager is that agent-scale software development needs intent, attempts, validation, lineage, and production outcomes to become queryable and transactional too.
