feat(eln): scientific spine (Strain→Experiment→Hypothesis→Result) + perception→HuggingFace flywheel#77
feat(eln): scientific spine (Strain→Experiment→Hypothesis→Result) + perception→HuggingFace flywheel#77pskeshu wants to merge 21 commits into
Conversation
…connector + 4 seed programs On feature/eln-scientific-spine (off #72). Strain->Experiment->Hypothesis->Result as optional co-authored file-based overlays; assisted claim-testing loop on existing Expectation.resolve + ground_truth. Adds the approval-gated HuggingFace connector (per-experiment export target, default pskeshu/gently-perception-benchmark, HF_TOKEN from env, never auto) and the 4 real seed programs (stage-classification; K-pump thermo-cycling -> hatching-time distributions; mutant screen; dopaminergic outgrowth 'catching outgrowth'). References existing gently/dataset annotator, dopaminergic_signal detector, test_embryo_strain. Non-goals: Actuator, rename, multi-tenant, dataset-tool migration. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
FileContextStore gains create_strain/get_strain/list_strains/resolve_strain
(agent/strains/{id}.yaml) + the strains/experiments/hypotheses/results skeleton.
resolve_strain matches by id or case-insensitive name; a bare string that matches
no record returns None (back-compat — callers keep the display string). 3 tests.
Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
New gently.eln package (reads from FileStore, not the deprecated gently.dataset).
hf_connector: build_records (pure formatting of {image ref, prediction, human
ground truth, provenance, strain}) + push_dataset (approval-gated — caller confirms;
HF_TOKEN from env; lazy 'datasets' import; default repo pskeshu/gently-perception-
benchmark). Never auto-pushes; clear error when token/records absent. 5 tests.
seed/programs/: real version-controlled Campaign/Strain/Experiment/Hypothesis (not stubs) instantiating the spine — stage-classification (RW10226 nuclear+BF fusion, hatching), temperature-hatching (Richard K-pump thermo-paralytic -> hatching-time distributions), mutant-screen (WT vs mutant ID), dopaminergic-outgrowth (BZ555 dat-1p::GFP 'catching outgrowth', expression-triggered high-cadence via dopaminergic_signal.py). Dogfoods the spine + gives real notebook data. docs/product-ideation/ELN-USAGE.md: biologist journey over TIME (accumulate datasets, annotate incrementally, flywheel across sessions), 11 iterative stories, 23 little- things, 8 Playwright story-flow specs, 14 design implications for the build. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
gently/eln/accuracy.py: pure functions comparing per-timepoint stage predictions against range-based human ground truth -> an accuracy Result (n_scored/n_correct/ accuracy/unscored/confusion). stage_at_timepoint resolves the covering GT range (latest-start wins on overlap; open-ended end reaches on). accuracy_result builds a spine Result dict (draft when nothing scorable). 4 tests.
POST /api/embryos/{id}/ground_truth (require_control) -> FileStore.set_ground_truth
with annotator provenance; range-based (start/end timepoint, end null = open-ended)
so a confirmed stretch is one write. Replaces the localStorage-only Agree/Disagree
dead-end. 4 tests (persist range, open-ended end, stage required, int start).
Documents the shipped backend (Strain/HF/accuracy/GT route, tested) + the remaining TDD tasks: Experiment/Hypothesis/Result stores (full code), strain resolution, seed loader, export service + approval-gated export route, agent tools, and the in-browser UI tasks (stage-strip range authoring, assisted batch-confirm, experiment inspector) flagged for browser verification, + ELN US- story flows (after #76).
…ader Spine data layer complete (all file-based, optional overlays): - Experiment store (arms/controls/session-linking; strain alias); create/get/list/ link_session_to_arm. - Hypothesis store (proposed/supported/refuted/inconclusive); create/get/list/ set_hypothesis_status (validates status). - Result store; save/get/list_results (experiment_ref filter). - strain_resolution: non-mutating display-side resolve_ref (bare-string shim) + enrich_imaging_spec. - seed_loader: load_program/load_seed_dir imports seed/programs/*.yaml. Tests (12) incl. loading the 4 REAL seed programs into a fresh store.
Fixes the inverted confirmation guards (flagged in docs/product-ideation/CONSULT.md): the UI confirmed reversible map edits (Remove/Move embryo, Stop) but fired the two specimen-HARMING actions unconfirmed. Now toggleLaser (OFF->ON), setTemperature, and setLightsheetTemperature ask an explicit confirm naming the effect on live embryos. Turning the laser OFF never confirms (safe direction). require_control unchanged.
…n flywheel UI)
Replaces the localStorage-only Agree/Disagree dead-end with a real ground-truth
stage picker: canonical-stage <select> pre-filled with the model's predicted stage +
a Confirm button that POSTs to /api/embryos/{id}/ground_truth (with currentSessionId),
shows a persisted 'GT: {stage}' badge, and a save flash. GT route made store-flexible
(agent.store -> server.gently_store -> server.store) so annotation works in
benchmark/no-agent mode too; +1 test. CSS matches the detection-card theme.
…rejected A store-less / errored save previously only console.debug'd — now the Confirm button briefly shows 'Save failed' (amber) so a correction never silently vanishes.
|
Follow-up commits: Safety fix — inverted confirmation guards ( Ground-truth authoring UI ( Still remaining (in the plan): assisted batch-confirm, live accuracy readout, experiment inspector + export button, agent tools, and the ELN |
…store Makes the 4 seed research programs actually loadable into a gently FileContextStore (they were version-controlled YAML with no loader wired). Verified: loads 4 campaigns + strains + experiments + hypotheses; they render in the Plans tab (screenshot).
New GET /api/campaigns/{id}/eln returns the campaign's experiments (arms x strain x
condition + controls), the strains they use (resolved), and deduped hypotheses.
campaigns.js renders an ELN section in the plan doc: Strains (chips w/ genotype),
Experiments (cards, arms with strain chip + condition, controls distinguished),
Hypotheses (status-badged). Verified in-browser against the seeded Temperature program
(2 strains, 3 experiments, 4 hypotheses). CSS in campaigns.css.
…tore empty) launch_gently.py --seed loads the 4 bundled seed research programs into an empty FileContextStore on boot, so a fresh instance opens with real notebook content (Campaign/Strain/Experiment/Hypothesis). No-op if the store already has campaigns. Verified: fresh store + --seed -> 4 campaigns render with full experiment/strain/ hypothesis detail in the Plans tab (native integrated render, screenshot).
|
Now visible in the UI (the earlier commits were backend/data; these make it show):
Run it: |
…ion C) Deep study of how Plan Mode and the ELN spine relate. Verdict: 3 of 4 ELN entities (Strain/Hypothesis/Result) are genuinely additive and KEPT; only Experiment is one-thing-modeled-twice (a controlled comparison is already a phase of imaging PlanItems) and is the direct cause of the bolted-on render. Recommend Option C: Plan Mode absorbs the ELN as native nodes+fields, Experiment demoted to a phase-role; ship the model-agnostic perception->HF flywheel FIRST, then the fold. Not deprecate/absorb/ unify (A/B/E discard or rewrite the working incumbent; D keeps the redundancy).
gently/eln/export_service.py: collect_embryo_annotations pairs each prediction with the
ground-truth stage covering its timepoint (range-based, latest-start wins) into HF-ready
rows (annotated-only by default); annotation_summary gives {n_predictions, n_annotated}
for the push affordance. Robust to dict/dataclass store returns. 4 tests.
…push)
POST /api/embryos/{id}/export (require_control) — approval-gated (confirm + HF_TOKEN)
push of an embryo's annotated timelapse to HuggingFace via export_service +
hf_connector; 400 without confirm, 503 without token, 400 with no annotated timepoints,
502 on push failure. GET /api/embryos/{id}/annotation-summary -> {n_predictions,
n_annotated} for the push affordance. Extracted a shared _gt_store() resolver (agent ->
gently_store -> store), reused by the GT route. 5 route tests + GT route intact.
Each Vitals strip shows 'N/M annotated' (GET annotation-summary) + a Push-to-HF button (disabled when nothing annotated). pushEmbryoToHf confirms, POSTs the approval-gated /export, and surfaces the outcome inline (Pushed N / Set HF_TOKEN / Log in / Failed). The per-embryo timelapse+annotations is the flywheel's unit; this is its terminal step.
|
Flywheel push landed (per-embryo timelapse → HuggingFace, the active project's terminal step):
Per the plan-mode↔ELN fold study (committed), the flywheel is model-agnostic so it ships independent of the fold. Next: ELN agent tools, then the fold (Experiment→phase-role). |
… fold 15 stories across the 4 seed programs + longitudinal/multi-user, written to SEE the fold. Verdict: they CONFIRM Option C (fold; Experiment->phase-role; keep Strain/ Hypothesis/Result) and refine it with a 5-item punch list: (a) paired within-embryo designs break the 1:1 arm<->session assumption (one session scored N ways); (b) no Result->next-PlanItem authoring tool (the loop-closer); (c) Strain is lifecycle state minted by a genetics PlanItem, not a static ref; (d) multi-annotator ground_truth + adjudication node; (e) cross-campaign spanning Results have no home. Deepest insight: the fold is 'one perception call wearing two hats' (live decision gate AND retrospective ground-truth whose accuracy IS the Result).
- ruff format over 15 ELN files; wrap an overlong line in load_seed_programs.py (E501) - export_service: only call asdict on dataclass instances, not types - data.py ground-truth route: index start_timepoint directly and catch KeyError, so mypy sees no None reaching int()
ELN scientific spine — Strain → Experiment → Hypothesis → Result
Adds a co-authored electronic-lab-notebook scientific spine to gently as optional,
file-based overlays, plus the perception-annotation → HuggingFace flywheel. Turns
data-collection into claim-testing and gives the human a write-path that isn't chat.
Origin:
docs/product-ideation/ENTITY-EVOLUTION.md(Fable's entity-evolution assessment)Built + tested (backend, 28 tests)
FileContextStore,agent/{strains,experiments,hypotheses,results}/):Strain (create/get/list/resolve with bare-string back-compat), Experiment
(arms × controls,
link_session_to_arm), Hypothesis (proposed/supported/refuted/inconclusive), Result (accuracy/measurement/derived +
experiment_reffilter).gently/eln/package:accuracy(per-timepoint predicted-vs-ground-truth →accuracyResult),hf_connector(approval-gated push;HF_TOKENfrom env; defaultrepo
pskeshu/gently-perception-benchmark; lazydatasets; never auto-pushes),strain_resolution(non-mutating display shim),seed_loader.POST /api/embryos/{id}/ground_truth(require_control,range-based →
set_ground_truth) — replaces the localStorage-only Agree/Disagreedead-end.
The flywheel
gently-perceptionpredicts → in-ELN ground-truth (assisted batch-confirm: agentpre-screens ranges, human confirms uncertain frames) →
accuracyResult → approval-gatedexport to HuggingFace → retrain. Bidirectional: the model's calls become the human's
annotation queue; the corrections become the next training set.
Real seed programs (dogfood, not stubs)
seed/programs/: stage-classification (nuclear+BF fusion, hatching), temperature-hatching (Richard's K-pump thermo-paralytic → hatching-time distributions), mutant-
screen (WT vs mutant ID), dopaminergic-outgrowth ("catching outgrowth", BZ555
dat-1p::GFP, expression-triggered high-cadence viadopaminergic_signal.py). A testloads all four into a fresh store.
Docs
docs/superpowers/specs/2026-07-02-eln-scientific-spine-design.mddocs/superpowers/plans/2026-07-02-eln-scientific-spine.mddocs/product-ideation/ELN-USAGE.md(biologist journey over time, iterativestories, story-flow specs, design implications)
Remaining (in the plan, flagged for in-browser verification)
Vitals stage-strip range-authoring UI + assisted batch-confirm, experiment inspector +
export button, agent tools, and the ELN
US-story flows (after #76 lands the crawlerharness on the base). Deliberately deferred because they need the running app + a Chrome
MCP UI/UX audit before shipping.
Non-goals (YAGNI)
Setpoint→Actuator,Embryo→Specimenrename, multi-tenant, re-inventing the organismplugin,
gently/datasetmigration.🤖 Generated with Claude Code — Fable 5