Jordan Diaz 5b87676ef4 Fix permisos: appuser con UID 1001 (mismo que acai en container app)
El MCP server creaba archivos con UID 1000 que el server Python
(UID 1001) no podía modificar ni borrar. Ahora ambos containers
usan UID 1001, eliminando conflictos de permisos en volúmenes compartidos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 12:22:12 +00:00
2026-04-01 23:16:45 +01:00
2026-04-01 23:16:45 +01:00
2026-04-02 00:14:11 +01:00
2026-04-01 23:16:45 +01:00
2026-04-01 23:16:45 +01:00
2026-04-02 00:14:11 +01:00
2026-04-01 23:16:45 +01:00

Agentic Microservice

Microservicio de orquestación agéntica con context engineering avanzado, multi-agent orchestration, integración MCP, SSE streaming y knowledge base. Diseñado como backend para Acai Code.

Arquitectura

Client → API (FastAPI) → Orchestrator → Subagents (planner/coder/collector/reviewer)
                              ↓                         ↓
                        Context Engine            Model Adapter (Claude / OpenAI)
                              ↓                         ↓
                     Memory Store + KB            MCP Client (stdio)
                              ↓
                     Redis (sessions, events, knowledge)

Principios:

  • Context over chat history — el modelo recibe estado estructurado, nunca raw tool outputs
  • Semantic search — embeddings (OpenAI text-embedding-3-small) para seleccionar los docs más relevantes por similitud semántica
  • Task history compaction — al completar una tarea, se comprime a ~50 tokens; en sesiones largas el agente recuerda todo sin desbordar el contexto
  • Artifact summarization — outputs de tools resumidos antes de entrar al contexto
  • Error recovery — steps fallidos se saltan, timeout global, sesión nunca se queda en executing
  • Concurrency lock — Redis SETNX impide ejecución simultánea en la misma sesión

Quick Start

Con Docker Compose

# 1. Configurar
cp .env.example .env
# Editar .env — al menos una API key (ANTHROPIC o OPENAI)

# 2. Levantar todo (Redis + microservicio)
docker compose up --build

# 3. Solo Redis (para desarrollo local)
docker compose up redis -d

Desarrollo local

# 1. Redis
docker compose up redis -d

# 2. Dependencias
pip install -r requirements.txt

# 3. Configurar .env
AGENTIC_OPENAI_API_KEY=sk-...
AGENTIC_DEFAULT_MODEL_PROVIDER=openai
AGENTIC_DEFAULT_MODEL_ID=gpt-4o
AGENTIC_REDIS_PORT=6380

# 4. Arrancar
python3 -m uvicorn src.main:app --reload --port 8001

# 5. Dashboard en http://localhost:8001/dashboard/

Tests

# Ejecutar todos los tests unitarios (no necesita Docker, Redis ni LLM)
pip install pytest
python3 -m pytest tests/ -v

# Ejecutar un archivo específico
python3 -m pytest tests/test_compactor.py -v

# Ejecutar un test específico
python3 -m pytest tests/test_cost_calculation.py::TestCostCalculation::test_1m_input_tokens -v

Los tests validan: compactación de contexto, extracción de key_data para historial, fingerprinting de tool calls, y cálculo de costes. Son 100% offline — no consumen tokens ni necesitan servicios externos.

Cargar Knowledge Base

# Cargar docs + generar embeddings para semantic search
curl -X POST http://localhost:8001/api/v1/knowledge/load \
  -H "Content-Type: application/json" \
  -d '{"docs_path": "docs"}'
# → {"status": "loaded", "count": 13, "embeddings": true, ...}

Los embeddings se generan en batch via OpenAI text-embedding-3-small (~$0.001 por los 13 docs) y se almacenan en Redis. Solo se recalculan al hacer /knowledge/load de nuevo.

Dashboard

Interfaz web para testing integrada en el microservicio. Se accede en /dashboard/.

Features:

  • Gestión de sesiones (crear, eliminar, seleccionar)
  • Chat con envío sync (Send) o streaming (Stream)
  • Event Log en tiempo real con filtros por categoría y export JSON
  • Inspector de estado (task, plan, facts, constraints, timeline)
  • Context Debug — muestra exactamente qué recibe cada agente (secciones, tokens, previews)
  • Dark/light mode
  • Responsive (3 columnas → 1 columna en móvil)

Atajos de teclado:

  • Enter → Send
  • Ctrl+Enter → Stream
  • Shift+Enter → Nueva línea

API Reference

Base URL: /api/v1

Sesiones

# Crear sesión
curl -X POST http://localhost:8001/api/v1/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "project_profile": {"name": "mi-proyecto", "tech_stack": ["python", "twig"]},
    "immutable_rules": ["Responde siempre en español", "Usa Tailwind CSS"],
    "metadata": {}
  }'
# → {"session_id": "abc123...", "status": "idle"}

# Obtener estado
curl http://localhost:8001/api/v1/sessions/{session_id}

# Eliminar
curl -X DELETE http://localhost:8001/api/v1/sessions/{session_id}

Mensajes

# Sync — espera la respuesta completa
curl -X POST http://localhost:8001/api/v1/sessions/{session_id}/messages \
  -H "Content-Type: application/json" \
  -d '{"message": "Crea un módulo FAQ con acordeón"}'

# Streaming — retorna inmediatamente, resultados vía SSE
curl -X POST http://localhost:8001/api/v1/sessions/{session_id}/messages \
  -H "Content-Type: application/json" \
  -d '{"message": "Crea un módulo FAQ con acordeón", "stream": true}'

Respuesta sync:

{
  "session_id": "...",
  "task_id": "...",
  "content": "### Step 1\n...\n### Review\n...",
  "steps_completed": 5,
  "steps_failed": [],
  "artifacts_count": 0,
  "review": "...",
  "status": "completed"
}

SSE Streaming

curl -N http://localhost:8001/api/v1/sessions/{session_id}/stream

Tipos de evento:

Evento Cuándo
session.created Sesión inicializada
execution.started Pipeline arranca
subagent.assigned Step ruteado a un agente
agent.delta Chunk de texto del agente
tool.started Herramienta MCP ejecutándose
tool.completed Herramienta terminó
execution.completed Task completado
error Error en step, planning, o timeout

Knowledge Base

# Cargar docs desde directorio
curl -X POST http://localhost:8001/api/v1/knowledge/load \
  -H "Content-Type: application/json" \
  -d '{"docs_path": "docs"}'

# Cargar desde ruta absoluta
curl -X POST http://localhost:8001/api/v1/knowledge/load \
  -H "Content-Type: application/json" \
  -d '{"docs_path": "/ruta/a/mis/docs"}'

# Listar docs cargados
curl http://localhost:8001/api/v1/knowledge

# Eliminar un doc
curl -X DELETE http://localhost:8001/api/v1/knowledge/{doc_id}

Los docs se cargan como *.md del directorio. Se sobreescriben si ya existen. No requiere reinicio — la siguiente conversación usa los docs actualizados. Al cargar se generan embeddings automáticamente para semantic search.

Debug

# Historial de eventos (persistidos en Redis)
curl http://localhost:8001/api/v1/sessions/{session_id}/events

# Context debug — qué recibió cada agente
curl http://localhost:8001/api/v1/sessions/{session_id}/context-debug

# Health check
curl http://localhost:8001/health

Context Engine

El componente más crítico del sistema. Ensambla el prompt que recibe el modelo con secciones priorizadas:

Sección Prioridad Contenido
immutable_rules 100 System prompt del agente + reglas de sesión. Nunca se recorta
project_profile 80 Perfil del proyecto (nombre, tech stack)
knowledge_base 60 Docs relevantes por semantic search + índice de títulos de todos
task_history 55 Resúmenes compactos de tareas anteriores (~50 tokens c/u)
task_state 70 Objetivo, plan, step actual, facts, constraints
artifact_memory 50 Resúmenes de outputs de herramientas (solo tarea actual)
working_context 30 Items de trabajo recientes entre steps

Semantic search: genera embedding del objetivo/step del usuario y busca por cosine similarity contra los embeddings de todos los docs. Los más relevantes entran completos (budget 15k tokens), el resto aparece como título + summary para que el agente sepa que existen y pueda pedir rehidratación.

Task history compaction: al completar cada tarea, sus artifacts, facts y resultados se comprimen en un bloque de ~50 tokens que se guarda en task_history. Los artifacts viejos se borran de Redis. En una sesión de 20 tareas, el historial ocupa ~1000 tokens vs los miles que ocuparían los artifacts completos. El agente nunca pierde contexto de lo que hizo antes.

Token counting: usa tiktoken (encoding cl100k_base) para conteo real. Fallback a estimación si tiktoken no está instalado.

Compaction: si el total excede el context window (120k tokens default), las secciones de menor prioridad se comprimen o eliminan. immutable_rules nunca se toca.

Presupuesto típico tras 20 tareas:

immutable_rules:  ~250 tokens (fijo)
project_profile:   ~15 tokens (fijo)
knowledge_base: ~15,000 tokens (semantic search)
task_history:    ~1,000 tokens (20 tareas × ~50 tokens)
task_state:       ~400 tokens (tarea actual)
artifact_memory:  ~500 tokens (solo tarea actual)
working_context:  ~500 tokens (solo step actual)
─────────────────────────────────────
TOTAL:          ~17,700 tokens / 120,000 disponibles

Orchestrator Pipeline

mensaje → planner → [step₁ → step₂ → ... → stepₙ] → reviewer → respuesta
  1. Planner descompone el mensaje en pasos con agent role asignado
  2. Router rutea cada step al agente apropiado (keyword matching: implement→coder, buscar→collector, etc.)
  3. Subagent ejecuta el step con su propio contexto controlado
  4. Si un step falla, se marca como failed y el pipeline continúa con el siguiente
  5. Reviewer valida el trabajo si hubo >1 step
  6. Timeout global de 5 min (configurable) — si se excede, la sesión pasa a error

Concurrency: Redis lock (SETNX) con TTL de 5 min. Si llega un segundo mensaje mientras uno se ejecuta → {"status": "busy"}. El lock se libera automáticamente si el proceso muere.

MCP (Model Context Protocol)

Arquitectura per-session: el microservicio corre como un Docker único, y cada sesión arranca sus propios subprocesos MCP con las env vars del proyecto del usuario.

Modelo de operación

Docker (microservicio — uno solo corriendo)
  └── mcp.json            ← template: QUÉ servers arrancar (global)
  └── mcp-server/         ← código MCP (baked-in)

POST /sessions { mcp_env: { ACAI_WEB_URL: "https://tienda.com", ... } }
  → Arranca subprocesos MCP con esas env vars
  → Session aislada con su propio MCPManager
  → 55 tools descubiertas (acai-code: 33, playwright: 21, fetch: 1)

DELETE /sessions/{id}
  → Mata los subprocesos MCP de esa sesión

Configuración

1. Template global (mcp.json en la raíz — define QUÉ servers existen):

{
  "mcpServers": {
    "acai-code": {
      "command": "node",
      "args": ["mcp-server/stdio.js"],
      "env": {},
      "timeout": 30,
      "startup_timeout": 10
    },
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp", "--headless"],
      "timeout": 30,
      "startup_timeout": 15
    },
    "fetch": {
      "command": "uvx",
      "args": ["mcp-server-fetch"],
      "timeout": 30,
      "startup_timeout": 15
    }
  }
}
# En .env
AGENTIC_MCP_CONFIG_PATH=mcp.json

2. Per-session env vars (cada usuario/proyecto pasa las suyas al crear sesión):

curl -X POST http://localhost:8001/api/v1/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "project_profile": {"name": "tienda-online"},
    "mcp_env": {
      "ACAI_WEB_URL": "https://superadmin_tienda.forge.acaisuite.com/",
      "ACAI_WEBSITE": "tienda.com",
      "ACAI_PROJECT_DIR": "/projects/tienda"
    }
  }'

Las env vars de mcp_env se fusionan con las del template y se pasan al subproceso MCP. El MCP server lee el token desde el .acai del proyecto automáticamente.

3. Legacy (un solo server global, sin mcp.json):

AGENTIC_MCP_SERVER_COMMAND=node
AGENTIC_MCP_SERVER_ARGS=["mcp-server/stdio.js"]

Tool namespacing

En modo multi-server, los tools se namespean con __ (compatible con OpenAI que no acepta puntos): acai_code__compile_module, playwright__browser_navigate. En modo single-server los tools mantienen su nombre original.

API de gestión MCP

# Ver estado de todos los MCP servers activos (por sesión)
curl http://localhost:8001/api/v1/mcp/status

# Hot-reload del template (re-lee mcp.json, no afecta sesiones activas)
curl -X POST http://localhost:8001/api/v1/mcp/reload

Ciclo de vida

  1. POST /sessions con mcp_env → arranca subprocesos MCP para esa sesión
  2. POST /sessions/{id}/messages → el agente usa los tools del MCP de esa sesión
  3. Si el server se reinicia y la sesión sigue en Redis → el MCP se reconecta automáticamente al siguiente mensaje
  4. DELETE /sessions/{id} → mata subprocesos MCP de esa sesión

Los resultados de tools nunca entran al contexto como raw output — se resumen como artifacts.

Configuración

Variables de entorno con prefijo AGENTIC_:

Variable Default Descripción
AGENTIC_ANTHROPIC_API_KEY API key de Anthropic
AGENTIC_OPENAI_API_KEY API key de OpenAI
AGENTIC_DEFAULT_MODEL_PROVIDER claude claude o openai
AGENTIC_DEFAULT_MODEL_ID claude-sonnet-4-20250514 Modelo por defecto
AGENTIC_REDIS_HOST localhost Host de Redis
AGENTIC_REDIS_PORT 6379 Puerto de Redis
AGENTIC_REDIS_DB 0 Base de datos Redis
AGENTIC_REDIS_PASSWORD Password de Redis
AGENTIC_MAX_TOKENS 4096 Max tokens de salida por llamada
AGENTIC_CONTEXT_MAX_TOKENS 120000 Max context window
AGENTIC_MAX_EXECUTION_STEPS 25 Max steps por task
AGENTIC_MAX_EXECUTION_TIMEOUT_SECONDS 300 Timeout global (5 min)
AGENTIC_SUBAGENT_MAX_STEPS 10 Max iterations por subagent
AGENTIC_MCP_CONFIG_PATH Ruta a mcp.json (multi-MCP per-session)
AGENTIC_MCP_SERVER_COMMAND Legacy: comando del servidor MCP (single)
AGENTIC_MCP_SERVER_ARGS [] Legacy: argumentos del servidor MCP
AGENTIC_MCP_TIMEOUT_SECONDS 30 Timeout por tool call
AGENTIC_DEBUG false Logging verbose

Redis Key Structure

agentic:session:{id}                    → SessionState JSON (TTL 24h)
agentic:session:{id}:artifacts          → Hash de ArtifactSummary
agentic:session:{id}:events             → Lista de SSE events (cap 500)
agentic:session:{id}:lock               → Execution lock (SETNX, TTL 5min)
agentic:sessions:index                  → Set de session IDs activos
agentic:memory:knowledge:{doc_id}       → MemoryDocument JSON
agentic:memory:knowledge:_index         → Set de doc IDs
agentic:embeddings:knowledge:{doc_id}   → Embedding vector (1536 floats JSON)
agentic:memory:_tag:{tag}               → Set de doc IDs por tag
agentic:memory:_type:{type}             → Set de doc IDs por tipo

Project Structure

.
├── src/
│   ├── main.py                 # FastAPI app, lifecycle, wiring
│   ├── config.py               # Pydantic settings
│   ├── models/                 # Pydantic v2 data models
│   │   ├── session.py          # SessionState, TaskState, TaskStep
│   │   ├── context.py          # ContextPackage, MemoryDocument
│   │   ├── agent.py            # AgentProfile, SubAgentDefinition
│   │   ├── artifacts.py        # ArtifactSummary
│   │   └── tools.py            # ToolExecution, ToolDefinition
│   ├── context/                # Context Engine (core)
│   │   ├── engine.py           # Prompt assembly, knowledge filtering, debug
│   │   └── compactor.py        # Compaction, summarization, tiktoken
│   ├── memory/                 # Persistent memory
│   │   ├── store.py            # Redis-backed memory + similarity search
│   │   └── embeddings.py       # OpenAI embedding service
│   ├── adapters/               # Model provider adapters
│   │   ├── base.py             # ModelAdapter interface
│   │   ├── claude_adapter.py   # Anthropic Claude (streaming)
│   │   └── openai_adapter.py   # OpenAI GPT (streaming)
│   ├── mcp/                    # MCP (per-session, multi-server)
│   │   ├── client.py           # stdio transport, tool registry
│   │   ├── config.py           # mcp.json parser (Pydantic)
│   │   ├── manager.py          # MCPManager (aggregates tools, routes calls)
│   │   └── registry.py         # Per-session MCP lifecycle
│   ├── orchestrator/           # Agent orchestration
│   │   ├── engine.py           # Pipeline + error recovery + timeout
│   │   ├── router.py           # Step-to-agent routing
│   │   └── agents/
│   │       ├── base.py         # Shared execution loop
│   │       ├── planner.py      # Plan decomposition
│   │       ├── coder.py        # Implementation
│   │       ├── collector.py    # Context gathering
│   │       └── reviewer.py     # Validation
│   ├── streaming/              # SSE streaming
│   │   └── sse.py              # Event emitter + Redis persistence
│   ├── storage/                # Persistence
│   │   └── redis.py            # Sessions, events, locks
│   └── api/                    # REST endpoints
│       └── routes.py           # Sessions, messages, knowledge, debug
├── dashboard/                  # Testing UI (vanilla JS, zero build)
│   ├── index.html
│   ├── css/main.css
│   └── js/
│       ├── app.js              # State, SSE handlers
│       ├── api.js              # Fetch + EventSource
│       └── components/         # Sidebar, chat, event log, inspector, timeline
├── mcp-server/                 # Acai MCP server (Node.js, stdio)
├── docs/                       # Knowledge base documents (*.md)
├── mcp.json                    # MCP server template (qué servers arrancar)
├── mcp.json.example            # Ejemplo con múltiples servers
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example
Description
No description provided
Readme 1.5 MiB
Languages
JavaScript 56.8%
Python 35.8%
HTML 4.3%
CSS 2.7%
Dockerfile 0.4%