Skip to content

Part I · What Foundry is

Architecture overview

Foundry is a single-tenant web application with a deliberately small number of moving parts: a React SPA, one backend API process, one embedded agent runtime, and Postgres. Agent orchestration, model access, artifact versioning, and execution all hang off that spine. This page walks the runtime chain, the data layer, the provider catalog, the execution model, the MCP tool registry, and the deployment shape.

The runtime chain: Foundry → Polis → OpenCode → providers

The backend is a Hono API server exposing REST and SSE endpoints under /api. It does not talk to model providers directly for agent work. Instead:

  • The backend spawns one OpenCode server subprocess (the model runtime), bound to loopback only. OpenCode owns provider connections and tool execution.
  • Polis, Swisper's agent-orchestration layer, is not a separate service: it is loaded as an OpenCode plugin in-process, and exposes an internal HTTP listener. The Foundry-to-Polis seam is HTTP on localhost, authenticated with a shared secret.
  • Four Foundry plugins (artifact editing, dev-lead tools, record tools, plus an optional spike plugin) load alongside Polis and attach back to the backend's host context — plugins never spawn their own runtime.
  • Boot order is load-bearing: Polis migrations run, the OpenCode process is warmed up, all agent behaviours are registered, and the backend fails fast (exits) if the behaviour registry is incomplete.

A phase chat turn — talking to the Architect on an epic, or the BA on a feature — travels the full chain: the backend resolves the phase to its agent binding, posts the message to Polis, and drains Polis's event stream back into Foundry's SSE vocabulary (chat deltas, reasoning deltas, tool status, subagent dispatches, completion) for the browser.

flowchart TB
  SPA["Browser SPA (React)"] --> API["Foundry API (Hono, REST + SSE, JWT)"]
  API --> SVC["Backend services"]
  SVC -->|"HTTP + shared secret"| POLIS["Polis (agent orchestration, in-process plugin)"]
  SVC -->|"SDK, loopback only"| OC["OpenCode server (model runtime, subprocess)"]
  OC --> PLG["Polis plugin + Foundry plugins (attach to host)"]
  OC --> PROV["Model providers: Vertex ADC, Anthropic, OpenAI-compatible, z.ai"]
  OC --> MCP["MCP servers: loopback built-ins, Prism, Context7, chrome-devtools"]
  SVC --> PG[("Postgres: foundry + polis schemas")]
  POLIS --> PG
  COP["Copilot surfaces"] -->|"direct HTTPS"| ANT["Anthropic Messages API"]

The one exception: Copilot goes direct

The generic Copilot — the assistant behind onboarding interviews, prompt-editor assist, and the side panel — does not go through Polis or OpenCode. A Copilot turn is a single direct, non-streaming call to the Anthropic Messages API. It is configured by one instance-level agent settings row, and its provider must use the Anthropic API schema; if no suitable provider is configured, the UI shows an explicit "Copilot disconnected" banner rather than failing quietly. This keeps the lightest-weight assist path free of orchestration overhead — and makes it the one provider constraint an admin must know about.

Data layer

Foundry runs on Postgres with Drizzle ORM. Schema changes ship as SQL migrations that run at boot — idempotent, on a single connection — so a deployed container is always at the schema it was built against; CI enforces that migrations and the schema definition never drift apart. Boot also runs seeding and reconciliation: schema definitions, provider presets, an admin user, Polis's own migrations, and recovery of any runs stranded by a restart.

Two storage patterns matter for evaluation:

Artifacts are versioned, and approval pins a version. Each phase owns one artifact row; its content is JSONB, validated against the phase's JSON Schema (ajv, JSON Schema 2020-12) on write, with build provenance (model, cost, tokens) stored alongside — never inside — the content. History is append-only at episode granularity: one agent turn, or one 30-minute block of human edits, becomes one version row; milestones mark significant states and can be pinned. Phase approval records the exact milestone version approved; any later edit flips the phase from approved to drifted. Nothing is overwritten, and the audit trail is structural, not procedural.

Records are a register, not a document store. The product_records table backs the Decisions/Tech-debt/Bugs/Ideas/Screens registers: per-product, per-kind sequence numbers yield stable citable IDs (AD-11), authorship distinguishes agent from human, human sign-off is a distinct field (who decided, when), and supersession links records into a chain. One service is the sole writer; agents reach it through dedicated record tools, humans through the REST API and UI.

Catalog-driven providers and models

Foundry does not hardcode a provider list. The model catalog is sourced from models.dev, refreshed on a 24-hour cycle, with a vendored snapshot shipped in the image so a cold start with no network still boots. If both the live fetch and the snapshot fail, boot fails loudly.

  • Pricing comes from the catalog and is normalized as USD per million tokens (a documented unit trap: some upstream sources quote per-token).
  • A provider config carries a catalog binding — the catalog entry it resolves through — which determines available models, pricing, and subscription capability (configured in Providers). Resolution falls back from explicit binding to name match to API-schema lookup.
  • Live model lists are fetched per provider adapter into the model catalog table; adapters exist for Anthropic, Google, Vertex-hosted Anthropic, OpenAI, and OpenAI-compatible endpoints (which covers Anthropic-wire-compatible vendors such as z.ai).
  • Supported auth methods span API key, bearer token, service account, application-default credentials (keyless Vertex), and three distinct subscription mechanisms: OAuth sign-in (per-user only — an instance-wide OAuth session would pool everyone's traffic through one login), plan key (an instance-level credential for flat-fee coding plans, pinned to the plan's own endpoint), and session (instance-level and keyless — a container-local CLI session reached over a loopback proxy). Credentials are encrypted at rest and decrypted only at the moment the runtime configuration is emitted; the session mechanism stores no credential at all, which is why its health is probed rather than decrypted.

Execution model

Execution is where Foundry departs furthest from a chat product. A run is a supervised, multi-agent build on real git infrastructure (the user-facing view is the Run Cockpit):

  • Worktree per task. Every task gets an isolated git worktree under temp-scoped roots; those roots are also the single source of truth for the runtime's file-permission pre-grants, so agents can edit and run commands only inside their own workspace.
  • Dev-lead-rooted runs. Executing a feature roots the run on the dev-lead agent, which plans the task DAG and dispatches QA, engineer, and reviewer agents per task.
  • Tests first, then locked. QA writes and commits tests before implementation; the QA lock then protects them — deleted tests are restored byte-identical, and modifications are not reverted but are captured as a diff for the reviewer.
  • Merge queue with a reviewer gate. A task branch merges only with a passing reviewer verdict; a fix loop is capped at two attempts per engineer tier before forced escalation to a stronger tier. Steps marked for human review park structurally until a human approves — the gate is in the merge machinery, not in a prompt.
  • Ship review, fail-safe red. After the task DAG drains, a whole-branch ship review runs: green unlocks opening the ship PR; anything else — including the review itself failing — is red. There is deliberately no auto-fix loop at this stage.
  • Runs are versioned and disposable. A fresh run snapshots the outgoing run, wipes the hot state, and mints the next version in one transaction; discard stops the run and frees its resources while the branch stays on the remote read-only. The scoreboard compares runs; selecting a winner records it on the feature.
sequenceDiagram
  participant U as User
  participant API as Foundry API
  participant SUP as Run supervisor
  participant DL as Dev lead agent
  participant TT as Task agents
  U->>API: Start execution (fresh or follow-up)
  API->>API: Validate, snapshot old run, mint new version
  API->>SUP: Detach run from the HTTP request
  SUP->>DL: Clone repo, create branch, seed task ledger
  loop For each task in the plan
    DL->>TT: Create isolated worktree
    DL->>TT: QA writes tests first (then locked)
    DL->>TT: Engineer implements (tier selects model)
    DL->>DL: Enforce QA lock
    DL->>TT: Reviewer verdict
    DL->>DL: Adjudicate (cap 2 fix loops, then escalate tier)
    DL->>DL: Merge task branch (human gate can park it)
  end
  DL->>SUP: Finalize: push branch, whole-branch ship review
  SUP->>API: Set run status (first write wins)
  API-->>U: Live SSE: roster, costs, reasoning, diffs

The supervisor detaches the run from the originating HTTP request, so closing the browser never kills a run; the UI re-attaches to the live stream. On backend restart, a boot reconciler resumes or cleanly rejects any in-flight runs.

MCP tool registry

Agent tools beyond the model itself are managed through a first-class MCP server registry, not environment configuration:

  • Built-ins are loopback servers. Foundry's own capabilities — artifact editing, design-system access, mock build and screenshot, execution tools, records — are protected internal MCP servers, code-built at runtime with a per-instance secret. They appear in the registry but are not externally configurable.
  • External servers are configured and tested. Prism (semantic code intelligence) runs as local stdio — deliberately, so it sees the uncommitted working tree; Context7 (library docs) is remote HTTP; a pinned headless chrome-devtools server provides browser automation. Custom servers can be added over stdio or HTTP.
  • Discovery is real. Registering or testing a server performs an actual MCP tools/list against it; the discovered tool catalog is replaced transactionally, and a failed test records the error while keeping the previously known tools. The registry row acts as a kill switch.
  • Auth per server supports none, bearer, custom headers, and OAuth — including dynamic client registration when no client ID is supplied; secrets are sealed at rest, masked in reads, and decrypted only when the runtime config is emitted.
  • Grants are per agent. Each agent's allowed tools and subagents layer over its behaviour definition, so a reviewer, an engineer, and a UX agent see different tool surfaces from the same registry.

Deployment shape

Production is intentionally boring: one GCE VM running docker compose, Cloud SQL for Postgres, and Caddy for TLS. Caddy terminates TLS, serves the built SPA, and proxies /api (with SSE-friendly flushing and long timeouts) to the backend, which is the only service with an exposed port — and only inside the compose network. Foundry and Polis share one database, with Polis in its own schema. Vertex model access is keyless via the VM's service account; container logs flow to Cloud Logging.

flowchart TB
  CI["GitHub Actions CI (keyless WIF)"] -->|"green CI on main"| CD["Deploy workflow"]
  CD --> RUN["Self-hosted runner on the VM"]
  subgraph VM["GCE VM (docker compose)"]
    CADDY["Caddy (TLS, static SPA, /api proxy)"] --> BE["Backend (internal port only)"]
    BE --> CSP["Cloud SQL proxy"]
  end
  CSP --> SQL[("Cloud SQL Postgres: foundry + polis schemas")]
  SM["Secret Manager"] -->|"env fetch at deploy"| VM
  AREG["Artifact Registry"] -->|"Polis package at image build"| VM
  BE --> PRV["Model providers"]
  VM --> LOG["Cloud Logging"]

Deployment is continuous but gated: the deploy workflow triggers only after CI passes on main, runs on a self-hosted runner on the VM itself, resets to origin/main, fetches environment secrets from Secret Manager, and holds a health gate before declaring success. Because migrations run at boot, a deploy and a schema upgrade are the same event.

For development, the same compose shape runs locally — Postgres, backend, and frontend in containers — so the local rig exercises the same boot chain, migrations, and runtime wiring as production. The operational detail — CI gates, deploy workflow, migrations at boot — is in Deployment.

What this adds up to

Three properties fall out of this architecture that matter in an enterprise evaluation:

  1. One spine, few seams. A single API process, an embedded runtime, and one database. The seams that exist (Foundry↔Polis, backend↔OpenCode, registry↔MCP servers) are explicit, authenticated, and local.
  2. State is structural. Versioned artifacts with pinned approvals, append-only histories, citable records with human sign-off, and immutable run versions — traceability is a property of the schema, not a reporting layer.
  3. Nothing exotic to operate. Postgres, docker compose, a reverse proxy, and boot-time migrations. The deployment story fits inside conventional infrastructure and conventional review.