PrimAeon, the application for HyphAeon, live at
https://veg.github.io/primaeon/ (a single-threaded preview deployment; see deploy/), a neural surrogate for HyPhy's
MEME and BUSTED with co-selection networks, a digital deep mutational scan, phenotype
association, a molecular clock and temporal selection on top. This repository holds
everything that runs:
| Workspace | What it is |
|---|---|
web/ |
The site. SvelteKit 2 + Svelte 5, fully prerendered static build. Drop an alignment and one report streams in; every analysis runs in the browser under ONNX Runtime WASM. /time is the dated route: read the sampling dates, fit the clock, and run temporal selection. It takes a BEAST 1.x/2.x XML too, and one such file can be the alignment, the dates and the starting tree at once. Sequences never leave the browser on this path. |
runtime/ |
@veg/hyphaeon-runtime (private): the ONNX sessions for the browser and Node, manifest loading and sha256 verification, the pipelines and the report orchestrator over the library. Shared by the three surfaces below. |
mcp/ |
@veg/hyphaeon-mcp: one hyphaeon_analyze tool that runs the whole report in-process, the per-pillar tools (including hyphaeon_dates, hyphaeon_dating and hyphaeon_temporal) and MEME concordance, over stdio or streamable HTTP. |
server/ |
@veg/hyphaeon-server: the REST job API (SSE progress, progressive sections, stop-and-keep cancel) and the MCP mounted at /mcp behind an auto-approving OAuth ceremony, for a claude.ai connector. |
e2e/ |
Playwright: origins, headers, bytes per route, and the browser leg of the parity harness. |
deploy/ |
The runbook: Apache vhost with COOP/COEP, pm2 or Docker for the server, the rsync script. |
Every result carries is_surrogate and a path to run the real analysis on Datamonkey. No Python
and no HyPhy run anywhere in the product (PLAN.md D16, D22): a tree with branch lengths is used
as given; without one, pairwise TN93 distances feed the model, the reference's own --use-tn93.
HyphAeon is two repositories split by what they are, and the line between them is not negotiable (PLAN.md D9, §5.5):
veg/HyphAeonholds the methods: the Python reference (hyphaeon/*.py), the JavaScript library that mirrors it function for function (js/, published as@veg/hyphaeon-js), the fixtures generated from the Python and replayed by the JavaScript in the same CI run, the exported ONNX graphs and their manifest (models/), and the end-to-end parity harness (scripts/parity.py,PARITY.md). One tag publishes the Python to PyPI and the library to npm from one commit. The library is pure functions: no onnxruntime, no I/O, no workers, no server, no UI.- This repository holds everything that runs the methods, and pins the library. If two different apps (a browser, an MCP, a batch CLI) would want a piece of code unchanged, it belongs in the library; if it knows about a URL, a file, a session, a thread count or a warning to show, it belongs here.
A method is ported from the reference source and never "improved" during the port. A Python bug is replicated, flagged, fixed upstream, the fixtures regenerated, then fixed in the JavaScript.
The library is consumed by a file: link (runtime/package.json:
"@veg/hyphaeon-js": "file:../../HyphAeon/js"), and the runtime tests, the web build and the
e2e read the engine's models/ and fixtures/ from the same place, so the engine must be
checked out as a sibling of this repository, at the ref this repository is developed against
(ENGINE_REF in .github/workflows/ci.yml; phase-5d today):
parent/
├── HyphAeon/ git clone git@github.com:veg/HyphAeon.git && git checkout phase-5d
└── hyphaeon-app/ this repository
Node 22 (.nvmrc; nvm use). Then, from this repository's root:
npm ci # every workspace; links @veg/hyphaeon-js from ../HyphAeon/js
npm test # vitest in runtime/, web/, mcp/, server/ (2 min 18 s measured; scores the examples through the real graphs)
cd web && npm run check && npm run build # svelte-check, then the static build into web/build/
cd ../e2e && npx playwright install chromium && npx playwright test # Playwright against `vite preview` of that build
cd ../web && npm run dev # the dev server (copies the ORT WASM and the graphs into static/ first)The build's prebuild copies ONNX Runtime's WASM and the engine's graphs and manifest into
web/static/, writes web/static/_headers, prebakes the five gallery reports under
onnxruntime-node (stamp-cached; a full rebake is about three minutes) and validates
web/caveats.json against the manifest. Nothing served by the site comes from another origin.
Environment variables you may need:
| Variable | Read by | Meaning |
|---|---|---|
HYPHAEON_ENGINE_DIR |
scripts/copy-assets.mjs, web/scripts/*.mjs, e2e/ |
The engine checkout, when it is not ../HyphAeon. |
HYPHAEON_MODELS_DIR |
mcp/, server/, their tests |
A directory with manifest.json and the .onnx graphs. Defaults search web/static/models (after a build) and ../HyphAeon/models. |
HYPHAEON_PREBAKE |
web/scripts/prebake-gallery.mjs |
skip keeps the committed gallery records (a machine without onnxruntime-node bindings); force rebakes every example. |
HYPHAEON_BASE |
web/svelte.config.js |
The path prefix the site is served under ('' for its own origin). |
HYPHAEON_SERVER_PORT, HYPHAEON_SERVER_ISSUER, HYPHAEON_DATA_DIR, … |
server/ |
See deploy/README.md. |
onnxruntime-node is pinned exactly to 1.23.2 (the last release with darwin/x64 bindings; see
CLAUDE.md). On an Apple-silicon Mac running an x64 Node under Rosetta it is the version that
loads; production is linux/x64 where the pin costs nothing.
Local, over stdio (private; nothing leaves your machine):
claude mcp add hyphaeon -- npx @veg/hyphaeon-mcpOr from this checkout, after npm ci at the root:
claude mcp add hyphaeon -- node /path/to/hyphaeon-app/mcp/bin/hyphaeon-mcp.jsSet HYPHAEON_MODELS_DIR if the graphs are not where the search order in mcp/README.md
expects them. Remote, over streamable HTTP: the server (server/) mounts the same package at
/mcp behind its OAuth ceremony; add it as a connector the way the Datamonkey connector is added.
Fifteen tools: hyphaeon_validate, hyphaeon_analyze (the whole report), the nine pillars
— hyphaeon_meme, hyphaeon_busted, hyphaeon_epistasis, hyphaeon_dms, hyphaeon_phenotype,
hyphaeon_dates, hyphaeon_dating, hyphaeon_temporal, hyphaeon_evaluate — and job_status,
get_results, cancel_job, list_models. hyphaeon_dates loads no model at all: it reads the
sampling dates out of the sequence names or a metadata document — FASTA headers, an Auspice JSON, a
name-to-date map, a CSV/TSV or a BEAST 1.x/2.x XML — and reports what it understood, including the
rule that read each date.
hyphaeon_temporal is always a job, and its record is paged through get_results section=.
mcp/README.md is the reference.
cd server && HYPHAEON_MODELS_DIR=../web/static/models npm start # listens on HYPHAEON_SERVER_PORT (7040)
curl -s localhost:7040/api/v1/healthTen analyses on POST /api/v1/jobs: analyze (the whole report), meme, busted, epistasis,
dms, phenotype, dates, dating, temporal and evaluate. The three time analyses take a
second input, dates_file — an Auspice JSON, a name-to-date map, a CSV/TSV or a BEAST 1.x/2.x XML,
as text in the body, never a server path. A BEAST XML gives up only its DATES here, exactly as
hyphaeon dating -d run.xml does (dating.py:433-442); the alignment and starting tree such a file
also carries are reported in date_review.beast and not used, and an XML sent as the alignment
is refused with ALIGNMENT_IS_XML rather than misparsed. POST /api/v1/jobs/:id/cancel stops a run and KEEPS what it produced (a
temporal run classified at the draw count its null actually reached); DELETE is what removes it.
deploy/README.md is the runbook: what the host needs (Node and the model files, nothing else),
the Apache vhost, pm2 or Docker, and the smoke checks.
node runtime/scripts/parity-node.mjs --examples all --analyses meme,busted,epistasis,dms,phenotype --busted-examples all
cd ../HyphAeon && HYPHAEON_WEIGHTS=$PWD/model.safetensors HF_HUB_OFFLINE=1 \
python scripts/parity.py --examples all --surfaces python,node,node-tn93The runner writes the node surface (and node-tn93 for the runs that went tree-free) into the
engine's parity/; parity.py runs the reference CLI on the same examples and compares at the
classes of PLAN.md §5.4 (../HyphAeon/PARITY.md is the contract). The e2e writes the browser
surface the same way. CI runs both on every push (.github/workflows/ci.yml; the parity job).
dating and temporal are not in that harness: neither runner writes a surface for them and
parity.py has no comparator for either. Their numbers are held against the reference by
runtime/test/dating-port.test.js and runtime/test/temporal-port.test.js, which replay the
reference CLI's own committed output, and the honesty block on every dating and temporal result
says in the record itself what the run can and cannot reproduce.
.github/workflows/ci.yml runs five jobs on every push to main and every pull request: scope
(which of the slow gates this change can move), unit (one runner per workspace, in parallel),
web (svelte-check, the build and Playwright in three shards), gallery (rebakes the prebaked
demos and fails if the committed records are stale) and parity (the node surfaces, then the
Python reference and the comparison). gallery and parity run on pushes to main, nightly, and
on the pull requests that can move a number. Every job checks out veg/HyphAeon at
ENGINE_REF beside this repository with the ENGINE_DEPLOY_KEY secret (a read-only deploy key), because the engine is
private; the section "CI" in CLAUDE.md says what to set and how to bump the ref.
PLAN.md: the plan of record — architecture, the port, parity classes, phases and every decision (D1–D22). The time pillars were built to a second plan,PLAN-TEMPORAL.md, whose decisions (D26–D34) the reports below cite by number — that document is in neither checkout, so those citations currently resolve to nothing (PHASE6.md§7).CLAUDE.md: the project notebook — commands, why each non-obvious configuration is the way it is, working rules, release notes per phase.- Phase reports, each with the checks that were run and what they printed, the parity table and
the gaps carried forward. The product:
PHASE0.md,PHASE1.md,PHASE2.md,PHASE3.md,PHASE4.md. The time pillars:PHASE2-DATES.md,PHASE3-DATING.md,PHASE4-DATING-MODEL.md,PHASE5-TEMPORAL.md, andPHASE6.md, which closes the plan and carries the consolidated list of everything still open across all of it. HANDOFF.md: everything that needs an account, a secret, a decision or another repository, and cannot be done from inside these two checkouts.- In the engine:
PHASE0.md,PHASE1A.md,PHASE2A.md,PHASE3A.md(the library side of each phase),PARITY.md(the parity contract) andMDS_SIGN.md(the eigenvector sign convention, D20). mcp/README.md,server/README.md,deploy/README.md.
MIT.