FlowForge
An LLMOps tool for apps that already use AI: a map of how your app's LLM, MCP and API calls fit together, and a scheduler that runs them.
You connect the apps your project uses (LLMs, MCP servers, HTTP APIs, local commands). The Planner reads your repo and runs the app in test mode, then draws the flow on the home page: the calls, the decisions, what runs together, the retries and fallbacks, and a gate in front of anything that moves money or sends a message. Only apps your code actually calls go on the map, and every block cites its evidence (a file:line or a traced call). You read the map, edit it if you want (your edits never touch your code), and re-check it when the code changes.
Underneath, FlowForge runs workflows as a DAG: it orders the steps, runs independent ones together under rate limits, caches repeat calls, and (on the roadmap) holds the secrets and pauses risky steps for a human to approve.
What makes it different: it knows which chain of steps is the bottleneck and schedules around it.
- orders steps with topological sort (Kahn's), and reports the exact loop if there is a cycle
- finds the bottleneck chain with a longest-path DP (critical path), predicted before the run and measured after it
- runs independent steps concurrently under limited resources (
kslots + per-provider rate limits) using critical-path list scheduling (HLFET), which is within(2 − 1/k)of optimal (Graham's bound) - makes duplicate calls only once through memoization (single-flight in-run cache + a safe persistent cache)
Free by default. The default install, the tests and CI need no paid service: LLM steps use NVIDIA NIM's free tier, and local models (Ollama) are planned. Paid providers will work if you bring your own key, but are never needed.
Local first. One process, SQLite, files on disk. No cloud account, no Docker, no Redis.
FlowForge started as a Design and Analysis of Algorithms course project. That version is frozen on the
DAAbranch.mainis the product.
Quick start
Install (from the first release, v0.1.0)
uvx flowforge-map --example # try it: a sample app's maps in your browser, no keys, nothing sent
pip install flowforge-map # or install it (the command is `flowforge`)
flowforge --repo ~/my-app # map your own repo
You need Python 3.12 or newer (uv installs it for you). The dashboard is inside the package: no Node needed. Your data lives in ~/.flowforge.
From source (to work on FlowForge)
You need uv (it installs Python for you) and Node.js 20 or newer for the dashboard.
git clone https://github.com/Vishwa-Thangapandiyan/flowforge-agent-orchestrator.git
cd flowforge-agent-orchestrator
uv sync # Python ≥ 3.12 and dependencies, into .venv
cd frontend && npm ci && npm run build && cd ..
uv run flowforge --example # opens http://127.0.0.1:8000: a sample app's maps, no keys needed
uv run flowforge (without --example) runs on your own data. Example data lives in its own database, so the two never mix.
Map your own repo
uv run flowforge scan path/to/your/repo # what it depends on, as JSON: nothing stored, nothing sent
uv run flowforge --repo path/to/your/repo # opens its dependency map; add --replace to switch folders
The scan is plain code: it reads your files, never runs them, and sends nothing to anyone, AI included. Every dependency (LLM, MCP server, HTTP API, local command) comes with the file:line that shows it, and code excerpts are stored with known key formats masked.
Then press Check now. Every dependency gets one harmless check, and the map turns green, amber or red:
- APIs: one request, with no key, cookie or query.
- LLMs: their model list is read.
- MCP servers: each is started in your repo folder, without your environment variables, asked for its tools, then stopped.
- Local programs: only looked for, never run.
Starting an MCP server runs code, so the first check lists exactly what will start and waits for your yes. When something is down, the Checks page says why, in the server's own words, and the likely fix.
See the order your app runs in
Switch the map to Flow. FlowForge reads your Python (by its syntax tree), Dart, JavaScript and TypeScript (by patterns, and says so), never running anything, and draws the order your code calls its dependencies: which call waits for which, the ifs that choose, and where the code stops early. Every block and line points to its file:line.
- The critical path is the chain that sets the total time, weighted by the latencies your last check measured (LLMs are estimates, and say so).
- Faster compares "as written" (the slowest action, one call after another) with "could be", and lists the calls that don't need each other's results. Show the change writes out the
asyncio.gather, Dart's(a, b).waitorPromise.all(now → could be, ready to copy) when it's safe; Copy prompt for your coding agent hands the rest to Claude Code, Codex or any agent. FlowForge never edits your code. - Watch it run plays the could-be schedule; pointing at a block lights up its path. Five map styles: Studio, Boxed, Blueprint, Metro, Soft.
Use it from your coding agent
FlowForge is also an MCP server, so Claude Code, Codex or Antigravity can ask it about your app and then make the change themselves. Open Connect agent in the dashboard: it gives you the one line (or config block) for your agent, with the paths filled in. For Claude Code it looks like:
claude mcp add flowforge -- uv --directory /path/to/flowforge run flowforge mcp --repo /path/to/your/app
Your agent gets six read-only tools: get_map, get_flow, get_advice, check_health, get_security_findings and what_changed. Every answer is masked (key values never appear), FlowForge never edits your code, and a check starts an MCP server only after you said yes to it on the Checks page. FlowForge needs no AI of its own for any of this.
Send someone the map
Export on the map opens the MCP Map: a standard picture of an app's MCP architecture (lanes by kind, time left to right, one shape per kind, a title block and a key; rules in docs/mcp-map.md). Download it as SVG (documents), PNG 2× (slides), PDF (reports, A4 landscape), copy it as Mermaid (GitHub draws it in a README or pull request), or save the Flow JSON behind it. It's all made in your browser, from data whose key formats were already masked.
On Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # uv, once
winget install OpenJS.NodeJS.LTS # Node, once
git clone https://github.com/Vishwa-Thangapandiyan/flowforge-agent-orchestrator.git
cd flowforge-agent-orchestrator
uv sync
cd frontend; npm ci; npm run build; cd ..
uv run flowforge --example
- Inside OneDrive (Desktop or Documents is often synced), OneDrive can lock files while uv installs. Set
$env:UV_LINK_MODE = "copy"first, and simply re-runuv syncif it reports "Access is denied". - Behind antivirus or a company proxy that inspects HTTPS, uv may report
invalid peer certificate. Useuv sync --native-tls. - Keep the folder path short (for example
C:\code\flowforge). Some dependency files have long names, and Windows refuses paths over 260 characters unless long paths are enabled. The symptom is aModuleNotFoundErrorfrom inside.venvwhen FlowForge starts.
Developing the dashboard
cd frontend
npm run dev # API with example data on :8000 + the dashboard on http://localhost:5173, both reload on change
npm test # dashboard tests (offline)
uv run pytest # backend tests, offline: no key, no network
uv run ruff check # lint
The API works on its own too, for scripts and CI: uv run uvicorn flowforge.main:app --reload, then POST /runs with workflow JSON (see below). The original status page is still at /classic.
To use real LLM steps, cp .env.example .env and add a free NVIDIA_API_KEY from build.nvidia.com; api-connect.md lists every key and where it goes. MCP steps use mcp-server-fetch, which uvx downloads on first use.
The dashboard
| Page | What it shows |
|---|---|
| Map (home) | Your app's dependencies as blocks with a health dot; hover for file:line, click for details. The header has three controls: ⚠ N to fix (keys in code, plain http://, risky MCP servers; values are never shown), Check now, and ⋯ (re-scan, scan another folder, your edits, export, legend, minimap, the five styles). Flow shows the order the code calls them, one action at a time, slowest first, with the Faster panel |
| MCP Map (Export) | The map to send: SVG, PNG, PDF, Mermaid or Flow JSON |
| Checks | Check now, each dependency's verdict, why it's down and the likely fix, and the history |
| Connect agent | The set-up for Claude Code, Codex or Antigravity; what your agent gets from this repo (dependencies, health, time, ways to be faster); and, once connected, what it asked |
| How the Planner drew it | The analysis replayed: read the repo, run the app in test mode, trace the calls, draft, check. What it read, what it dropped and why |
| Re-check | What changed in your repo since the last map: added, changed, removed, and conflicts with your edits. You pick what to take; the old version stays listed |
| Apps and connectors (Map ⋯) | A catalog of 56 apps with search and categories, honest Live / Demo only badges, and a mock test run for every one. Apps you connected but your code doesn't call wait here, off the map |
| Connector page | One app's connection (keys shown only as configured, missing or example), what it has done, and its recent activity |
| Add an app (from Apps and connectors) | LLM, MCP server, HTTP API or local command; colour and logo; a live preview; and exactly what gets saved, with no secrets |
| Workflow runs (under Checks, advanced) | Money and time saved, and every scheduled run: live bars with the slowest chain outlined, outputs, and why a failed run failed |
Both maps are drawn by plain code from your repo, with no AI, and nothing on them is drawn by hand. Edits on the map change how FlowForge maps your app, never your code.
Connect any provider
Each app you connect is a connector: an LLM, an MCP server, an HTTP API or a local command. Two are ready on first start: nim (the default LLM) and fetch (the default MCP server). Add more in ~/.flowforge/connectors.json (or $FLOWFORGE_HOME/connectors.json); it's checked and loaded at start-up:
[
{ "id": "gemini", "type": "llm", "name": "Gemini", "rate_limit_rpm": 15,
"secret_ref": "env:GEMINI_API_KEY",
"connection": { "provider": "openai_compatible",
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
"model": "gemini-3.8-flash" } },
{ "id": "claude", "type": "llm", "name": "Claude", "fallback": "gemini",
"secret_ref": "env:ANTHROPIC_API_KEY",
"connection": { "provider": "anthropic", "model": "claude-opus-5-5" } },
{ "id": "scripts", "type": "local", "name": "My scripts",
"connection": { "command": ["python", "tools/report.py"], "cwd": "/home/me/project" } }
]
Then point a step at it with "connector": "gemini". Steps without a connector use their type's default, so existing workflows run unchanged.
- Keys stay out of the file. A connector holds a reference such as
env:GEMINI_API_KEY, and the key lives in.env. Connectors and API responses show only the reference; HTTP connectors send the key only to their ownbase_urland redact it from error messages. (Full secret handling, the vault and redaction, is Phase 4.) - Each connector gets its own rate limit and cache, so two providers never share a budget or an answer.
fallbacknames another connector of the same type, tried only after every retry failed with a transient error (rate limit, 5xx, timeout).- MCP servers have their tools discovered (
GET /connectors/{id}/tools), and every call is checked against the tool's schema before it is sent. - Local commands never run through a shell, stay inside their folder and see only the environment you give them (plus the few variables the OS needs to start a program).
Ready-made presets: NVIDIA NIM, Gemini, Claude, Ollama, Razorpay, Stripe and the fetch MCP server (backend/flowforge/connectors/presets.py). The rules are in DECISIONS.md D10, D12 and D13.
Status and roadmap
| Phase | What | Status |
|---|---|---|
| — | Scheduler core: five policies, caches, retries, SSE run stream, status page, benchmarks | Done |
| 1 | Any LLM (OpenAI-compatible + Anthropic), any MCP server with tool discovery, local command steps, connector registry, ruff + CI |
Done |
| 2 | Dashboard v1: live run, tool health, money and time, run history, add/edit apps, example data | Done |
| 3 | The flow map: canvas, step panel and edits, replay, re-check diff, connector catalog | Done |
| 4 | Repo scan: every dependency with its file:line, no LLM |
Done |
| 5 | Health checks: Check now, why it's down, the Checks page | Done |
| 6 | Flow order from Python code, the critical path, "run these together" with the change written out, map styles, the MCP Map export | Done |
| 6b | Flow order from Dart and JS/TS | Done |
| 7 | Security findings: hard-coded keys, http:// APIs, risky MCP configs |
Done |
| 8 | Launch: docs, demo, pip install |
Planned |
| 9 | CLI agent and a desktop app (Tauri) | Planned |
| 10 | V2: a local coding agent adds a dependency on a branch, for you to approve | Later |
| 11 | FlowForge's own small local model | Later |
The full roadmap with acceptance criteria is in CLAUDE.md §5.
Security
The scan looks for keys written in your code or MCP configs (known key formats, plus long random-looking values given to key-like names), APIs called over plain http://, and MCP servers with no pinned version or given the whole disk. It catches these patterns, not everything. A key's value is never stored, shown, logged or sent: findings hold the place, a masked excerpt and the fix. Test and fake-data files aren't searched.
FlowForge itself keeps keys out of its runs, events, cache, logs and the dashboard (known key formats and the keys you configured are masked), and the scan sends nothing to any LLM. Your own app's calls are outside FlowForge; for private code or data, a local model is the safe choice.
API
| Endpoint | Purpose |
|---|---|
POST /validate |
Workflow JSON → run order, levels, predicted critical path, warnings. Returns 422 with the cycle, schema error, unknown connector or wrong MCP call |
POST /runs?policy=critical_path&use_cache=true |
Starts a run in the background → {"run_id", "warnings"} |
GET /runs?status=&limit=&before= |
Run history, newest first |
GET /runs/{id} |
Status, the full result and the plan outline (kept after a restart) |
GET /runs/{id}/events |
Event stream (SSE): live while running, replayed from storage afterwards |
POST /runs/{id}/stop |
Stops a running run |
GET /connectors, GET /connectors/{id} |
Connectors, with key status set / missing / example, never values |
POST /connectors, PUT /connectors/{id}, DELETE /connectors/{id} |
Add, edit, remove (apps from connectors.json are read-only) |
POST /connectors/{id}/logo, GET /connectors/{id}/logo |
PNG, JPEG or WebP logo, up to 1 MB |
POST /connectors/{id}/test |
One harmless check (MCP and local apps) |
GET /connectors/{id}/activity, GET /connectors/{id}/tools |
What an app has done; an MCP server's tools |
GET /security |
The scan's security findings, by level, with the fix and a prompt for each (D31) |
GET /health/tools, GET /stats/savings, GET /meta, GET /presets |
Tool health, money and time, app info, ready-made apps |
GET /workflows, GET /workflows/{name} |
The example workflows |
GET /flow?layer=mine|planner |
The flow map: blocks, lines, positions, critical path, your edits |
PUT /flow/positions |
Save where blocks sit |
POST /flow/overrides, DELETE /flow/overrides/{id}, DELETE /flow/nodes/{id}/overrides |
Edit the map (swap an app, rename, hide, confirm, add a step or line), undo one edit, reset a block. Gates can't be hidden |
GET /flow/trace, GET /flow/analysis |
The traced test order the map replays; how the Planner drew the map |
POST /flow/recheck, GET /flow/versions, GET /flow/versions/{n}, POST /flow/versions/{n}/accept|discard |
Re-check: a new version and its diff; take what you pick |
GET /catalog, POST /catalog/{id}/test |
The connector catalog with each app's status; a mock test run (offline) |
Runs, step results and events are stored in SQLite after known key formats and your configured keys are replaced with [REDACTED]. A browser asking for HTML on a path like /runs/abc gets the dashboard; everything else gets JSON.
Workflow format
{
"id": "example",
"max_concurrency": 4,
"type_concurrency": { "llm": 3 },
"steps": [
{ "id": "fetch", "type": "http", "params": { "url": "https://docs.stripe.com/api/payment_intents.md" } },
{ "id": "summarize", "type": "llm", "depends_on": ["fetch"],
"params": { "prompt": "Summarize: {{steps.fetch.output.body}}", "temperature": 0 } }
]
}
Step types are llm, http, mcp, local and mock. Optional fields per step are connector, estimated_ms, timeout_s, retries (default 2), cache (true/false) and description. A step may only reference outputs of steps in its own depends_on. The full rules are in DECISIONS.md.
Scheduling policies and benchmarks
Five policies, selectable per run, so the scheduler can be compared against the obvious alternatives:
| Policy | Behaviour |
|---|---|
sequential |
one at a time, topological order |
levels |
BFS layers, wait for each layer to finish |
greedy |
unlimited concurrency |
fifo |
k slots, first ready first served |
critical_path |
k slots, longest remaining chain first (default) |
On 200 random DAGs with k = 4, critical_path finishes 8.3% ± 1.1% sooner than fifo and sits effectively at the theoretical lower bound. Full results: benchmarks/RESULTS.md.
# Simulated track: random DAGs with mock steps. Free and offline; 100 graphs take about 5 minutes
uv run python benchmarks/run_benchmark.py sim --graphs 500 --k 4
# Real track: an example workflow against real NIM + HTTP + MCP. Needs NVIDIA_API_KEY
uv run python benchmarks/run_benchmark.py real
Both tracks run every policy with the cache off and on, and write CSV plus a markdown summary to benchmarks/results/. The real track saves measured call latencies, and later simulated runs sample their step durations from them.
Layout
| Path | What |
|---|---|
backend/flowforge/scheduler/graph.py |
DAG, Kahn's topological sort, cycle reporting, levels |
backend/flowforge/scheduler/critical_path.py |
Critical path DP, bottom levels (priorities), lower bound |
backend/flowforge/scheduler/durations.py |
Duration estimates (EWMA history → estimate → default) |
backend/flowforge/scheduler/executor.py |
The list scheduler: slots, priority heap, retries, timeouts, failure handling, the five policies |
backend/flowforge/scheduler/rate_limit.py |
Async token bucket |
backend/flowforge/scheduler/cache.py |
Single-flight memo + persistent cache |
backend/flowforge/nodes/ |
llm (any provider), http, mcp, local, mock step types |
backend/flowforge/connectors/ |
Connector models, secret references, presets and the catalog, registry |
backend/flowforge/scan/ |
The repo scan: dependencies and their evidence, by plain code |
backend/flowforge/checks/ |
One harmless health check per dependency |
backend/flowforge/flow/ |
Flow order from code (Python by ast, Dart/JS/TS by patterns in curly.py): the calls, the ifs, the critical path weights, the advice |
backend/flowforge/flowmap/ |
The flow map: Flow JSON models, validation and code-inserted gates, the Planner interface and its example fixture, re-check diff |
backend/flowforge/schema.py, templating.py, storage.py |
Validation, {{steps.x.output}} templates, SQLite |
backend/flowforge/main.py, api/ |
The API: start-up wiring, runs, connectors, health and savings |
backend/flowforge/security/logfilter.py |
Redaction of keys before anything is stored, streamed or logged |
backend/flowforge/example_data.py, cli.py |
Example-data mode and the flowforge command |
frontend/ |
The dashboard (Vite + React + TypeScript; the map uses React Flow and dagre); the old status page is frontend/classic.html |
backend/workflows/ |
Example workflows |
benchmarks/ |
Random DAG generator + benchmark runner |
Tests are in backend/tests/ and never touch the network. They include property tests (hypothesis) that check the topological order, the levels and the critical-path DP against brute force on random DAGs, and the scheduler's invariants under every policy.
Docs
- DECISIONS.md: every design decision and its reason (D1–D9 for the scheduler core, D10–D18 for connectors, gates, MCP, local commands, planning, the dashboard, run history and the flow map, D19–D32 for the repo scan, health checks, flow order, the MCP Map, the MCP server for coding agents, security findings and Dart/JS flows)
- docs/mcp-map.md: the MCP Map's six rules and its export formats
- CLAUDE.md: roadmap, security spec, data model and working rules
- docs/design/: the design artboards;
flow-map-artboards/is the current spec for the flow map - AGENTS.md: the build workflow for coding agents, and the processes deployed at runtime
- FlowForge_V1_Plan.md: the original course-project plan (complete)
- FlowForge_Server_Orchestrator_Plan.md: Phase 7, server + workers
Contributing
Issues and pull requests are welcome. CONTRIBUTING.md has the setup, the checks a pull request must pass (offline tests and ruff, on Linux and Windows in CI) and the rules that keep FlowForge safe and free.
Licence
Metadata
Release files for flowforge-map 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| flowforge_map-0.1.0.tar.gz | 583.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flowforge_map-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.2 MB
Release files / flowforge_map-0.1.0.tar.gz
| Download URL | flowforge_map-0.1.0.tar.gz |
|---|---|
| Size | 583.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cd6eb3fba8b9bba055056e8ea26c12fbd60308a64b402693d3c6b488df5d30fc
|
|
BLAKE2b-256 checksum How to use checksums |
2d922b607d7eff85a978d5818f8f4629f6ebdfa7777d22a97aeaca5bbb09b72e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / flowforge_map-0.1.0-py3-none-any.whl
| Download URL | flowforge_map-0.1.0-py3-none-any.whl |
|---|---|
| Size | 627.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9747430e933ab428668962f2b75fb8e6d6d4a02157bbabe18eae80806159b40d
|
|
BLAKE2b-256 checksum How to use checksums |
da35df613638dd0e2398a2f0855298533e8c7f9d5c4cc270bff2114ce372a9cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for flowforge-map 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for flowforge_map-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.