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 (⚪)
| Status | Color | Meaning | Set By |
|---|---|---|---|
ready | 🟠 Orange | Waiting for pickup | Dan / AI Lead |
running | 🔵 Blue | CC is actively executing | Dispatch runner |
complete | 🟢 Green | Story executed, PR opened | Dispatch runner |
failed | 🔴 Red | Execution failed | Dispatch runner |
cancelled | ⚪ Gray | Cancelled before completion | Dan / 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
## Dispatchmetadata 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_idon the annotation matches a trusted list - Trusted users:
dan, configured AI Lead identities - Unknown
user_id→ dispatch rejected, annotation updated tocancelledwith 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
acceptEditspermission 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
| Feature | Requires E12? | Auto-executes? |
|---|---|---|
| Manual dispatch ("Clay, run story X") | No | No — Dan is present |
| Dispatch runner (polling + auto-exec) | Yes | Yes — that's the point |
| Execute button in Foundry UI | Yes | Yes — 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
launchdservice 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_idon 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)
launchdplist 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 serverSQLite run store + state machineProcess control (PID tracking, SIGTERM/SIGKILL)Async orchestratoruv packaging + pyproject.tomlSlack integrationThe 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
-
Execute button placement. Sidebar (near Submit Review)? Doc header/toolbar? Floating action button? Need to see what feels natural in the existing layout.
-
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.
-
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.
-
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.
-
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.
-
How does
csdlc:craft-agent-promptinteract? 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. -
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
| Date | Decision | Rationale |
|---|---|---|
| 2026-04-09 | Foundry IS the bridge, not a separate MCP server | Both tools already speak Foundry MCP. Adding a third system is unnecessary complexity. |
| 2026-04-09 | Auto-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-09 | E12 (Foundry auth) is the hard blocker | Auto-execution without auth = RCE via prompt injection. Non-negotiable. |
| 2026-04-09 | Annotation-based dispatch | Dispatch is metadata, not content. Keeps story docs clean. Status lives on the annotation. |
| 2026-04-09 | Dispatch runner is a simple Python daemon | ~150 lines, launchd service, polls every 60s. Not an MCP server, not a package. |
| 2026-04-09 | Repo whitelist is a security boundary | CC only executes in pre-approved directories. Config file, not code. |
| 2026-04-09 | Trusted user list on dispatch annotations | Only user_id values in the trusted list can trigger execution. |
| 2026-04-09 | Original bridge design doc is archived | Good analysis, wrong solution shape. Keep for reference, don't build it. |
| 2026-04-09 | GitHub mobile notifications for PR results | No Slack integration needed. GitHub already pushes on PR open/review/checks. |
| 2026-04-09 | Worktree isolation for every dispatch run | Blast radius containment. Main repo is read-only. |
Visual Design Exploration (TODO)
The UI components need iteration. Key screens to mock:
- Execute button + dispatch form — where does it live, what fields, what the interaction feels like
- Status badge on doc header — how it looks in the three states (waiting, running, done/failed)
- Status dots on pages list — information density, color choices, elapsed time formatting
- Dispatch status panel — sidebar detail view with result summary, PR link, re-run/cancel actions
- 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.