FoldForge is a local-first structure-prediction workflow app. The long-term product is a GUI-centered application for preparing, validating, running, and interpreting folding jobs, but the backbone is being built through CLI-first slices so the core models, backend adapters, runners, parsers, and reports are fast to develop and easy to test.
The current slices turn a small, validated FoldSpec YAML file into deterministic AlphaFold 3 input JSON, a dry-run command preview, and a normalized summary of AF3 output directories.
The product center is the deterministic core:
FoldSpecis the source of truth.- Backend JSON is generated output.
- The CLI is the development and testing interface for the core.
- The GUI will become the primary user-facing application once the core workflows are stable.
- LLM patching remains an optional assistant layer, not the source of truth.
FoldForge is being built from the inside out:
- Deterministic core:
FoldSpec, validation, backend exports, runners, parsers, and reports. - CLI backbone: commands that expose every workflow in a scriptable, testable form.
- Visualization/report layer: static reports and confidence plots generated from parsed results.
- GUI application: a thin visual workflow over the same core modules, with no backend logic hidden in UI code.
- Optional assistant layer: natural-language patch generation that proposes inspectable changes to
FoldSpec.
The CLI is not the final user experience. It is the scaffold that lets us move quickly without sacrificing correctness before the GUI arrives.
Implemented in this scaffold:
- Python 3.12 package named
foldfoundry - CLI command named
foldfoundry - Guided terminal command named
foldforge - PyInstaller packaging target for a macOS terminal executable at
dist/foldforge - YAML validation for inline
FoldSpecfiles - AlphaFold 3 export-only JSON
- AlphaFold 3 local dry-run command preparation
- AlphaFold 3 result parsing and
summarize - AlphaFold Server upload JSON and explicit post-login Chrome browser submission automation
- Plain-language structure-builder inputs with FASTA ingestion, preview, fusion linkers, segment/reference FoldSpec normalization, and AlphaFold Server export
- DeepSeek/GPT-Nano parser benchmarking through a shared adapter and scorer
- Protein, DNA, RNA, ligand SMILES, and ligand CCD inputs
- Golden fixture tests for deterministic export
Explicitly out of scope unless a command says otherwise:
- job execution, Docker invocation, subprocesses, or network calls
- GUI code, except for roadmap/design planning
- GUI code and full Textual-style terminal apps
- report rendering
- SDF, MSA, template, modification, userCCD, and path-based non-FASTA inputs
- AF3 multi-copy entity IDs
- compatibility with AF3 JSON versions older than version 4
For a packaged terminal executable on macOS:
uv sync --group packaging
uv run pyinstaller packaging/foldforge.spec
./dist/foldforgeRunning ./dist/foldforge with no arguments opens the guided terminal workflow.
Advanced users can pass CLI arguments through the same executable:
./dist/foldforge validate examples/simple_protein.yaml
./dist/foldforge export examples/simple_protein.yaml --backend alphafold3 --out input.jsonFor development, use the same entry point without building the binary:
uv run foldforgeFor scriptable and advanced workflows, keep using the canonical package CLI:
uv run foldfoundry --help
uv run foldfoundry validate examples/simple_protein.yamlFoldForge keeps biological sequences local unless you explicitly choose a live provider, public resolver lookup, AlphaFold Server browser/session command, or other command documented as remote-facing.
Minimal example:
name: simple_protein
backend: alphafold3
seeds: [1]
entities:
- id: A
type: protein
sequence: "MKT"
run:
mode: export_onlyThis tiny MKT job is the golden fixture for deterministic local tests. For live
AlphaFold Server upload checks, use examples/server_toy_protein.yaml; the
server rejects very short toy sequences before it will enable draft submission.
Protein, DNA, and RNA entities use inline sequence. Ligands use exactly one of smiles or ccd_code.
name: protein_ligand
backend: alphafold3
seeds: [1, 2]
entities:
- id: A
type: protein
sequence: "MKT"
- id: L
type: ligand
ccd_code: ATP
run:
mode: export_onlyDry-run local AF3 command preview:
name: dry_run_protein
backend: alphafold3
seeds: [1]
entities:
- id: A
type: protein
sequence: "MKT"
run:
mode: dry_run
model_dir: /models/af3
database_dir: /databases/af3
container_image: alphafold3:testValidation rules:
backendmust bealphafold3run.modemust beexport_onlyordry_rundry_runmode requiresmodel_diranddatabase_dirseedsmust be a non-empty list of unique strict integers greater than or equal to 1entitiesmust be non-empty- entity IDs must be unique strings
- unknown fields are rejected
Validate a job:
foldfoundry validate examples/simple_protein.yamlExport AlphaFold 3 JSON:
foldfoundry export examples/simple_protein.yaml --backend alphafold3 --out runs/simple_protein/input.jsonBuild structure prediction jobs from plain language:
foldfoundry build \
"Model HER2 extracellular domain fused to this affibody sequence with GGGGSx3" \
--fasta AFFIBODY=affibody.fasta \
--assembly fusion \
--linker GGGGSx3 \
--out her2_affibody.foldspec.json
foldfoundry preview her2_affibody.foldspec.json
foldfoundry export her2_affibody.foldspec.json --backend alphafoldserver --out her2_affibody.afserver.json
foldfoundry submit her2_affibody.afserver.jsonThe builder layer sits above the existing submission system. Existing direct
sequence FoldSpecs continue to work; richer builder specs use a sequence
registry plus chain segments, then normalize deterministically into export-ready
FoldSpec entities. Submission remains the already-automated AlphaFold Server
flow; the builder only prepares validated inputs for it.
Repeated FASTA inputs are supported with optional labels:
foldfoundry build "Model these as separate chains" \
--fasta AFFIBODY=affibody.fasta \
--fasta CARGO=cargo.fasta \
--out job.foldspec.jsonIf the request uses ambiguous language such as "connect" or "attach", build
defaults to separate_chains, marks the spec ambiguous, and emits a warning
instead of silently connecting sequences. Use --assembly fusion, --linker,
and --allow-defaults to make non-interactive choices explicit.
Benchmark DeepSeek and GPT-Nano request parsing without live providers:
foldfoundry benchmark request.txt --fasta affibody.fasta --mock --jsonBenchmark behavior is intentionally conservative:
| Command | Behavior |
|---|---|
foldfoundry benchmark request.txt |
mock/default, no network |
foldfoundry benchmark request.txt --mock |
mock, no network |
foldfoundry benchmark request.txt --providers deepseek,gpt_nano |
error requiring --live |
foldfoundry benchmark request.txt --providers deepseek,gpt_nano --live |
real provider calls after credential preflight |
foldfoundry benchmark request.txt --mock --live |
error |
Real provider calls are optional and require --live. API keys are read from
environment variables or local .env only; .env is ignored by Git and must not
be committed. Supported key names are DeepSeek_API / DEEPSEEK_API_KEY and
OpenAI_API / OPENAI_API_KEY. Environment variables take precedence over
.env. DEEPSEEK_MODEL and OPENAI_MODEL configure model IDs; the GPT-Nano
default is gpt-5.4-nano. Benchmark JSONL records are stored under
.foldfoundry/benchmarks/ by default, or at --benchmark-log PATH; raw model
output is omitted unless --include-raw-model-output is explicitly supplied, and
private FASTA sequences are redacted from those debug logs.
Use --live only for sequences you are comfortable sending to the selected
provider.
LLMs propose assembly plans only. Deterministic code resolves, validates,
assembles, previews, and exports. Auto-found sequences are risky candidate
sequences, stored as source="auto" and verified=false; they are shown in
preview and require explicit confirmation with --allow-auto-sequence before
export.
Interactive terminal menus are available as a thin InquirerPy layer:
./run-app
uv run foldforgeThe TUI menu covers build, resolve, benchmark, preview, and export flows without
making you remember the CLI flags. The prompts include short inline guidance and
contextual help choices for assembly mode, fusion linkers, export backends, and
browser/submission boundaries. The interactive prompts call the same services as
the non-interactive CLI; tests target the core modules and scriptable commands.
After a guided build writes a FoldSpec, the TUI stays in the job workflow and
asks for the next action: submit to AlphaFold Server in Chrome, export AlphaFold
Server JSON, export AlphaFold 3 JSON, preview again, return to the main menu, or
exit. A FoldSpec named qbeta_zher2.foldspec.json defaults to
qbeta_zher2.afserver.json for AlphaFold Server and qbeta_zher2.af3.json for
AlphaFold 3.
If the standalone sequence resolver is given a full structure description, it
offers to resolve the inferred molecules separately or jump into the guided build
with the same description.
Sequence lookup is federated. When a TUI build has no FASTA input, FoldFoundry asks before searching public databases, then searches the local/private registry, UniProtKB, NCBI Protein, and RCSB/PDB. Candidates are source-labeled and can be opened in Chrome before selection. Public resolvers try the exact search phrase first, then bounded contiguous phrase variants when the exact phrase misses; this keeps search from stopping at the first brittle full-text miss without adding domain-specific keyword rules.
foldfoundry registry add "HER2 nanobody 27A05" --fasta nanobody.fasta --alias "anti HER2 binder"
foldfoundry registry search "anti HER2 binder"
foldfoundry resolve "anti HER2 nanobody" --sources local,uniprot,ncbi,rcsb --limit-per-source 5
foldfoundry resolve "private binder" --local-onlyThe default private registry is ~/.foldfoundry/sequences.jsonl. Project-local
.foldfoundry/sequences.jsonl is ignored by Git if it is created. NCBI Protein
lookup requires NCBI_EMAIL; NCBI_TOOL and NCBI_API_KEY are optional.
Selected public candidates become resolved FoldSpec sequences only after user
selection and deterministic validation, with accession, URL, query, and source
provenance retained.
Normal tests are mock-only and do not call UniProt, NCBI, RCSB, AlphaFold Server, or any model provider. To verify public resolver integration on a machine with network access, opt in explicitly:
FOLDFOUNDRY_RUN_LIVE_TESTS=1 uv run pytest -m liveThe live smoke tests cover UniProtKB, NCBI Protein, and RCSB/PDB. NCBI's live
test also requires NCBI_EMAIL; NCBI_TOOL and NCBI_API_KEY remain optional.
The same live resolver path can be exercised manually through the CLI:
uv run foldfoundry resolve "P04626" --sources uniprot --limit-per-source 3 --live
uv run foldfoundry resolve "NP_004439.2" --sources ncbi --limit-per-source 3 --live
uv run foldfoundry resolve "HER2 extracellular domain" --sources rcsb --limit-per-source 3 --liveReal DeepSeek/OpenAI parser benchmarking is also opt-in. Normal tests and normal
foldfoundry benchmark runs are mock-only. To run tiny provider smoke tests on a
machine with configured keys:
FOLDFOUNDRY_RUN_PROVIDER_TESTS=1 uv run pytest -m provider_live -rsManual live benchmark smoke:
uv run foldfoundry benchmark request.txt \
--providers deepseek,gpt_nano \
--live \
--fasta AFFIBODY=tests/fixtures/affibody_toy.fasta \
--fasta CARGO=tests/fixtures/cargo_toy.fasta \
--benchmark-log .foldfoundry/benchmarks/provider-smoke.jsonl \
--jsonShort compatibility flow:
foldfoundry build "HER2 extracellular domain fused to this sequence" --fasta affibody.fasta --allow-defaults --out job.foldspec.json
foldfoundry preview job.foldspec.json
foldfoundry export job.foldspec.json --backend alphafoldserver --out exported_job.json
foldfoundry submit exported_job.jsonPrepare an AlphaFold 3 dry-run directory and print the Docker command:
foldfoundry run examples/af3_dry_run.yaml --backend alphafold3-local --dry-run --out-dir runs/dry_run_proteinSummarize an AlphaFold 3 output directory:
foldfoundry summarize tests/fixtures/af3_outputs/hello_foldThe summary is deterministic tab-separated text with one row per ranked prediction. It includes job name, backend, rank, seed, sample, ranking score, mean atom pLDDT when atom_plddts is available, pTM, ipTM, clash status, and relevant result paths.
Inspect login-form detection on a local dummy page:
foldfoundry vault inspect tests/fixtures/dummy_login_site.html --url https://dummy.test/loginThis is the first local vault-helper test harness. It detects username/password field candidates from ordinary HTML and prints deterministic selector output. It does not require Carbonyl, Chrome, Google, AlphaFold Server, or network access.
Prepare an AlphaFold Server upload JSON file for manual web submission:
foldfoundry server examples/server_toy_protein.yaml --out runs/toy_protein/alphafold_server.json --openThis writes dialect: "alphafoldserver" JSON for AlphaFold Server's Upload JSON flow and optionally opens AlphaFold Server in your system browser. FoldForge does not log in, click through the site, scrape results, or submit jobs automatically from the export command. AlphaFold Server upload JSON supports the server's allowed CCD ligands; arbitrary SMILES ligands are rejected for this workflow.
The old foldfoundry browser command remains as explicit Carbonyl debug plumbing
for preparing upload JSON plus a local debug bundle. It is not the recommended
AlphaFold Server workflow; guided submission and the default submit command use
Chrome.
foldfoundry browser examples/server_toy_protein.yaml --out-dir runs/toy_protein/browserIf foldfoundry is not on your shell PATH, run it through the local project environment:
python3 -m uv run --no-editable --reinstall-package foldfoundry foldfoundry browser examples/server_toy_protein.yaml --out-dir runs/toy_protein/browserThis writes the same AlphaFold Server upload JSON plus a local debug bundle under the session directory, then launches Carbonyl at AlphaFold Server. The browser command remains completely user-operated. Use --dry-run to prepare the files and print the Carbonyl command without launching a browser:
foldfoundry browser examples/server_toy_protein.yaml --out-dir runs/toy_protein/browser --dry-runCarbonyl may render AlphaFold Server's upload button without exposing a usable native file picker in the terminal. The browser command prints both the relative and absolute upload JSON paths; use the absolute path if the browser asks for a file path or if a user-directed path-injection workflow is needed.
Carbonyl is not bundled yet; install it separately or pass --carbonyl /path/to/carbonyl. Carbonyl is BSD-3-Clause licensed, so it is a reasonable candidate for future FoldForge release packaging with the required license notices.
Google Sign-In may reject plain Carbonyl with a "browser or app may not be secure" warning. That is an OAuth policy limitation for nonstandard, embedded, headless, automation-capable, or text-based browsers, not a TLS problem. If you have carbonyl-stealth, pass it explicitly:
python3 -m uv run --no-editable --reinstall-package foldfoundry foldfoundry browser examples/server_toy_protein.yaml --out-dir runs/toy_protein/browser --carbonyl carbonyl-stealth --forceFor a live server smoke test, stop after AlphaFold Server shows a green check for
toy_protein and enables Submit job as draft. That verifies the generated JSON
is accepted without consuming quota or submitting a job.
Submit an AlphaFold Server job after manual Google login:
foldfoundry submit examples/server_toy_protein.yaml --out-dir runs/toy_protein/submitsubmit is the explicit automation command. It generates alphafold_server.json,
opens a browser for manual Google login, then automates the AlphaFold Server
upload, submit, poll, and download. FoldForge does not automate Google login,
read credentials, interact with hidden AlphaFold Server APIs, or bypass quotas.
The default backend is Chrome:
foldfoundry submit examples/simple_protein.yaml --out-dir /tmp/test --dry-runFoldForge uses a two-stage Chrome flow. If the selected profile already has an AlphaFold Server session, FoldForge opens it for visible upload automation. If Google login is required, FoldForge first opens a normal Chrome window with no CDP, Playwright, or automation flags so you can authenticate the profile safely:
mkdir -p "$HOME/chromium-profiles/foldfoundry"
PYTHONPATH=src uv run --no-sync python -m foldfoundry.cli submit \
examples/server_toy_protein.yaml \
--browser chrome \
--profile "$HOME/chromium-profiles/foldfoundry" \
--out-dir /tmp/foldfoundry-live-submit \
--forceIf the normal login window appears, log in and complete 2FA there. Once
AlphaFold Server shows the Server page, quit that Chrome window and press Enter
in the terminal; FoldForge then reopens the same profile for visible upload,
submit, poll, and download automation. FoldForge launches that normal login
window with extensions disabled so local browser extensions are not injected into
AlphaFold Server or Google sign-in. Later runs reuse the same --profile so the
session persists.
For local development and packaged runs, run-app is the only repo-root launch
shortcut:
./run-appWith no arguments it opens the guided FoldForge terminal workflow. If
dist/foldforge exists, the script runs that packaged executable; otherwise it
falls back to uv run foldforge for development. Pass any advanced CLI command
after ./run-app:
./run-app --help
./run-app submit examples/server_toy_protein.yaml \
--profile "$HOME/chromium-profiles/foldfoundry" \
--out-dir /tmp/foldfoundry-live-submit \
--force
./run-app submit-json /path/to/alphafold_server.json --dry-runIf Chrome is already running with the same profile and you want FoldForge to
attach to it rather than open a new window, use --connect-only. This mode
connects over CDP, so --port must match the --remote-debugging-port Chrome
was started with:
./run-app submit \
examples/server_toy_protein.yaml \
--browser chrome \
--profile "$HOME/chromium-profiles/foldfoundry" \
--connect-only \
--port 9222 \
--out-dir /tmp/foldfoundry-live-submit \
--forceThe browser profile directory is sensitive session state. It can contain Google
cookies, auth tokens, local storage, browser history, and cached page data. Keep
it outside the repository, never commit it, and do not share it in bug reports.
FoldForge defaults to profiles outside the checkout:
~/.local/share/foldfoundry/chrome-profile/ for Chrome. If you pass --profile,
prefer an absolute path under your home directory, such as
$HOME/chromium-profiles/foldfoundry, rather than a repo-relative path. The
project .gitignore also includes defensive browser-profile patterns in case a
local profile is accidentally created inside the checkout. Do not run two Chrome
instances against the same profile at the same time.
Useful submit options:
--browser chromeuses Playwright's persistent context to launch Chrome directly and is the default.--browser carbonyluses the legacy Carbonyl terminal browser over CDP for explicit debugging.--port 9333changes the CDP port; relevant for--connect-onlyand legacy Carbonyl sessions.--timeout-hours 12extends or shortens result polling.--forceoverwrites existing generated submit artifacts and ZIP conflicts.--carbonyl /path/to/carbonylselects a Carbonyl binary.--chrome /path/to/chromeselects a Chrome binary (default: Playwright locates the installed Chrome automatically).--profile PATHselects the persistent browser user-data-dir.--connect-onlyattaches to an already-running browser over CDP without launching a new one.
Manual acceptance test requiring a Google account and network:
PYTHONPATH=src uv run --no-sync python -m foldfoundry.cli submit \
examples/server_toy_protein.yaml \
--browser chrome \
--profile "$HOME/chromium-profiles/foldfoundry" \
--out-dir /tmp/live-test \
--forceLog in to Google in the Chrome window, allow AlphaFold Server to run, and verify
a results ZIP appears in /tmp/live-test/. If examples/simple_protein.yaml is
rejected by the live server for being too small, use examples/server_toy_protein.yaml.
For foldfoundry export, the CLI --backend value must match the YAML backend. It validates intent but never overrides FoldSpec.
For foldfoundry run, the YAML backend remains alphafold3; --backend alphafold3-local selects the local runner preview. The command requires --dry-run and never executes Docker or AlphaFold.
For foldfoundry summarize, the input is an existing local AF3 output directory. It only reads files and never runs AlphaFold.
For foldfoundry server, the input is a validated local FoldSpec; the command writes a JSON draft for manual upload and never transmits biological data itself.
For foldfoundry browser, the input is a validated local FoldSpec; the legacy command writes a browser session directory and launches Carbonyl only after an explicit user command. FoldForge does not upload or submit anything automatically in this mode.
For foldfoundry submit, the input is a validated local FoldSpec; the command writes a submit directory and, after manual Google login, automates only the visible AlphaFold Server upload, submit, poll, and download flow.
The CLI now supports a Carbonyl-powered mini-browser session, and the GUI should eventually include the same idea as an embedded Chromium-based mini-browser for AlphaFold Server work. The browser command is user-operated: users sign in, upload JSON, review drafts, and submit jobs themselves. FoldForge's role is to keep the submission context inspectable and debuggable:
- generate the official AlphaFold Server upload JSON from validated
FoldSpec - show the current local spec and generated JSON next to the browser
- expose the absolute upload JSON path because terminal browsers may not support a usable file picker
- capture browser console and network errors for local debugging when the selected browser backend exposes them
- capture optional screenshots and downloaded result zips when the user asks
- associate downloads with the originating
FoldSpec
The submit command is the separate direct user action that may click visible
upload/submission controls and poll visible result state after manual login. No
FoldForge command may automate Google login, bypass quotas, scrape hidden APIs,
or upload biological data without an explicit user command.
Output behavior:
- missing parent directories are created
--outmust be a file path, not a directory- existing files fail unless
--forceis passed foldfoundry runwritesinput.jsoninto the run directory and creates anoutput/directory for later AF3 results
FoldForge emits one AF3 job per JSON file as a top-level object:
{
"name": "simple_protein",
"modelSeeds": [1],
"sequences": [
{
"protein": {
"id": "A",
"sequence": "MKT"
}
}
],
"dialect": "alphafold3",
"version": 4
}Entity mappings:
type: protein->{"protein": {"id": "...", "sequence": "..."}}type: dna->{"dna": {"id": "...", "sequence": "..."}}type: rna->{"rna": {"id": "...", "sequence": "..."}}- ligand
smiles->{"ligand": {"id": "...", "smiles": "..."}} - ligand
ccd_code->{"ligand": {"id": "...", "ccdCodes": ["..."]}}
The AF3 parser expects the output layout documented by Google DeepMind:
<job_name>_ranking_scores.csv- top-ranked root files:
<job_name>_model.cif,<job_name>_summary_confidences.json, and optionally<job_name>_confidences.json - per-sample directories named
seed-<seed>_sample-<sample>/
The parser reads summary confidence metrics such as ranking_score, ptm, iptm, fraction_disordered, and has_clash. It computes mean atom pLDDT only when atom_plddts is present in a confidence JSON.
Install dependencies with uv when available:
uv sync --devWhen testing the installed console script from a source checkout, uv's non-editable install mode can force the local package to rebuild:
uv run --no-editable --reinstall-package foldfoundry foldfoundry --helpFor local development, use the normal console command after syncing the project:
uv sync
uv run foldforge
uv run foldfoundry --help
uv run foldfoundry validate examples/simple_protein.yamlOn macOS, editable installs can fail if .pth files in .venv are marked with
the Finder hidden flag. Python skips hidden .pth files, so the generated
foldfoundry console script may exist while import foldfoundry still fails.
Check for this with:
PYTHONPATH=src uv run --no-sync python -m foldfoundry.cli doctorIf doctor reports a hidden FoldFoundry editable .pth file, repair only that
project-related file:
PYTHONPATH=src uv run --no-sync python -m foldfoundry.cli doctor --fixdoctor --fix reports unrelated hidden .pth files but does not modify them.
Required checks:
uv run pytest
uv run pytest --cov=foldfoundry --cov-report=term-missing --cov-fail-under=85
uv run ruff check .
uv run ruff format --check .
uv run mypy srcGitHub Actions runs the same checks on Linux and macOS for Python 3.12, including the coverage gate and CLI smoke tests.
No unit test should require network access, Docker, GPUs, model weights, databases, or live AlphaFold installation.