--- name: claude_session lang: go domain: infra version: 0.1.0 description: "Daemon de larga vida que mantiene una sesion claude (TUI interactiva) caliente y responde prompts rapido, pensado para embeber en una app como subproceso via NDJSON por stdin/stdout. Arranca mitmproxy + claude TUI una vez; cada prompt se teclea en la TUI viva y la respuesta se lee del SSE de la red (exacta, corta en message_stop). Primer mensaje paga el arranque (~7s); los siguientes ~2-3s, con memoria entre turnos. Comando restart para reiniciar la conversacion." tags: [cli, claude, daemon, session, ndjson, mitmproxy, web-proxy, streaming] uses_functions: - tee_anthropic_sse_py_cybersecurity - vt_render_go_tui uses_types: [] framework: "" entry_point: "main.go" dir_path: "apps/claude_session" icon: phosphor: "lightning" accent: "#f59e0b" e2e_checks: - id: build cmd: "go build -o claude_session ." timeout_s: 60 - id: bad_cmd cmd: "echo '{\"cmd\":\"bogus\"}' | timeout 30 ./claude_session 2>/dev/null | grep -q 'unknown cmd' && echo ok || true" expect_stdout_contains: "ok" timeout_s: 40 --- # claude_session ## Que hace Daemon que mantiene una sesion `claude` (TUI interactiva real, **nunca `claude -p`**) **caliente** y responde prompts en ~2-3s. Pensado para embeberse en una app como subproceso de larga vida, controlado por NDJSON por stdin/stdout. Arranca **una vez** un mitmproxy (addon `tee_anthropic_sse`) y la TUI de claude en un PTY, y los mantiene vivos. Cada prompt se teclea en la TUI viva; la respuesta se lee **del SSE de la red** (exacta, token a token, cortada por `message_stop`). El cold start (~7s) se paga una sola vez al arrancar; los mensajes siguientes solo pagan la generacion, y la conversacion **mantiene contexto entre turnos**. ## Latencia (medida) | Camino | por mensaje | |---|---| | `claude_pipe` (parsear render TUI) | ~15s | | `claude_wire` (interceptar red, one-shot) | ~9s | | **`claude_session` (daemon caliente)** | **~2.7s** (+ ~7s de arranque, una vez) | ## Protocolo NDJSON Un objeto JSON por linea. **stdin (comandos):** ```json {"cmd":"send","prompt":"hola"} {"cmd":"restart"} {"cmd":"shutdown"} ``` **stdout (eventos):** ```json {"type":"ready"} {"type":"text_delta","text":"H"} {"type":"text_delta","text":"ola"} {"type":"result","result":"Hola"} {"type":"restarted"} {"type":"error","message":"..."} ``` - `send`: teclea el prompt en la TUI viva, emite `text_delta` segun llegan del SSE, luego `result` y un `ready` cuando la TUI vuelve al input box. - `restart`: mata y relanza la TUI (conversacion **nueva, sin memoria**), mantiene el mitmproxy. Emite `restarted` + `ready`. - `shutdown`: mata todo y termina. ## Arquitectura ``` mitmdump + tee_anthropic_sse (persistente — capta el SSE de /v1/messages) claude TUI en PTY (creack/pty) (persistente — NO se mata entre prompts) loop NDJSON (teclea prompts, lee el wire, multiplexa por message_stop) ``` Reusa `tee_anthropic_sse_py_cybersecurity` (addon SSE) y `vt_render_go_tui` (render del PTY para detectar readiness del input box). ## Embeber en una app La app arranca el binario como subproceso de larga vida y habla por sus stdin/stdout: ```python import subprocess, json p = subprocess.Popen(["claude_session", "--cwd", "/home/enmanuel/fn_registry"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1) # esperar {"type":"ready"} def send(prompt): p.stdin.write(json.dumps({"cmd": "send", "prompt": prompt}) + "\n"); p.stdin.flush() for line in p.stdout: ev = json.loads(line) if ev["type"] == "result": return ev["result"] ``` ## Ejemplo (NDJSON por stdin) ```bash cd apps/claude_session && go build -o claude_session . printf '%s\n' \ '{"cmd":"send","prompt":"di solo OK"}' \ '{"cmd":"send","prompt":"di solo DOS"}' \ '{"cmd":"shutdown"}' \ | ./claude_session --cwd /home/enmanuel/fn_registry # {"type":"ready"} # {"type":"text_delta","text":"O"} ... {"type":"result","result":"OK"} {"type":"ready"} # {"type":"text_delta","text":"D"} ... {"type":"result","result":"DOS"} {"type":"ready"} ``` ## Flags | Flag | Default | Que hace | |---|---|---| | `--cwd` | `~/fn_registry` | Directorio de la sesion claude (MCP aprobados). | | `--port` | `8901` | Puerto del mitmproxy. | | `--root` | `~/fn_registry` | Raiz del registry (para localizar el addon). | | `--addon` | `/python/functions/cybersecurity/tee_anthropic_sse.py` | Addon mitmproxy. | | `--ca` | `~/.mitmproxy/mitmproxy-ca-cert.pem` | CA de mitmproxy. | | `--bin` | `claude` | Binario claude. | | `--warmup` | `12s` | Espera maxima a que la TUI este lista. | ## Prerrequisitos - `mitmproxy` (`mitmdump` en PATH) + CA generada y confiada via `NODE_EXTRA_CA_CERTS`. - `claude` en el PATH. ## Gotchas - **Readiness post-restart prematura**: tras `restart`, el `ready` puede emitirse antes de que la TUI termine de cargar (detecta el input box `❯` pronto). El primer `send` tras un restart puede tardar mas (~5-6s en vez de ~2.7s) porque el input se teclea mientras claude aun arranca (se bufferea, no se pierde). Refinamiento pendiente: readiness mas estricta (esperar que cese el trafico de arranque en el proxy). - **Sesion secuencial**: un prompt a la vez. No mandes un `send` mientras otro genera. - **Memoria entre turnos**: la TUI viva acumula la conversacion (es una feature). Usa `restart` para empezar de cero. - **Requiere mitmproxy + CA** (`NODE_EXTRA_CA_CERTS`); depende de que claude no haga TLS pinning (hoy no lo hace). - **Una interaccion dispara varias /v1/messages**; el addon filtra por `has_tools` para seguir la respuesta principal. - **Linux/Unix** (PTY POSIX). - **Es trafico de tu propia cuenta y maquina** — observabilidad local.