Skip to content

tombarry-maker/foundry

Foundry

Turn Telegram messages into shipped open-source code.

Foundry is an AI-assisted development tool that takes a project from idea → built → tested → deployed → public open source, driven from a Telegram bot on your phone. Each consequential step is human-gated. Every consequential action is audited. Secrets are caught before they leave your machine.

Status: v0.1 engine — feature-complete at the v1.0 milestone (Block 9d Step 6 conformance seal, commit e994c37, 2026-05-20). Full suite: 687 pass / 2 skip / 0 failed. The version number in pyproject.toml is still 0.1.0; "v1.0" is the milestone state of the v0.1 engine, not a semver bump. See The v1.0 milestone below.

Foundry is the successor to Loom — Loom builds throwaway prototypes; Foundry builds persistent projects with multi-cycle development, git history, GitHub publishing, private Tailscale previews, public deploys, and a strict open-source readiness gate.

Fit-for-purpose deployment. Each template type routes to the deploy backend that matches its shape, not a one-size-fits-all platform. The architecture is structured around per-template deploy_backend dispatch (TEMPLATE_REGISTRY in foundry/projects.py). At the v1.0 milestone, Fly.io is wired up for web and python-service templates; nextjs and python-cli decline with sign-posted reasons explaining the right alternative path (Vercel for Next.js, PyPI / GitHub Releases for CLIs). See Templates.


Table of contents


The v1.0 milestone

The pyproject.toml version is 0.1.0. "v1.0" refers to the milestone of the original 10.5-block Foundry plan having shipped end-to-end, not a semantic-version bump. The milestone consists of:

  • Blocks 1–7 — persistent project model, multi-template support, Telegram bot, builder + spawner, git verbs, mid-build interaction, long-running build infrastructure.
  • Block 5.5BACKLOG.md per project + proposed-delta workflow.
  • Block 6.5 — open-source prevention: gitleaks pre-commit hook + trufflehog history scan + /audit verb + build-cycle commit blocking on secret detection.
  • Block 8/ship deploy verb to Fly.io for web and python-service templates.
  • Block 8.6 (a/b/c) — minimal runtime slice: /serve for Fly secret injection + persistent volume attachment, scoped to one OAuth token + one append-only state file.
  • Block 9 (a/b/c/d) — open-source curation: /release with seven subcommands ending in flip-public, the strongest gate in the project.
  • D.4 — re-register seam closure for the runtime layer.

The v1.0 commit is e994c37 (Block 9d Step 6 — END-TO-END CONFORMANCE SEAL). The full audit trail and lessons file accumulate from there.


What Foundry does

You text a Telegram bot. The bot drives an agentic development loop on a headless Mac Mini — building, testing, committing, scanning for secrets, pushing to GitHub, deploying to Fly.io, generating open-source documentation, scrubbing history, flipping public. Each consequential step is human-gated; nothing destructive happens without explicit confirmation; every decision is recorded in an append-only causation-graph audit log.

A complete workflow looks like:

You: /project new dispatch python-service
You: /build a FastAPI endpoint at /publish that posts to LinkedIn
You: /push
You: /approve
You: /ship
You: /ship confirm
You: /release prepare
You: /release license mit
You: /release readme
You: /release docs
You: /release flip-public
You: /release flip-public confirm tombarry-maker/dispatch

Twelve Telegram messages take a project from nothing to live-on-Fly + public-on-GitHub. The bot replies between each step with status, review URLs, and confirmation prompts. The whole loop runs on your phone.


Why Foundry exists

Three reasons, in priority order.

Mobile-first agentic development. Most AI development tooling assumes a desktop with a keyboard. Foundry assumes a phone, a Tailscale-bound Mac Mini doing the work, and a workflow that fits into the gaps in a real life — evenings, weekends, the 20 minutes between meetings. The Telegram surface is deliberately the primary interface, not a notification afterthought.

Persistent projects, not throwaway prototypes. Foundry's predecessor, Loom, builds ephemeral one-off prototypes with hard auto-shutdown. Foundry builds projects: multi-week iteration, real git history, a backlog that compounds, a path from prototype to shipped public software. Loom still works for throwaway builds; Foundry adds the persistent layer above it.

Human-gated security all the way to public. The path from "I have a working prototype" to "this is open source on GitHub" is where most personal projects either never make it (too much friction) or leak secrets (too little friction). Foundry encodes the full readiness gate as a deterministic checklist: secret scanning forward (Block 6.5) and backward through history (Block 9), license generation, README/CONTRIBUTING/CODE_OF_CONDUCT/SECURITY templates, confidentiality denylist, personal-info scanning, and a typed-name confirmation defense against chained-paste mistakes on the irreversible public flip. The strongest gate in the project is the one that says "this is now public," and it earned its strength deliberately.


The agentic builder

Foundry doesn't call the Anthropic API directly. There is no anthropic SDK dependency in pyproject.toml. The /build verb spawns a subprocess of Claude Code (the claude CLI binary), which runs against your Claude Max subscription. This is a deliberate architectural choice: Claude Code already handles model dispatch, streaming-output protocol, budget enforcement, and the agentic loop — Foundry composes it rather than reimplementing it.

The exact invocation lives in builder/run.py:

cmd = [
    "claude",
    "-p",
    "--model", "sonnet",
    "--output-format", "stream-json",
    "--verbose",
    "--permission-mode", "bypassPermissions",
    "--max-budget-usd", str(governance.MAX_SPEND_USD),
    "--no-session-persistence",
    "Read your CLAUDE.md and proceed.",
]

Five things worth understanding:

The model alias, not a pinned version. --model sonnet is the alias; Claude Code resolves it to whatever the current Sonnet model is at spawn time. As of v1.0 (May 2026), that's Claude Sonnet 4.6. When Anthropic releases a new Sonnet, Foundry builds use it automatically without code changes. The audit log captures whatever model string the CLI reports in its system/init event, so the actual model used per build is recoverable from the log via event="builder_start". The choice of Sonnet over Opus is deliberate cost/capability balancing — Sonnet 4.6 handles structured, well-spec'd agentic build work effectively at a fraction of Opus's cost; Opus would be overkill for most builds.

Cost math vs. cost cap are different things. builder/run.py carries pricing constants pinned to Sonnet 4.6 standard-tier (input $3/M tokens, output $15/M, cache-read $0.30/M, cache-5m $3.75/M, cache-1h $6/M) for token-cost forecasting and audit-log accounting. The actual spend cap is --max-budget-usd enforced by Claude Code itself. If sonnet resolves to a model with different pricing, the forecasts drift; the cap doesn't.

Permission bypass is deliberate. --permission-mode bypassPermissions means the builder doesn't pause to ask permission for individual file operations. This is the mode that makes overnight unattended builds possible — Foundry compensates for the lack of fine-grained per-file gating with structural defenses: the workspace is sandboxed to ~/foundry/projects/<slug>/, the subprocess env is scrubbed of credentials by foundry/env.py (so the builder cannot reach Anthropic, GitHub, or any other authenticated service of its own accord), all commits go through the pre-commit secret scanner, and gated verbs (/ship, /release flip-public) require explicit human confirmation outside the build loop. The tradeoff is intentional: high build velocity, with the security perimeter drawn at the network and release boundaries rather than at each file write.

Sessions don't persist across builds. --no-session-persistence means each /build starts fresh — no Claude Code conversation memory carries over. The persistent project state lives in BACKLOG.md (cross-build task tracking), CLAUDE.md (project-specific build context), and the git history (commits with full context). The builder reads these on each spawn; it doesn't remember the last conversation. This is the architectural commitment behind cross-build state being filesystem-grounded rather than session-grounded.

The prompt is one sentence. The actual prompt is "Read your CLAUDE.md and proceed." All the substantive instruction lives in the per-project CLAUDE.md (template-scaffolded, then evolved by you) and BACKLOG.md (the build-cycle backlog). The builder doesn't get verbose instructions; it gets context. The discipline is to make that context complete enough to drive correct behavior — there is no fallback prompt-engineering layer to compensate for incomplete CLAUDE.md.

If you want to see what model a specific build actually used, grep the audit log for event="builder_start" and read the model field — Claude Code reports its resolved model in the system/init event and Foundry captures it.


What Foundry doesn't do

Foundry is honest about its scope. The following are deliberately out of scope:

  • Heavy backends. No relational database scaffolding, no schema migrations, no ORM integration. Foundry deploys stateless application code plus a minimal runtime slice (one OAuth token via Fly secrets, one persistent volume). If you need crash-safe relational data, multi-user auth, or scheduled background work, Foundry is not the right tool.
  • Multi-machine orchestration. Foundry runs on a single Tailscale-bound Mac Mini. There is no cluster mode, no remote worker pool, no cloud-deployment of Foundry itself.
  • Multi-user operation. Foundry is single-user by design (one TELEGRAM_CHAT_ID whitelisted at startup). It is not a SaaS, not a team tool, and has no concept of authentication beyond "is this message from the whitelisted chat."
  • Vercel deployment. Templates that need Vercel (nextjs) decline /ship with a sign-posted reason. The TemplateSpec.deploy_backend field structurally accommodates additional backends, but only Fly.io is wired at v1.0. The decline reason directs you to import the repo at vercel.com/new manually.
  • PyPI / GitHub Releases distribution. Templates with no deploy URL (python-cli) decline /ship with a sign-posted reason. Use uv build + uv publish (PyPI) or gh release create (GitHub Releases) directly.
  • Heavy CI/CD integration. No GitHub Actions templates, no auto-release-notes, no semver-bumping automation. /release generates the docs scaffolding; you wire CI yourself if you want it.
  • Flip-back-to-private. The /release flip-public verb is one-way. If you need to flip a repo back to private, run gh repo edit --visibility private directly.

If these gaps would block your use case, Foundry isn't the right tool for that work — and that's fine. The architecture is shaped to be honest about what it does, not to pretend to be everything.


Installation

Foundry is designed to run on a single Tailscale-bound Mac Mini as the headless automation host, with Telegram as the user-facing surface.

Prerequisites

  • macOS with Apple Silicon (tested on M4 Pro, 64GB / 2TB). Linux may work; not tested.
  • Python 3.12+ (per pyproject.toml requires-python = ">=3.12").
  • uv for Python package management.
  • git (system git is fine).
  • git-filter-repo for history scrubbing. Install via uv pip install git-filter-repo (no setup script — install manually).
  • Tailscale for remote access and /preview URLs.
  • gh CLI authenticated to your GitHub account (gh auth login).
  • flyctl authenticated to your Fly account (fly auth login). Installed by setup_deploy_tools.sh.
  • gitleaks 8.30.1 and trufflehog 3.95.3 binaries on PATH. Installed by setup_scanning_tools.sh to ~/.local/bin/ (versions pinned in the script; bump deliberately).
  • A Telegram bot token (BotFather) and your Telegram chat ID.
  • An Anthropic API key for the builder subprocess.

One-time host setup

# Clone
git clone git@github.com:tombarry-maker/foundry.git
cd foundry

# Install secret-scanning binaries (gitleaks 8.30.1 + trufflehog 3.95.3 → ~/.local/bin/)
sh setup_scanning_tools.sh

# Install deploy tooling (flyctl 0.4.52 → ~/.local/bin/)
sh setup_deploy_tools.sh

# Install Python dependencies
uv sync

# Install git-filter-repo for /release scrub-history
uv pip install git-filter-repo

# Run the full test suite (expect 687 passed / 2 skipped / 0 failed at v1.0)
uv run pytest -q

Environment configuration

Create a .env file at the repo root with these three variables:

ANTHROPIC_API_KEY=sk-ant-...
TELEGRAM_BOT_TOKEN=...
TELEGRAM_CHAT_ID=...
  • ANTHROPIC_API_KEY — for the builder subprocess. Scrubbed from all child processes by foundry/env.py before the build subprocess runs (the builder cannot see it; it talks to Anthropic through Claude Code's own auth context).
  • TELEGRAM_BOT_TOKEN — from @BotFather.
  • TELEGRAM_CHAT_ID — your numeric Telegram user ID (Foundry is single-user by design).

.env is gitignored. Never commit it.

Optional environment variables:

  • FOUNDRY_AUDIT_LOG — override the default audit log location. Used by tests to redirect to a tmp path.
  • FOUNDRY_SKIP_SCANNING=1TEST-ONLY. Set by tests/conftest.py at module level to bypass the pre-commit hook during the test suite. Production must NEVER set this variable.

Global Foundry configuration

Create ~/.foundry/global-config.toml with your default copyright holder (used by /release license):

[release]
copyright_holder = "Your Name"
preferred_license = "mit"

One-time external auth

Foundry never holds these tokens — they live in their respective CLI's auth context:

gh auth login        # for GitHub remote creation and public flips
fly auth login       # for /ship and /serve deploys

Running

Foundry runs as a long-lived bot process. Recommended pattern is a tmux session that survives SSH disconnect:

tmux new -s foundry
cd ~/foundry
uv run python -m bot

# Detach with Ctrl-b d; reattach later with: tmux attach -t foundry

You're now connected. Open Telegram, find your bot, and try /project list.


The verb surface

Foundry's complete command surface as of v1.0 — 18 top-level Telegram commands. Each verb is a Telegram command. Subcommands are space-separated arguments. Verified against bot/handlers.py at HEAD e994c37.

Project lifecycle

/project new <slug> [template]      Create a new project workspace. Templates:
                                    web | python-service | python-cli | nextjs.
                                    Scaffolds template files, BACKLOG.md,
                                    .foundry/release-config.toml, .gitleaks.toml,
                                    fly.toml (web/python-service only), git init,
                                    pre-commit hook install.
/project list                       List all projects with current status.
/project switch <slug>              Set the active project for this chat.
/project current                    Show the active project for this chat.
/project archive <slug>             Mark a project archived. Workspace stays;
                                    won't appear in default /project list.
/project remote [<url>]             Show or set the GitHub remote URL.
                                    No url → show current.
/project create-remote <user/repo>  Create the GitHub remote via gh CLI
                                    (private by default; flip via /release
                                    flip-public). Sets origin and pushes
                                    initial main branch.

Build cycle

/build                              Spawn an agentic build subprocess against
                                    the active project's workspace. Bare /build
                                    reads the top "Up next" item from BACKLOG.md.
                                    If that item is shorter than 80 chars,
                                    Foundry replies with a /build confirm
                                    short-spec confirmation prompt rather than
                                    spawning immediately.
/build confirm                      Confirm the short-spec /build proposal.
/build <spec text>                  Spawn with the explicit natural-language
                                    spec. CLAUDE.md and BACKLOG.md are injected
                                    as persistent context.
/build auto                         Same as bare /build (explicit auto-spec).

/iterate <feedback>                 Continue the most recent build with new
                                    feedback. Preserves session state across
                                    Foundry restarts.

/status                             Show the current build's progress: turn
                                    count, elapsed time, last milestone,
                                    pending commit. Refreshes from build session.

/tail <n>                           Show the last N lines of the build's
                                    streaming output. Default 20.

/quiet                              Mute build milestone notifications for the
                                    current session.
/unquiet                            Resume build milestone notifications.

/pause                              Pause the build subprocess (Block 7.3).
                                    Caps freeze; state persists.
/resume                             Resume a paused build.

/kill                               Abandon the current build. Two-step
                                    confirmation: bare /kill replies with the
                                    current state; /kill confirm actually
                                    terminates. Confirmation expires after
                                    60 seconds (KILL_CONFIRMATION_TIMEOUT_SECONDS).
/kill confirm                       Confirm the kill within 60 seconds.
/kill cancel                        Cancel the pending kill.
/kill -y [reason]                   Force-kill without confirmation (escape hatch
                                    for stuck builds).

/approve                            Approve the build's pending commit + push.
                                    Used by /push's two-step flow.

Build caps default to 12 hours pause-aware wall-clock / 500 turns / $100 per session (Block 7's long-running infrastructure). The build state survives Foundry restarts via .foundry/build-session.json.

Backlog

BACKLOG.md lives at the root of each project and is the single source of truth for project state outside the audit log. Build cycles read it as injected context and propose deltas as part of their output, which you accept, reject, or edit.

/backlog                            Same as /backlog show.
/backlog show                       Show the full current backlog.
/backlog status                     Show only Status + In progress + top 3 Up next.

/backlog add <text>                 Append to "Up next".
/backlog add-priority <text>        Insert at position 1 of "Up next".

/backlog start <item-id>            Move "Up next" → "In progress".
/backlog done <item-id>             Move "In progress" → "Done" with commit/
                                    build attribution.
/backlog defer <item-id>            Move "In progress" → bottom of "Up next".
/backlog block <item-id> <reason>   Move to "Blocked" with reason.
/backlog unblock <item-id>          Move "Blocked" → "Up next".

/backlog decision <text>            Add to "Decisions made" with current date.
/backlog question <text>            Add to "Open questions".
/backlog answer <q-id> <answer>     Resolve open question + auto-create a
                                    "Decisions made" entry.
/backlog oos <text>                 Add to "Out of scope (deliberately)".

/backlog accept-delta               Apply a build's proposed delta verbatim.
/backlog reject-delta [reason]      Discard a proposed delta.
/backlog show-delta                 Display the pending delta text.
/backlog edit-delta                 Get the delta text for inline editing
                                    before applying.

/backlog edit                       Reply with the file path; edit BACKLOG.md
                                    directly in your editor of choice.
/backlog history                    Last 10 backlog modifications with
                                    attribution.

Item IDs are short-form ([U1], [B1], [D3]) generated by Foundry and stable until the item changes section.

Secret scanning

Block 6.5 prevents secrets from leaking forward; Block 9's scrub-history (below) cleans up past commits. The /audit verb is the human-readable check.

/audit secrets                      Run gitleaks against working tree + staged
                                    changes. Read-only. Reports findings with
                                    file:line + first 8 chars of the secret
                                    redacted.
/audit secrets-history              Run trufflehog against the entire git
                                    history with credential verification.
                                    Slower (30s–5min). Read-only.
/audit gitignore                    Verify .gitignore covers the Foundry
                                    baseline. Reports gaps.

The pre-commit hook fires automatically on every commit (including build-cycle commits). A detected secret pauses the build with three options:

  • /build resolve fix — you fix the secret manually; the next /iterate retries
  • /build resolve allow — false positive; Foundry adds an inline allowlist comment and proceeds
  • /kill — abandon the build entirely

Foundry-generated commits never bypass the hook. --no-verify does not appear anywhere in Foundry's commit logic.

Push and preview

/push                               Push the active project's main branch to
                                    its GitHub remote. Stages the push and
                                    waits for /approve.
/approve                            Approve the pending push.

/preview                            Start a Tailscale-bound preview of the
                                    project. Returns a private URL only your
                                    Tailscale network can reach.
/preview stop                       Stop the preview.

/preview works for any template that can run a dev server (web, python-service, nextjs); python-cli has no preview by design.

Deploy

/ship                               Deploy the current main branch SHA to its
                                    template's deploy backend. At v1.0, Fly.io
                                    is wired for web and python-service;
                                    nextjs and python-cli decline with
                                    sign-posted reasons. See Templates above
                                    for the full dispatch table.
                                    Two-step: bare /ship stages and shows the
                                    readiness summary; /ship confirm performs.
                                    Refuses to ship code that isn't pushed
                                    (sync precondition).
                                    Confirmation does NOT expire (SP-1 LOCKED).
/ship confirm                       Perform the deploy.
/ship cancel                        Cancel the pending deploy.

/serve register [--yes]             Register the project's Fly app for the
                                    Block 8.6c runtime layer (secret injection
                                    + persistent volume). Two-step via --yes
                                    flag.
/serve unregister [--yes]           Unregister the runtime layer.
/serve start                        Start the registered runtime.
/serve stop                         Stop the registered runtime.
/serve status                       Show registered runtime state.

The Fly API token never enters any Foundry artifact. flyctl owns its own auth context (~/.fly/). /ship calls flyctl auth whoami as a pre-flight (identity, not token) and refuses cleanly if you're not authed.

Open-source release

The capstone block, and the strongest gate in the project. Seven subcommands take a project from "private repo with a working build" to "public on GitHub with a proper open-source posture."

/release prepare                    Run the full pre-flight checklist
                                    (13 readiness checks). Read-only. Reports
                                    PASS/FAIL per check with actionable
                                    symptoms on failures.

/release status                     Quick summary of release readiness.

/release license [id]               Add LICENSE file. Supported:
                                    mit | apache-2.0 | bsd-3 | gpl-v3.
                                    Bare /release license uses
                                    .foundry/release-config.toml's
                                    preferred_license. Replace-existing
                                    requires confirm.

/release readme [--overwrite]       Generate/regenerate README.md from a
                                    template + project state. Marker-anchored
                                    (FOUNDRY:README_GENERATED) — your edits
                                    below the marker are preserved across
                                    regenerations. --overwrite forces
                                    regeneration of a README that has no
                                    marker.

/release docs [--overwrite]         Generate companion files:
                                    CONTRIBUTING.md, CODE_OF_CONDUCT.md
                                    (Contributor Covenant v2.1), SECURITY.md,
                                    .github/ISSUE_TEMPLATE/*, PULL_REQUEST_
                                    TEMPLATE.md. Same marker-anchored
                                    preservation as readme.

/release scrub-history [--yes]      Remove a pattern from the entire git
                                    history via git-filter-repo. DESTRUCTIVE.
                                    --yes flag confirms; without it, the verb
                                    lists matches and refuses. Always creates
                                    a backup branch. Manual force-push is your
                                    responsibility (Foundry does not
                                    auto-force-push).

/release flip-public                Flip the GitHub repo from private to
                                    public via gh CLI. Two-step with the
                                    strongest confirmation in the project:
                                    bare verb renders the gate; confirm
                                    requires the typed nameWithOwner.
/release flip-public confirm <nameWithOwner>
                                    Perform the flip. The typed nameWithOwner
                                    must match the staged repo name EXACTLY
                                    (case-sensitive, no normalization).
/release flip-public cancel         Cancel the pending flip.

The flip-public gate

A bare /release flip-public evaluates four hard pre-flip conditions:

  1. /release prepare passed against the current HEAD. Stale prepare runs fail. The bound is release-state.json's git_head_sha == HEAD.
  2. Scrub-history was evaluated for this project. Either a scrub_history_succeeded event (secrets found and removed) or a scrub_history_noop event (clean check) must exist in the audit log for this project's slug. "Never ran" fails — the question must have been posed.
  3. Fresh secret scan returns zero findings. Run live at gate time; not a cached result.
  4. A LICENSE file exists at the workspace root.

If any condition fails, the gate refuses with specific actionable refusal naming each failed ordinal and its symptom. If all four pass, Foundry stages a pending entry and renders this gate message verbatim (sample with project integ-test and repo tombarry-maker/integ-test):

/release flip-public staged for integ-test.

REPO: tombarry-maker/integ-test  (currently PRIVATE)
TARGET: PUBLIC

Pre-flip checklist (D.5):
  (1) prepare passed — [PASS] HEAD abc1234
  (2) scrub-history ran — [PASS] last: succeeded
  (3) zero scan findings — [PASS] fresh
  (4) license exists — [PASS] LICENSE present

!! THIS IS IRREVERSIBLE AT THE PUBLIC LAYER !!
The gh CLI will run:
  gh repo edit tombarry-maker/integ-test --visibility public --accept-visibility-change-consequences

Consequences (per gh upstream --help, verbatim):
  - Losing stars and watchers, affecting repository ranking
  - Detaching public forks from the network
  - Disabling push rulesets
  - Allowing access to GitHub Actions history and logs

This is a HARD HUMAN GATE — confirmation does NOT expire (SP-1 LOCKED). The confirm sub-verb requires the operator to TYPE the repo's full name as anti-chained-paste friction — no bare `confirm`.

To proceed: /release flip-public confirm tombarry-maker/integ-test
To cancel:  /release flip-public cancel

The typed nameWithOwner is the chained-paste defense. The project's design spec recognizes that the identical slip on flip-public is permanent and unrecoverable, so confirmation cannot be a bare /release flip-public confirm (which a chained paste could trigger). Instead, you must type the exact repo name the gate just rendered — case-sensitive, no normalization, no case-folding. The defense is bound by a regression-oracle test that plants a case-different input and asserts refusal.

Once you confirm with the matching typed name, Foundry mints an authorization token, logs a CRITICAL flip_public_confirmed audit event (the human-authorization moment, threaded into the causation chain), and invokes gh repo edit with both flags. The repo is now public.

There is no flip-back-to-private verb. If you need it, run gh repo edit --visibility private directly.


Templates

Foundry ships four templates. Choose at /project new <slug> <template>. Verified against TEMPLATE_REGISTRY in foundry/projects.py:55-110:

Template Shape Deploy backend Status at v1.0
web FastAPI + HTMX + Jinja2 server-rendered Python app Fly.io Shipped. /ship deploys via flyctl.
python-service FastAPI JSON API + uvicorn Fly.io Shipped. /ship deploys via flyctl.
python-cli Command-line tool, no web surface Declines /ship. A CLI has no server, no URL, no health check. Decline reason directs to uv build + uv publish (PyPI) or gh release create. Your source is already published via /push.
nextjs Next.js application Declines /ship. Vercel owns the Next.js framework and has first-party support for it; Fly is the wrong platform. Decline reason directs to vercel.com/new manual import (your GitHub repo already exists via /push).

Fit-for-purpose deployment as architecture

The deploy backend is not a global Foundry setting; it's a per-template field on TemplateSpec:

TemplateSpec(
    directory="...",
    required_paths=(...),
    deploy_backend="fly",      # or None (no deploy at v1.0)
    decline_reason="..."       # required if deploy_backend is None
)

on_ship reads project.template_type, looks up the TemplateSpec, and dispatches:

  • deploy_backend == "fly"flyctl deploy path.
  • deploy_backend is None → decline with the embedded decline_reason (no half-served deploys, no opaque errors).

This is fit-for-purpose deployment as architecture, not as configuration. The "missing deploy entry" bug class is structurally impossible: a template with deploy_backend=None must also have a decline_reason (a decline_reason is the explicit acknowledgment that this template's deploy path is something other than Fly). The architecture is shaped to accommodate per-template backend dispatch; v1.0 ships one (Fly).

What each template scaffolds

  • Source layout (app/main.py, package structure, etc.)
  • pyproject.toml with pinned dependencies (Python templates)
  • .gitignore with the security baseline (env files, credentials, audit dir)
  • .gitleaks.toml (default rules, from templates/_shared/)
  • .foundry/scanning-config.toml (custom-pattern slots, from templates/_shared/)
  • .foundry/release-config.toml (license preference, copyright holder, denylist, allowlist)
  • BACKLOG.md with template-appropriate starter content
  • CLAUDE.md with project-specific build-cycle context
  • fly.toml (web + python-service only — required for /ship)
  • Pre-commit hook (gitleaks)

Security model

Foundry's security model is built around three principles:

Tokens never live in Foundry. gh, flyctl, and the Telegram bot token are the three external secrets the system uses. The first two are owned by their respective CLI auth contexts (~/.config/gh, ~/.fly/); Foundry never reads, copies, or persists either. The Telegram bot token, the Telegram chat ID, and the Anthropic API key live in .env (gitignored). foundry/env.py actively scrubs ANTHROPIC_API_KEY, GH_TOKEN, and GITHUB_TOKEN from every build subprocess's environment before the subprocess starts — the builder subprocess cannot see those values; it talks to Anthropic through Claude Code's own auth context, and it cannot push to GitHub directly (only Foundry's main process holds the privilege to invoke gh). No token of any kind appears in any audit event, any log file, any persisted state, any commit, or any subprocess captured-output that gets persisted.

Scanning is bidirectional. Block 6.5 prevents leaks going forward (pre-commit hook on every Foundry-generated commit; build-cycle commit blocking on secret detection). Block 9 cleans up past leaks (trufflehog history scan, scrub-history wrapper around git-filter-repo with mandatory backup branch). The gate that lets a project go public requires both directions to be green.

Irreversible actions are human-gated with structural defenses, not just confirmation prompts. The flip-public gate's typed-nameWithOwner confirmation is not a UX nicety — it's a deliberate defense against the specific failure mode of chained-paste mistakes on permanently-unrecoverable actions. The same chained input that would trigger a bare confirm cannot trigger a confirm that requires typing the just-rendered repo name. The defense is encoded as exact string equality with no case-folding, bound by oracle tests that fire on case-fold regression.

The audit log itself is the system's forensic backbone. Every consequential event has a UUID, a parent_uuid pointing back to its causal antecedent, a project_slug, a timestamp, and a structured data payload. The chain runs from project_create through every build, push, deploy, audit-scan, and release event. CRITICAL events (ship_confirmed, ship_started, ship_succeeded, flip_public_confirmed) mark human-authorization moments and are queryable by event type. The log is append-only JSONL — no update or delete operations exist in the codebase.

Audit event taxonomy

Verified by git grep across bot/ foundry/ governance/ remote/ at HEAD e994c37:

flip_public_ (8 events):*

  • flip_public_requested — bare /release flip-public staged a pending entry
  • flip_public_confirmedCRITICAL — confirm path with matching typed name, human-authorization moment
  • flip_public_refused — confirm refused (typed-name missing, mismatch, or project drift)
  • flip_public_declined/release flip-public cancel invoked
  • flip_public_precondition_failed — bare verb refused on failing checklist
  • flip_public_noop — bare verb refused because repo is already PUBLIC
  • flip_public_failed — wrapper flip_repo_visibility_public() raised
  • flip_public_succeeded — wrapper succeeded (logged from remote/github.py)

ship_ (8 events):*

  • ship_requested — bare /ship staged a pending entry
  • ship_confirmedCRITICAL — human-authorization moment
  • ship_startedCRITICALflyctl deploy invoked
  • ship_succeededCRITICALflyctl deploy exited 0
  • ship_declined/ship cancel invoked, or template declined (nextjs/python-cli)
  • ship_refused_unsynced — sync precondition failed (workspace ahead of origin)
  • ship_failedflyctl deploy raised
  • ship_state_persist_faileddeploy-state.json write failed (ambiguous-outcome path)

scrub_history_ (4 events):*

  • scrub_history_succeeded
  • scrub_history_noop
  • scrub_history_refused
  • scrub_history_failed

backlog_ (5 events):*

  • backlog_create
  • backlog_delta_proposed
  • backlog_delta_applied
  • backlog_delta_rejected
  • backlog_delta_none

Plus the Block 6.5 scanning events (gitleaks_scan_complete, trufflehog_scan_complete, gitignore_audit, secret_finding_detected, secret_resolved_fix, secret_resolved_allow, build_paused_for_secrets), the Block 4 build events (build_spawn, builder_complete, build_pause, build_resume, build_kill_*), and project lifecycle events (project_create, project_archive).

If you want to understand a specific decision, trace its parent_uuid chain backward to the root. You will find every decision that led to it, in chronological order, with full structured payloads.


Architecture

foundry/                      Core library
  __init__.py
  audit.py                    Append-only JSONL audit log writer
  authorization.py            SubBlockAuth factories, _FACTORY_IMPORT_ALLOWLIST
  backlog.py                  BACKLOG.md parsing, delta application
  deploy_state.py             /ship deploy-state persistence
  env.py                      Token scrubbing, GIT_CONFIG_GLOBAL isolation,
                              bot_token() and chat_id() accessors
  projects.py                 TEMPLATE_REGISTRY, create_project, project state
  release.py                  FlipPublicChecklist + 4-condition engine + 
                              license/readme/docs generation
  runtime.py                  Block 8.6c /serve runtime layer
  scanning/                   Block 6.5 secret-scanning package
  session_state.py            Build-session state persistence
  state.py                    ActiveState (pending_* fields per gate verb)

bot/                          Telegram bot
  __init__.py
  __main__.py                 Entry point (uv run python -m bot)
  event_tee.py
  handlers.py                 All 18 verbs registered as CommandHandlers
  startup.py

builder/                      Agentic build subprocess orchestration
  run.py

spawner/                      Build-spawn context injection (CLAUDE.md +
                              BACKLOG.md → builder subprocess)
  inject.py

remote/                       External CLI wrappers
  fly.py                      flyctl wrappers (Block 8)
  git_filter_repo.py          scrub-history wrapper (Block 9)
  github.py                   gh CLI wrappers (create_remote_via_gh,
                              read_repo_visibility,
                              flip_repo_visibility_public, push,
                              decide_sync)
  launchctl.py                Block 8.6c launchd integration

preview/
  tailscale.py                Tailscale-bound preview discovery

governance/                   Cross-cutting policy
  release.py                  Release-flow orchestration

templates/                    Project scaffolding
  _shared/                    Cross-template assets (.gitleaks.toml,
                              .foundry-scanning-config.toml)
  web/                        FastAPI + HTMX + Jinja2
  python-service/             FastAPI JSON
  python-cli/                 CLI tool
  nextjs/                     Next.js

tests/                        56 test files
  conftest.py                 Shared fixtures (tmp_audit_log,
                              FOUNDRY_SKIP_SCANNING=1)
  test_block_*.py             Per-block unit/integration test files

audit/                        Audit log directory (gitignored)
docs/                         Project documentation (plans, lessons)

Top-level configuration files: .env (gitignored), .gitignore, pyproject.toml, uv.lock, CLAUDE.md (build-time agent context), BACKLOG.md, README.md (this file), how-to-work-with-this-repo.md, setup_scanning_tools.sh, setup_deploy_tools.sh.

The architecture is intentionally flat. Each block ships a contained surface (one or two new modules, additive edits to existing ones), with the AR-mitigation reviewable-diff pattern preventing structural drift: any module that imports a sub-block authorization factory (for_ship_confirm, for_scrub_history_confirm, for_flip_public_confirm) must be on the factory's allowlist, with the import and the allowlist update landing as an atomic reviewable diff in the same commit.

Foundry is built to be debuggable over magical. When something goes wrong, you can trace it: the audit log gives you the causation chain; the build session JSON gives you the agentic build's full state; the pending-state fields on ActiveState tell you exactly what's awaiting confirmation; the BACKLOG.md tells you what the project thought it was doing.


The discipline

A few things about how Foundry was built that show up in how it works.

State-not-narrative verification. Every load-bearing claim is verified against literal bytes on disk rather than paraphrased from prior context. If you ask /audit secrets, the bot runs the scan and reports the actual output, not a summary of what the scan probably would have said. The build log explicitly records seven inversions (cases where an assertion was made about the codebase without first verifying it against disk); each one is a reminder that the practice of re-grounding from disk before asserting is non-negotiable on a project that gates irreversible actions on disk state.

Sanity-pair test discipline. Every positive assertion is paired with its negative counterpart. test_flip_public_with_all_green_proceedstest_flip_public_requires_prepare_all_green. test_scrub_history_creates_backup_branchtest_scrub_history_dry_run_does_not_create_backup. The test suite at v1.0 is 687 passing / 2 skipped / 0 failed, with every regression oracle bound to a specific behavioral property rather than a happy-path assertion.

Smoke gate every block. Each block ships with its smoke gate before the next begins. Block 1 shipped with a real bug already caught and fixed by its smoke gate: an order-of-operations issue where project metadata was written after the initial git commit, leaving the metadata file untracked. This is the kind of thing the discipline catches in five minutes that survives a year in undisciplined code. The audit/lessons trail accumulates from there.

Bank-and-resume at high-stakes boundaries. Long agentic sessions accumulate cognitive load; high-stakes work (the irreversibility-class crossing in Block 9d, the long-running build infrastructure of Block 7) was deliberately opened in fresh sessions with full attention. This is encoded in the build log; it shows up in the commit messages.

Atomic AR-mitigation diffs. When a new module first imports a privileged factory, the import and the allowlist update land in the same commit. The §5 enforcing test verifies callsite ∈ allowlist via membership-not-equality, so adding a permitted importer requires the atomic diff to stay atomic — split into two commits, the test fails between them.

Comprehension paces velocity. The work isn't yours if you can't explain what it does. Velocity that outruns comprehension is the failure mode the discipline most aggressively guards against.


Provenance

Foundry was scaffolded the same evening Loom shipped — May 14, 2026.

The Loom → Foundry lineage

Loom        Ephemeral prototypes — one-off, throwaway by default.   [shipped May 2026]
Foundry     Persistent single projects — multi-cycle, git history,  [v1.0 May 20, 2026]
            /push, /preview, /ship, /serve, /release.

Each step up the abstraction ladder composes the prior; it doesn't replace it. Loom still works for ephemeral one-off prototypes. Foundry is the right tool when a build is worth keeping.

Commits worth knowing

  • Block 1 (May 14, 2026) — Root scaffolding + persistent project model. First commit. Smoke gate caught and fixed the metadata-after-commit bug.
  • Block 5.5BACKLOG.md per project + proposed-delta workflow. Inserted into the plan after Block 5, when the need for cross-build state became clear.
  • Block 6.5 — Open-source prevention. Split from Block 9 when the prevention/cleanup distinction became architectural.
  • Block 9d (commits 18f850d through e994c37, May 18–20, 2026) — Open-source curation's irreversibility-class subblock. The capstone. Six gated commits across six planned steps, with the chained-paste defense bound by oracle tests and the AR-mitigation atomic diff active. v1.0 sign-off at e994c37.

Contributing

Foundry is currently a single-operator project. Public contribution flows are not yet defined.

If you find a bug or have an idea, open an issue. If you want to discuss the architecture, open a discussion. Pull requests are welcome but expect a high bar — see CONTRIBUTING.md for contribution guidelines and CODE_OF_CONDUCT.md for the participation policy.

See SECURITY.md for the vulnerability reporting process.


License

MIT — see LICENSE. Copyright © 2026 Signal + Pattern LLC.


Acknowledgments

Foundry was built between May 14 and May 20, 2026 on a Mac Mini M4 Pro, primarily on weekends, primarily by Telegram. The discipline of state-not-narrative verification, sanity-pair test pairing, atomic AR-mitigation diffs, and bank-and-resume at high-stakes boundaries was developed across the build and is documented in the project's audit log and lessons file.

Loom, Foundry's predecessor, shipped May 14, 2026 and is still the right tool for ephemeral one-off prototypes.

About

Turn Telegram messages into shipped open-source code. AI-assisted development driven from your phone.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages