Foam-Agent
by KasperHonore
README.md
# Foam-Agent
<p align="center">
<em>OpenFOAM CFD driven by the AI coding agent you already pay for — no API key in the box.</em>
</p>
<p align="center">
<img src="https://img.shields.io/badge/Claude_Code-supported-6B4FBB" alt="Claude Code">
<img src="https://img.shields.io/badge/Cursor-supported-000000" alt="Cursor">
<img src="https://img.shields.io/badge/Codex-supported-10A37F" alt="Codex">
<img src="https://img.shields.io/badge/OpenCode-supported-F5A623" alt="OpenCode">
<img src="https://img.shields.io/badge/API_keys-none_required-2EA44F" alt="No API keys">
</p>
Describe a flow problem in your own words. Foam-Agent picks the solver, writes the case files, runs them on pinned Foundation OpenFOAM v10, works the debug loop when the solve diverges, and plots the residuals while it runs.
The model is yours — your Claude Code, Cursor, Codex or OpenCode subscription. The container holds OpenFOAM, a local tutorial index and local embeddings, and never calls an LLM.
Everything is pinned to **Foundation OpenFOAM v10** and tested as one unit — the tutorial index, the skill references and the container toolchain. ESI OpenFOAM is not a second runtime: `translate_case_to_esi` is a best-effort export for taking a finished case elsewhere, not a target you can work in.

*One window: the Conversation, the tool calls it made, and the Case's residuals while it solves.*
## Quick start
### 1. Serve the shell
```bash
git clone https://github.com/KasperHonore/Foam-Agent.git
cd Foam-Agent
git lfs pull
just up
```
Needs Python 3.10+, Node 20+ with npm, and [`just`](https://github.com/casey/just). **No pip install** — the host serves from the standard library, run as a module off `packages/host/src`. `just up` builds the frontend and opens `http://127.0.0.1:8760`, bound to loopback only.
`git lfs pull` is not optional: the FAISS tutorial indices ship via git-lfs, and without it you get pointer stubs and silently empty retrieval. The Setup panel checks for exactly that.
**The shell serves with no Docker and no agent CLI.** You can look round before installing either. What each one adds is below.
### 2. Start the OpenFOAM container
This is what actually runs a solve.
```bash
docker build -f packages/mcp/docker/Dockerfile -t foamagent:latest . # first build: ~10 min, ~10 GB
mkdir -p ~/foamagent/runs
docker run -d --name foamagent-mcp --init --restart unless-stopped -p 7860:7860 \
-v "$HOME/foamagent/runs:/home/openfoam/Foam-Agent/runs" \
foamagent:latest python -m foamagent_mcp.fastmcp_server --transport http --host 0.0.0.0 --port 7860
```
**Build rather than pull on this branch.** The published `ghcr.io/kasperhonore/foamagent:latest` image is only ever pushed from `main` ([`docker-publish.yml`](.github/workflows/docker-publish.yml) triggers on `branches: [main]`), so it predates everything here. The Setup panel's tool-lockstep check opens a real connection to the server and diffs its tool list against the host's table, so a stale image shows up as a red row rather than as a confusing failure mid-run.
`--init` gives the container a real init as PID 1. Detached work orphans its process there when it finishes; without an init that reaps, every finished run lingers as a `<defunct>` entry until the container restarts. Nothing breaks without it — the Setup panel warns if your container predates the flag.
### 3. Give it an agent seat
Install one of Claude Code, Cursor, Codex or OpenCode, then open **Setup → Agents**, pick one and press **Check this works**. The check spawns the CLI, completes an ACP handshake and records that it worked from this machine.
Nothing ever spawns a CLI without a click, and a failure names the step it died at — `spawn`, `initialize` or `session/new` — with the CLI's own stderr passed through untouched. Which step failed is the diagnosis: authentication is enforced at `session/new`, so a failure there means sign in, while one at `initialize` means the CLI itself is broken.
Then type into the composer:
```
Simulate lid-driven cavity flow at Re=1000
```

*The starting points fill the composer and never send. The pills lock at first send — a Conversation's agent is fixed for its life.*
## What you get
A three-pane application: Conversations on the left, chat in the middle, instruments on the right.
| Surface | What it is |
|---|---|
| **Landing** | One composer, and five starting points that fill it and never send. The pills on it pick the agent and the Project, and lock at first send — a Conversation's agent is fixed for its life. |
| **Chat** | The transcript, streamed. Tool calls fold into groups with a plain-English trigger line and a running clock: `used 5 tools · 12.0s — waiting on bash 6.0s`. |
| **Consent cards** | Before anything executes, a card in the conversation shows the facts the host computed: the payload verbatim, core count, **what gets overwritten**, the SLURM ask, what the last run cost. Buttons are the agent's own options in its own order. Dismissing denies — Escape answers no. |
| **Dashboard** | Bound to a Case. Overview with solve progress and a stall notice, residuals on a log scale, parameters with provenance, a per-Case comparison column, the run console. Tabs are absent when the Case has nothing behind them, not greyed out. |
| **Library** | The Project's shelf of geometry and meshes. Uploaded material and material the Project's own Cases produced are separated, because you can remove one and not the other. Delete means archive. |
| **Scene** | A 2D cut with mesh line-work and velocity arrows; 3D geometry and streamlines in the run console. |
| **Search** | `Ctrl/Cmd+K` searches Project, Case and Conversation names **plus the full text of every transcript**, in one answer. |
| **Setup** | A live board — git-lfs, Docker, image, container, version lockstep, runs mount, orphan reaping, tool lockstep, agent CLIs — polled every 15s, with two one-click fixes and fixed argv behind them. |

*A consent card. The host computes the facts — the payload verbatim, what gets overwritten, what the last run cost — and the buttons are the agent's own options in its own order.*

*The Library, across every Project. An empty shelf says what would fill it.*
Two things there are worth calling out, because they are the product's own arguments rather than features.
**Runs are detached, and the app says so.** A solve is launched with `start_case` in its own session, streams to files rather than pipes, and outlives the host process. Stop the agent mid-turn and you get told plainly: you stopped the agent, not the Case — the run was left going, and its outcome lands on the dashboard whether or not anyone is watching. Restart the host and it re-adopts the live runs it finds.
**Setup repair is a conversation, not a wizard.** When the board is red and you have a proven seat, a row appears offering to work through it with your agent. It opens a Conversation seeded with the entire board verbatim — green rows included — plus a briefing on what the agent cannot observe from where it sits.
## Skills only, without the application
**This repo is for people who want the application.** Everything above assumes you cloned it and ran `just up`.
If you would rather not — you already live in your CLI, and you want the CFD skills there and nothing else — there is exactly one other supported way in, and it is built from two published artifacts: the skills, installed with `npx skills add`, and the container image, pulled from GHCR. No clone involved; what you build around those two is your own. You give up the dashboard, the Library, the consent cards and the Setup board; you keep the skills, the container and the 34 tools. (A clone opened directly in your CLI is neither route — the checkout is for developing Foam-Agent.)
**1. Install the skills into your CLI.**
```bash
npx skills add KasperHonore/Foam-Agent
```
This installs the four skills (`foam`, `foam-onboard`, `foam-setup`, `foam-runs`) and the three subagent roles into Claude Code, Cursor or OpenCode.
**2. Start the container.** No clone here, so pull the published image rather than building it:
```bash
mkdir -p ~/foamagent/runs
docker pull ghcr.io/kasperhonore/foamagent:latest
docker tag ghcr.io/kasperhonore/foamagent:latest foamagent:latest
docker run -d --name foamagent-mcp --init --restart unless-stopped -p 7860:7860 \
-v "$HOME/foamagent/runs:/home/openfoam/Foam-Agent/runs" \
foamagent:latest python -m foamagent_mcp.fastmcp_server --transport http --host 0.0.0.0 --port 7860
```
**3. Point your CLI at the server**, so the skills have hands. The server speaks HTTP MCP on `localhost:7860`:
| CLI | Where it goes |
|---|---|
| Claude Code | `claude mcp add --transport http foamagent http://localhost:7860/mcp` |
| Cursor | `~/.cursor/mcp.json` — `{"mcpServers": {"foamagent": {"url": "http://localhost:7860/mcp"}}}` |
| Codex | `~/.codex/config.toml` — `[mcp_servers.foamagent]` with `url = "http://localhost:7860/mcp"` |
| OpenCode | `opencode.json` — `{"mcp": {"foamagent": {"type": "remote", "url": "http://localhost:7860/mcp", "enabled": true}}}` |
Then say what you want: *simulate flow over a cylinder at Re=40*. Your runs land in the same `~/foamagent/runs` the application uses, so you can switch between the two whenever you like.
> **Two caveats, stated rather than discovered.** This repo now has well-known skills directories (`.claude/skills` and friends), but they hold the vendored engineering-process skills, not the CFD skills, which live in `agents/` — so exactly what a given `skills` version picks up from that mix has not been pinned down against the current tree. And `foam-setup` on this path has no bundled doctor, so diagnose-my-server is degraded; run `foamagent-doctor` from a clone instead. Making this route first-class — a publish target the CLI reads unambiguously — is its own workstream.
The CFD skills live in exactly one place, [`agents/`](agents), so a skill change is a one-file diff. (The skill trees under `.agents/`, `.claude/` and `.vibe/` are a separate family — engineering-process skills for developing this repo, vendored from `mattpocock/skills`, not the CFD skills.) When the application opens a Conversation it copies those canonical bytes into whatever layout your harness reads, and a manifest-backed refresh replaces what moved upstream while never touching what you edited.
The 34 tools are catalogued in [`packages/mcp/README.md`](packages/mcp/README.md), together with local non-Docker installation. Environment variables are documented in [`AGENTS.md`](AGENTS.md); sample prompts and meshes are in [`examples/`](examples).
## How a Case gets built
```
"Simulate dam break with two fluids"
│
▼
PLAN find_similar_case → closest v10 tutorial as reference
│
▼
GENERATE write_case_file × N → 0/, system/, constant/, Allrun
│
▼
RUN start_case → detached solve ──────► VISUALIZE (PyVista → PNG)
│ ▲
▼ errors │
DEBUG foam-debugger: diagnose → rewrite → rerun (until converged)
```
Every Case lands in its own directory under `~/foamagent/runs`, with full logs, and gets a row in `~/foamagent/runs/ledger.md` — the run ledger the server maintains as a side effect of the run lifecycle. The ledger is a Markdown table you can read yourself; skills may write only its `Notes` column.
There is also an agentless loop worth knowing: pick a Case in the sider, press **Run**, approve the disclosure. No agent, no tokens.
## Your work lives outside the clone
```
~/foamagent/ # FOAMAGENT_HOME
├─ runs/ # bind-mounted into the container
│ ├─ ledger.md # the run ledger — one row per Case
│ ├─ preferences.json # your standing CFD defaults
│ └─ <Project>/ # a Project is a directory; there is no registry
│ ├─ .library/ # the Library's shelf
│ └─ <Case>/ # 0/, constant/, system/, logs, dashboard/
└─ state/ # host-owned, and structurally unreachable from a
# session — the container's mount IS the runs root
```
One runs directory serves every clone and every Project, so `git pull` cannot reach your work — not because it is gitignored, but because it is not in the tree. Transcripts live under `state/`, never inside a Case, so a Case you share carries no chat logs. `FOAMAGENT_HOME` moves the root; the Setup panel and your `-v` mount must agree on it.
Nothing persisted under the runs root names the machine it was written on. That is what makes a Project portable.
## Vocabulary
The words below are load-bearing — they are the same ones the code, the issues and the tests use. [`CONTEXT.md`](CONTEXT.md) is the full glossary.
- **Project** — a directory holding the Cases of one piece of work; the Conversation's working directory, and the only grouping the model has.
- **Case** — one OpenFOAM case directory and its one ledger row.
- **Conversation** — one chat thread with an agent, held in a Project. Disposable by design; its Case is fixed when it opens.
- **Asset** — a file or mesh directory in a Project. Geometry and meshes shelve in the Library; anything else is still `@`-mentionable.
- **Seat** — one agent CLI seen from your chair: is it installed, is it your chosen one, has a handshake ever completed here.
*Study* and *Run* are deliberately **not** entities.
## Development
```bash
just test # both suites — ~3,600 tests, no conditional skip on either half
just typecheck # tsc --noEmit, strict — the one automated gate the frontend has
just e2e # the ten-minute e2e: no image, no Docker, no CLI, no secrets
```
`just test` and `just typecheck` assume `just web` has installed the frontend, same as `just up` does. The layering contract runs separately, exactly as CI runs it:
```bash
PYTHONPATH=packages/core/src:packages/mcp/src:packages/host/src lint-imports
```
[`CONTRIBUTING.md`](CONTRIBUTING.md) has the real workflow, including the evidence standard for browser-driven work — a pure module with tests, a live browser drive, and a written UNVERIFIED boundary. CI gates exactly one of those three, which is why the other two are written down. [`AGENTS.md`](AGENTS.md) documents the architecture for coding agents working on this repo.
## Troubleshooting
First stop is the **Setup** panel, or `foamagent-doctor` for the same checks without an agent. For everything else — disk space, custom meshes, updating, ESI vs Foundation — see the [FAQ](FAQ.md).
| Problem | Cause |
|---|---|
| The page answers 503 | The shell isn't built. `just web`, or just `just up`. |
| MCP connection refused | Container not running. |
| First retrieval call takes minutes | One-time ~1.2 GB embedding model download inside the container — not a hang. |
| Retrieval returns nothing | `git lfs pull` before building the image. |
| Tool lockstep red | Your image is older than your clone. Rebuild it. |
## Share your simulation
Ran something interesting? Open a ["Share your simulation"](https://github.com/KasperHonore/Foam-Agent/issues/new?template=share-your-simulation.yml) issue — prompt, solver and a picture is all it takes. Real cases shape which skills and error-playbook entries get improved next.
## Credits
Foam-Agent began as a hard fork of [csml-rpi/Foam-Agent](https://github.com/csml-rpi/Foam-Agent) by Yue et al. It is developed independently and PRs land here, not upstream.
The fork has since replaced the original end to end: the self-contained LangGraph pipeline that made its own LLM calls has been removed, the MCP server rewritten, and the host application, browser shell and Library built new. What is inherited and still in use is the OpenFOAM v10 tutorial corpus the retrieval index is built from, and the rules-based ESI translation module.
The original pipeline is preserved at the [`legacy-pipeline`](../../tree/legacy-pipeline) git tag for a future harness-vs-harness-less comparison. Upstream's [FoamBench](https://arxiv.org/abs/2509.20374) evaluation reached 100% on 110 tasks with Claude Opus 4.6 at 25 correction loops — **that figure benchmarks upstream's removed pipeline, not this fork.** This fork has no published benchmark of its own.
### Citation
If you use Foam-Agent in research, please cite the upstream papers:
```bibtex
@article{yue2025foam,
title={Foam-Agent: Towards Automated Intelligent CFD Workflows},
author={Yue, Ling and Somasekharan, Nithin and Zhang, Tingwen and Cao, Yadi and Chen, Zhangze and Di, Shimin and Pan, Shaowu},
journal={arXiv preprint arXiv:2505.04997},
year={2025}
}
@article{somasekharan2026cfdllmbench,
title={CFDLLMBench: A Benchmark Suite for Evaluating Large Language Models in Computational Fluid Dynamics},
author={Somasekharan, Nithin and Yue, Ling and Cao, Yadi and Li, Weichao and Emami, Patrick and Bhargav, Pochinapeddi Sai and Acharya, Anurag and Xie, Xingyu and Pan, Shaowu},
journal={Journal of Data-centric Machine Learning Research},
year={2026},
url={https://openreview.net/forum?id=kTcH1MnkjY}
}
```
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessSyncing