Saltar a contenido

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:

  1. Crear cuenta Anthropic Console con billing.
  2. Pre-pagar créditos o configurar postpago.
  3. Asumir costo marginal por token (~$3/M input · ~$15/M output para Opus).
  4. 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 claude por 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:

  1. Binario: /home/illanes00/.local/bin/claude (Claude Code 2.x). Versión pin gestionada por el usuario (no auto-update).
  2. Output: --output-format json (no streaming en MVP). Parser extrae result, session_id, num_turns, total_cost_usd, usage{input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens}, subtype, is_error.
  3. Multi-turn: --resume <session-id>. El session-id se persiste en chat_threads.cli_session_id (nuevo campo TEXT NULLABLE).
  4. Tools: 69 MCP tools de cis-core ya están conectados al CLI globalmente. Sin re-wiring por servicio. Gating per-surface vía --allowedTools y --disallowedTools (CSV).
  5. 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 haya fes_receipt_id válido. Si Claude intenta usar uno bloqueado, Claude Code retorna permission_denials en el JSON; backend captura y responde HTTP 403 al cliente con el desafío FES.
  6. cwd: /srv/projects/cis/ por default (Claude Code lee CLAUDE.md ahí). Override por surface si se requiere.
  7. Conversaciones largas: arq + Redis worker queue claudia.chat. MVP A1 solo síncrono con timeout 60s; arq queda como follow-up A1.5.
  8. Sin ANTHROPIC_API_KEY en vault. La suscripción Pro Max del binario claude provee el modelo.
  9. Schema: chat_threads.cli_session_id VARCHAR(64) NULL. Sin tokens_cached, sin cache_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_code provee 70% del worker. Refactor en cli_worker.py toma 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.md del cwd y skills configuradas en ~/.claude/. Claudia hereda contexto del workspace gratis.
  • Permission gating ya implementado: --allowedTools / --disallowedTools cubren 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 claude desaparece del PATH (cleanup, update fallida), Claudia cae. Mitigación: health check + alert critical.
  • Sin streaming nativo trivial: --output-format stream-json existe 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 sobre cache_read_input_tokens en 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_use blocks 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 en chat_tool_audits directamente. 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 --model por 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 de cache_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:

  1. La suscripción Pro Max no escala con el uso de Claudia.
  2. Se requiere streaming SSE de baja latencia (ej. UX premium para clientes externos).
  3. 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.md con el formato de ADRs previos + actualizar BACKLOG.md.