# 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 ```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 + 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 ```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. Al cargar se generan embeddings automáticamente para semantic search. ### 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 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): ```json { "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 } } } ``` ```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 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 ```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: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 ```