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())