AshbyForge

Architecture

Two processes, one database, one git library. Everything else is discipline about which of them owns what.

Youweb UI · CLI · API
asks · answers · approvals
forge daemonscheduler · merge queue
budget · journal · attention
SQLitework · targets · attempts
journal · facts · scores
↑ claims (pull) · heartbeats · structured completions ↓
forge workerworktree → agent → checks
sandboxed, capability-aware
your reposisolated branches in,
merged green work out
directives librarygit: personas · directives
fragments · scripts
the library is itself a repo Forge works on — promotion and reflection run through the same pipeline

The pieces

PieceWhat it owns
forge daemonThe control plane: HTTP API and web UI, the scheduler and cron, the merge queue (one integrator per repository), budget policy and admission, the human-attention sweep, journaling. The only writer of SQLite.
forge workerExecution: claims work over the API, prepares isolated git worktrees, launches the agent executor with a frozen prompt and a result JSON schema, runs declared checks, reports heartbeats and a structured completion. Advertises repositories and capabilities (sandbox, browser).
SQLiteAll runtime state: work, targets, attempts, journal, facts, verifications, budgets, workflows, the scratch-script cache. One file; the journal table is the audit log.
~/.forge/directivesA plain git repository holding everything agents read — directives, personas, fragments, scripts — forked from a shared base library and merged forward like code.

The load-bearing decisions

Frozen snapshots

When work is created, the fully-composed prompt — directive body, persona, fragments, operational envelope — is serialized byte-for-byte onto the work row, with a hash. Whatever the library does afterwards, you can always answer exactly what did this attempt run? Prompt experiments and A/B reverts hang off that hash.

The journal

Every state-changing store method appends a journal row — work.created, target.merged, supervise.assessment, scratch.promoted — with the entity and a payload. The SSE stream, the plugins, and the operator's 3am debugging all read the same log. Nothing important happens silently.

Provenance

Every spawned task records its immediate cause (plan_task, verify, tool, promotion, continuation), the work that caused it, and the root ask it serves. The tree is walkable in one indexed query, which is what makes per-ask cost rollups and the learning loop possible.

Pull-based scheduling

Workers claim; the daemon never pushes. Each claim recomputes the queue from committed state — dependencies, budget class, path leases, stack depth — so there is no scheduler state to drift. Blocked and deferred are derived at read time, never stored.

Budget as admission control

Three classes — interactive, normal, backlog — gate admission against subscription-window usage, with hard stops and quiet hours. An agent that wants more turns mid-flight asks an adjudicator; extensions are granted, bounded, and journaled.

Design stance: the daemon is the only process that touches SQLite, engine code never imports HTTP, workers hold leases rather than truth, and anything a test can't reach through the public API is treated as a design smell.

A run, end to end

  1. An ask arrives — UI, CLI, API, a schedule, or another agent through a tool.
  2. The daemon freezes the snapshot, creates the work and its per-repository targets, journals it.
  3. A worker claims a target, builds a worktree on a fresh branch, launches the agent with the frozen prompt and the mode's result schema.
  4. The completion is parsed, claims are verified (L0/L1), follow-ups spawn — a verify agent, a plan's task batch, a supervise continuation.
  5. Green, integrating work enters the merge queue: rebase onto the integration head, re-run checks, land or bounce.
  6. Facts land in the analytics table: phases, tokens, cost, verification outcome, scores, size, tree root.

Next: Work & verification — the state machine that makes "done" mean something.