Effort: deep
scidex/agora/skill_evidence.py:_log_invocation already writes one row per
K-Dense skill call into agent_skill_invocations (migration
20260427_agent_skill_invocations.sql — 7 columns: skill_name,
artifact_class, artifact_id, persona, success, latency_ms,
cited_in_artifact). Today nothing reads those rows: there is no rollup, no
endpoint, no view. Build the first analytics layer so we can answer "which
skills did the Theorist actually use on hypothesis H, and how many of those
calls became evidence in the final artifact?". Without this loop the table is
write-only and the 23 tool skills + 100+ Anthropic skills are an opaque cost
center.
scidex/forge/skill_telemetry.py exposing four queriesusage_by_skill(window_days: int) -> list[dict] — per skill_name,total_calls, success_rate, p50_latency_ms, p95_latency_ms,cited_rate (= cited / success).usage_by_persona(window_days: int) -> list[dict] — per persona,distinct_skills, total_calls, top_skill, top_skill_share.usage_by_artifact_class(window_days: int) -> list[dict] — perartifact_class (the 10 enum values from the migration's CHECKcoverage_for_artifact(artifact_class, artifact_id) -> dict —created_at.
mv_skill_usage_daily(skill_name, day, calls,scidex-skill-telemetry-rollup systemd timer (template the unit filedeploy/bootstrap/systemd/); refresh interval 15 min. Manualpython -m scidex.forge.skill_telemetry refresh.
api_routes/forge.py:GET /api/forge/skills/telemetry?window=7d (returns all fourGET /api/forge/skills/{skill_name}/calls?limit=50 (recent rows).
/forge/skills/telemetry renders the four tables withskill_name links/forge/skills/{skill_name} (already exists for canonicalpersona links to /persona/{slug}.
tests/test_skill_telemetry.py: synthetic 200-row fixture withcited_rate[] if the table is genuinely empty)mv_skill_usage_daily.scidex/senate/quality_dashboard.py for the existing rollup-APIagent_skill_invocations only —percentile_cont(0.5) andpercentile_cont(0.95) for latency percentiles.
migrations/20260428_mv_skill_usage_daily.sql. ScheduleREFRESH MATERIALIZED VIEW CONCURRENTLY from the new systemd timer.
templates/forge/base.html sotests/test_skill_evidence.py setup).agent_skill_invocations table (migration 20260427_agent_skill_invocations.sql).scidex/agora/skill_evidence.py — current writer; this task only reads.q-skills-quality-leaderboard — consumes cited_rate + success_rate.q-skills-cost-rationality — consumes p50/p95 latency for cost models.Files created:
scidex/forge/skill_telemetry.py — four pure-SQL rollup functionsusage_by_skill, usage_by_persona, usage_by_artifact_class,coverage_for_artifact), recent_calls_for_skill, build_telemetry_bundle,refresh_materialized_view, and __main__ CLI for refresh/smoke.
migrations/20260428_mv_skill_usage_daily.sql — materialized view with(skill_name, day) required for CONCURRENT refresh.
deploy/bootstrap/systemd/scidex-skill-telemetry-rollup.service + .timerapi_routes/forge.py — new router withGET /api/forge/skills/telemetry?window=7d,GET /api/forge/skills/{skill_name}/calls?limit=50,GET /forge/skills/telemetry (HTML dashboard, 4 sortable tables, vanilla JS).
tests/test_skill_telemetry.py — 11 tests against 200-row synthetic fixtureapi.py change: added from api_routes.forge import router as _forge_router
and app.include_router(_forge_router) at line ~1244.
Note on templates/forge/base.html: the spec referenced this template but
it does not exist in the codebase. The HTML page uses api_shared.nav.page_template
instead, consistent with all other route files (e.g. api_routes/senate.py).