System Overview
Context Foundry is an autonomous build loop that runs AI coding agents through a structured pipeline (Scout, Plan, Implement, Doubt) to complete software engineering tasks. Written in Rust (foundry v0.6.1), it provides a ratatui-based TUI dashboard, pattern learning, extension system, dual-model execution arena, and full git integration with PR workflows. The system reads tasks from TASKS.md, processes each through the SPID pipeline, and commits results -- optionally creating per-task pull requests for human review.
SPID Pipeline
The build loop executes a 5-stage pipeline per task: Scout, Plan, Implement, Doubt, Ship. Two ancillary stages -- Discovery and Pattern Extraction -- run outside the per-task pipeline (Discovery triggers when all tasks complete; Pattern Extraction runs after each successful task).
Pipeline Variants
| Variant | Trigger | Behavior |
|---|---|---|
| Full | pipeline_mode: "full" (default) |
All 5 stages run for every task |
| Fast | pipeline_mode: "fast" |
Scout skipped when scout-report.md exists. Doubt can be batched to end of session. |
| Backpressure | pipeline_mode: "backpressure" or backpressure_only: true |
Skip LLM review entirely. Rely on build/test/lint commands for verification. |
| Batched Doubt | batch_doubt: true |
Doubt runs only on the last pending task. All prior tasks skip Doubt. The final Doubt audits all accumulated changes. |
Execution Modes
| Mode | run_mode | Task Source | Review Gate | WIP Commits | PR Poller | Ctrl+M |
|---|---|---|---|---|---|---|
| Auto (Loop) | "auto" |
TASKS.md, then Discovery appends more | None -- auto-commits after Doubt | On Doubt FAIL: WIP commit preserves progress | Not used | Cycles to "sprint" |
| Sprint | "sprint" |
TASKS.md only -- no Discovery | None -- auto-commits after Doubt | On Doubt FAIL: WIP commit preserves progress | Not used | Cycles to "review" |
| Review | "review" |
TASKS.md, one task at a time | Human gate: waits for Enter or PR approval | On Doubt FAIL: WIP commit on feature branch | Polls every pr_poll_interval_secs (default 30s) for PR approval/close |
Cycles to "auto" |
Ctrl+D: Dual-Selection States
Ctrl+D cycles through the DualSelection enum (state.rs:216-251).
When builder_models has 2+ entries, this controls which model(s) execute:
| State | Behavior | Next (Ctrl+D) |
|---|---|---|
| Off (default) | Single-model execution using builder_model/builder_provider |
First |
| First | Uses first entry from builder_models array |
Second |
| Second | Uses second entry from builder_models array |
Both |
| Both | run_dual_pipelines() -- both models in parallel git worktrees |
First |
Note: After "Both", cycling returns to "First" (not "Off"). Confirmed from
DualSelection::next() at state.rs:243-249.
Ctrl+M cycles run_mode through: auto -> sprint -> review -> auto.
Dual-Model Arena
When builder_models has 2+ entries and dual_selection is "both",
run_dual_pipelines() forks execution into two independent git worktrees.
Each model runs the full Plan-Implement-Doubt pipeline independently in its own copy
of the project.
Worktree Layout
.buildloop/arena/
├── pipeline-0-{provider}/ (git worktree for model[0])
│ ├── .buildloop/
│ │ ├── scout-report.md (copied from main)
│ │ ├── current-plan.md (generated by model[0]'s planner)
│ │ ├── build-claims.md (generated by model[0]'s builder)
│ │ └── review-report.md (generated by model[0]'s reviewer)
│ └── ... (full project copy)
└── pipeline-1-{provider}/ (git worktree for model[1])
├── .buildloop/
│ ├── scout-report.md
│ ├── current-plan.md
│ ├── build-claims.md
│ └── review-report.md
└── ... (full project copy)
Event Tagging
Each pipeline wraps its events with AppEvent::DualPipelineEvent(usize, Box<AppEvent>)
where the usize is the pipeline index (0 or 1). The TUI dispatches these to the
correct stream in DualBuildState.streams[idx].
TUI Tab Bar
The agent output pane shows a tab bar when dual build is active.
DualBuildState.tab (0 or 1) controls which stream is displayed.
The tab bar shows model labels from DualBuildState.models[0] and
DualBuildState.models[1].
Winner Selection
No automated winner selection runs. The human evaluates both worktrees and decides which to keep.
Per-Pipeline State
Tracked in DualBuildState (state.rs:254-264):
| Field | Type | Purpose |
|---|---|---|
streams | [Vec<String>; 2] | Agent output lines per pipeline |
event_counts | [usize; 2] | Event count per pipeline |
models | [String; 2] | Model display labels |
context_pcts | [Option<u8>; 2] | Context window usage per pipeline |
finished | [bool; 2] | Completion flag per pipeline |
stages | [Option<AgentRole>; 2] | Current SPID stage per pipeline |
stage_models | [String; 2] | Model label for current stage per pipeline |
Git Integration
commit_task_pr Flow (Review Mode)
commit_and_push (Auto/Sprint Mode)
The simpler flow for auto and sprint modes:
git add -Agit reset -- .buildloop/logs/git diff --cached --quiet(bail if nothing staged)git commit -m "feat({task_id}): {desc}"(or WIP prefix)maybe_push_commit()-- push toauto_push_remoteif set
Key Details
| Aspect | Detail |
|---|---|
| Branch naming | foundry/{task_id} (task_id lowercased, e.g. foundry/t1.1) |
| Push strategy | --force-with-lease (safe force push, fails if upstream diverged) |
auto_push_remote | Defaults to None (local commits only). When set, pushes to that remote. |
| WIP commits | When is_wip is true, branch + commit still happen, but PR creation is skipped. |
create_issue_on_wip | When enabled and task is WIP, create_wip_issue() creates a GitHub issue with review findings. |
TUI Layout
The running screen layout is defined in tui/mod.rs:122-165. Five vertical
zones, with the middle zone split 60/40 horizontally.
┌────────────────────────────────────────────────────────────────────────┐
│ HEADER (5 lines) │
│ Project name │ Phase badge (Startup/Planning/Running) │
│ Mode badge (auto/sprint/review) │ Task: T1.1 -- description │
│ Agent: IMPLEMENT (claude:opus) ⠋ 2m34s │
│ [dual tab bar when active: model[0] │ model[1]] │
├────────────────────────────────────────────────────────────────────────┤
│ PIPELINE DIAGRAM (7 lines) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ SCOUT │──>│ PLAN │──>│IMPLEMENT │──>│ DOUBT │──>│ SHIP │ │
│ │ sonnet │ │ opus │ │ opus │ │ sonnet │ │ GitHub │ │
│ │scout-rpt │ │curr-plan │ │bld-claim │ │fresh ctx │ │ git+pr │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ (disconnected) │
│ │ DISCOVER │ │ PATTERNS │ │
│ └──────────┘ └──────────┘ │
├──────────────────────────────────┬─────────────────────────────────────┤
│ AGENT OUTPUT (60% width) │ TASK QUEUE (40% width, min 6 lines) │
│ [scrollable log of agent text, │ H1.1: Create overview... │
│ tool calls, tool results] │ D32.1: Fix pipeline bug... │
│ │ (H=yellow, D=blue, T=white) │
│ Events: AppEvent::AgentOutput │ │
│ ├─────────────────────────────────────┤
│ │ PATTERNS LEARNED (6 lines) │
│ │ [pattern activity feed] │
│ ├─────────────────────────────────────┤
│ │ EXTENSIONS USED (6 lines) │
│ │ [extension injection feed] │
├──────────────────────────────────┴─────────────────────────────────────┤
│ STATS PANEL (6 lines) │
│ Progress: [████████░░░░] 3/7 │ Git: main (3 dirty) │
│ Context%: S:45% P:72% I:89% D:-- │ Patterns: 3 injected, 1 used │
│ Session: 12m │ Cost: $0.47 │ Commits: 2 feat, 0 WIP │
│ Agent: IMPLEMENT (claude:opus) running... ⠋ │
├────────────────────────────────────────────────────────────────────────┤
│ STATUS BAR (1 line) │
│ q stop Ctrl+C quit ↑↓ scroll i inject PgUp/PgDn queue p patt │
│ ^T theme Tab explore [Enter continue] [f findings] │
└────────────────────────────────────────────────────────────────────────┘
Pane-to-Event Mapping
| Pane | Widget | Driven by AppEvent |
|---|---|---|
| Header | render_header |
LoopEvent::TaskStarted, LoopEvent::AgentStarted, DualBuildStarted |
| Pipeline Diagram | render_pipeline_map |
LoopEvent::AgentStarted (highlights active stage) |
| Agent Output | render_agent_output |
AppEvent::AgentOutput(AgentOutputEvent::*), DualPipelineEvent |
| Task Queue | render_task_queue |
LoopEvent::QueueUpdated, LoopEvent::TaskCompleted |
| Patterns Learned | render_patterns |
LoopEvent::PatternsUsed |
| Extensions Used | render_extensions_used |
LoopEvent::ExtensionInjected |
| Stats Panel | render_dashboard_stats |
AppEvent::AgentOutput(Usage{...}), LoopEvent::CountsUpdated |
| Status Bar | render_status_bar |
AppPhase changes, awaiting_review, awaiting_pr |
Keybinds
From tui/running.rs:695-814:
| Key | Action |
|---|---|
q | Stop after current task |
Ctrl+C | Force quit |
↑↓ | Scroll agent output |
i | Inject text into running agent |
PgUp/PgDn | Scroll task queue |
p | Toggle pattern overlay |
^T | Cycle TUI theme (dark -> catppuccin -> solarized -> dark) |
Tab | Toggle explore/dashboard view |
Enter | Continue (when awaiting review) or skip wait (when awaiting PR) |
f | Toggle findings (when orchestrator outcome exists) |
Configuration Reference
Configuration Reference (.foundry.json) -- 55 fields
| Field | Type | Default | Description |
|---|---|---|---|
scout_model |
String | "sonnet" |
Model name for Scout agent |
planner_model |
String | "opus" |
Model name for Planner agent |
builder_model |
String | "opus" |
Model name for Builder agent |
builder_models |
Vec<String> | [] |
Dual-model execution specs, format "provider:model". When len >= 2, overrides builder_model/builder_provider. |
dual_selection |
String | "" (off) |
Dual-build selection: "first", "second", "both", or "" (off). Ctrl+D cycles through states. |
reviewer_model |
String | "sonnet" |
Model name for Reviewer agent |
fixer_model |
String | "sonnet" |
Model name for Fixer agent |
discovery_model |
String | "opus" |
Model name for Discovery agent |
scout_provider |
String | "claude" |
Provider for Scout: "claude" or "codex" |
planner_provider |
String | "claude" |
Provider for Planner |
builder_provider |
String | "claude" |
Provider for Builder |
reviewer_provider |
String | "claude" |
Provider for Reviewer |
fixer_provider |
String | "claude" |
Provider for Fixer |
discovery_provider |
String | "claude" |
Provider for Discovery |
studio_claude_model |
String | "opus" |
Claude model for Studio mode |
studio_codex_model |
String | "" |
Codex model for Studio mode |
studio_theme |
String | "foundry" |
Studio UI theme name |
studio_custom_themes |
BTreeMap<String, StudioThemeConfig> | {} |
Custom Studio theme definitions |
pause_between_tasks_secs |
u64 | 10 |
Seconds to wait between tasks |
pause_between_agents_secs |
u64 | 3 |
Seconds to wait between agent spawns |
pause_between_cycles_secs |
u64 | 30 |
Seconds to wait between discovery cycles |
agent_timeout_secs |
u64 | 600 |
Max seconds before killing an agent (10 min) |
patterns_dir |
String | "~/.foundry/patterns" |
Directory for global pattern store |
backpressure_only |
bool | false |
Skip LLM review, rely on build/test/lint only |
simple_planner_model |
String | "sonnet" |
Override planner model for Simple tasks |
simple_builder_model |
String | "sonnet" |
Override builder model for Simple tasks |
simple_reviewer_model |
String | "" |
Override reviewer for Simple tasks ("" = skip review) |
max_pattern_injection |
usize | 10 |
Max patterns injected into agent prompts |
planning_iterations |
u64 | 0 |
Max iterations for foundry plan mode (0 = unlimited) |
adaptive_pauses |
bool | true |
Skip full sleep when agent was not rate-limited (500ms min) |
auto_push_remote |
Option<String> | null |
Git remote to auto-push after commits (null = local only) |
semantic_match_enabled |
bool | true |
Enable semantic pattern matching via Ollama embeddings |
embedding_model |
String | "nomic-embed-text" |
Ollama embedding model name |
ollama_url |
String | "http://127.0.0.1:11435" |
Ollama API base URL for embeddings |
embedding_timeout_ms |
u64 | 2000 |
Timeout for Ollama embedding requests in milliseconds |
orchestrator_proposer_provider |
String | "claude" |
Design mode: proposer model provider |
orchestrator_proposer_model |
String | "opus" |
Design mode: proposer model name |
orchestrator_reviewer_provider |
String | "claude" |
Design mode: reviewer model provider |
orchestrator_reviewer_model |
String | "opus" |
Design mode: reviewer model name |
orchestrator_max_iterations |
usize | 3 |
Design mode: max proposal/review iterations |
orchestrator_accept_policy |
String | "no-high-medium" |
Design mode: acceptance policy ("no-high", "no-high-medium", "no-findings") |
review_mode |
String | "diff-only" |
Reviewer input: "diff-only" or "file-list" |
skip_planner_for_simple |
bool | true |
Skip planner for Simple-complexity tasks |
discovery_cooldown_minutes |
u64 | 5 |
Minutes before running discovery after last H-task |
planner_lookahead |
bool | true |
Pre-plan task N+1 while building task N |
pattern_extraction_model |
String | "sonnet" |
Model for pattern extraction (lightweight) |
run_mode |
String | "auto" |
Execution mode: "auto", "sprint", or "review" |
pipeline_mode |
String | "full" |
Pipeline mode: "full", "fast", or "backpressure" |
batch_doubt |
bool | false |
Defer doubt to last task only |
extensions |
Vec<String> | [] |
Selected extension names (e.g., ["roblox", "extend"]) |
create_issue_on_wip |
bool | false |
Create GitHub issue on WIP (failed) commits |
preview_wrap |
bool | true |
Preview pane word-wrap preference |
pr_poll_interval_secs |
u64 | 30 |
Seconds between PR review status checks |
theme |
String | "dark" |
TUI color theme: "dark", "catppuccin", or "solarized" |
Extensions
Extensions provide domain-specific knowledge to agents. They are discovered from
~/.foundry/extensions/ (global), ancestor directories' extensions/
folders, and <project>/extensions/ (local). Each extension directory
must contain a CLAUDE.md file. Project-local extensions override global
ones with the same name. Extension context is built by load_extension_context() and
prepended to agent prompts via wrap_with_extensions().
8.0 Extension Taxonomy
An extension (or domain pack) is a directory containing any combination of the following component types:
| Component | Location | Purpose | Required? |
|---|---|---|---|
| Instructions | CLAUDE.md |
Authored domain rules, critical constraints, session-start checklists. Always injected into agent prompts when the extension is selected. | Yes |
| Patterns | patterns/*.json |
Learned recurring pitfalls in canonical pattern schema (pattern_id, title, issue, solution). Keyword-matched at plan time. |
No |
| Docs | docs/ |
Reference guides, specs, API documentation, governance lists, asset inventories, analyzer output. | No |
| Templates | templates/ |
Starter blueprints and proven command templates for common operations. | No |
| Examples | examples/ |
Working reference implementations, sample projects, demo flows. | No |
The patterns/ directory is ONLY for canonical-schema pattern JSON.
Every file in patterns/ must be parseable by the pattern loader
(src/patterns.rs) as either a Vec<Pattern>, a
PatternWrapper, or a single Pattern. Reference docs, asset
catalogs, expertise metadata, and governance lists belong in docs/.
extensions/<name>/
├── CLAUDE.md # Instructions (required)
├── patterns/
│ └── <name>-common-issues.json # Canonical pattern JSON only
├── docs/ # Reference guides, specs, asset catalogs
├── templates/ # Starter blueprints, command templates
├── examples/ # Working reference projects
├── scripts/ # Utility scripts
└── config/ # Configuration files
8.1 Roblox
extensions/roblox/
- Purpose: Domain knowledge for Roblox world generation using Lune scripting and .rbxl/.rbxm files.
- Key files:
CLAUDE.md,patterns/roblox-common-issues.json - Critical patterns:
- Use
add_to_world.luau, NOTgenerate_world.luau-- terrain has varying elevations - Use CFrame, not Position, for moving parts (Position does not persist in Lune)
- Do not extract and reapply rotation -- offset CFrame directly to preserve rotation
- Use
- Gotchas: Generating worlds from scratch causes floating/sinking objects due to terrain elevation.
8.2 Workday Extend
extensions/extend/
- Purpose: Workday Extend PaaS application development -- building custom apps inside Workday tenants.
- Key files:
CLAUDE.md,WORKDAY_EXTEND_DEVELOPER_GUIDE.md,WORKDAY_EXTEND_ARCHITECTURE.md,orchestrations-integrations-guide.md,security-reporting-birt-notes.md,LATEST_DEVELOPMENTS.md,patterns/extend-common-issues.json - Critical rules:
- No local file-based development -- Extend apps built entirely in browser App Builder
- Security policy activation is mandatory after ANY security change
- Credentials never migrate between tenants
- WIDs are tenant-specific -- use Reference IDs instead
- CORS requires explicit configuration for REST API clients
- Test before every biannual Workday release
- Gotchas: Extend requires a license; not all Workday customers have access.
8.3 Recon
extensions/recon/
- Purpose: Fleet reconnaissance from a management server -- inventory lookups, iDRAC queries, racadm commands, batch operations against Dell servers.
- Key files:
CLAUDE.md,config/inventory-schema.json,templates/idrac-checks.md,templates/batch-loops.md,patterns/recon-common-issues.json - Critical rules:
- Always use
grep -w(whole-word match) for hostname lookups - SSH to iDRAC needs
-o ConnectTimeout=5to avoid hanging loops - Always label batch output with the current hostname
- Quote variables in loops
- Always use
- Gotchas: Unlabeled batch output is useless; always prefix with hostname.
8.4 Flowise
extensions/flowise/
- Purpose: Building Flowise AgentFlow v2 workflows. Output is single JSON files.
- Key files:
CLAUDE.md,FLOWISE.md,docs/flowise-expertise.json,example-flows/masterclass-2025/*.json,node-templates/*.json - Critical rules:
- Use HTML span format for ALL variable references:
<span class="variable">$flow.state.varName</span> - Use correct field names:
llmStructuredOutput,llmUpdateState,directReplyMessage - Every flow needs a Start node with proper
startInputconfiguration
- Use HTML span format for ALL variable references:
- Gotchas: Follow the validation checklist in FLOWISE.md before importing.
8.5 Mindcraft
extensions/mindcraft/
- Purpose: Python orchestrator for an AI Minecraft bot ("Andy") that plays autonomously.
- Key files: No CLAUDE.md. Only
__pycache__remnants of Python modules:client,detector,goals,learner,models,monitor,orchestrator,persistence,planner. Config inconfig/. - Architecture: Monitors bot state, assigns goals, recalls bot if too far from spawn.
- Gotchas: The Python source files were cleaned up; only cached bytecode remains. Documentation is based on the root CLAUDE.md description and module names.
MCP Tools
Context Foundry exposes five MCP (Model Context Protocol) tools that allow AI agents to interact with the pattern store and delegate work. The MCP server source is not in this repo (only __pycache__ remnants in tools/); documentation is derived from CLAUDE.md and the patterns module API.
| Tool | Signature | Reads/Writes | Usage Note |
|---|---|---|---|
read_global_patterns |
read_global_patterns(category: str) |
Reads ~/.foundry/patterns/*.json |
Load patterns before starting work; category is typically "common-issues" |
save_global_patterns |
save_global_patterns(patterns: list, category: str) |
Writes to ~/.foundry/patterns/{category}.json |
Save newly discovered patterns after solving issues |
merge_project_patterns |
merge_project_patterns(path: str, category: str) |
Reads project patterns, writes to global store | Promotes project-local patterns to ~/.foundry/patterns/; deduplicates by pattern_id |
delegate_to_claude_code |
delegate_to_claude_code(task: str, ...) |
Spawns a fresh Claude Code instance | Delegate subtasks to a fresh agent with independent context |
search_skills |
search_skills(query: str) |
Reads pattern store and skills index | Find reusable code patterns and skills by keyword search |
Pattern Store
Location: ~/.foundry/patterns/ (configurable via patterns_dir
in .foundry.json). Tilde is expanded via $HOME (or
$LOCALAPPDATA/$USERPROFILE on Windows). Fallback:
/tmp/.foundry/patterns/ if HOME is unset.
JSON Schema
From src/patterns.rs:17-41:
{
"pattern_id": "string (unique identifier)",
"title": "string",
"first_seen": "string (ISO date)",
"last_seen": "string (ISO date)",
"frequency": 0,
"severity": "HIGH | MEDIUM | LOW | null",
"keywords": ["string"],
"tech_stack": ["string"],
"issue": "string | null",
"solution": {
"planner": "string (advice for planner agent)",
"reviewer": "string (advice for reviewer agent, alias: validator)"
},
"auto_apply": false,
"learned_from": "string | null (project/task that produced this pattern)"
}
Supported JSON Formats
Three formats are accepted:
- Bare array:
[{...}, {...}] - Wrapper object:
{"patterns": [{...}]}(viaPatternWrapperstruct) - Single object:
{...}
Pattern Matching at Plan Time
- Keyword scoring: Each pattern's
keywordsarray is compared against the task description and codebase context. Higher overlap = higher score. - Auto-apply bonus: Patterns with
frequency >= 3getauto_apply: trueand a +2 score bonus. - Semantic matching (optional): When
semantic_match_enabledis true, uses Ollama (embedding_model, default "nomic-embed-text") to compute cosine similarity. Circuit breaker with 60s cooldown on failure. - Injection cap: Top
max_pattern_injection(default 10) patterns are injected into planner and reviewer prompts as "REFERENCE DATA" blocks.
Pattern Lifecycle
- After each task: pattern_extraction agent writes
.buildloop/patterns-extracted.json - Extraction model:
pattern_extraction_model(default "sonnet") merge_patterns()deduplicates bypattern_id, incrementsfrequencyon duplicates, updateslast_seen- Auto-apply graduation: patterns reaching frequency >= 3 gain
auto_apply: true
Samsara / Discovery
When all tasks in TASKS.md are complete AND run_mode is "auto" (not sprint
or review), the Discovery stage triggers. The name "Samsara" refers to the cycle of
rebirth -- after Discovery, the build loop restarts with new tasks, forming an
infinite improvement cycle.
Discovery Agent
The Discovery agent (AgentRole::Discovery) scans:
- Codebase for bugs, gaps, missing features
- Build output and test results
- Git history for context
- Build history from prior session tasks (injected into prompt for focus)
Output
Appends new tasks under a ## Discovery Round N header in TASKS.md.
Task ID Convention
Format: D{round}.{sub} -- e.g., D33.1, D33.2.
The highest_discovery_round() function (task.rs:188-214) parses
both section headers (## Discovery Round N) and task ID regex
(D(\d+)\.) to find the max round number.
Cooldown
discovery_cooldown_minutes (default 5). When Discovery finds 0 new tasks,
the cooldown doubles (up to 30 minutes maximum). This prevents tight loops of empty
discovery scans.
Prompts
prompts::discovery() for scanning, prompts::append_tasks() for
writing new tasks to TASKS.md.
Samsara Loop
┌─────────────┐
│ TASKS.md │<──────────────────────────────┐
│ (pending) │ │
└──────┬──────┘ │
│ │
v │
┌─────────────┐ ┌───────────┐ ┌─────────┴───────┐
│ SPID Pipeline│──>│ SHIP │──>│ DISCOVERY │
│ per task │ │ commit/PR │ │ scan codebase │
└─────────────┘ └───────────┘ │ append new tasks │
└─────────────────┘
(auto mode only)