# 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 - **Knowledge filtering** — solo los docs relevantes al task entran en contexto (keyword scoring) - **Compaction** — extracción de facts, eliminación de redundancia, prioridades por sección - **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 ```bash # 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 ```bash # 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/ ``` ### Cargar Knowledge Base ```bash # Cargar docs desde el directorio docs/ curl -X POST http://localhost:8001/api/v1/knowledge/load \ -H "Content-Type: application/json" \ -d '{"docs_path": "docs"}' ``` ## 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 ```bash # 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 ```bash # 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: ```json { "session_id": "...", "task_id": "...", "content": "### Step 1\n...\n### Review\n...", "steps_completed": 5, "steps_failed": [], "artifacts_count": 0, "review": "...", "status": "completed" } ``` ### SSE Streaming ```bash 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 ```bash # 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. ### Debug ```bash # 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 filtrados por keywords del task | | `task_state` | 70 | Objetivo, plan, step actual, facts, constraints | | `artifact_memory` | 50 | Resúmenes de outputs de herramientas | | `working_context` | 30 | Items de trabajo recientes entre steps | **Knowledge filtering:** no carga todos los docs. Extrae keywords del objetivo y step actual, puntúa cada doc (título ×10, tags ×5, contenido ×1), y selecciona los más relevantes dentro de un budget de 15k tokens. Los docs que no caben entran como summary de una línea. **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. ## 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 → 33 tools descubiertas automáticamente 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): ```json { "mcpServers": { "acai-code": { "command": "node", "args": ["mcp-server/stdio.js"], "env": {}, "timeout": 30, "startup_timeout": 10 } } } ``` ```bash # En .env AGENTIC_MCP_CONFIG_PATH=mcp.json ``` **2. Per-session env vars** (cada usuario/proyecto pasa las suyas al crear sesión): ```bash 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`): ```bash AGENTIC_MCP_SERVER_COMMAND=node AGENTIC_MCP_SERVER_ARGS=["mcp-server/stdio.js"] ``` ### Tool namespacing En modo multi-server, los tools se namespean: `acai-code.compile_module`, `filesystem.read_file`. En modo single-server los tools mantienen su nombre original. ### API de gestión MCP ```bash # 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: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 + embeddings │ ├── 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 ```