- .claude/rules/registry_calls.md - apps/dag_engine/README.md - apps/dag_engine/app.md - docs/capabilities/INDEX.md - docs/capabilities/systemd.md - docs/execution_standard.md - dev/proposals_e2e_checks_0121/ - docs/capabilities/backends.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
Capability: backends
Catalogo de stacks backend usados en apps/. Para que un agente que va a construir un backend nuevo sepa que stack elegir, que funciones del registry componer y que esqueleto copiar sin reinventar nada.
No es un grupo tag: del registry — es una guia transversal (los stacks atraviesan dominios). Complementa cpp_apps.md (frontend C++) y deploy.md (entrega).
Stacks soportados
| Stack | Lang | Cuando elegir | Apps de referencia |
|---|---|---|---|
| Go net/http stdlib + SQLite | go | Default. API HTTP local o detras de Traefik. Persistencia SQLite con migraciones embed.FS. | sqlite_api, services_api, registry_api, kanban, deploy_server, dag_engine, agent_runner_api, call_monitor |
| Go MCP stdio/http | go | Tool server para Claude/agentes. Lectura/escritura registry. | registry_mcp |
| Go bubbletea TUI | go | CLI interactiva sin HTTP. Pipeline launcher, docker manager. | pipeline_launcher, docker_tui, dev_console |
| Go mautrix bot | go | Bot Matrix con E2EE, LLM tools, comandos. | agents_and_robots |
| Python httpx + YAML | py | Cliente declarativo de API REST externa (Metabase, Gitea, etc.). Pull/push contra disco. | auto_metabase, metabase_registry |
| Bash docker-compose | bash | Stack multi-container (Synapse + Element + LiveKit, PostGIS + Valhalla). | element_matrix_chat, footprint_geo_stack |
| C++ ImGui frontend | cpp | NO es backend — cliente HTTP/WS de los Go services. Ver cpp_apps.md. |
services_monitor, registry_dashboard, ... |
Decision tree
¿Necesitas HTTP API?
├─ si → ¿Es para Claude/agentes?
│ ├─ si → MCP server (Go) → copiar `registry_mcp`
│ └─ no → Go net/http stdlib + SQLite + embed.FS migrations → copiar `services_api`
│
└─ no → ¿Interactivo terminal?
├─ si → Go bubbletea → copiar `pipeline_launcher`
└─ no → ¿Cliente de API externa?
├─ si → Python httpx → copiar `auto_metabase`
└─ no → ¿Stack de containers?
├─ si → Bash + docker-compose.yml → copiar `element_matrix_chat`
└─ no → reevalua, probablemente sea funcion del registry no app
Esqueleto canonico: Go net/http stdlib + SQLite (el default)
Layout
apps/<name>/
app.md # frontmatter + service: block (issue 0105)
main.go # flags + listener + signal handling
server.go # http.ServeMux + routes + middleware chain
db.go # sql.Open con WAL + FK + apply migrations
handlers_*.go # 1 archivo por recurso
migrations/
001_init.sql # CREATE TABLE IF NOT EXISTS (idempotente)
002_*.sql # aditivo. NUNCA modificar 001 ya commiteado
operations.db # SQLite local. Gitignored.
Frontmatter app.md
---
name: my_service
lang: go
domain: infra # ver list_domains
version: 0.1.0
description: "1 linea: que hace + por que existe."
tags: [service, api, http, sqlite]
uses_functions:
- sqlite_apply_versioned_migrations_go_infra
- http_serve_go_infra
- http_json_response_go_infra
- http_parse_body_go_infra
- http_router_go_infra
- http_cors_middleware_go_infra
- http_logger_middleware_go_infra
- logger_go_infra
uses_types: []
framework: "net/http"
entry_point: "main.go"
dir_path: "apps/my_service"
service: # OBLIGATORIO si tag service. issue 0105
port: 8500
health_endpoint: /api/health
health_timeout_s: 3
systemd_unit: my_service.service
systemd_scope: user
restart_policy: always # NUNCA on-failure (ver gotcha cpp_apps.md)
runtime: systemd-user
pc_targets: [home-wsl]
is_local_only: false
---
main.go (minimo viable)
package main
import (
"context"
"flag"
"log"
"os/signal"
"syscall"
)
func main() {
bind := flag.String("bind", "127.0.0.1:8500", "addr to listen on")
dbPath := flag.String("db", "operations.db", "sqlite path")
flag.Parse()
ctx, cancel := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer cancel()
db, err := openDB(*dbPath) // db.go — usa sqlite_apply_versioned_migrations
if err != nil { log.Fatal(err) }
defer db.Close()
srv := newServer(db) // server.go — http.ServeMux + rutas
log.Printf("listening on %s", *bind)
if err := serve(ctx, *bind, srv); err != nil { log.Fatal(err) }
}
Build + service unit
CGO_ENABLED=1 go build -tags fts5 -o my_service .
systemctl --user enable --now my_service.service # tras generar unit
Esqueleto canonico: MCP server Go
Copia registry_mcp:
main.goparsea flags (--stdioo--http).- Cada tool = funcion Go con schema JSON y handler.
- Schema generation con
jsonschemareflect. - Read-only por default;
--enable-run/--enable-writegating.
Funciones del registry relevantes: cualquier de mcp__registry__fn_* para entender shape. Patron de gating en apps/registry_mcp/main.go.
Esqueleto canonico: Python httpx cliente declarativo
Copia auto_metabase:
python/.venv/bin/python3como interprete.- Importa wrappers del registry (
from infra import metabase_auth, metabase_get_dashboard, ...). - YAML manifest define estado deseado.
- Pull = volcar a YAML. Push = enviar a API.
- NUNCA
requests.post(...)directo — siempre via wrapper que ya hace auth + retry + telemetria.
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "python", "functions"))
from infra import metabase_auth, metabase_list_dashboards
session = metabase_auth(host, user, pwd)
for d in metabase_list_dashboards(session):
...
Esqueleto canonico: bubbletea TUI
Copia pipeline_launcher:
tea.ModelconInit/Update/View.- Sin HTTP. Estado local + acciones sobre disco/registry.
- Tag
launchersi debe aparecer en el Pipeline Launcher (function_tags.md).
Esqueleto canonico: Bash docker-compose stack
Copia element_matrix_chat:
docker-compose.yml+.env+ scriptsup.sh/down.sh.- Sin codigo Go/Py — solo composicion de imagenes.
- Tag
service+runtime: docker-composeenservice:. - Deploy via
docker_compose_remote_deploy_bash_infra(ver capabilitydeploy).
Funciones del registry — paleta backend Go
| ID | Para |
|---|---|
sqlite_apply_versioned_migrations_go_infra |
Aplicar migrations/*.sql con embed.FS al arrancar |
sqlite_open_*_go_infra |
Abrir SQLite con WAL + foreign keys + ping |
http_serve_go_infra |
Listener con graceful shutdown via ctx |
http_router_go_infra |
*http.ServeMux desde []Route declarativo |
http_json_response_go_infra |
Escribir JSON + status code |
http_error_response_go_infra |
Errores con shape consistente |
http_parse_body_go_infra |
Decode JSON con maxBytes (anti-DoS) |
http_cors_middleware_go_infra |
CORS para frontend ImGui/web |
http_logger_middleware_go_infra |
Log estructurado por request |
http_middleware_chain_go_infra |
Componer middlewares |
http_session_cookie_middleware_go_infra |
Sesiones cookie con TTL |
http_session_cookie_set_go_infra / clear / extract |
CRUD cookie sesion |
jwt_middleware_go_infra |
Auth JWT (alternativa a cookies) |
crud_generate_handlers_go_infra |
5 handlers REST a partir de CRUDResource |
crud_register_routes_go_infra |
Registrar GET/POST/PUT/DELETE /resource[/id] en mux |
file_serve_go_infra |
Static files con Cache-Control: max-age |
health_check_http_go_infra |
Polling de URL hasta 2xx (smoke en tests) |
logger_go_infra |
Logger estructurado (consumido por log_window apps C++) |
notify_telegram_go_infra |
Alertas a chat (opcional) |
ssh_exec_go_infra |
Ejecutar comandos en host remoto (services cross-PC) |
Buscar mas: mcp__registry__fn_search query="http" lang="go" tag="service".
Patron CRUD declarativo
Si el backend expone una entidad CRUD plana (cards, jobs, deploys), evita escribir 5 handlers a mano:
res := infra.CRUDResource{
Table: "cards",
PKColumn: "id",
Columns: []string{"id","title","status","created_at"},
}
mux := http.NewServeMux()
infra.CRUDRegisterRoutes(mux, "/api/cards", res, db)
// GET /api/cards → list
// GET /api/cards/{id} → get
// POST /api/cards → create
// PUT /api/cards/{id} → update
// DELETE /api/cards/{id} → delete
Service: block (issue 0105) — checklist obligatorio
Toda app con tag: service declara:
port(null si stdio).health_endpoint(ruta GET 2xx → sano).health_timeout_s(default 3).systemd_unitsiruntimeempieza consystemd-.systemd_scope:user|system.restart_policy:always(noon-failure— ver gotcha enfunction_tags.md).runtime:systemd-user|systemd-system|docker-compose|stdio|manual.pc_targets[]: pc_ids depc_locations.
Auditar: fn doctor services-spec.
Gotchas comunes
| Gotcha | Causa | Solucion |
|---|---|---|
Service muere en SIGTERM y systemd no reinicia |
Restart=on-failure en unit |
Cambiar a Restart=always. sqlite_api cayo 20h asi (2026-05-17) |
database is locked al escribir desde 2 procesos |
SQLite sin WAL | Abrir con ?_journal_mode=WAL&_foreign_keys=on (usar sqlite_open_*) |
| Frontend ImGui no recibe CORS preflight | Falta http_cors_middleware |
Anadir middleware antes del router |
| Body POST llega vacio | json.NewDecoder(r.Body) sin maxBytes — falla silenciosa |
Usar http_parse_body_go_infra con maxBytes=10MB |
Migracion 002 borra datos en otros PCs al hacer fn sync |
Migracion destructiva | Solo aditivo. Ver db_migrations.md |
| MCP tool no aparece en Claude tras anadirla | Schema no regenerado | Rebuild + claude mcp remove/add o reset cache |
Fronteras (que NO cubre esta capability)
- Apps C++ frontend →
cpp_apps.md+ capabilitycpp-dashboard-viz. - Deploy del backend a VPS → capability
deploy(Docker+Traefik, systemd, rsync). - Frontend web Vite/React/Mantine consumido por el backend → capability
mantine. - Migraciones de schema → regla
db_migrations.md. - Telemetria de calls del agente al registry →
registry_calls.md+call_monitorapp.
Cuando promover a pipeline / extraer al registry
Si tras construir el N-esimo Go service detectas patron repetido (>2 apps):
- Buscar funcion existente en registry (
mcp__registry__fn_search). - Si no existe → spawn
fn-constructorcon tag de grupo (http,service, etc.). - Migrar las apps existentes para consumirla.
- Capability page se actualiza sola via
fn doctor capabilities.