## Como invocar funciones del registry — patrones canonicos Toda invocacion del agente al registry sigue uno de **tres patrones**. Cualquier otro patron es antipatron auditable. Las invocaciones se loguean en `projects/fn_monitoring/apps/call_monitor/operations.db` (issue 0085) para alimentar el bucle reactivo. ### Patrones canonicos | Caso | Patron | Cuando | |---|---|---| | **Inspeccionar** (buscar, leer codigo, ver dependencias, listar dominios, leer proposals) | `mcp__registry__fn_search` / `fn_show` / `fn_code` / `fn_uses` / `fn_list_domains` / `fn_proposal` | SIEMPRE para descubrimiento, lectura de codigo, exploracion. | | **Ejecutar** UNA funcion/pipeline con sus args | `mcp__registry__fn_run [args]` (preferido) o `./fn run [args]` (fallback CLI) | ID conocido + args planos. Despacho automatico por lenguaje. | | **Componer** ad-hoc multi-funcion con logica intermedia | Heredoc `python/.venv/bin/python3 - <<'PYEOF' ... PYEOF` IMPORTANDO funciones del registry | Solo si hay loops/conditionals/dispatch entre N funciones. Las funciones del registry **se importan**, no se reescriben. | ### Antipatrones prohibidos (audit-targeted) | Patron | Razon | Sustituir por | |---|---|---| | `sqlite3 registry.db "SELECT ..."` para buscar funciones/tipos | Salta MCP, FTS5 gotchas, sin trazabilidad | `mcp__registry__fn_search` | | `sqlite3 registry.db "SELECT ... FROM proposals"` | Mismo problema | `mcp__registry__fn_proposal` | | `python -c "import metabase; dir(metabase)"` para descubrir helpers | Fuente de verdad = registry, no `__init__.py` | `mcp__registry__fn_search "metabase"` + `mcp__registry__fn_show ` | | Heredoc que reescribe logica que ya existe como funcion del registry | Reinvento + perdida de capitalizacion | Buscar primero; si falta, delegar a `fn-constructor` (no escribir inline) | | `client._http.request(...)` saltando wrapper del registry | Salta validacion del wrapper y telemetria | Usar wrapper; si firma incompleta, `fn proposal add --kind improve_function` | | Scripts en `temp/` para composiciones que se repiten >2 veces | Codigo perdido + sin monitoreo | Pipeline en `python/functions/pipelines/` o `bash/functions/pipelines/` | | `from import *` en heredoc | Imposible identificar funciones usadas | Imports explicitos `from import , ` | ### Excepciones autorizadas para `sqlite3` directo Casos donde el MCP no aplica y `sqlite3 registry.db` es legitimo: - Introspeccion de schema: `.schema`, `.tables`, `PRAGMA table_info(...)`, `PRAGMA index_list(...)`. - Agregaciones: `COUNT(*)`, `GROUP BY`, `SUM(...)`, `AVG(...)`. - JOINs custom entre tablas que el MCP no expone (`functions JOIN unit_tests ON ...`). - Columnas que el MCP no devuelve (rare; preferir proponer ampliacion del MCP). El hook `PreToolUse` (`.claude/scripts/hook_registry_mcp.sh`) ya deja pasar estas excepciones y solo avisa cuando ve `sqlite3 registry.db "SELECT ..."` plano. ### Verificacion previa — `fn doctor` Antes de empezar trabajo no trivial sobre el registry, ejecutar `fn doctor` para confirmar que el ecosistema esta sano: - Artefactos OK (sin `git_not_initialized`, `venv_broken_path`, etc.). - Services activos cuando se necesiten (`sqlite_api`, `registry_api`, `registry_mcp`). - Sin drift `pc_locations` vs disco. - Sin drift `uses_functions` vs imports reales. Si `fn doctor` reporta `service inactive` para `registry_mcp.service`, el MCP estara siendo invocado en modo stdio por Claude Code (normal); el systemd unit solo aplica al modo HTTP. Si el binario no responde, rebuild: `cd apps/registry_mcp && CGO_ENABLED=1 go build -tags fts5 -o registry_mcp .`. ### Tools MCP disponibles | Tool | Lectura/escritura | Gating | |---|---|---| | `fn_search` | read | siempre on | | `fn_show` | read | siempre on | | `fn_code` | read | siempre on | | `fn_uses` | read | siempre on | | `fn_list_domains` | read | siempre on | | `fn_proposal` | read | siempre on | | `fn_doctor` | read | siempre on | | `fn_run` | execute (mutating side-effects) | requiere `--enable-run` | | `fn_create_function` | write | requiere `--enable-write` | ### Heredoc Python — convenciones obligatorias Cuando el caso 3 (composicion) sea inevitable: 1. **Imports explicitos** desde paquetes del registry. Nunca `import *`. 2. **No reescribir** la firma de una funcion del registry — importarla. 3. **Args via env vars o stdin JSON**, nunca interpolacion shell directa (inyeccion). 4. **Output a stdout JSON** cuando vaya a ser consumido por el siguiente paso. 5. **Si el heredoc supera ~30 lineas**, extraer a `python/functions/pipelines/`. El monitor avisara automaticamente cuando un patron similar se repita >5 veces. ### Trazabilidad — bucle reactivo Cada evento alimenta a `call_monitor.db` (event-log append-only) y se rollupea en una vista `function_stats` con contadores por funcion del registry. Tablas event-log: | Tabla | Captura | |---|---| | `calls` | Cada invocacion (heredoc/mcp/fn_run): function_id, tool_used, duration_ms, success, error_class, args_hash | | `code_writes` | Cada Edit/Write sobre archivo del registry: function_id, session_id, lines_added/removed | | `test_runs` | Cada `go test`/`pytest` que toca codigo del registry: function_id, test_id, passed, duration_ms | | `e2e_runs_fn` | Cada check `e2e_checks` de app que usa la funcion: function_id, app_id, check_id, passed | | `violations` | Antipatron detectado: rule_id, session_id, command_snippet, severity | | `patterns` | Heredocs clusterizados: pattern_hash, session_ids[], occurrences, representative_snippet | | `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) | Datos sensibles: solo `args_hash`, NUNCA valores concretos. Snippets de error redactados via allowlist. ### Capas de monitorizacion (issue 0085) Cobertura por capa, no todas activas a la vez: | # | Capa | Activacion | Cobertura | |---|---|---|---| | 1 | Hook PostToolUse Bash | siempre (settings.local.json) | mcp, fn_cli_run, edit_registry, violations | | 2 | Wrapper Python `registry_telemetry` | `FN_TELEMETRY=1` env var | heredocs + notebooks Jupyter | | 3 | Wrapper Bash `telemetry_prelude.sh` | `source` explicito o `FN_TELEMETRY=1` | heredoc bash + apps bash | | 4 | Interceptor en `fn run` | siempre (binario Go) | duration/error real de invocacion CLI | | 5 | `fn doctor copied-code` | comando manual / cron | drift estatico: codigo copiado en apps | | 6 | `function_versions` + snapshot | poblado por `fn index` + edit-hook | historial de versiones | | 7-8 | Build-tag Go / macro C++ | opt-in por app | runtime de app (futuro) | **Boundary:** monitorizamos al **agente** y a **invocaciones canonicas**. Runtime de apps Go/C++ compiladas queda fuera. Compensar con tests + `e2e_checks` (issue 0068). ### Que NO se monitoriza - Funcion Go/C++ llamada internamente por app ya compilada. - Funcion ejecutada por systemd timer / cron / Dagu sin pasar por `fn run`. - Sub-agente (`Agent` tool) — sus tools no propagan a hook del padre. - Service de produccion recibiendo HTTP. **Implicacion:** una funcion con `calls_90d=0` puede ser huerfana real O usada en runtime invisible. Antes de proponer `deprecate_function`, cruzar con `consumer_apps_count > 0` (e2e) o con `fn doctor uses-functions` (declaraciones estaticas).