Skip to content

Commit 5b00d4a

Browse files
committed
Bundle Ferm DoE skill references
1 parent 4eb46d0 commit 5b00d4a

152 files changed

Lines changed: 15500 additions & 65 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scripts/check_markdown_links.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,20 @@ def iter_markdown_files(paths: list[Path], excluded_dirs: set[str]) -> list[Path
7272
for child in path.rglob("*.md"):
7373
if any(part in excluded_dirs for part in child.parts):
7474
continue
75+
if is_bundled_skill_reference(child):
76+
continue
7577
files.append(child)
7678
return sorted({path.resolve() for path in files})
7779

7880

81+
def is_bundled_skill_reference(path: Path) -> bool:
82+
parts = path.parts
83+
for index, part in enumerate(parts):
84+
if part == "skills" and len(parts) > index + 3 and parts[index + 2] == "references":
85+
return True
86+
return False
87+
88+
7989
def normalize_target(raw: str) -> str:
8090
target = raw.split("#", 1)[0].strip()
8191
if target.startswith("<") and target.endswith(">"):

skills/biosymphony-ferm-doe/SKILL.md

Lines changed: 65 additions & 65 deletions
Large diffs are not rendered by default.

skills/biosymphony-ferm-doe/references/docs/ADAPTER_DESIGN_NOTES.md

Lines changed: 259 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Adapter Map
2+
3+
Capability-centric map of optional extras. The README's [Optional extras](../README.md#optional-extras) table is install-centric; this one starts from "I want to do X" and tells you which extra activates it.
4+
5+
The CLI is stdlib-only at runtime. Every adapter degrades to a `not_available` report when its extra is missing, so the demos and the closed-loop path run on a clean install. Install the extra only when a campaign needs the capability.
6+
7+
## By capability
8+
9+
| I want to... | Extra | Install | CLI surface that activates it |
10+
|---|---|---|---|
11+
| Get Student-t p-values in first-batch analysis | `scipy` | `pip install "biosymphony-ferm-doe[scipy]"` | `ferm-doe analyze` (auto) |
12+
| Get t-quantile in DoE power | `scipy` | `pip install "biosymphony-ferm-doe[scipy]"` | `ferm-doe doe-power --sigma S` (auto) |
13+
| Generate Box-Behnken with k ≥ 5 | `pydoe3` | `pip install "biosymphony-ferm-doe[pydoe3]"` | `ferm-doe generate-design` with `doe.family = box_behnken` and 5+ numeric factors |
14+
| Generate maximin Latin Hypercube | `pydoe3` | `pip install "biosymphony-ferm-doe[pydoe3]"` | `ferm-doe generate-design` with `doe.family = latin_hypercube` |
15+
| Run follow-up Bayesian optimization with a Gaussian-process surrogate | `botorch` | `pip install "biosymphony-ferm-doe[botorch]"` | `ferm-doe plan-wave2 --backend botorch --acquisition qei` or `qucb` (see [`WAVE2_BOTORCH.md`](WAVE2_BOTORCH.md)) |
16+
| Route constrained DoE through BoFire (linear, total-mass, NChooseK) | `bofire` | `pip install "biosymphony-ferm-doe[bofire]"` | `ferm-doe plan-wave2 --backend bofire` or auto-routing when the manifest declares non-box constraints (see [`BOFIRE_POSITIONING.md`](BOFIRE_POSITIONING.md)) |
17+
| Route NChooseK DoE through BoFire main | `adaptive-nchoosek-doe` | `pip install "biosymphony-ferm-doe[adaptive-nchoosek-doe]"` | Use when a first-batch DoE screen has a load-bearing NChooseK `min_count`; see [`BOFIRE_CONSTRAINT_PATTERNS.md`](BOFIRE_CONSTRAINT_PATTERNS.md) |
18+
| Route multi-fidelity scale-bridge planning through BoFire | `bofire` | `pip install "biosymphony-ferm-doe[bofire]"` | `ferm-doe scale-recipe` with `MultiFidelityVarianceBasedStrategy` declared in the manifest |
19+
| Run NChooseK Bayesian optimization (cardinality is load-bearing in BO) | `entmoot` | `pip install "biosymphony-ferm-doe[entmoot]"` | ENTMOOT v2 adapter; the documented swap for BoFire's `SoboStrategy + NChooseK` stall (see [`ENTMOOT_SWAP_DESIGN.md`](ENTMOOT_SWAP_DESIGN.md)) |
20+
| Run MIP-optimized surrogate planning over linear and NChooseK constraints | `omlt` | `pip install "biosymphony-ferm-doe[omlt]"` | OMLT adapter at `adapters/omlt_strategy.py`; activates from the planner when MIP routing fits the constraint shape |
21+
| Use a token-gated foundation-model surrogate for low-data sequential planning | `tabpfn` | `pip install "biosymphony-ferm-doe[tabpfn]"` | TabPFN adapter at `adapters/tabpfn_strategy.py`; inactive unless `TABPFN_TOKEN` is set at runtime |
22+
| Compare follow-up candidate-generators (BayBE, Ax against the in-repo BoTorch route) | `backend-eval` | `pip install "biosymphony-ferm-doe[backend-eval]"` | `examples/adaptive-backend-eval/` fixtures; see [`BIOMANUFACTURING_ADAPTIVE_BACKENDS.md`](BIOMANUFACTURING_ADAPTIVE_BACKENDS.md) |
23+
| Run SALib Sobol / Morris sensitivity over first-batch result rows | `sensitivity` | `pip install "biosymphony-ferm-doe[sensitivity]"` | SALib adapter at `adapters/salib_sensitivity.py` |
24+
| Render Plotly figures in the BoFire HTML report | `report` | `pip install "biosymphony-ferm-doe[report]"` | `reporters/bofire_html.py` |
25+
| Frictionless-validate table contracts (run ledger, evidence, design, results) | `contracts` | `pip install "biosymphony-ferm-doe[contracts]"` | Validators that read `schemas/tables/*.yaml` |
26+
27+
## Catch-all install
28+
29+
```bash
30+
pip install "biosymphony-ferm-doe[all]"
31+
```
32+
33+
Installs everything that does not require a paid or token-gated service. ENTMOOT, OMLT, and TabPFN are not in the catch-all because they each pull in solver stacks (`pyomo` + `highspy` + `lightgbm`) or foundation-model weights. Install those individually when a campaign uses them.
34+
35+
## Routing rules
36+
37+
The planner picks an adapter route based on what the manifest declares plus optional CLI flags:
38+
39+
- **Box constraints only, n < 4 usable first-batch rows, or categorical-heavy**: stdlib closed-loop path. No extra needed.
40+
- **Numeric factors, n ≥ 4 usable rows, primary response declared**: `botorch` route is available; pass `--backend botorch`.
41+
- **Linear constraints, mixture sums, total-mass**: `bofire` route fits; routes automatically when the manifest declares non-box constraints. Pass `--backend bofire` to force.
42+
- **NChooseK cardinality matters in the BO loop**: route to `entmoot`. BoFire's `SoboStrategy + NChooseK` stalls on upstream issue #450; the ENTMOOT swap is documented at [`ENTMOOT_SWAP_DESIGN.md`](ENTMOOT_SWAP_DESIGN.md).
43+
- **Hard MIP constraints over a learned surrogate**: `omlt` route.
44+
- **Low-data prediction with a foundation model**: `tabpfn` route, token-gated.
45+
46+
When the requested route's extra is missing, the adapter writes a `not_available` report and the planner falls back to the stdlib path. The orchestrator can surface the short-circuit reason to the user.
47+
48+
## Health check
49+
50+
```bash
51+
ferm-doe doctor
52+
```
53+
54+
Reports which extras are installed and which adapters are active. Useful for debugging "why did this campaign route through stdlib when I expected BoFire?".
55+
56+
## Adapter status from the tool registry
57+
58+
See [`TOOL_REGISTRY.md`](TOOL_REGISTRY.md) and [`tool-registry.json`](tool-registry.json) for the curated 47-tool surface that includes each adapter's routing rationale, current signal, fit, and risks.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Adaptive Follow-Up Planning
2+
3+
Adaptive follow-up planning turns completed first-batch result rows into auditable next-step artifacts. The stable CLI and file identifiers still use `wave2` (`ferm-doe plan-wave2`, `planned_wave2_design`) because they are part of the repo contract. That label means "the next planning checkpoint after first-batch results," not a predetermined second experiment. Outputs are deliberately conservative: `planned_wave2_design`, not optimized, validated, production-ready, or scale-transfer proven.
4+
5+
## What It Does
6+
7+
`ferm-doe plan-wave2` reads:
8+
9+
- `campaign_manifest.json`
10+
- a first-batch results CSV
11+
- optional selected-design CSV presence check
12+
13+
It writes:
14+
15+
- `adaptive_wave2_plan.json`
16+
- `result_ingestion_report.json`
17+
- `assay_power_results.json`
18+
- `wave2_recommendation.json`
19+
- `wave2_recommendation.md`
20+
- `locked_prior_runs.csv`
21+
- `augment_design.csv`
22+
- `adaptive_trace.json`
23+
- `negative_result_memory.json`
24+
- `learning_ledger.csv`
25+
- `hiccup_review.md`
26+
- `wave2_manifest.patch.json`
27+
- `bofire_strategy_report.json` when the BoFire routing rule fires
28+
29+
## Recommendation Actions
30+
31+
The public planner can recommend the next action:
32+
33+
- `confirm`: repeat or confirm a modest winner.
34+
- `narrow`: plan local candidate rows near a strong non-boundary winner.
35+
- `expand`: the best row is on a factor boundary, so factor-space review comes before local narrowing.
36+
- `pause`: results are missing, low-trust, nonnumeric, cross-arm pooled without an active arm, or bridge eligibility blocks the requested move.
37+
- `stop`: the primary response is flat enough that more runs may not be useful under the current objective.
38+
- `scale_or_downscale`: only when explicitly requested and bridge eligibility passes. This still means planning next-arm candidates, not validated transfer.
39+
40+
## Result CSV Shape
41+
42+
Minimum columns:
43+
44+
```csv
45+
design_run_id,arm_id,qc_status,inclusion_status,trust_score,primary_response
46+
R001,plate,pass,include,0.95,12.4
47+
```
48+
49+
Recommended columns:
50+
51+
- `design_run_id` or `run_id`
52+
- `arm_id`
53+
- `qc_status`
54+
- `inclusion_status`
55+
- `trust_score`
56+
- one column per measured response
57+
- factor columns from the selected design, when available
58+
59+
Rows with failed QC, explicit exclusion, or trust score below `0.6` do not drive recommendations.
60+
61+
## Manifest Slot
62+
63+
```json
64+
{
65+
"adaptive_wave2": {
66+
"claim_level": "planned_wave2_design",
67+
"primary_response_id": "product_titer_g_l",
68+
"active_arm_id": "shake_flask",
69+
"allowed_actions": ["confirm", "narrow", "expand", "pause", "stop"],
70+
"require_assay_power": true,
71+
"self_learning": {
72+
"enabled": true,
73+
"learning_ledger_path": "wave2/learning_ledger.csv",
74+
"hiccup_review_path": "wave2/hiccup_review.md",
75+
"negative_memory_scope": "arm"
76+
}
77+
}
78+
}
79+
```
80+
81+
Use `active_arm_id` when multiple arms are present. Without it, the planner will not pool incompatible plate, flask, and reactor rows into one narrowing decision.
82+
83+
## Optional BoFire Route
84+
85+
`plan-wave2` can route through `adapters/bofire_strategy.py` when the campaign declares non-box constraints, multiple optimized responses, scale fidelity, or `--backend bofire`.
86+
87+
BoFire remains optional. If `bofire[optimization]` is missing, or if the adapter cannot safely translate a declared constraint, the packet records `bofire_strategy_report.json` and falls back to the stdlib augmentation path. BoFire-backed rows keep the same `planned_wave2_design` claim boundary.
88+
89+
## CLI
90+
91+
```bash
92+
ferm-doe assay-power examples/demo-xylanase-public
93+
94+
ferm-doe plan-wave2 examples/demo-pb-screening-public \
95+
--results examples/demo-pb-screening-public/inputs/wave1_results.csv \
96+
--out-dir wave2_public_plan \
97+
--selected-design examples/demo-pb-screening-public/expected/selected_wave_1_design.csv \
98+
--remaining-budget 3
99+
```
100+
101+
## Non-Claims
102+
103+
The public planner does not:
104+
105+
- generate commercial-grade optimal designs
106+
- validate assay data
107+
- validate scale transfer
108+
- approve lab execution
109+
- replace statistical review
110+
- write GxP batch records
111+
112+
It creates a deterministic planning packet so an agent or scientist can review the next move without losing the evidence trail.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Agent Harnesses
2+
3+
How to plug `biosymphony-ferm-doe` into the runtimes long-running agents actually use. The skill is runtime-agnostic; this doc is the bridge.
4+
5+
## Common ground
6+
7+
- **The campaign manifest is durable state.** `<campaign_dir>/campaign_manifest.json` is the single source of truth. Updating it across turns is the agent's job.
8+
- **The CLI is stdlib-only at runtime.** No deps to manage, no virtualenv to ship; just `python3 -m biosymphony_ferm_doe.cli`.
9+
- **JSON output is machine-readable.** `--summary` returns ~10 fields; full output returns the per-check list. Pipe it, parse it, post it.
10+
- **Hand-off lives in `expected/AGENTS.md`.** Every session ends by updating it.
11+
12+
## State persistence model
13+
14+
| Layer | Where it lives | Who owns it |
15+
|---|---|---|
16+
| Manifest (`campaign_manifest.json`) | repo / workspace | the skill (you write to it; the validator reads it) |
17+
| Inputs (`inputs/*.csv`) | repo / workspace | the agent / scientist |
18+
| Expected artifacts (`expected/*`) | repo / workspace | the agent |
19+
| Hand-off (`expected/AGENTS.md`) | repo / workspace | the agent that's pausing |
20+
| Validator output | stdout / `--out FILE` | ephemeral |
21+
| Issue tracker comments / labels | external system | the agent's harness |
22+
23+
Resume = read the manifest, read `expected/AGENTS.md`, run `validate --summary`, continue.
24+
25+
## Claude Code
26+
27+
`agents/claude.md` is the starting reference. Patterns specific to Claude Code:
28+
29+
- Use the Bash tool for `ferm-doe list-campaigns`, `ferm-doe inspect-campaign`, `ferm-doe agent-brief`, `ferm-doe validate`, and `audit`. The output is small and parseable.
30+
- Use the Read tool for the manifest, then the Edit tool for incremental updates. Avoid Write; it overwrites the whole file and risks losing state another agent might be holding.
31+
- Use TaskCreate to plan profile-by-profile work. Mark tasks completed as you fix warnings.
32+
33+
## OpenAI Agents SDK / Codex CLI
34+
35+
`agents/openai.yaml` is the starting reference. Patterns:
36+
37+
- Wrap `ferm-doe list-campaigns`, `ferm-doe inspect-campaign`, `ferm-doe agent-brief`, and `ferm-doe validate --summary` as JSON-schema-typed tools; parse them directly into the agent's reasoning loop.
38+
- For Codex CLI: pass the campaign directory as an argument; let Codex own the read/write of the manifest file.
39+
40+
## Long-horizon orchestrators (generic)
41+
42+
`agents/generic.md` covers the baseline. The pattern that always works:
43+
44+
1. Persist `<campaign_dir>` somewhere durable (repo, S3, local filesystem with backup).
45+
2. The orchestrator's worker calls `ferm-doe list-campaigns`, reads the selected manifest, calls `ferm-doe inspect-campaign`, `ferm-doe agent-brief`, and `ferm-doe validate --summary`, then decides the next action.
46+
3. The worker writes the updated manifest back atomically (write to `<path>.tmp`, then rename).
47+
4. The orchestrator re-dispatches when new evidence arrives (lab-execution results, instrument calibration logs, additional literature).
48+
49+
## Tracker-driven runners
50+
51+
For trackers like Linear, Jira, or GitHub Issues:
52+
53+
- Tracker issue ↔ campaign
54+
- Tracker sub-issue ↔ wave
55+
- Tracker comment ↔ readiness summary
56+
- Tracker label ↔ profile + worst_axis
57+
- Tracker status ↔ readiness verdict (RED / YELLOW / GREEN)
58+
59+
## What the skill does *not* do
60+
61+
- Schedule, dispatch, or orchestrate workers. That work belongs to the harness.
62+
- Authenticate to external systems. The harness brings credentials.
63+
- Track time, cost, or budget. Record those as `constraints[]` if needed; the validator will not model them.
64+
- Execute physical lab experiments. BioSymphony Ferm DoE is a pre-experiment planning system, not an execution system; the lab team owns physical execution and the batch record built on top of the DOE plan.
65+
66+
## Anti-patterns
67+
68+
- **Two agents updating the same manifest in parallel.** Use a lock or a worktree per agent. The validator is fast enough that serial updates are not a bottleneck.
69+
- **Agent writes a "summary" to the manifest's free-text fields instead of `assumptions[]`.** Use the structured slot. Free-text drifts.
70+
- **Agent commits a campaign manifest with private process data to a public repo.** Use a private workspace; keep the public examples synthetic.
71+
- **Agent ignores `stop_rules[]`.** They exist so a paused campaign cannot be silently resumed by a different agent.
72+
73+
## Testing your harness against the skill
74+
75+
```bash
76+
# 1. fresh checkout
77+
git clone https://github.com/BioSymphony/ferm-doe.git
78+
cd ferm-doe
79+
80+
# 2. validate your harness can call the CLI and parse the output
81+
PYTHONPATH=src python3 -m biosymphony_ferm_doe.cli validate examples/demo-warnings-walkthrough-public --summary
82+
83+
# 3. confirm the diagnostic warnings round-trip through your harness's tool plumbing
84+
# 4. wire stop-rule firing into your harness's escalation path
85+
```
86+
87+
If your harness can't surface the eight warnings from the diagnostic demo to a human, fix the harness before pointing it at a real campaign.

0 commit comments

Comments
 (0)