ADR-029 · Claudia integra Claude Code via CLI subprocess en lugar de Anthropic SDK¶
Fecha: 2026-05-03
Estado: Draft (pendiente review humano · NO aplicado a DECISIONS.md)
Owner: Martín
Reemplaza implícitamente: §4 #2 de cis-claudia/A1-DELTA.md (decisión humana sobre ANTHROPIC_API_KEY)
Documento companion: cis-claudia/A1-DELTA-v2-cli.md
Contexto¶
El plan maestro CIS v2 (Bloque A.1) contemplaba que cis-claudia v2 — host unificado del modelo Claude para email / web / WhatsApp / API — se integrara con la Anthropic SDK Python directamente, autenticada con ANTHROPIC_API_KEY registrada en vault, usando prompt caching nativo (cache_control) para reducir costos en conversaciones largas.
El audit del 2026-04-29 (A1-DELTA.md §4 decisión #2) reveló que ANTHROPIC_API_KEY no existe en ningún namespace de vault (vps-cis, innovacionsantiago, cis-claudia, cis-platform, cis-core, ai-budgets, foresight). Registrarla implica:
- Crear cuenta Anthropic Console con billing.
- Pre-pagar créditos o configurar postpago.
- Asumir costo marginal por token (~$3/M input · ~$15/M output para Opus).
- Mantener TWO suscripciones AI activas: Claude Code Pro Max (que Martín ya tiene) + API Anthropic.
El 2026-05-03 Martín pivota:
"Ya tengo Claude Code Pro Max. No voy a registrar otra API key encima. Que Claudia use el binario
claudepor subprocess."
Esto cambia la arquitectura de A.1.3 de "in-process SDK call" a "subprocess CLI worker headless".
Precedente interno: cis-claudia/backend/app/services/brain.py ya implementa este patrón para el surface WhatsApp desde marzo 2026. Reutilizable.
Decisión¶
cis-claudia v2 invoca Claude vía subprocess claude --print --output-format json -p "<prompt>" [...] en lugar de Anthropic SDK.
Detalles vinculantes:
- Binario:
/home/illanes00/.local/bin/claude(Claude Code 2.x). Versión pin gestionada por el usuario (no auto-update). - Output:
--output-format json(no streaming en MVP). Parser extraeresult, session_id, num_turns, total_cost_usd, usage{input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens}, subtype, is_error. - Multi-turn:
--resume <session-id>. El session-id se persiste enchat_threads.cli_session_id(nuevo campo TEXT NULLABLE). - Tools: 69 MCP tools de cis-core ya están conectados al CLI globalmente. Sin re-wiring por servicio. Gating per-surface vía
--allowedToolsy--disallowedTools(CSV). - Permission mode (auto vs FES, ADR-026): se materializa como filtro pre-exec sobre
--allowedTools. Tools que requieren FES no se incluyen en allowedTools hasta que hayafes_receipt_idválido. Si Claude intenta usar uno bloqueado, Claude Code retornapermission_denialsen el JSON; backend captura y responde HTTP 403 al cliente con el desafío FES. - cwd:
/srv/projects/cis/por default (Claude Code lee CLAUDE.md ahí). Override por surface si se requiere. - Conversaciones largas: arq + Redis worker queue
claudia.chat. MVP A1 solo síncrono con timeout 60s; arq queda como follow-up A1.5. - Sin
ANTHROPIC_API_KEYen vault. La suscripción Pro Max del binarioclaudeprovee el modelo. - Schema:
chat_threads.cli_session_id VARCHAR(64) NULL. Sintokens_cached, sincache_block_id— el caching es opaco al backend.
Alternativas consideradas¶
(a) Anthropic SDK + ANTHROPIC_API_KEY (propuesta original A1-DELTA)¶
Pros: latencia first-byte ~150ms; streaming SSE nativo; prompt caching explícito y predecible (cache_control con TTL 1h); tool calls estructurados nativos (content[].type=tool_use); independencia del binario Claude Code; rate limits de tokens conocidos; failover tipo retry trivial.
Contras: costo marginal por token (~$3/M in · $15/M out Opus, ~$0.30/M in · $1.50/M out Haiku) — para una conversación admin de 100 turnos con 2k tokens contexto, ~$0.50–2.00 USD; obliga a duplicar suscripciones AI; agrega billing surface a Anthropic; requiere implementar tool definitions en Python para los 69 tools de cis-core MCP (re-wiring no trivial).
Por qué se descarta: Martín ya paga Pro Max ($200/mes flat), que cubre uso ilimitado del binario claude. Pagar API encima es ineficiente. El re-wiring de los 69 tools MCP en SDK es trabajo de varios días para replicar lo que el binario tiene gratis.
(b) Claude Code CLI subprocess (elegida)¶
Detallada arriba.
(c) Provider-neutral via OpenRouter / proxy genérico¶
Pros: lock-in mínimo; switch a otros modelos (GPT, Gemini, Llama) sin cambiar código; OpenRouter da unified billing.
Contras: agrega un hop de latencia y un puntodependencia operacional más; pierde features Claude-specific (system prompt caching agresivo, content blocks); OpenRouter cobra surcharge ~5%; no tiene MCP tools — habría que implementar tools desde cero; el plan v2 explícitamente dice "host Claude unificado".
Por qué se descarta: contradice la decisión de plan ("Claude unificado"), y agrega costo operacional sin beneficio claro para CIS hoy. Si en el futuro se necesita multi-modelo, un ADR posterior puede revisar.
Consecuencias¶
Positivas¶
- $0 costo marginal por mensaje: la suscripción Pro Max es flat. Conversación admin típica = costo $0.
- MCP gratis: 69 tools (vault, services, infra, monitoring) ya conectados al binario. Sin re-wiring.
- Reuso de código:
app/services/brain.py::_call_claude_codeprovee 70% del worker. Refactor encli_worker.pytoma 1-1.5h vs días para SDK desde cero. - Sin nueva surface de billing: no se agrega cuenta Anthropic Console ni se cargan créditos.
- Modelo Claude actualizado automáticamente: cuando Claude Code actualice a Opus 4.7, Claudia lo hereda sin código.
- CLAUDE.md y skills: el binario lee
CLAUDE.mddel cwd y skills configuradas en~/.claude/. Claudia hereda contexto del workspace gratis. - Permission gating ya implementado:
--allowedTools/--disallowedToolscubren ADR-026 sin estructura ad-hoc.
Negativas¶
- Latencia spawn: ~1.5–3s extra por invocación vs SDK ~150ms. Notorio en chat web. Mitigación: arq worker async para conversaciones >30s + spinner UX. WhatsApp ya tolera esta latencia (el usuario humano es paciente vía mensaje).
- Dependencia binaria: si
claudedesaparece del PATH (cleanup, update fallida), Claudia cae. Mitigación: health check + alert critical. - Sin streaming nativo trivial:
--output-format stream-jsonexiste pero parser es más complejo. MVP solo síncrono. SSE queda como follow-up. - Caching opaco: no controlamos
cache_control. Si Claude Code cambia su estrategia interna en una update, perdemos hit rate sin warning. Mitigación: telemetry sobrecache_read_input_tokensen JSON output, alert si cae a 0. - JSON schema versioning: si Claude Code 2.x → 3.x cambia el formato del JSON, parser se rompe. Mitigación: golden test de regresión + version pin.
- Concurrencia: cada spawn ~600MB RSS. Máximo ~4 paralelos en VPS de 8GB. Mitigación: arq queue con
max_jobs=4. - Rate limits Pro Max: por mensajes/día, no tokens. Si chat web recibe ráfagas (>~100 msg/h sostenidos), pegamos contra el límite. Mitigación: telemetry + backoff + eventual upgrade a Team plan.
- Tool calls no estructurados: el JSON output no tiene
tool_useblocks tipados. Detección híbrida via DB side-effects + heurística sobre stop_reason / errors / permission_denials. Aceptable para audit, peor para introspection en tiempo real. - Audit log limitado: para tools sin side-effect persistente (ej.
vault_get), la única traza queda en logs MCP del cis-core (/var/log/cis-core/mcp-claude.log). No queda enchat_tool_auditsdirectamente. Cross-reference manual.
Neutrales¶
- Versionado del modelo: pasamos de "pin explícito en código (
claude-opus-4-7-20251022)" a "lo que Claude Code tenga instalado". Override posible vía--modelpor invocación. Trade-off: menos control reproducibilidad vs siempre actualizado. - Schema migration: 1 columna nueva (
cli_session_id) vs el plan original que tenía conceptos decache_block_id/tokens_cached. Más simple en realidad.
Riesgos y mitigaciones¶
| Riesgo | Probabilidad | Impacto | Mitigación |
|---|---|---|---|
Binario claude ausente del PATH |
Baja | Alto (Claudia cae) | Health check /api/v1/chat/health + alert critical |
| Update Claude Code rompe JSON schema | Media | Medio (parser falla) | Golden JSON test + version pin del binario en deploy |
| Rate limit Pro Max alcanzado | Media (futuro) | Medio (UX degradada) | Telemetry + backoff + plan team upgrade |
| Latencia subprocess percibida | Alta | Bajo (UX, no functional) | Async worker + spinner + pre-ack patrón brain.py |
| RAM agotada por concurrencia | Baja (con cap) | Alto (OOM) | arq max_jobs=4 + monitor RSS + alert |
| FES pre-filter pierde tool peligroso | Baja | Alto (acción no firmada) | Whitelist estricto deny-by-default + audit obligatorio |
| Suscripción Pro Max cancelada | Muy baja | Crítico (todo el AI cae) | Opción de fallback a SDK queda en backlog (re-abrir este ADR) |
Implementación pendiente (fuera del scope de este ADR)¶
Detallada en cis-claudia/A1-DELTA-v2-cli.md §11. Resumen:
- SQL migration
005_chat_v2_cli_pivot.sql(escrita pero no aplicada) app/services/cli_worker.py(refactor desde brain.py)app/api/v1/chat.py(router + auth)- Tests pytest happy/timeout/tool_calls/FES
- Health endpoint + métricas Prometheus
- arq worker (follow-up A1.5)
- UI cis-mando
/chat(follow-up A1.4)
Relaciones¶
- Reemplaza la asunción de SDK en A1-DELTA.md original §3 Fase A1.3.
- Resuelve la decisión humana abierta en A1-DELTA.md §4 #2 (ANTHROPIC_API_KEY).
- Complementa ADR-026 (permission_mode auto vs FES) — concreta cómo se materializan los modos en flags CLI.
- No afecta ADR-014 (Authentik = source of truth) ni ADR-017 (Google scopes).
- Habilita Bloque A.1 del plan maestro sin necesidad de billing Anthropic adicional.
- Reusa patrón existente en
cis-claudia/backend/app/services/brain.py(subprocess wrapper para WhatsApp surface).
Reversibilidad¶
Este ADR es reversible con cost moderado si en el futuro:
- La suscripción Pro Max no escala con el uso de Claudia.
- Se requiere streaming SSE de baja latencia (ej. UX premium para clientes externos).
- Se quiere reproducibilidad estricta del modelo (ej. compliance).
En cualquiera de esos casos: nuevo ADR re-introduce SDK opcional como segundo path (CLI para tareas internas, SDK para flows con SLA estricto). El schema y las APIs ya soportan ambos — solo cambia la implementación de cli_worker.py (pasaría a llamarse chat_worker.py con dos backends).
Pendiente review humano¶
- Martín valida la decisión y los trade-offs documentados.
- Ajustes opcionales en §10 de A1-DELTA-v2-cli.md (decisiones humanas vivas).
- Una vez aprobado: append a
DECISIONS.mdcon el formato de ADRs previos + actualizar BACKLOG.md.