Foundry Foundry

Foundry Dispatch — Design Doc (Draft v1)

Status: Draft v1 — refined with Dan, auto-execution model Owner: Dan (Human), Clay (AI Partner) Date: 2026-04-09


TL;DR — Use Foundry as an async dispatch queue between Cowork (refinement, iPad) and Claude Code (execution, Mac). No custom MCP server. No standalone bridge. Foundry is the bridge. Dan dispatches stories from his iPad at work, CC auto-executes at home, results appear as annotations + status updates in Foundry.


Problem Statement

Dan's workflow splits across two environments:

  • iPad (anywhere, usually at work): Refine docs, think through designs, review results — via Cowork + Foundry
  • Mac (home): Execute code, run tests, ship PRs — via Claude Code (Clay)

These tools don't talk to each other. Cowork can't spawn CC. CC can't reach into a Cowork session. Today the handoff is manual: refine in Cowork, context-switch to the Mac, tell Clay what to do, check results, go back to Cowork.

The insight: Foundry is already the shared surface. Both Cowork and CC can read and write to it via MCP. The "bridge" isn't a new system — it's a dispatch convention on top of Foundry.

The goal: Dan hits a button on a story doc from his iPad. CC picks it up at home, executes it, and Dan sees the result next time he checks.


How It Works

The Loop

  Dan (iPad, Cowork)                    Foundry                     Clay (Mac, CC)
  ──────────────────                    ───────                     ──────────────

  1. Refines story doc            ───►  Story doc updated

  2. Hits "Execute" button              Dispatch annotation         
     or AI Lead creates           ───►  created (status: ready)     
     dispatch annotation                                            
                                        ┌─── Status: READY (🟠) ───┐
                                        │                           │
                                        │  3. Dispatch runner   ◄───┤ (polling every 60s)
                                        │     picks up dispatch     │
                                        │                           │
                                        │  4. Verifies:             │
                                        │     - user_id is trusted  │
                                        │     - repo is whitelisted │
                                        │                           │
                                        └─── Status: RUNNING (🔵) ─┘
                                                                    
                                        5. CC spawns intern in      
                                           worktree, executes       
                                           story autonomously       
                                                                    
                                        ┌─── Status: DONE (🟢) ────┐
                                        │    or FAILED (🔴)         │
                                        │                           │
                                        │  6. Result annotation ◄───┤ CC writes back:
                                        │     appears on doc        │ PR link, test results,
                                        │                           │ pass/fail, summary
                                        └───────────────────────────┘
                                        
  7. Dan sees result on iPad  ◄─────── Status badge + annotation
     whenever he checks                 visible on the story doc

No Human Gate (Post-E12)

This is the target workflow. Dan dispatches from his iPad, walks away, and CC handles it. The security boundary is Foundry auth (E12), not a human confirmation step.

Interim Workflow (Pre-E12)

Until E12 ships, CC does NOT auto-execute. Instead:

  • Dan tells Clay directly: "run the story at projects/foundry/epics/e12/story-3"
  • Clay reads it from Foundry, confirms intent, executes, writes back
  • Same loop, just manually triggered instead of polled

Dispatch Convention

Dispatch Annotation Schema

{
  "type": "dispatch",
  "status": "ready | running | complete | failed | cancelled",
  "user_id": "dan",
  "doc_path": "projects/foundry/epics/e12/story-3.md",
  "body": {
    "repo": "/Users/danhannah/projects/foundry",
    "base_branch": "main",
    "notes": "Focus on the API route changes, skip the UI for now",
    "dispatched_at": "2026-04-10T14:30:00Z"
  }
}

Status Lifecycle

  ready (🟠)  ──►  running (🔵)  ──►  complete (🟢)
                       │                    
                       ├──►  failed (🔴)    
                       │                    
                       └──►  cancelled (⚪)  
StatusColorMeaningSet By
ready🟠 OrangeWaiting for pickupDan / AI Lead
running🔵 BlueCC is actively executingDispatch runner
complete🟢 GreenStory executed, PR openedDispatch runner
failed🔴 RedExecution failedDispatch runner
cancelled⚪ GrayCancelled before completionDan / AI Lead

Completion Annotation

On terminal state, CC writes a result annotation back to the story doc:

On success:

Story executed successfully.
Branch: story/auth-middleware-a1b2c3
PR: https://github.com/danhannah94/foundry/pull/114
Tests: 47 passed, 0 failed
Duration: 4m 32s
Summary: Added requireAuth middleware to /mcp/* routes...

On failure:

Story execution failed (exit code 1).
Error: Test suite failed — 3 assertions broken in reviews.test.ts.
Branch: story/auth-middleware-a1b2c3 (no PR opened)
Duration: 2m 15s
Last 20 lines of output:
[truncated stderr]

Foundry UI — Dispatch Experience

Story Doc: Execute Button

A button on story docs that creates a dispatch annotation. Similar pattern to the existing "Submit Review" button.

┌─────────────────────────────────────────────────────┐
│  📄 E12 Story 3: Auth Middleware                    │
│                                                     │
│  ## Acceptance Criteria                             │
│  - [ ] /mcp/sse requires Bearer token              │
│  - [ ] /mcp/message requires Bearer token          │
│  ...                                                │
│                                                     │
│  ┌─────────────────────────────────────────────┐    │
│  │  ⚡ Execute Story                            │    │
│  │                                              │    │
│  │  Repo:   /Users/danhannah/projects/foundry   │    │
│  │  Branch: main                                │    │
│  │  Notes:  [optional textarea]                 │    │
│  │                                              │    │
│  │           [ Cancel ]  [ 🚀 Dispatch ]        │    │
│  └─────────────────────────────────────────────┘    │
│                                                     │
└─────────────────────────────────────────────────────┘

Repo field: Could be a dropdown of whitelisted repos, or freeform with validation. Dropdown is safer.

Where does this button live? Options:

  • In the review/annotation sidebar (next to Submit Review)
  • As a floating action button on the doc
  • In a ## Dispatch metadata section at the bottom
  • In the doc header/toolbar area

Story Doc: Status Badge

A visual indicator on the story doc showing dispatch status. Visible at a glance without opening the full annotation list.

┌─────────────────────────────────────────────────────┐
│  📄 E12 Story 3: Auth Middleware    🔵 Running 4m   │
│                                                     │
│  ...                                                │
└─────────────────────────────────────────────────────┘

Or in a more detailed format in the sidebar:

┌──────────────────────────┐
│  ⚡ Dispatch Status       │
│                          │
│  🔵 Running              │
│  Started: 2:30 PM        │
│  Elapsed: 4m 32s         │
│  Repo: foundry           │
│                          │
│  [ Cancel Run ]          │
└──────────────────────────┘

After completion:

┌──────────────────────────┐
│  ⚡ Dispatch Status       │
│                          │
│  🟢 Complete             │
│  Duration: 4m 32s        │
│  PR: #114                │
│  Tests: 47 ✓  0 ✗        │
│                          │
│  [ View PR ] [ Re-run ]  │
└──────────────────────────┘

Pages List: Status Indicators

On the main pages list, story docs with active dispatches show a status dot:

  projects/foundry/epics/e12/
    story-1.md          🟢
    story-2.md          🟢
    story-3.md          🔵 4m
    story-4.md          🟠
    story-5.md

Stories with no dispatch have no indicator. At a glance, Dan can see: stories 1-2 are done, 3 is running, 4 is queued, 5 hasn't been dispatched yet.


Security Model

The Threat

The dispatch annotation is a prompt that CC will execute with file-edit permissions on Dan's Mac. If an attacker can create or modify a dispatch annotation in Foundry, that's prompt injection → RCE.

The Kill Chain

Attacker creates dispatch annotation in Foundry
        ↓
Dispatch runner picks it up (auto-execution, no human gate)
        ↓
CC executes with acceptEdits in a worktree
        ↓
Arbitrary code execution on Dan's Mac (scoped to worktree)

Defense Layers (Post-E12)

Layer 1: Foundry Auth (E12) — WHO can dispatch

  • Only authenticated users can create annotations
  • Dispatch runner verifies user_id on the annotation matches a trusted list
  • Trusted users: dan, configured AI Lead identities
  • Unknown user_id → dispatch rejected, annotation updated to cancelled with reason

Layer 2: Repo Whitelist — WHERE CC can execute

  • Dispatch runner maintains a config of allowed repos
  • Dispatch pointing at a non-whitelisted path → rejected

Layer 3: Worktree Isolation — blast radius containment

  • Every dispatch runs in a fresh git worktree
  • CC runs with acceptEdits permission mode (not --allow-all-tools)
  • Worktree is a sandbox — main repo is read-only from CC's perspective
  • Failed/abandoned worktrees are cleaned up on a TTL (24h default)

Layer 4: CC's Own Permission System — defense in depth

  • CC's built-in permission model governs what tools the intern can use
  • Even inside the worktree, destructive operations require explicit permission config

Pre-E12 Interim: Human Gate

Until E12 ships, auto-execution is disabled. The dispatch runner doesn't exist yet. Instead Dan manually tells Clay to execute a story. Same security as today — Dan is in the loop.

Auth Dependency Map

FeatureRequires E12?Auto-executes?
Manual dispatch ("Clay, run story X")NoNo — Dan is present
Dispatch runner (polling + auto-exec)YesYes — that's the point
Execute button in Foundry UIYesYes — button creates annotation, runner picks up

E12 is the hard blocker for the target workflow. No E12 = no auto-execution = no "dispatch from iPad while away."


Mac-side: Dispatch Runner

A simple daemon that polls Foundry and spawns CC when work is found. NOT an MCP server. Just a script.

Core Loop

POLL_INTERVAL = 60  # seconds
TRUSTED_USERS = ["dan"]
ALLOWED_REPOS = [
    "/Users/danhannah/projects/foundry",
    "/Users/danhannah/projects/routr",
]

async def poll():
    while True:
        dispatches = await foundry.search_annotations(
            type="dispatch", status="ready"
        )
        for d in dispatches:
            if d.user_id not in TRUSTED_USERS:
                await foundry.update_annotation(
                    d.id, status="cancelled",
                    body="Rejected: untrusted user_id"
                )
                continue
            if d.body.repo not in ALLOWED_REPOS:
                await foundry.update_annotation(
                    d.id, status="cancelled",
                    body="Rejected: repo not in allowlist"
                )
                continue
            asyncio.create_task(execute(d))
        await asyncio.sleep(POLL_INTERVAL)

async def execute(dispatch):
    await foundry.update_annotation(dispatch.id, status="running")
    story = await foundry.get_page(dispatch.doc_path)
    prompt = assemble_prompt(story, dispatch.body.notes)
    result = await run_claude(
        prompt=prompt,
        repo=dispatch.body.repo,
        branch=dispatch.body.base_branch,
    )
    if result.success:
        await foundry.create_annotation(
            doc_path=dispatch.doc_path,
            body=format_success(result),
        )
        await foundry.update_annotation(dispatch.id, status="complete")
    else:
        await foundry.create_annotation(
            doc_path=dispatch.doc_path,
            body=format_failure(result),
        )
        await foundry.update_annotation(dispatch.id, status="failed")

Deployment

  • Runs as a launchd service on Dan's Mac (survives reboots, restarts on crash)
  • Config lives at ~/.foundry-dispatch/config.json
  • Logs to ~/.foundry-dispatch/logs/
  • No Docker, no cloud, no CI/CD — it's a local daemon

Size Estimate

~150-200 lines of Python. Dependencies: httpx, asyncio, structlog. That's it.


What Needs to Be Built

Phase 1: E12 — Foundry Auth (BLOCKER)

  • OAuth 2.0 + identity on Foundry
  • user_id on annotations is trustworthy (not spoofable)
  • Design doc already drafted (570 lines, parked at local gitignored path)
  • This is the critical path. Everything else is blocked on this.

Phase 2: Dispatch Convention + Runner

  • Annotation schema (type, status, body fields)
  • Dispatch runner script (~150 lines Python)
  • launchd plist for daemon mode
  • Config file (trusted users, allowed repos)
  • Effort: 1 evening

Phase 3: Foundry UI — Execute Button + Status

  • "Execute Story" button component (similar to Submit Review)
  • Status badge on doc header
  • Status dots on pages list
  • Dispatch status panel in sidebar
  • Effort: 1-2 evenings (Astro components, mostly frontend)

Phase 4: Polish + Iteration

  • Elapsed time display (live-updating while running)
  • "Re-run" button for failed dispatches
  • "Cancel" button for running dispatches
  • Worktree cleanup policy (auto-delete on PR merge or TTL)
  • Effort: Ongoing, as the workflow reveals friction

What Does NOT Need to Be Built

  • Standalone MCP server
  • SQLite run store + state machine
  • Process control (PID tracking, SIGTERM/SIGKILL)
  • Async orchestrator
  • uv packaging + pyproject.toml
  • Slack integration
  • The entire original foundry-cc-bridge

What About the Original Bridge Design?

The foundry-cc-bridge design doc at projects/foundry-cc-bridge/design-draft was solving a real problem with too much infrastructure.

Keep (ideas, not code):

  • Worktree-per-run isolation (CC does this natively)
  • Foundry annotation write-back (the feedback loop)
  • The "bridge is dumb" principle
  • The security analysis (prompt injection risk is real)

Drop (all of it):

  • The standalone Python MCP server
  • Everything in the Phase Plan (v0-v4)
  • The entire tool surface (execute_story, get_run_status, etc.)
  • SQLite, process control, packaging

The original bridge was ~2 weeks of work. This version is ~2-3 evenings total (after E12).


Open Questions

  1. Execute button placement. Sidebar (near Submit Review)? Doc header/toolbar? Floating action button? Need to see what feels natural in the existing layout.

  2. Repo field UX. Dropdown of whitelisted repos (safer, less flexible) or freeform path with validation (more flexible, riskier)? Leaning dropdown — the whitelist is the security boundary, might as well make it the only option.

  3. Elapsed time — live or polled? The status badge could show live elapsed time (WebSocket/SSE from Foundry) or polled (refresh to see updated time). Live is nicer but more complex. Polled is fine for v1.

  4. Result annotation density. What does Dan actually want to see on his iPad? Minimum: status + PR link. Maximum: full test output + diff stats + duration + branch name. Need to find the sweet spot.

  5. What makes a story "dispatchable"? Does the story need a specific structure? Or is any doc dispatchable? Leaning: any doc is dispatchable, the story quality is the AI Lead's responsibility.

  6. How does csdlc:craft-agent-prompt interact? That skill already turns refined tickets into sub-agent prompts. The dispatch runner's prompt assembly step might just be a headless version of that skill. Worth merging or at least sharing the prompt template.

  7. Multiple dispatches on one doc? Can Dan re-dispatch a story after a failure? After a success (for iteration)? Probably yes — each dispatch is a separate annotation. The UI shows the latest one's status. History is the annotation thread.


Decisions Log

DateDecisionRationale
2026-04-09Foundry IS the bridge, not a separate MCP serverBoth tools already speak Foundry MCP. Adding a third system is unnecessary complexity.
2026-04-09Auto-execution is the target (post-E12)Dan works remote from iPad. The whole point is fire-and-forget dispatch. Human gate is interim only.
2026-04-09E12 (Foundry auth) is the hard blockerAuto-execution without auth = RCE via prompt injection. Non-negotiable.
2026-04-09Annotation-based dispatchDispatch is metadata, not content. Keeps story docs clean. Status lives on the annotation.
2026-04-09Dispatch runner is a simple Python daemon~150 lines, launchd service, polls every 60s. Not an MCP server, not a package.
2026-04-09Repo whitelist is a security boundaryCC only executes in pre-approved directories. Config file, not code.
2026-04-09Trusted user list on dispatch annotationsOnly user_id values in the trusted list can trigger execution.
2026-04-09Original bridge design doc is archivedGood analysis, wrong solution shape. Keep for reference, don't build it.
2026-04-09GitHub mobile notifications for PR resultsNo Slack integration needed. GitHub already pushes on PR open/review/checks.
2026-04-09Worktree isolation for every dispatch runBlast radius containment. Main repo is read-only.

Visual Design Exploration (TODO)

The UI components need iteration. Key screens to mock:

  1. Execute button + dispatch form — where does it live, what fields, what the interaction feels like
  2. Status badge on doc header — how it looks in the three states (waiting, running, done/failed)
  3. Status dots on pages list — information density, color choices, elapsed time formatting
  4. Dispatch status panel — sidebar detail view with result summary, PR link, re-run/cancel actions
  5. Mobile (iPad) experience — these screens need to work great on iPad since that's the primary consumption device

This doc supersedes the foundry-cc-bridge design at projects/foundry-cc-bridge/design-draft. The original is preserved for reference but is no longer the plan.

Review

🔒

Enter your access token to view annotations