Foundry Foundry

foundry-cc-bridge — Design Doc (Draft)

Status: Draft v0 — scaffold for collaborative refinement Owner: Dan (Human), Cowork session (AI Lead) Related: CSDLC Thesis, Process


TL;DR — A small MCP server that hands refined stories from the Cowork AI Lead to Claude Code sub-agents, runs them in isolated git worktrees, and reports back via Foundry annotations. Closes the execution loop in CSDLC without mid-flight human gates.


Overview

What Is This?

foundry-cc-bridge is a Model Context Protocol server that sits between the Cowork AI Lead and Claude Code sub-agents. The AI Lead, after refining a story to "ready for execution" in Foundry, calls a single tool (execute_story) on the bridge. The bridge creates an isolated git worktree, assembles a prompt from the story doc plus repo conventions, spawns Claude Code with that prompt, captures the result (tests, diff, PR link), and reports back via a Foundry annotation write-back and/or Slack notification.

It exists because today the AI Lead → sub-agent handoff is entirely manual: copy the refined story into a terminal, paste context, invoke claude, watch, come back. The CSDLC thesis treats that as the critical path friction — refinement quality is wasted if the execution step still requires a human babysitter.

Who Is It For?

Exactly one user archetype in v1: Dan, running the Claymore methodology across his own projects. No multi-tenant, no team features, no hosted option. If the pattern proves out, v2+ can generalize.

Design Principles

  • The bridge is dumb. All intelligence lives in the AI Lead's prompt-crafting and the story doc itself. The bridge only executes.
  • One tool call, one story. v1 does not batch, does not orchestrate multi-story epics. That's the AI Lead's job.
  • Worktrees are cheap; sessions are expensive. Every run gets a fresh worktree. Claude Code sessions are spawned per run, not pooled.
  • Foundry is the source of truth. Story doc, completion status, review annotations — all live in Foundry. The bridge is stateless-ish (just a runs table).
  • Security by locality. v1 runs on Dan's Mac, inherits Dan's git/GitHub credentials. No remote hosting until the threat model is re-examined.

Tech Stack

LayerTechnologyRationale
LanguagePython 3.12Dan's primary language; rich subprocess/asyncio; matches Anvil's stack
MCP SDKmcp (official Python SDK)First-party, actively maintained
Transportstdio or localhost Streamable HTTPLocal-only. stdio preferred if Cowork supports it; otherwise 127.0.0.1 HTTP + bearer token
Process controlasyncio.create_subprocess_exec driving claude CLIclaude -p headless with --output-format stream-json
Git operationsgit CLI via subprocessWorktree support is cleanest via CLI
Run storeSQLite via aiosqliteSingle-file, zero-ops, survives bridge restarts
Foundry integrationHTTPS calls to Foundry API (reuse FOUNDRY_WRITE_TOKEN)Bridge posts completion annotations back to the story doc
Notifications (v2)Slack Web API via slack_sdkFirst-party connector in Cowork; phone push works out of the box
Packaginguv + pyproject.tomlFast installs, lockfile, Dan's standard

Key Libraries

mcp, aiosqlite, httpx, pydantic v2, structlog. Nothing surprising.


System Architecture

Data Flow

  Cowork session (AI Lead)
         │
         │ 1. refines story to ready-for-execution in Foundry
         │ 2. calls execute_story(story_path, repo, ...) via MCP
         ▼
  ┌─────────────────────────┐
  │   foundry-cc-bridge     │
  │   (local daemon)        │
  │                         │
  │  ┌──────────────────┐   │
  │  │  MCP server      │◄──┼─── stdio / localhost HTTP
  │  └──────────────────┘   │
  │         │               │
  │         ▼               │
  │  ┌──────────────────┐   │
  │  │  Run orchestr.   │   │
  │  │  (asyncio)       │   │
  │  └──────────────────┘   │
  │    │        │     │     │
  │    ▼        ▼     ▼     │
  │  SQLite  Git    Claude  │
  │  (runs)  CLI    Code    │
  │                 CLI     │
  └─────────────────────────┘
         │           │
         │           ▼
         │    Worktree @ ../worktrees/<story-slug>
         │           ▼
         │    `claude -p` headless run
         │           ▼
         │    git push + gh pr create
         │
         ▼
  Foundry API  ←── annotation write-back
         │
         ▼
  Slack DM     ←── phone notification (v2)

Layer Descriptions

MCP Server Layer. Pure transport + schema. Receives tool calls, validates args against pydantic models, hands work to the orchestrator, streams progress back as MCP progress notifications. Knows nothing about git or Claude Code.

Run Orchestrator. The brain of the bridge. State machine: pending → preparing_worktree → running → capturing → reporting → complete | failed | cancelled. Persists every transition to SQLite so the bridge can survive a restart mid-run.

Git Adapter. Thin wrapper around git worktree add/remove, git status, git diff. All git ops happen in the worktree directory; the main repo is read-only from the bridge's perspective.

Claude Code Adapter. Spawns claude -p <prompt_file> --output-format stream-json --permission-mode acceptEdits in the worktree, streams stdout/stderr to a run log, waits for exit. Parses the stream-json to extract final diff summary, test results, and any PR link the agent created.

Foundry Reporter. HTTPS client that posts an annotation to the original story doc via create_annotation. Uses FOUNDRY_WRITE_TOKEN. Best-effort — the run is considered complete even if reporting fails.


Data Model

Two stores: SQLite at ~/.foundry-cc-bridge/bridge.db for the persistent runs table, and filesystem under ~/.foundry-cc-bridge/runs/<run_id>/ for per-run artifacts (prompt, stdout.log, stderr.log, diff.patch, result.json).

class RunStatus(str, Enum):
    PENDING = "pending"
    PREPARING = "preparing"
    RUNNING = "running"
    CAPTURING = "capturing"
    REPORTING = "reporting"
    COMPLETE = "complete"
    FAILED = "failed"
    CANCELLED = "cancelled"

class Run(BaseModel):
    run_id: str                    # ulid
    story_path: str                # foundry doc path
    story_heading: str | None      # optional section anchor
    story_title: str               # denormalized for display
    repo_path: Path                # absolute path to main repo
    base_branch: str               # e.g. "main"
    worktree_path: Path            # absolute path to worktree
    feature_branch: str            # "story/<slug>-<short-ulid>"
    status: RunStatus
    pid: int | None
    started_at: datetime
    ended_at: datetime | None
    exit_code: int | None
    pr_url: str | None             # populated if CC opened a PR
    test_summary: dict | None      # parsed from stream-json
    foundry_annotation_id: str | None
    error: str | None

One Run per execute_story call. Runs are immutable after reaching a terminal state. No foreign keys — runs reference Foundry docs by path string (Foundry is the system of record).

pending ──► preparing ──► running ──► capturing ──► reporting ──► complete
                │            │            │             │
                └────────────┴────────────┴─────────────┴──► failed
                                │
                                └──► cancelled  (via cancel_run tool)

Deployment & Infrastructure

Dependencies

  • Claude Code CLI installed and authenticated on the Mac (claude on PATH)
  • git ≥ 2.20 (for worktree support)
  • gh CLI authenticated (for PR creation — invoked by Claude Code, not the bridge directly)
  • Foundry API reachable (currently https://foundry-claymore.fly.dev)
  • Slack MCP connected in Cowork (v2, for push notifications)

If Foundry is down, runs still execute and complete — they just don't get an annotation write-back. The bridge logs the failure and moves on.

Installation

uv tool install foundry-cc-bridge (or uv tool install -e . from a local checkout during development). No separate release pipeline. No staging environment — local IS production in v1.


Security Model

Known Attack Surfaces

  1. Prompt injection via Foundry story doc. The AI Lead crafts the prompt for Claude Code, but the story content comes from Foundry, which has been observed to accept unauthenticated writes on /mcp/* routes. A malicious story doc could contain instructions that alter CC's behavior. Mitigation v1: the AI Lead is the only reader of story docs before handoff, and Dan trusts his own Foundry instance. Mitigation v2: Foundry auth hardening (already on the Foundry bug list); the bridge could also diff-check the story doc against a signed version.
  2. Arbitrary code execution via Claude Code. CC runs with acceptEdits permission mode in the worktree. It can modify files, run tests, install dependencies, and push branches. This is by design — that's the whole point — but it means a compromised prompt is effectively RCE on Dan's machine, scoped to the worktree. Mitigation: worktree isolation limits blast radius; CC does NOT get --allow-all-tools by default; sensitive operations are governed by CC's own permission system.
  3. Token leakage. FOUNDRY_WRITE_TOKEN lives in the bridge's env. GitHub PAT lives in gh CLI's auth store. Bridge logs redact tokens.

Auth Summary

  • Bridge ← Cowork: stdio or localhost HTTP with bearer token. No exposed network surface.
  • Bridge → Foundry: FOUNDRY_WRITE_TOKEN env var.
  • Bridge → GitHub: inherited from gh CLI auth.
  • Bridge → Claude Code: inherited from claude CLI auth.

Tool Surface (v1)

ToolPurposeSync / Async
execute_storyKick off a CC run for a refined storyAsync — returns run_id immediately
get_run_statusPoll a run's status, partial logs, final resultSync
list_runsList active + recent runs (last 50)Sync
cancel_runKill a running CC subprocess, clean worktreeSync
get_run_logsFetch stdout/stderr tail for a runSync

execute_story signature

class ExecuteStoryArgs(BaseModel):
    story_path: str              # Foundry doc path
    story_heading: str | None    # Optional heading anchor within the doc
    repo_path: str               # Absolute path to the main repo on local disk
    base_branch: str = "main"
    prompt_preamble: str | None  # AI Lead injects additional context
    allowed_tools: list[str] | None  # Default: edit+bash+test
    max_turns: int = 50          # CC turn budget

The AI Lead is expected to have already read the story doc via Foundry MCP, decided it's ready for execution, and now hands the path off. The bridge re-reads the story doc at execution time to ensure freshness.

Prompt Assembly

The bridge builds the CC prompt from:

  1. A fixed preamble (role, output format, test/PR conventions)
  2. The story doc content (fetched fresh from Foundry)
  3. Repo-specific CLAUDE.md if present in repo_path
  4. Any prompt_preamble passed by the AI Lead (for last-mile context)

Written to PROMPT.md inside the worktree before claude -p is invoked. Preserved on disk for audit.

Completion Signaling

Two channels, in order of importance:

  1. Foundry annotation write-back (primary, always-on). On run complete/failed, the bridge posts an annotation to the original story doc: "Run {run_id} {status}. Branch: {feature_branch}. PR: {pr_url}. Tests: {test_summary}." The AI Lead sees this on the next get_page or list_annotations call — which is the natural next thing to do when Dan says "any updates on that story?"
  2. Slack DM (v2, optional). Bridge posts the same message to a configured Slack DM channel. Phone push notifies Dan even if Cowork isn't open. Dan triages from his phone and opens iPad remote desktop if he wants to dig in.

Why This Matters

The bridge is the piece that lets the AI Lead actually close the loop in CSDLC. Without it, "ready for execution" is an aspirational label — something a human still has to act on. With it, the AI Lead can say "I've refined story 3, I'm kicking off execution now" and mean it. That's the CSDLC thesis made operational.


Phase Plan

Each phase is independently shippable. We don't start a phase until the previous one is running in production on Dan's CSDLC work.

v0 — Hello World (1-2 evenings). Python package skeleton, uv setup, MCP SDK wired up. Single tool: execute_story(repo_path, prompt_text) — no Foundry integration, takes a raw prompt. Runs claude -p synchronously, waits for completion, returns stdout. No worktree, no SQLite, no state. Success metric: AI Lead can call execute_story from Cowork and see CC's output.

v1 — Real Execution Loop (1 week). Fetch story doc from Foundry by path. Git worktree isolation, auto-generated branch names. SQLite runs table, async orchestrator, status polling via get_run_status. Prompt assembly from story + repo CLAUDE.md + preamble. Success metric: AI Lead refines a story in Foundry, calls execute_story, polls until complete, reviews the PR.

v2 — Async + Write-back (3-5 days). Run returns immediately with run_id; execution happens in background asyncio task. Foundry annotation write-back on completion. cancel_run, list_runs, get_run_logs tools. Success metric: AI Lead kicks off execution, moves on to refining the next story, sees the annotation appear when the first one finishes.

v3 — Phone Notifications (2-3 days). Slack DM channel configured via env var. Bridge posts run-complete message to Slack on terminal state transitions. Same content as Foundry annotation. Success metric: Dan gets a push notification on his phone when a run completes while he's away from the Mac.

v4 — Concurrency + Polish (deferred). Remove the single-run mutex; support N concurrent runs. Worktree cleanup policy (auto-delete on PR merge via webhook). Run timeout + force-kill. Structured run reports.


Risks & Constraints

Known Limitations

  • Single-user, single-host. Bridge runs on Dan's Mac. Won't work from a different machine unless we add HTTP + real auth.
  • No concurrent runs in v1. The orchestrator is designed for it, but v1 ships with a mutex to keep the first version debuggable.
  • Worktrees accumulate. Cleanup policy is "delete on successful PR merge" — manual check required for failed runs.
  • CC turn budget caps complexity. Stories that need >50 turns will fail. AI Lead should refine stories small enough to fit.

Technical Risks

RiskLikelihoodImpactMitigation
claude -p stream-json format changesMediumBridge can't parse resultsPin CC version; treat parse failure as "unknown result" not crash
Foundry goes down mid-runMediumNo write-back annotationRetry with exponential backoff, then log and continue
Prompt injection from malicious story docLow (v1)RCE scoped to worktreeWorktree isolation; Foundry auth hardening on roadmap
Worktree conflicts with main repo stateLowFailed git worktree addUnique branch names per run (ulid suffix)
CC hangs indefinitelyMediumOrphaned process, blocked mutexPer-run timeout (default 30min) with SIGTERM then SIGKILL

Decisions Log

DateDecisionRationaleAlternatives
2026-04-08Standalone project, not embedded in FoundryClear separation: Foundry owns docs, bridge owns executionEmbed in Foundry API; CC subagent in Cowork itself
2026-04-08Python over TypeScriptDan's primary language; cleaner async subprocess storyTypeScript (matches Foundry stack); Go (single binary)
2026-04-08Local-only, not hostedInherits gh/claude/git auth for free; eliminates remote-auth threat modelHost on fly.io; Dan's homelab
2026-04-08Primary completion signal is Foundry annotation write-backNatural fit for "Dan asks for updates" workflowCowork notification (doesn't exist); webhook to session (can't inject)
2026-04-08iPad remote desktop is the inbound mobile UX, NOT a custom messaging MCPPreserves session continuity; no injection surface; ~0 setup costCustom Telegram MCP; Dispatch-only
2026-04-08Git worktrees, one per runIsolation; parallelism-ready; easy cleanupShared repo w/ branch switching; Docker
2026-04-08Single mutex in v1Debuggability > throughputFull concurrent orchestrator (deferred to v4)

Open Questions

These are things to push on during refinement. Each is a real decision that could swing the architecture.

1. Transport: stdio or localhost HTTP? Cowork's current docs say it doesn't load stdio MCPs from claude_desktop_config.json. But the connector UI accepts remote HTTPS URLs — does it accept http://127.0.0.1:8765? Action: try adding it as a connector during v0 and see what happens.

2. Foundry as shared async queue / chat substrate. Separate from the bridge itself, should we stand up a Foundry page template for "session chat" — where Dan can post messages from his phone during the day and the parent Cowork session batch-polls them on demand? This doesn't solve session continuity (the session still only advances on Cowork turns), but it gives us a durable async queue and a shared memory surface between the parent session and any Dispatch-spawned ephemeral sessions. Leaning: yes, but as its own mini-project.

3. Dispatch integration. Any programmatic way to start a Dispatch session from an MCP tool? Registry says no, and the security argument says there never will be. Answer: no. Dispatch stays a manual fallback. Don't design for it.

4. Prompt template source of truth. The fixed preamble — lives in the bridge source? In Foundry as a doc the bridge fetches? In the repo's CLAUDE.md? Foundry is tempting for refinability, but creates a circular dependency (bridge needs Foundry to start a run). Leaning: embed a minimal default in bridge source, allow override via a Foundry path passed in tool args.

5. Auth model if we ever go remote. If v2+ moves the bridge off Dan's Mac, how does it authenticate to GitHub? Bot PAT? GitHub App? Status: flag and defer.

6. Multi-repo support. execute_story takes repo_path per call, so technically already multi-repo. But assumes claude and gh are authenticated once and work everywhere. If Dan has private repos across multiple GitHub accounts, this breaks. Leaning: punt until it's actually a problem.

7. Worktree location. ../worktrees/<slug> (sibling of main repo) or ~/.foundry-cc-bridge/worktrees/<slug> (centralized)? Sibling is nicer for IDE auto-discovery; centralized is nicer for cleanup. Leaning: sibling for v1.

8. What does "refined enough to execute" mean, formally? The AI Lead decides this via vibes today. Should the bridge enforce any contract (e.g. story doc must have an ## Acceptance Criteria section)? Leaning: AI Lead's job. The bridge stays dumb.


This draft lives at projects/foundry-cc-bridge/design-draft. Once refined, it will replace the template at projects/foundry-cc-bridge/design (which is currently in a broken state due to tool quirks during drafting).

Review

🔒

Enter your access token to view annotations