Files
fn_registry/dev/issues/0085-registry-call-standardization-and-usage-tracking.md

24 KiB

id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
id title status type domain scope priority depends blocks related created updated tags
0085 Estandarizar llamadas a funciones del registry desde Claude + app de monitorizacion de uso completado feature
meta
multi-app alta
0068
0069
2026-05-13 2026-05-17

Cierre 2026-05-15

Todas las piezas del plan implementadas:

  • Schema event-log + vistas (0085a/0085l) — 7 tablas + function_stats view + function_versions.
  • Hook Bash PostToolUse (0085b) capturando mcp/heredoc/sqlite_direct/edit_registry/violations.
  • Wrappers opt-in (0085c py + 0085c-bash) activables via FN_TELEMETRY=1, smoke verificado.
  • Interceptor en fn run (0085d-go) con duration real medida.
  • UI tab "Claude Usage" en registry_dashboard (0085d/0085e) con KPIs + sub-tabs.
  • Clusterizacion de patrones inline (0085f) — call_monitor cluster-patterns [--persist], 11 clusters detectados, upsert idempotente.
  • Reglas violation declarativas (0085g, parcial) — dev/violation_rules.yaml source-of-truth con 4 activas + 4 propuestas inactivas; runtime YAML reader TBD.
  • Pipeline call_monitor propose (0085h) genera proposals con evidencia desde function_stats+copied_code+violations.
  • Auditoria estatica de copia (0085k) fn doctor copied-code.
  • Documentacion (0085j) — CLAUDE.md + .claude/rules/registry_calls.md.

Piezas futuras documentadas pero fuera del MVP: build-tag Go telemetry (0085m), macro C++ FN_CALL (0085n), runtime YAML reader del hook, vistas adicionales del dashboard (drill-down por sesion + diff entre sesiones).

Contexto

Claude actualmente invoca funciones del registry de formas heterogeneas y sin trazabilidad:

Patron de invocacion Frecuencia esta sesion (meta_bigq) Trazabilidad
Heredoc Python inline (python/.venv/bin/python3 - <<'PYEOF' ... PYEOF) ~15 veces ninguna (queda en transcript)
Bash inline con sqlite3 registry.db "..." 1 (violando regla MCP-first) ninguna
./fn run <id> CLI 0 log en stdout, no persistido
mcp__registry__fn_run MCP tool 0 mensaje tool-result
mcp__registry__fn_search/show/code 0 (deberia haber sido obligatorio) ninguna
Imports directos from metabase import ... en heredoc en cada heredoc ninguna
client._http.request(...) directo saltando funciones del registry varias veces (para PUT custom como result_metadata) ninguna

Consecuencias:

  • No sabemos que funciones del registry usa Claude realmente. Hay ~1200 funciones indexadas pero solo unas pocas se usan en cada sesion. Imposible decidir cuales deprecar, cuales mejorar, cuales son criticas.
  • Cada sesion reinventa boilerplate. Patrones repetitivos (refresh result_metadata, dispatch dimension vs variable mapping, batch param config) se reescriben inline. Si se extraen como funciones del registry nadie lo nota porque no hay metricas de "esto se repite mucho".
  • CLAUDE.md tiene reglas (MCP-first, registry-first) que se violan silenciosamente. Sin telemetria, la regla es aspiracional.
  • No hay datos para que el bucle reactivo mejore el registry. El analizador/mejorador deberian saber "esta funcion se uso 50 veces, esta 0 veces, esta fallo 8 veces" — info que ahora mismo no existe.

Objetivo

  1. Estandarizar como Claude invoca funciones del registry (un patron canonico por caso de uso).
  2. Instrumentar todas las invocaciones para que dejen rastro en una BD local.
  3. App claude_call_monitor (TUI o web) que muestra uso por funcion, latencia, errores, patrones repetidos, violaciones de reglas.
  4. Feedback al registry: marcar funciones "huerfanas", proponer extracciones cuando un mismo bloque inline se repite N veces, sugerir helpers nuevos.

Diseno

Fase 1 — Estandarizacion de invocaciones

Tres patrones canonicos. Cada uno con su tool de entrada y formato de log.

Caso de uso Patron canonico Cuando usar
Inspeccion del registry (buscar, leer, ver dependencias) mcp__registry__fn_search/show/code/uses SIEMPRE. Reemplaza sqlite3 registry.db "..." inline. CLAUDE.md ya lo exige; ahora se hace cumplir via lint del log
Ejecucion de pipeline/funcion 1-shot mcp__registry__fn_run <id> [args] o ./fn run <id> [args] Cuando hay UNA funcion/pipeline a lanzar con sus args. Salida estructurada
Composicion ad-hoc multi-funcion Heredoc Python via Bash, importando del registry Cuando hay logica intermedia (loops, conditionals, dispatch). Esta sesion casi todo el trabajo cae aqui

Para composiciones que se repiten: extraer a python/functions/pipelines/ o a una funcion del registry. Decision basada en datos del monitor (ver fase 3).

Fase 2 — Instrumentacion

Hook + libreria que captura cada invocacion. Stack propuesto:

2a. Hook en Bash tool: parsea cada comando Bash. Si contiene heredoc Python python/.venv/bin/python3 o invoca ./fn run o mcp__registry__fn_*, captura:

  • timestamp_start, timestamp_end, duration_ms
  • session_id (del log de Claude Code)
  • tool_used (Bash heredoc / fn_run / mcp_fn_X / mcp_fn_search / sqlite_direct / etc.)
  • functions_imported (parse from <pkg> import <names>)
  • functions_called (mejor esfuerzo: regex sobre el codigo del heredoc + para fn_run el id explicito)
  • success / exit_code / error_snippet
  • patron_detected (refresh_metadata, build_mappings, etc — clasificadores configurables)

Implementacion: hook PostToolUse en ~/.claude/settings.json que llama a un binario Go que escribe en ~/.claude/projects/<proj>/call_monitor.db (SQLite).

2b. Wrapper Python: from registry_telemetry import wrap que parchea las funciones del paquete metabase/bigquery/etc al importarse en heredoc. Cada llamada se loguea (function_id, args_hash, duration, success). Solo se activa si env var FN_TELEMETRY=1 (no romper otros usos).

2c. Hook directo en MCP registry: si el server MCP esta bajo nuestro control, anadir logging en cada tool call (mas confiable que parsear bash).

Las 3 fuentes se cruzan: si una sesion tiene Bash heredoc usando metabase_get_dashboard pero no aparece en MCP logs, es violacion (deberia haber usado mcp__registry__fn_show para inspeccionar antes).

Fase 3 — App claude_call_monitor

App standalone en apps/claude_call_monitor/ o projects/fn_monitoring/apps/claude_call_monitor/. Stack:

  • Backend Go (sirve datos de call_monitor.db + agregados)
  • Frontend React + Mantine (consume @fn_library) o TUI con cpp/framework ImGui — segun preferencia

Vistas minimas:

  1. Top funciones por uso — tabla rankada: function_id, calls_24h, calls_7d, mean_duration_ms, error_rate, last_used_at. Filtros por dominio/lang/purity.
  2. Funciones huerfanas — listado de funciones del registry con calls_30d = 0. Cruzado con fn doctor unused para distinguir "nunca usada" vs "no usada por Claude pero si por humanos".
  3. Patrones repetidos — clusterizacion de heredocs Python por similitud. Detecta cuando un bloque inline se repite N veces → proposal automatico para extraer a funcion.
  4. Violaciones de regla — usos de sqlite3 registry.db directo, uso de Centros_ISO_Limpio cuando deberia ser via card snippet, etc. Reglas configurables en YAML.
  5. Sesion view — timeline de una sesion (Claude Code session_id) con todas las llamadas, errores, duraciones. Util para post-mortem.
  6. Health score — score 0-100 por sesion: ratio de invocaciones canonicas vs ad-hoc, errores, repeticiones. Telemetria para mejorar prompts del agente.

Fase 4 — Feedback al registry

Hooks de salida del monitor:

  • Proposals automaticas: cuando un patron inline se repite >5 veces en distintas sesiones, se crea proposal new_function en registry.db con evidencia (lista de session_ids + snippet representativo). El humano (o fn-mejorador) decide si aprobar.
  • Deprecation candidates: funciones con calls_90d = 0 y sin uses_functions upstream → proposal deprecate_function.
  • Performance regressions: funciones cuyo mean_duration_ms crece >50% entre semanas → flag al humano.

Implementacion por pasos

Paso Tarea Sub-issue
1 Migracion call_monitor.db schema (calls, sessions, patterns, violations) 0085a
2 Hook Bash PostToolUse que parsea comandos y escribe a calls 0085b
3 Wrapper Python opcional con FN_TELEMETRY=1 0085c
4 App claude_call_monitor skeleton (Go API + frontend) 0085d
5 Vistas: top usage, huerfanas, sesiones 0085e
6 Clusterizacion de heredocs + deteccion de patrones 0085f
7 Reglas de violacion configurables (YAML) 0085g
8 Pipeline fn-monitor proposal que crea proposals automaticas en registry.db desde patrones detectados 0085h
9 e2e_checks para la propia app del monitor 0085i
10 Documentacion en CLAUDE.md: patrones canonicos + como leer el monitor 0085j

Criterios de exito

  • Todas las invocaciones de funciones del registry desde Claude quedan registradas (>95% cobertura medida cruzando 3 fuentes).
  • App claude_call_monitor muestra top-20 funciones usadas por Claude en los ultimos 7 dias con metricas reales.
  • Se detectan al menos 5 patrones repetidos como candidatos a extraccion (con evidencia trazable).
  • Se identifican >50 funciones huerfanas para decision (deprecar/promover/dejar).
  • CLAUDE.md tiene seccion "Como llamar a funciones del registry" con los 3 patrones canonicos + tabla "cuando usar cual".
  • El bucle reactivo (0068) tiene un nuevo input: assertions sobre uso de funciones → proposals.

Anti-patrones a prohibir explicitamente

Patron Por que Alternativa
sqlite3 registry.db "SELECT ..." para inspeccionar funciones Salta MCP, no hay logging, FTS5 gotchas mcp__registry__fn_search "..."
python -c "import metabase; print(dir(metabase))" para descubrir helpers Salta el registry como fuente de verdad mcp__registry__fn_search "metabase" + mcp__registry__fn_show <id>
Heredoc que reescribe logica que ya existe como funcion Reinvento + perdida de capitalizacion Primero fn_search, luego importar
client._http.request(...) directo cuando hay un wrapper del registry Salta validacion y telemetria del wrapper Usar la funcion del registry; si falta una, delegar a fn-constructor
Crear scripts en temp/ o paths sueltos cuando es composicion repetida Codigo se pierde, no se monitoriza Si patron se repite → pipeline en python/functions/pipelines/

Stakeholders

  • Usuario humano (Lucas / Emanuel): revisa proposals automaticas, prioriza extracciones, decide deprecaciones.
  • Claude (agente principal): lee CLAUDE.md actualizado, usa patrones canonicos, recibe feedback de monitor en CLAUDE.md (top huerfanas, top errores).
  • fn-mejorador (fase 5 bucle reactivo): consume call_monitor.db para generar proposals con evidencia real de uso.
  • fn-orquestador (issue 0069): usa health score del monitor como criterio adicional de exito.

Notas

  • Esta issue no requiere refactorizar las 1200 funciones existentes — solo capturar como se invocan.
  • El monitor empieza pasivo (solo loguea). En una fase posterior puede bloquear violaciones criticas (ej. sqlite3 registry.db directo aborta con mensaje + sugerencia).
  • Datos sensibles: el args_hash se guarda pero los valores concretos NO. Para queries SQL que contienen secretos por accidente, mantener allowlist de redaccion.
  • Compatible con el patron de task_runs del issue 0069 — comparten el concepto de "ejecucion trazable".

Decisiones 2026-05-13

  • UI: tab dentro de registry_dashboard (proyecto fn_monitoring). Reusa C++/ImGui + sqlite_api. Razon: pregunta clave "que deprecar" = JOIN entre functions y function_stats; una sola app gana.
  • Plumbing: projects/fn_monitoring/apps/call_monitor/ (Go + hook Bash PostToolUse). Repo Gitea propio (dataforge/call_monitor).
  • Hook scope: solo proyecto (.claude/settings.local.json). Captura solo sesiones de fn_registry.
  • Orden de sub-issues: 0085j (docs) → 0085a (schema) → 0085b (hook) → 0085c (wrapper Python).
  • 0085j hecho (2026-05-13): seccion "Como invocar funciones del registry (CANONICO)" en .claude/CLAUDE.md, regla .claude/rules/registry_calls.md (rule #27), entrada INDEX.md, memoria feedback_canonical_registry_calls.md.
  • MCP ampliado: tool nuevo fn_proposal (read-only, list + show by id) en apps/registry_mcp/tool_proposal.go. Cierra el gap de sqlite3 registry.db "SELECT ... FROM proposals" inline. Rebuild aplicado.

Schema extendido — contadores por funcion

Event-log tables (append-only):

Tabla Captura
calls function_id, tool_used, session_id, duration_ms, success, error_class, error_snippet, args_hash, ts
code_writes function_id (derivado del path), session_id, lines_added, lines_removed, ts
test_runs function_id, test_id, passed, duration_ms, output_snippet, ts
e2e_runs_fn function_id, app_id, check_id, passed, ts (cruza con apps.uses_functions)
violations rule_id, session_id, command_snippet, severity, ts
patterns pattern_hash, session_ids[], representative_snippet, occurrences, last_seen
sessions session_id, cwd, started_at, ended_at, health_score, mcp_ratio

Vista agregada function_stats por function_id:

  • Uso: calls_total, calls_24h/7d/30d/90d, last_used_at
  • Errores: errors_total, error_rate, last_error_class, last_error_ts
  • Performance: mean_duration_ms, p95_duration_ms
  • Codigo: writes_count, last_write_at
  • Tests: tests_total, tests_failed, test_fail_rate, last_test_failed_at
  • E2E: e2e_total, e2e_failed, e2e_fail_rate, consumer_apps_count
  • Salud: violations_caused

Assertions derivadas → proposals automaticas:

Regla Threshold Proposal
Huerfana absoluta calls_90d=0 AND writes_count=0 deprecate_function
Bug prioritario error_rate>0.1 AND calls_7d>5 improve_function (bug)
Regresion performance p95_24h > 1.5 * p95_30d improve_function (perf)
Test flaky test_fail_rate>0.1 AND tests_total>10 improve_function (flaky)
Wrapper saltado violations_caused>3 improve_function (API gap)
Patron inline sin funcion patterns.occurrences>5 AND no match FTS new_function con snippet
Blast radius alto e2e_fail_rate>0 AND consumer_apps_count>=3 improve_function (critical)

Migracion: projects/fn_monitoring/apps/call_monitor/migrations/001_init.sql con las 7 tablas + indices sobre function_id, session_id, ts. Vista function_stats se construye en 002_function_stats_view.sql (materializar como TABLE si performance bajo, recalcular cada N min).

Cobertura por lenguaje — capas de monitorizacion

Cada lenguaje del registry tiene un "techo" de lo monitorizable runtime. Las 8 capas de cobertura propuestas, ordenadas por ROI:

# Capa Lenguaje Cubre Esfuerzo Estado
1 Hook PostToolUse Bash universal mcp__registry__*, ./fn run, Edit/Write sobre functions/, violations bajo hecho (0085b)
2 Wrapper Python (registry_telemetry) activable con FN_TELEMETRY=1 py heredocs Python + notebooks Jupyter + scripts dentro del registry bajo pending 0085c
3 Wrapper Bash bash/lib/telemetry_prelude.sh (redefine cada funcion del registry con cronograma + log) bash heredocs bash + scripts/apps Bash bajo pending 0085c-bash
4 Interceptor en fn run (binario Go) go/py/bash/ts invocaciones via CLI con duration/error real medido medio pending 0085d-go
5 fn doctor copied-code — fingerprint match registry vs apps universal (estatico) codigo copiado/parafraseado sin import medio pending 0085k
6 Tabla function_versions + snapshot en Edit-hook universal (estatico) drift de versiones, forks silenciosos bajo pending 0085l
7 Build-tag telemetry Go + codegen wrappers go runtime de apps Go (parcial, opt-in por build) alto futuro
8 Macro FN_CALL(name, ...) C++ opt-in cpp C++ apps cooperantes alto futuro

Reglas duras:

  • Go y C++ compilados: sin monkey-patch dinamico. Apps que linkean estaticamente la funcion no se pueden auditar runtime salvo capas 7/8 con opt-in.
  • Solucion realista Go/C++: medir 3 caminos legitimos — ./fn run, mcp_fn_run, go test/ctest. Runtime de app en produccion queda fuera.
  • Wrapper Python/Bash: activacion explicita via FN_TELEMETRY=1. Si la app no exporta esa env var, no se mide.

Drift detection — funciones copiadas y modificadas

5. Codigo copiado en apps (sin import)

Problema: app reescribe el cuerpo de una funcion del registry en vez de importarla. Invisible runtime — el codigo nunca pasa por el registry. Solo se detecta por analisis estatico.

Tecnicas (ordenadas por precision/coste):

Tecnica Pilla Limitacion
Hash exacto del cuerpo normalizado (strip whitespace + comments) copy-paste literal 0 false-positives, miss si renombran 1 var
AST fingerprint via Tree-Sitter → token sequence → SimHash copia con renames requiere parser por lenguaje
ssdeep / TLSH fuzzy hash sobre cuerpo normalizado copia con tweaks pequeños umbral arbitrario ~85% true match
Embeddings de codigo (Code2Vec, starcoder, etc.) parafraseado / refactor parcial costoso, opaco

MVP propuesto: fn doctor copied-code que cruza fingerprints de cada funcion del registry contra cuerpos de funciones declaradas en apps/, projects/*/apps/. Salida: {app_id, app_file, app_func_name, matched_registry_id, similarity, kind}. Severidad:

  • exact_copy → critical → proposal import_instead
  • near_copy (>0.85 fuzzy) → warning
  • partial_match (>0.6) → info

Persistencia: tabla nueva copied_code en call_monitor.operations.db. Aporta a function_stats columna copies_detected.

6. Versiones modificadas (function_versions)

registry.db.functions.content_hash ya existe — fn index lo recalcula. Falta historial.

Schema propuesto (migracion 003_function_versions.sql en call_monitor):

CREATE TABLE function_versions (
  function_id   TEXT NOT NULL,
  content_hash  TEXT NOT NULL,
  version       TEXT NOT NULL,
  snapped_at    INTEGER NOT NULL,
  source        TEXT NOT NULL,  -- 'index' | 'edit_hook' | 'copy_detected'
  lines_added   INTEGER DEFAULT 0,
  lines_removed INTEGER DEFAULT 0,
  PRIMARY KEY (function_id, content_hash)
);

Llenado:

  • Cada fn index inserta snapshot con source='index'.
  • Hook PostToolUse Edit/Write sobre functions/... inserta snapshot con source='edit_hook' (ya tenemos code_writes — extender o cross-reference).
  • fn doctor copied-code registra version observada en la copia con source='copy_detected'.

Consultas utiles:

  • Forks silenciosos: app copio version X, registry esta en version Y, app sigue en X.
  • Velocidad de cambio: funciones con muchas versions en poco tiempo → inestables.
  • Backport candidates: app tiene version vieja, version nueva resuelve bug → flag.

Que se escapa del monitor (boundary explicito)

Caso Capturado? Capa que lo cubriria
mcp__registry__fn_* si hook PostToolUse
./fn run X desde Bash si hook PostToolUse
Edit/Write sobre functions/*/*.{go,py,sh,ts} si hook PostToolUse
Heredoc Python importando registry parcial (solo heredoc_py, no funciones internas) wrapper Python (0085c)
Heredoc Bash sourcing registry parcial wrapper Bash (0085c-bash)
Notebook Jupyter (MCP jupyter) ejecutando codigo registry parcial wrapper Python (0085c) — el kernel hereda env vars
Sub-agente (Agent tool) NO transitivo requiere registrar hooks en cada sub-agente
Funcion Go llamada por codigo de app en runtime NO capa 7 (build-tag) — futuro
Funcion Bash sourceada por otro script en runtime NO wrapper Bash (0085c-bash) si se sourcea el prelude
Funcion C++ compilada y llamada por app NO capa 8 (macro FN_CALL) — futuro
Test automatico que ejecuta funciones NO si corre fuera de Claude capa 4 (fn run instrumentado captura go test via fn)
Service de produccion (registry_api.service) recibe HTTP NO sin cobertura — runtime sistema, no agente
Cron / Dagu / systemd timer NO sin cobertura
Funcion clonada/copiada (sin import) NO runtime capa 5 (fn doctor copied-code) detecta estatico

Verdad operativa: monitorizamos al agente y a las invocaciones canonicas del registry. El runtime de cada app en produccion queda fuera. Compensar con: tests, e2e_checks (capa propia por app, issue 0068), y telemetria de invocacion via fn run/MCP.

Implementacion por pasos (actualizado 2026-05-13)

Paso Tarea Sub-issue Estado
1 Migracion call_monitor.operations.db schema (7 tablas event-log + vista function_stats) 0085a hecho
2 Hook Bash PostToolUse que parsea tools y escribe calls/code_writes/violations 0085b hecho
3a Wrapper Python registry_telemetry (activable con FN_TELEMETRY=1) 0085c hechopython/functions/infra/registry_telemetry.py, sys.meta_path importer + wrap_namespace. Smoke verificado 2026-05-15: filter_list_py_core logged con tool_used=python_wrapper.
3b Wrapper Bash telemetry_prelude 0085c-bash hechobash/functions/infra/telemetry_prelude.sh, autowrap idempotente via declare -f + eval rename. Smoke verificado 2026-05-15: wait_for_http_bash_infra logged con tool_used=bash_wrapper.
3c Interceptor en fn run (binario Go) 0085d-go hecho
4 Tab "Claude Usage" en registry_dashboard (datasource ops:call_monitor, KPIs + 3 sub-tabs) 0085d hecho
5 Top usage, huerfanas, sesiones (vistas UI) 0085e hecho — implementadas en registry_dashboard tab "Claude Usage" (projects/fn_monitoring/apps/registry_dashboard/views.cpp, data.h, data_http.h). KPIs Reg%, MCP, Errors, Violations + sub-tabs top/huerfanas/sesiones.
6 Clusterizacion heredocs + tabla patterns populada 0085f hecho 2026-05-15 — call_monitor cluster-patterns [--persist] (cluster.go). Normaliza snippets (quoted strings -> STR, paths -> /PATH, hex 8+ -> HEX, numbers -> N), hashea sha256-truncado, agrega ocurrencias + session_ids. 11 clusters detectados de 286 calls inline; persistencia con UPSERT idempotente. 3 unit tests (TestNormalizeSnippet/TestHashSnippetStable/TestSplitCSV) pass.
7 Reglas violation configurables YAML 0085g parcial 2026-05-15 — dev/violation_rules.yaml cataloga 4 reglas activas (sqlite3_registry_select, python_dir_inspect, import_star_in_heredoc, client_http_request_direct) + 4 propuestas inactivas (mcp_ratio_low, heredoc_repetition, edit_registry_without_fn_index, protected_path_modified). YAML es source-of-truth declarativo. Runtime reader TBD: el hook PostToolUse sigue hardcoded; futura iteracion requiere jq/yq + refactor para leer reglas dinamicamente.
8 Pipeline call_monitor propose (funcion infra.GenerateProposalsFromTelemetry + infra.PersistProposalDrafts) que escribe a registry.db.proposals desde function_stats + copied_code + violations. 4 reglas MVP: copy_detected, orphan, bug, wrapper_skip. INSERT OR IGNORE con id determinista 0085h hecho
9 e2e_checks propios de call_monitor (en app.md, ya declarados) 0085i parcial (declarado)
10 Documentacion CLAUDE.md + rules (registry_calls.md) 0085j hecho
11 fn doctor copied-code + tabla copied_code + subcomando call_monitor copied-code 0085k hecho
12 Tabla function_versions + subcomando call_monitor snapshot + edit-hook con sha256 file 0085l hecho
13 (futuro) Go build-tag telemetry con codegen 0085m futuro
14 (futuro) C++ macro FN_CALL opt-in 0085n futuro