Skip to content

Token Stack — Guía de instalación

Dashboard de tokens, cuota API y tareas para el universo AI OS. Muestra cuota Anthropic (ventana 5h), ahorro de tokens via Headroom, modo Caveman y kanban de proyectos.


Herramienta Versión mínima Dónde corre
Windows 11 — Host
WSL 2 (Ubuntu 22.04+) — Host
Node.js v22+ Windows
Python 3.12+ WSL
Claude Code CLI última Windows
Headroom 0.26.0+ WSL (venv)
Caveman plugin — Windows (.claude)
Ponytail plugin — Windows (.claude)
Node.js v22+ WSL (vía nvm, solo para CodeBurn WSL)

Paso 0 — Fix de red WSL (evita EACCES en puerto 8788)

Section titled “Paso 0 — Fix de red WSL (evita EACCES en puerto 8788)”

WSL2 en modo NAT (default) reserva bloques random de puertos TCP en cada boot — si el bloque pisa el 8788, caveman-stats-server.js falla con EACCES: permission denied 127.0.0.1:8788 aunque nadie más lo esté usando. Reiniciar winnat “arregla” temporal pero vuelve a romperse en el próximo boot/restart de WSL.

Fix definitivo — modo mirrored (elimina el NAT, Windows/WSL comparten red, sin exclusiones random; requiere Windows 11 22H2+ y WSL ≥2.0.0):

Terminal window
# 1. Copiar wslconfig.example a tu perfil (si ya tenés un .wslconfig, fusionar [wsl2] a mano)
Copy-Item "$PSScriptRoot\wslconfig.example" "$env:USERPROFILE\.wslconfig"
# 2. Reiniciar WSL (mata todas las distros corriendo)
wsl --shutdown

Verificar:

Terminal window
netsh interface ipv4 show excludedportrange protocol=tcp

Solo deben aparecer 5357 y 50000-50059 (fijos de Windows) — nada más.


Terminal window
# En WSL (Ubuntu)
python3 -m venv ~/.venv-headroom
source ~/.venv-headroom/bin/activate
pip install "headroom-ai[all]"

Verificar:

Terminal window
headroom --version # debe mostrar 0.26.0+

Paso 2 — Parchear Headroom para capturar cuota API

Section titled “Paso 2 — Parchear Headroom para capturar cuota API”

Anthropic envía la cuota de plan Pro como headers anthropic-ratelimit-unified-* en cada respuesta streaming. Headroom no los expone en /stats, así que hay que parchear su handler para escribirlos a disco.

Importante: Este parche se pierde al hacer pip install --upgrade headroom. Guarda este script para re-aplicarlo tras cada actualización.

Terminal window
# En WSL — ejecutar una sola vez (o tras actualizar headroom)
python3 << 'PATCH'
path = '/home/TU_USUARIO/.venv-headroom/lib/python3.12/site-packages/headroom/proxy/handlers/streaming.py'
# Cambia TU_USUARIO y TU_USUARIO_WINDOWS por los tuyos
WINDOWS_USER = 'TU_USUARIO_WINDOWS' # ej: Amed
with open(path, 'r') as f:
content = f.read()
old = ''' forwarded_headers = {
k: v
for k, v in upstream_response.headers.items()
if "ratelimit" in k.lower()
or k.lower().startswith("x-codex")
or k.lower() in ("request-id", "anthropic-request-id", "x-request-id")
}
async def generate():'''
new = f''' forwarded_headers = {{
k: v
for k, v in upstream_response.headers.items()
if "ratelimit" in k.lower()
or k.lower().startswith("x-codex")
or k.lower() in ("request-id", "anthropic-request-id", "x-request-id")
}}
# Capture Anthropic unified rate-limit headers → Windows .headroom dir
_fh = forwarded_headers
_u5h = _fh.get("anthropic-ratelimit-unified-5h-utilization")
_u7d = _fh.get("anthropic-ratelimit-unified-7d-utilization")
if _u5h is not None or _u7d is not None:
import json as _json, time as _time, os as _os
_rl_path = "/mnt/c/Users/{WINDOWS_USER}/.headroom/rate_limit.json"
_rl_data = {{
"type": "unified",
"fh_utilization": float(_u5h) if _u5h is not None else None,
"fh_reset": _fh.get("anthropic-ratelimit-unified-5h-reset"),
"fh_status": _fh.get("anthropic-ratelimit-unified-5h-status"),
"7d_utilization": float(_u7d) if _u7d is not None else None,
"7d_reset": _fh.get("anthropic-ratelimit-unified-7d-reset"),
"7d_status": _fh.get("anthropic-ratelimit-unified-7d-status"),
"overall_status": _fh.get("anthropic-ratelimit-unified-status"),
"ts": int(_time.time() * 1000),
}}
try:
_tmp = _rl_path + ".tmp"
with open(_tmp, "w") as _f:
_json.dump(_rl_data, _f)
_os.replace(_tmp, _rl_path)
except Exception:
pass
async def generate():'''
if old in content:
content = content.replace(old, new, 1)
with open(path, 'w') as f:
f.write(content)
print('PATCH OK')
else:
print('ERROR: snippet no encontrado — versión de headroom distinta, revisar manualmente')
PATCH

Verificar sintaxis:

Terminal window
python3 -c "import py_compile; py_compile.compile('$HOME/.venv-headroom/lib/python3.12/site-packages/headroom/proxy/handlers/streaming.py', doraise=True); print('OK')"

Paso 2bis — Parchear tool_search_deferral (evita respuestas vacías 200)

Section titled “Paso 2bis — Parchear tool_search_deferral (evita respuestas vacías 200)”

Headroom 0.35.0 reescribe incondicionalmente el array tools de cada request (sin flag, sin env var) para diferir schemas no-core — inject_tool_search_deferral en proxy/helpers.py. Bug conocido (headroom issue #3040): cuando esa reescritura coincide con un cache-miss del bloque de tools, Anthropic devuelve 200 OK con body vacío, sin excepción — Claude Code lo reporta como API returned an empty or malformed response (HTTP 200). No depende de cuántas tools haya (reproduce con 27) ni de sesiones concurrentes — solo del cache-miss en ese momento.

--compressor (que excluye el compresor search) NO desactiva esto — es un mecanismo de texto/replace distinto, nombre engañoso. No hay flag real.

Importante: este parche también se pierde con pip install --upgrade headroom. Re-aplicar tras cada actualización, igual que el parche de cuota (Paso 2).

Terminal window
# En WSL — ejecutar una sola vez (o tras actualizar headroom)
python3 << 'PATCH'
path = '/home/TU_USUARIO/.venv-headroom/lib/python3.12/site-packages/headroom/proxy/helpers.py'
with open(path) as f:
content = f.read()
old = ''' prefix still caches.
"""
if not isinstance(tools, list) or len(tools) < _TOOL_SEARCH_MIN_TOOLS:
return tools'''
new = ''' prefix still caches.
"""
return tools # ponytail: noop'd, headroom issue #3040 (empty 200 responses)
if not isinstance(tools, list) or len(tools) < _TOOL_SEARCH_MIN_TOOLS:
return tools'''
if old in content:
content = content.replace(old, new, 1)
with open(path, 'w') as f:
f.write(content)
print('PATCH OK')
else:
print('ERROR: snippet no encontrado — versión de headroom distinta, revisar manualmente')
PATCH

Verificar sintaxis:

Terminal window
python3 -c "import py_compile; py_compile.compile('$HOME/.venv-headroom/lib/python3.12/site-packages/headroom/proxy/helpers.py', doraise=True); print('OK')"

Costo del parche: se pierde el ahorro de tokens de ese transform específico (35-58k tokens/request en sesiones con varios MCP servers) — el resto de Headroom (kompress, cache, rate-limit) sigue funcionando normal. Revisar el issue #3040 en releases futuras; si Anthropic/Headroom lo arreglan río arriba, sacar este parche y recuperar el ahorro.


Paso 2ter — Desactivar CCR (evita OTRA causa de respuestas 200 vacías)

Section titled “Paso 2ter — Desactivar CCR (evita OTRA causa de respuestas 200 vacías)”

Mismo síntoma que el Paso 2bis (API returned an empty or malformed response (HTTP 200)) pero mecanismo distinto — puede reaparecer con el parche 2bis intacto y aplicado. Diagnosticado leyendo ~/.headroom/logs/proxy.log (2026-08-17): la respuesta rota tiene tok_out=0 (Anthropic respondió 200 pero sin generar nada) y el log muestra la causa exacta:

event=outbound_request ... body_mutated=true
mutation_reasons=structural_diff_vs_original,ccr_streaming_retrieve_buffered_non_stream
CACHE-BUST: expected_cached=111,605 actual_read=0 tokens_lost=111,605
CACHE-MISS-ATTRIBUTION: reason=prefix_change prefix_changed=True

CCR (“Context/Compression Retrieval”, subsistema de headroom.ccr.*) es lo que le permite a Headroom no reenviar contenido grande y repetido (p. ej. el body completo de una skill) en cada turno: la primera vez lo comprime y guarda en ~/.headroom/ccr_store.db; en turnos siguientes solo manda un marcador + una tool headroom_retrieve, para que el modelo la pida de vuelta solo si la necesita — así ahorra tokens sin perder el contenido (a diferencia de una compresión lossy que directamente lo recorta).

El bug: cuando CCR decide que el modelo puede necesitar ese retrieve, convierte el request de stream:true a un request bufferado stream:false para resolver la recuperación del lado servidor (ccr_streaming_retrieve_buffered_non_stream). Ese cambio de forma del request no coincide con el prefix que Anthropic tenía cacheado de turnos anteriores → cache-miss total (111,605 tokens de prefix cacheado, perdidos de golpe) → Anthropic devuelve 200 con salida vacía en vez de error. No depende de MCP tools (a diferencia del 2bis) — dispara con cualquier sesión donde CCR decide inyectar headroom_retrieve.

Fix: variable de entorno HEADROOM_NO_CCR=1 al lanzar headroom proxy — ya seteada en headroom-start.ps1. A diferencia de los parches 2 y 2bis, esto no se pierde con pip install --upgrade (no toca archivos del paquete, es solo una env var al lanzar el proceso) — no requiere re-aplicación tras actualizar.

Costo de HEADROOM_NO_CCR=1: se pierde el ahorro de reenvíos-evitados de CCR (el headroom_retrieve on-demand) — vuelve a mandar el contenido completo en cada turno donde antes mandaba solo el marcador. Kompress (compresión general), el cache normal de Anthropic y el parche de cuota (Paso 2) siguen funcionando igual. Si Headroom arregla el cache-bust río arriba, sacar la env var y recuperar ese ahorro extra.

Verificar que está activa en el proceso corriendo:

Terminal window
# En WSL
PID=$(pgrep -f 'headroom proxy' | head -1)
tr '\0' '\n' < /proc/$PID/environ | grep HEADROOM_NO_CCR
# Debe imprimir: HEADROOM_NO_CCR=1

Paso 3 — Instalar Caveman plugin en Claude Code

Section titled “Paso 3 — Instalar Caveman plugin en Claude Code”
Terminal window
# En Windows PowerShell
claude plugin install caveman

O siguiendo las instrucciones del repositorio del plugin.(https://github.com/juliusbrussee/caveman#install) (https://caveman.so)


Paso 4 — Instalar Ponytail plugin en Claude Code

Section titled “Paso 4 — Instalar Ponytail plugin en Claude Code”
Terminal window
# En Windows PowerShell
claude plugin install ponytail

O siguiendo las instrucciones del repositorio del plugin: https://github.com/dietrichgebert/ponytail


Paso 5 — Instalar CodeBurn (Windows + WSL)

Section titled “Paso 5 — Instalar CodeBurn (Windows + WSL)”

CodeBurn no es un servidor persistente — lee los logs de sesión que ya existen en disco (Claude Code, Cursor, Antigravity, OpenCode) y se levanta on-demand via npx. No requiere instalación previa, solo Node disponible en cada entorno donde vas a correrlo.

Windows (ve Claude Code, Cursor, Antigravity — puerto 4747):

Terminal window
npx codeburn web --port 4747

WSL (necesario para ver OpenCode, que guarda sus sesiones en ~/.local/share/opencode/opencode.db, invisible desde Windows — puerto 4748):

Requiere Node ≥22 en WSL. Si no lo tenés, instalar vía nvm:

Terminal window
# En WSL
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
node --version # debe mostrar v22+

Habilitar linger para que el proceso no muera al cerrar la sesión WSL (systemd mata procesos huérfanos sin linger):

Terminal window
sudo loginctl enable-linger $(whoami)
Terminal window
# En WSL
npx codeburn web --port 4748

Ambos se auto-arrancan desde iniciar_claude_code.ps1 / iniciar_opencode.ps1 si no están vivos — no hace falta lanzarlos a mano en el día a día.

Repo: https://github.com/getagentseal/codeburn


Copia esta carpeta completa (_shared/token-stack/) a tu universo AI OS.

Estructura mínima necesaria:

_shared/token-stack/
caveman-stats-server.js ← Stats server (Node, puerto 8788)
stats-db.js ← SQLite helpers
tasks-api.js ← Lector de TASKS.md por mundo
prompts-api.js ← Lector/escritor de _shared/prompts/prompts.json
dashboard.html ← Dashboard principal
headroom-dashboard.html ← Dashboard de Headroom (iframe)
headroom-start.ps1 ← Arranca headroom + stats server
iniciar_claude_code.ps1 ← Lanzador Claude Code (marca active-tool=claude)
iniciar_opencode.ps1 ← Lanzador OpenCode (marca active-tool=opencode)
wslconfig.example ← Fix networkingMode=mirrored (ver Paso 0)

Paso 7 — Crear directorio de datos en Windows

Section titled “Paso 7 — Crear directorio de datos en Windows”
Terminal window
New-Item -ItemType Directory -Force "$env:USERPROFILE\.headroom"

Desde cualquier mundo del universo:

Terminal window
# En PowerShell (Windows), desde la carpeta del mundo
.\iniciar_claude_code.ps1 # o .\iniciar_opencode.ps1

Lo que hace iniciar_claude_code.ps1/iniciar_opencode.ps1:

  1. Detecta _shared/token-stack/ hacia arriba en el árbol
  2. Si 8787/8788 no están activos, lanza headroom-start.ps1
  3. Abre http://127.0.0.1:8788 en el browser
  4. Lanza Claude Code/OpenCode con ANTHROPIC_BASE_URL=http://127.0.0.1:8787

O lanzar el stack solo (sin Claude Code):

Terminal window
.\_shared\token-stack\headroom-start.ps1
# Luego abre: http://127.0.0.1:8788

Con Claude Code abierto via iniciar_claude_code.ps1, envía cualquier mensaje. Tras la primera respuesta, el dashboard mostrará la cuota API en tiempo real.


http://127.0.0.1:8788 — Dashboard principal (Universe Command Center)

  • Tab Tareas: Kanban por mundo + overview
  • Tab Headroom: iframe del dashboard de Headroom
  • Tab CodeBurn Win: iframe de codeburn (npx codeburn web --port 4747) — gasto/tokens leído de logs de sesión ya existentes en disco Windows (~/.claude/projects/, Cursor, Antigravity, etc.). No es proxy, no compite con Headroom — datos complementarios (multi-tool, waste analysis, budgets)
  • Tab CodeBurn WSL: misma herramienta pero corriendo dentro de WSL (:4748) — necesario porque OpenCode (lanzado vía iniciar_opencode.ps1) guarda sus sesiones en el filesystem de WSL (~/.local/share/opencode/opencode.db), invisible para la instancia Windows. Requiere Node ≥22 en WSL (instalado vía nvm, ver Paso 5) y sudo loginctl enable-linger <usuario> para que el proceso no muera al cerrar la sesión WSL (systemd mata procesos huérfanos sin linger). Se auto-arranca desde iniciar_opencode.ps1 si no está vivo.
  • Tab Agentes / Skills: catálogo instalado
  • Tab Prompts: biblioteca de prompts reutilizables (título, descripción, cuerpo), CRUD, copiar al portapapeles — persiste en _shared/prompts/prompts.json

En el iframe de Headroom (tab Headroom):

  • Cuota API (ventana actual) — barra de % usado en 5h · <50% verde · 50-89% naranja · ≥90% rojo · reset countdown
  • Resumen de sesión — badge Claude Code o OpenCode según herramienta activa · tokens enviados, ahorro cache/compresión, coste total
  • Caveman Mode — turns, tokens ahorrados, modelo (solo Claude Code)
  • Ahorro acumulado lifetime (solo Claude Code)
  • Historial diario — gráficas y tabla filtrable por proyecto y herramienta (Claude/OpenCode)

El dashboard detecta automáticamente qué herramienta está activa y adapta la vista.

Lanzar Claude Code:

Terminal window
.\iniciar_claude_code.ps1 # desde cualquier mundo

Lanzar OpenCode:

Terminal window
.\iniciar_opencode.ps1 # desde cualquier mundo

Cada script:

  1. Levanta el stack (8787/8788) si no está activo
  2. Escribe claude u opencode en ~/.headroom/active-tool
  3. El dashboard cambia de vista automáticamente en ~5 segundos

Cuando OpenCode está activo, se ocultan las secciones exclusivas de Claude Code (Caveman Mode, Ahorro lifetime, Features activos) y aparece un panel con el proyecto activo.


Puerto Servicio Proceso
8787 Headroom proxy WSL / Python
8788 Stats server + dashboard HTTP Windows / Node
4747 CodeBurn web dashboard (Windows) Windows / Node (npx codeburn web)
4748 CodeBurn web dashboard (WSL, ve OpenCode) WSL / Node ≥22 vía nvm (npx codeburn web --port 4748)

“Sin datos aún” en cuota:

  • Verifica que el parche del paso 2 está aplicado: grep -c 'rate_limit.json' ~/.venv-headroom/lib/python3.12/site-packages/headroom/proxy/handlers/streaming.py debe dar >0
  • Reinicia headroom tras aplicar el parche
  • Envía un mensaje en Claude Code (el parche solo se activa en requests streaming)

Dashboard no carga:

  • Verifica que el stats server corre: curl http://127.0.0.1:8788/
  • NO abrir dashboard.html como archivo (file://) — siempre via HTTP

Parche perdido tras pip upgrade headroom:

  • Re-ejecutar los scripts del paso 2 y paso 2bis (son 2 parches independientes, ambos se pierden)

API returned an empty or malformed response (HTTP 200) con Claude Code atrás del proxy:

  • Dos causas distintas posibles, mismo síntoma — revisar ~/.headroom/logs/proxy.log (buscar tok_out=0 en la línea PERF del request que falló) para saber cuál:
    • Causa 1 (paso 2bis): parche de tool_search_deferral no aplicado o perdido tras upgrade. Re-ejecutar.
    • Causa 2 (paso 2ter): CCR reescribe el request (mutation_reasons=...ccr_streaming_retrieve_buffered_non_stream) y rompe el prefix cacheado (CACHE-BUST). Verificar HEADROOM_NO_CCR=1 en el proceso (ver paso 2ter). Esta NO se pierde con upgrade, así que si reaparece con el parche 2bis intacto, es esta.

Cambio de usuario Windows:

  • Editar la línea _rl_path en el parche y en streaming.py con el nuevo usuario

EACCES: permission denied 127.0.0.1:8788 al arrancar el stats server:

  • No es otro proceso ocupando el puerto (eso sería EADDRINUSE) — es Hyper-V/WSL2 excluyendo el puerto vía NAT. Confirmar: netsh interface ipv4 show excludedportrange protocol=tcp — si 8788 cae dentro de algún rango listado, es esto.
  • Fix temporal: net stop winnat && net start winnat (como Administrador) — se rompe de nuevo en el próximo boot/restart de WSL.
  • Fix definitivo: ver Paso 0 (wslconfig.example → modo mirrored).

Script automatizado (recomendado):

Terminal window
# En Windows PowerShell, desde la carpeta del universo o cualquier mundo
.\_shared\token-stack\headroom-update.ps1

O directamente desde WSL:

Terminal window
bash /mnt/d/Proyect/empresa\ mia/ai-os/_shared/token-stack/headroom-update.sh

Lo que hace el script:

  1. pip install --upgrade headroom en el venv
  2. Localiza streaming.py automáticamente (detecta versión Python)
  3. Re-aplica el parche de cuota API (salta si ya está presente)
  4. Verifica sintaxis Python

Verificar estado sin modificar nada:

Terminal window
.\_shared\token-stack\headroom-update.ps1 --check

Manual (fallback si el script falla):

headroom NO está en PyPI — se distribuye como wheel en GitHub releases. pip install --upgrade headroom fallará siempre (paquete no existe en PyPI, y sin venv activo además da externally-managed-environment).

Terminal window
# 1. Activar venv y actualizar desde el wheel (ver TARGET_WHEEL en headroom-update.sh)
source ~/.venv-headroom/bin/activate
pip install "https://github.com/headroomlabs-ai/headroom/releases/download/vX.Y.Z/headroom_ai-X.Y.Z-cp310-abi3-manylinux_2_28_x86_64.whl"
# 2. Re-aplicar (ver paso 2 arriba)
python3 mi-parche.py