diff --git a/Makefile b/Makefile index e98c30d..74cc079 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build validate audit eval site graph track synthesize recap-dry clockbench fair repos hub check help +.PHONY: build validate audit eval site graph track synthesize recap-dry clockbench fair repos hub skill check help help: @echo "longevity-loop — an AI-native compounding loop for aging science" @@ -11,7 +11,8 @@ help: @echo " make fair Score open aging datasets for FAIR/reproducibility → docs/FAIR.md (G2)" @echo " make repos Per-repo KG + agentic-tooling generation recipes → docs/REPOS.md (hub)" @echo " make hub Validate the JIT deep artifacts under hub/ (add --refresh for staleness)" - @echo " make check validate + audit + eval + clockbench + fair + repos + hub + build drift-gate (CI finish line)" + @echo " make skill Regenerate the /longevity-loop SKILL.md (backbone + hub toolset disclosure)" + @echo " make check validate + audit + eval + clockbench + fair + repos + hub + skill + build drift-gate (CI finish line)" build: python3 scripts/build.py @@ -65,8 +66,12 @@ repos: hub: python3 scripts/hub_gen.py --check +# Regenerate the invocable /longevity-loop skill (backbone tools + hub toolset disclosure). +skill: + python3 scripts/skill_gen.py + # Finish line: data well-formed, still an AI-native loop, executions honestly eval'd (no fake ✅), -# the clock-disagreement + FAIR + repo-recipe + hub artifacts self-verify, and docs match. +# the clock-disagreement + FAIR + repo-recipe + hub + skill artifacts self-verify, and docs match. check: validate audit eval python3 scripts/clockbench.py --selftest python3 scripts/fair.py --selftest @@ -76,4 +81,6 @@ check: validate audit eval python3 scripts/repos.py --check python3 scripts/hub_gen.py --selftest python3 scripts/hub_gen.py --check + python3 scripts/skill_gen.py --selftest + python3 scripts/skill_gen.py --check python3 scripts/build.py --check diff --git a/README.md b/README.md index 0627341..3b01831 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ Sequenced bridge: convert the computational finding + your credential network (G

✅ Roadmap Execution

-**13/24 done (54%).** Each execution is a checkbox with a before→after eval; a box only ticks with a real result (`done` requires a non-pending before AND after — no evidence ⇒ not done, gated in CI). +**14/25 done (56%).** Each execution is a checkbox with a before→after eval; a box only ticks with a real result (`done` requires a non-pending before AND after — no evidence ⇒ not done, gated in CI). - [x] **E1** (P1 · Foundation) — Stand up the AI-native loop (repo + self-audit + gated `make check`) - [x] **E2** (P1 · Foundation) — Map the field into a verified knowledge base @@ -183,6 +183,7 @@ Sequenced bridge: convert the computational finding + your credential network (G - [x] **E22** (P1 · Foundation) — Publish the tracker.json data contract for the interactive portfolio instance (data-first slice) - [x] **E23** (P1 · Foundation) — Hub architecture (knowledge·tooling·experts per cited repo) + the per-repo generation-recipe layer - [x] **E24** (P1 · Foundation) — Hub Phase-2: JIT deep artifacts for one repo (pyaging) — knowledge graph + gated skill, SHA-stamped +- [x] **E25** (P1 · Foundation) — Make /longevity-loop an invocable skill that uses every hub toolset (backbone + progressive disclosure) ### Eval reports — before → after @@ -212,6 +213,7 @@ Sequenced bridge: convert the computational finding + your credential network (G | ✅ E22 | a stable, derived JSON contract the agentic-portfolio instance can consume (no drift) | no machine-readable mission/progress feed for an external interactive surface | site/tracker.json — mission + 48% progress + 21 milestones + roadmap + low-hanging + frontier + 12 hubs + links, generated by build_site.py, deployed on Pages | | ✅ E23 | a fresh, cheap, future-proof way to offer a KG + agentic tooling for each cited repo | cited repos listed in stack.yml but no KG/tooling per repo; no design for how to do it well | docs/HUB_ARCHITECTURE.md (index+JIT design; reuse graphify/understand/reverse; MCP; SHA-cached) + scripts/repos.py → docs/REPOS.md (22 repos, per-repo KG + reverse recipes), gated in make check | | ✅ E24 | a real, grounded, provenance-stamped KG + skill generated from a cited repo, with a freshness gate | hub had the recipe layer only; no deep artifact proving the JIT layer works | hub/pyaging/ — knowledge-graph.json (20 nodes/edges) + compute-aging-clocks SKILL.md + manifest (SHA c2b3000e3c2c); scripts/hub_gen.py gates integrity (make check) + checks live staleness (FRESH); grounded in pyaging's real API | +| ✅ E25 | an invocable /longevity-loop skill that reaches all per-repo toolsets without bloat | hub toolsets existed (hub/*/SKILL.md) but nothing exposed them through one invocable skill | SKILL.md generated by scripts/skill_gen.py — backbone = the loop's own tools; progressive disclosure = hub toolsets (auto-discovered from hub/, 1 today: compute-aging-clocks) + a JIT recipe; drift-gated in make check; installed at ~/.claude/skills/longevity-loop | --- diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..51f4fba --- /dev/null +++ b/SKILL.md @@ -0,0 +1,43 @@ +--- +name: longevity-loop +description: Drive the longevity-loop (an AI-native compounding loop for aging science). Backbone = the loop's own gated tools (clockbench, FAIR, synthesizer, recap, the make-check gate). Progressive disclosure = per-repo toolsets under hub/ (e.g. compute aging clocks with pyaging), loaded on trigger. Use for aging-biomarker analysis, the Biolearn leaderboard, tracking the aging frontier, or any longevity-loop task. +--- + +# /longevity-loop + +_GENERATED by `scripts/skill_gen.py` from the repo's tools + `hub/` — do not hand-edit; run `make skill`. Paths are relative to the longevity-loop repo root (this file's repo)._ + +The loop's discipline governs every action: **no evidence ⇒ no claim** · computational results on open data stay separate from any wet-lab claim · **`make check` is the gate** (nothing ships red). Full dev guide: `CLAUDE.md`; what we're shooting: `docs/PROBLEMS.md`. + +## Backbone — the loop's own tools (always available) + +| Tool | When | Notes | +|---|---|---| +| `make check` | before any commit — the finish-line gate | validate + audit + eval + clockbench + fair + repos + hub + drift | +| `python3 scripts/clockbench.py` | quantify cross-clock disagreement (gaps-analysis G1) | `--demo` or `--input clocks.csv` | +| `python3 scripts/fair.py` | score open aging datasets for FAIR/reproducibility (G2) | → docs/FAIR.md | +| `python3 scripts/synthesize.py` | draft frontier→roadmap proposals (Europe PMC) | → data/_synthesis.md (human-gated) | +| `python3 scripts/recap.py` | daily build-in-public explainer | `--dry-run` offline | +| `make build` | regenerate README/ROADMAP/NULLS/FAIR from data/*.yml | never hand-edit generated docs | + +## Progressive disclosure — per-repo toolsets (load on trigger) + +**1 toolset(s)** generated from cited repos (the hub — see `docs/HUB_ARCHITECTURE.md`). Load a toolset's `SKILL.md` **only when its trigger matches** — don't carry them all. Each is grounded in its source repo at a pinned commit SHA (freshness: `make hub` / `python3 scripts/hub_gen.py --refresh`). + +| Toolset | Load when (trigger) | Read | Source @ SHA | +|---|---|---|---| +| `compute-aging-clocks` | Compute a panel of biological-age (epigenetic) clocks from methylation/omics data with pyaging, then hand the panel to clockbench to quantify cross-clock disagreement. Trigger when you need biological-age estimates, a clock panel, or the disagreement signal for the Biomarkers-of-Aging / Turn-01 work. | `hub/pyaging/SKILL.md` | rsinghlab/pyaging @ `c2b3000e3c2c` | + +## Reaching a toolset that doesn't exist yet (just-in-time) + +For any repo in **`docs/REPOS.md`** without a `hub/` toolset, generate it on demand (the hub is index + JIT, not a frozen warehouse): + +```bash +anyagent reverse --markdown # → an agentic-tooling blueprint +# shape it into hub//SKILL.md + a grounded knowledge-graph.json, +# stamp manifest.json with the repo's HEAD SHA (git ls-remote HEAD), then: +make hub # gate integrity + freshness +make skill # this skill re-discovers it automatically +``` + +> Backbone vs disclosure, the rule (from anyagent): a tool useful on *every* longevity task is backbone; a tool that names one repo/technology is disclosed on demand. Adding a hub toolset never bloats the backbone — it appears in the table above, loaded only on its trigger. diff --git a/data/executions.yml b/data/executions.yml index ae46664..eabb29e 100644 --- a/data/executions.yml +++ b/data/executions.yml @@ -177,3 +177,12 @@ before: "hub had the recipe layer only; no deep artifact proving the JIT layer works" after: "hub/pyaging/ — knowledge-graph.json (20 nodes/edges) + compute-aging-clocks SKILL.md + manifest (SHA c2b3000e3c2c); scripts/hub_gen.py gates integrity (make check) + checks live staleness (FRESH); grounded in pyaging's real API" proof: "hub/pyaging/* · scripts/hub_gen.py · docs/HUB_ARCHITECTURE.md" +- id: E25 + phase: "P1 · Foundation" + item: "Make /longevity-loop an invocable skill that uses every hub toolset (backbone + progressive disclosure)" + done: true + eval: + metric: "an invocable /longevity-loop skill that reaches all per-repo toolsets without bloat" + before: "hub toolsets existed (hub/*/SKILL.md) but nothing exposed them through one invocable skill" + after: "SKILL.md generated by scripts/skill_gen.py — backbone = the loop's own tools; progressive disclosure = hub toolsets (auto-discovered from hub/, 1 today: compute-aging-clocks) + a JIT recipe; drift-gated in make check; installed at ~/.claude/skills/longevity-loop" + proof: "SKILL.md · scripts/skill_gen.py · make check" diff --git a/scripts/skill_gen.py b/scripts/skill_gen.py new file mode 100644 index 0000000..658deb9 --- /dev/null +++ b/scripts/skill_gen.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""skill_gen — generate the invocable `/longevity-loop` SKILL.md. + +Answers "make sure /longevity-loop can use all the toolsets made from other repos" with the +right split (mirrors anyagent's own backbone-vs-attachment rule): + + • BACKBONE (baked in, always loaded): the loop's OWN gated tools — make check, clockbench, + fair, synthesize, recap, build. Small + relevant on every longevity task. + • PROGRESSIVE DISCLOSURE (loaded on trigger): the per-repo toolsets under hub//SKILL.md + (pyaging today, more later). Domain-specific — carried only when the task needs that repo, + or backbone bloat wins. + +The disclosure index is DERIVED from hub/*/ (frontmatter + manifest), so a new toolset wires +itself into the skill automatically — no hand-editing. `make check` drift-gates the result. + + skill_gen.py # write SKILL.md + skill_gen.py --check # compare (drift gate) + skill_gen.py --selftest # assert hub toolsets are discovered +""" +from __future__ import annotations + +import json +import pathlib +import re +import sys + +ROOT = pathlib.Path(__file__).resolve().parent.parent +HUB = ROOT / "hub" +OUT = ROOT / "SKILL.md" + +# Backbone: the loop's own tools, always available (repo-relative commands). +BACKBONE = [ + ("`make check`", "before any commit — the finish-line gate", "validate + audit + eval + clockbench + fair + repos + hub + drift"), + ("`python3 scripts/clockbench.py`", "quantify cross-clock disagreement (gaps-analysis G1)", "`--demo` or `--input clocks.csv`"), + ("`python3 scripts/fair.py`", "score open aging datasets for FAIR/reproducibility (G2)", "→ docs/FAIR.md"), + ("`python3 scripts/synthesize.py`", "draft frontier→roadmap proposals (Europe PMC)", "→ data/_synthesis.md (human-gated)"), + ("`python3 scripts/recap.py`", "daily build-in-public explainer", "`--dry-run` offline"), + ("`make build`", "regenerate README/ROADMAP/NULLS/FAIR from data/*.yml", "never hand-edit generated docs"), +] + + +def frontmatter(md: str) -> dict: + m = re.match(r"^---\n(.*?)\n---\n", md, re.S) + if not m: + return {} + out = {} + for line in m.group(1).splitlines(): + if ":" in line: + k, v = line.split(":", 1) + out[k.strip()] = v.strip() + return out + + +def toolsets() -> list[dict]: + """Discover hub// toolsets from their SKILL.md frontmatter + manifest.""" + out = [] + for man_path in sorted(HUB.glob("*/manifest.json")): + d = man_path.parent + man = json.loads(man_path.read_text()) + fm = frontmatter((d / "SKILL.md").read_text()) if (d / "SKILL.md").exists() else {} + out.append({ + "repo": d.name, "name": fm.get("name", d.name), + "trigger": fm.get("description", man.get("one_line", "")), + "path": f"hub/{d.name}/SKILL.md", "source": man.get("repo", ""), + "sha": man.get("source_sha", ""), + }) + return out + + +def render() -> str: + ts = toolsets() + L = [ + "---", + "name: longevity-loop", + "description: Drive the longevity-loop (an AI-native compounding loop for aging science). " + "Backbone = the loop's own gated tools (clockbench, FAIR, synthesizer, recap, the make-check " + "gate). Progressive disclosure = per-repo toolsets under hub/ (e.g. compute aging clocks with " + "pyaging), loaded on trigger. Use for aging-biomarker analysis, the Biolearn leaderboard, " + "tracking the aging frontier, or any longevity-loop task.", + "---", "", + "# /longevity-loop", "", + "_GENERATED by `scripts/skill_gen.py` from the repo's tools + `hub/` — do not hand-edit; " + "run `make skill`. Paths are relative to the longevity-loop repo root (this file's repo)._", "", + "The loop's discipline governs every action: **no evidence ⇒ no claim** · computational " + "results on open data stay separate from any wet-lab claim · **`make check` is the gate** " + "(nothing ships red). Full dev guide: `CLAUDE.md`; what we're shooting: `docs/PROBLEMS.md`.", "", + "## Backbone — the loop's own tools (always available)", "", + "| Tool | When | Notes |", "|---|---|---|", + ] + for tool, when, note in BACKBONE: + L.append(f"| {tool} | {when} | {note} |") + L += ["", + "## Progressive disclosure — per-repo toolsets (load on trigger)", "", + f"**{len(ts)} toolset(s)** generated from cited repos (the hub — see `docs/HUB_ARCHITECTURE.md`). " + "Load a toolset's `SKILL.md` **only when its trigger matches** — don't carry them all. Each is " + "grounded in its source repo at a pinned commit SHA (freshness: `make hub` / " + "`python3 scripts/hub_gen.py --refresh`).", "", + "| Toolset | Load when (trigger) | Read | Source @ SHA |", "|---|---|---|---|"] + for t in ts: + L.append(f"| `{t['name']}` | {t['trigger']} | `{t['path']}` | {t['source']} @ `{t['sha']}` |") + L += ["", + "## Reaching a toolset that doesn't exist yet (just-in-time)", "", + "For any repo in **`docs/REPOS.md`** without a `hub/` toolset, generate it on demand (the hub " + "is index + JIT, not a frozen warehouse):", "", + "```bash", + "anyagent reverse --markdown # → an agentic-tooling blueprint", + "# shape it into hub//SKILL.md + a grounded knowledge-graph.json,", + "# stamp manifest.json with the repo's HEAD SHA (git ls-remote HEAD), then:", + "make hub # gate integrity + freshness", + "make skill # this skill re-discovers it automatically", + "```", "", + "> Backbone vs disclosure, the rule (from anyagent): a tool useful on *every* longevity task " + "is backbone; a tool that names one repo/technology is disclosed on demand. Adding a hub " + "toolset never bloats the backbone — it appears in the table above, loaded only on its trigger.", ""] + return "\n".join(L) + + +def selftest() -> int: + ts = toolsets() + assert ts, "expected ≥1 hub toolset (hub/*/SKILL.md)" + assert any(t["repo"] == "pyaging" for t in ts), "pyaging exemplar should be discovered" + for t in ts: + assert t["name"] and t["trigger"] and t["sha"], f"toolset {t['repo']} missing fields" + print(f"skill_gen selftest: OK — {len(ts)} hub toolset(s) discovered " + f"({', '.join(t['repo'] for t in ts)}).") + return 0 + + +def main() -> int: + if "--selftest" in sys.argv: + return selftest() + text = render() + if "--check" in sys.argv: + current = OUT.read_text() if OUT.exists() else "" + if current != text: + print("Out of date: SKILL.md. Run `make skill`.", file=sys.stderr) + return 1 + print("SKILL.md is up to date.") + return 0 + OUT.write_text(text) + print(f"Wrote {OUT.relative_to(ROOT)} — {len(toolsets())} toolset(s) disclosed.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())