From 6da93ae856ec15dfa2ae850ecca9e466bba5b8cd Mon Sep 17 00:00:00 2001 From: Himanshu Dongre Date: Mon, 13 Apr 2026 23:06:34 +0530 Subject: [PATCH] Add capabilities manifest to /health for stale-backend detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The health endpoint now returns git_sha and a capabilities list so agents can detect when the running backend is missing features they need (e.g., claims, structured_tasks). Skill pack v1.8 teaches the capabilities probe: check /health when a 404 or missing section suggests the backend is stale, tell the human to restart. Diagnosed from the autonomy validation where Codex hit a backend without /api/v5/claims — the backend process was running old code. --- backend/app/main.py | 41 +++++++++++++++++++++++- backend/tests/integration/test_health.py | 38 ++++++++++++++++++++++ cli/smriti_cli/skill_pack/template.md | 30 ++++++++++------- cli/tests/test_skill_pack.py | 7 ++-- 4 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 backend/tests/integration/test_health.py diff --git a/backend/app/main.py b/backend/app/main.py index d01a699..6cad461 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,10 +1,29 @@ import logging +import pathlib import re +import subprocess from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.config import settings + +def _resolve_git_sha() -> str: + """Return the short git SHA of the backend directory, or 'unknown'.""" + try: + result = subprocess.run( + ["git", "rev-parse", "--short", "HEAD"], + cwd=pathlib.Path(__file__).resolve().parent.parent, + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0: + return result.stdout.strip() + except Exception: + pass + return "unknown" + class SecretGuardFilter(logging.Filter): def filter(self, record): if isinstance(record.msg, str) and re.search(r'sk-[a-zA-Z0-9_\-]+', record.msg): @@ -48,9 +67,29 @@ def create_app() -> FastAPI: app.include_router(lineage.router, prefix="/api/v5", tags=["lineage-v5"]) app.include_router(claims.router, prefix="/api/v5", tags=["claims-v5"]) + # ── Capabilities manifest ──────────────────────────────────────── + # Computed once at startup so /health is zero-cost at request time. + _git_sha = _resolve_git_sha() + + # Hardcoded feature flags matching the route modules included above. + # When a new feature ships (new route module, new query param, new + # schema shape), add it here so agents can detect stale backends. + _capabilities = [ + "claims", # /api/v5/claims + "structured_tasks", # task objects with intent_hint/blocked_by/status + "checkpoint_notes", # /api/v5/checkpoint/{id}/notes + "branch_disposition", # PATCH /api/v5/lineage/branches/disposition + "freshness", # since_commit_id on state endpoint + "compact_state", # --compact mode on state brief + ] + @app.get("/health") async def health_check(): - return {"status": "ok"} + return { + "status": "ok", + "git_sha": _git_sha, + "capabilities": _capabilities, + } @app.on_event("startup") async def startup_event(): diff --git a/backend/tests/integration/test_health.py b/backend/tests/integration/test_health.py new file mode 100644 index 0000000..4d3885a --- /dev/null +++ b/backend/tests/integration/test_health.py @@ -0,0 +1,38 @@ +"""Integration tests for GET /health capabilities manifest. + +Validates that the health endpoint returns the capabilities list and +git_sha that agents use to detect stale backends. +""" + + +def test_health_returns_capabilities(client): + """Health endpoint includes status, git_sha, and capabilities.""" + r = client.get("/health") + assert r.status_code == 200 + data = r.json() + + assert data["status"] == "ok" + assert "git_sha" in data + assert isinstance(data["git_sha"], str) + assert len(data["git_sha"]) > 0 + + assert "capabilities" in data + assert isinstance(data["capabilities"], list) + + +def test_health_includes_required_capabilities(client): + """All shipped features are listed in capabilities.""" + r = client.get("/health") + data = r.json() + caps = data["capabilities"] + + required = [ + "claims", + "structured_tasks", + "checkpoint_notes", + "branch_disposition", + "freshness", + "compact_state", + ] + for cap in required: + assert cap in caps, f"Missing capability: {cap}" diff --git a/cli/smriti_cli/skill_pack/template.md b/cli/smriti_cli/skill_pack/template.md index c74d618..9035baa 100644 --- a/cli/smriti_cli/skill_pack/template.md +++ b/cli/smriti_cli/skill_pack/template.md @@ -1,5 +1,5 @@ --- -smriti_skill_pack_version: 1.7 +smriti_skill_pack_version: 1.8 title: Smriti — how to use it well target: {{display_name}} --- @@ -214,7 +214,7 @@ Say out loud: **"Session complete. Branch pushed, checkpoint written, working tree clean."** or **"Stopping — checkpointed findings, branch is [disposition]."** -### 3.5 Backend reachability +### 3.5 Backend reachability and capabilities The Smriti backend is a shared service started by the human. You are a client of it. You do not own it. @@ -231,14 +231,22 @@ are a client of it. You do not own it. tool loop creates environment-variable inheritance issues that cause silent mock fallback on all LLM-backed endpoints. The human starts the backend; you use it. -- **Runtime freshness after code changes.** If backend code has - been merged to main since the backend was last started (e.g., - new API routes, schema changes, config fixes), the backend - must be restarted before agents can rely on the new endpoints. - Check `git log --oneline -5` against the running server's - behavior. If a new endpoint returns 404 or the behavior does - not match the merged code, tell the human: "The backend may - need a restart to pick up recent changes on main." +- **Check capabilities before using advanced features.** After + reading state, before creating claims or using features like + structured tasks, probe the backend: + {{mcp:`curl -s http://localhost:8000/health`}}{{cli:`curl -s http://localhost:8000/health`}} + The response includes `git_sha` and a `capabilities` list. If + you need `claims` but the capabilities list does not include it, + the backend is running stale code. Tell the human: "The backend + at localhost:8000 does not support [feature]. Its git_sha is + [sha] but the current repo is at [repo sha]. Please restart + the backend with `make dev` to pick up recent changes." +- **When to check capabilities:** You do NOT need to check on every + session. Check when: + - A Smriti API call returns 404 on a route you expect to exist + - The state brief is missing sections you expect (e.g., no + `## Active work` when you know claims were recently merged) + - You are about to use a feature for the first time in a session ### 3.6 Work claims: declare intent before working @@ -825,7 +833,7 @@ tell you. Do not guess. --- -*Smriti skill pack version {{primary_mode}}-1.7 — this file is +*Smriti skill pack version {{primary_mode}}-1.8 — this file is authoritative for agent behaviour on this project. If you catch it contradicting itself or your observed behaviour of the tools, tell the human; the skill pack is versioned and meant to be updated.* diff --git a/cli/tests/test_skill_pack.py b/cli/tests/test_skill_pack.py index f68f151..df15d5a 100644 --- a/cli/tests/test_skill_pack.py +++ b/cli/tests/test_skill_pack.py @@ -44,7 +44,7 @@ def test_load_template_nonempty(): def test_get_version_parses_frontmatter(): version = get_version() - assert version == "1.7" + assert version == "1.8" def test_get_version_raises_when_frontmatter_missing(): @@ -142,8 +142,11 @@ _REQUIRED_PHRASES = [ "clean finish", "branch disposition", "push your branch", - # Section 3.5 — backend reachability + # Section 3.5 — backend reachability and capabilities "backend reachability", + "capabilities", + "git_sha", + "stale code", "do not attempt to start", # Section 3.6 — work claims "work claims",