From 264acc90b4422a8323ca48e380272673b0b6ea93 Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 1 Apr 2026 23:47:26 +0100 Subject: [PATCH] Add .gitignore, remove __pycache__ from tracking, and update MCP/orchestrator modules - Add .gitignore to exclude .env, __pycache__, node_modules, and IDE files - Remove all __pycache__ bytecode files from version control - Add MCP config files (mcp.json, mcp.json.example) - Add MCP manager, registry, and config modules - Update routes, orchestrator engine, and agent base with latest changes Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 28 ++ README.md | 105 ++++++- mcp.json | 11 + mcp.json.example | 19 ++ src/__pycache__/__init__.cpython-312.pyc | Bin 167 -> 0 bytes src/__pycache__/config.cpython-312.pyc | Bin 2812 -> 0 bytes src/__pycache__/main.cpython-312.pyc | Bin 5684 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 424 -> 0 bytes src/adapters/__pycache__/base.cpython-312.pyc | Bin 3390 -> 0 bytes .../claude_adapter.cpython-312.pyc | Bin 6959 -> 0 bytes .../openai_adapter.cpython-312.pyc | Bin 7370 -> 0 bytes src/api/__pycache__/__init__.cpython-312.pyc | Bin 238 -> 0 bytes src/api/__pycache__/routes.cpython-312.pyc | Bin 15912 -> 0 bytes src/api/routes.py | 89 +++++- src/config.py | 3 +- .../__pycache__/__init__.cpython-312.pyc | Bin 311 -> 0 bytes .../__pycache__/compactor.cpython-312.pyc | Bin 11322 -> 0 bytes .../__pycache__/engine.cpython-312.pyc | Bin 22637 -> 0 bytes src/main.py | 63 ++-- src/mcp/__init__.py | 6 +- src/mcp/__pycache__/__init__.cpython-312.pyc | Bin 241 -> 0 bytes src/mcp/__pycache__/client.cpython-312.pyc | Bin 13675 -> 0 bytes src/mcp/client.py | 6 +- src/mcp/config.py | 55 ++++ src/mcp/manager.py | 289 ++++++++++++++++++ src/mcp/registry.py | 135 ++++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 245 -> 0 bytes src/memory/__pycache__/store.cpython-312.pyc | Bin 11689 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 636 -> 0 bytes src/models/__pycache__/agent.cpython-312.pyc | Bin 2204 -> 0 bytes .../__pycache__/artifacts.cpython-312.pyc | Bin 1489 -> 0 bytes .../__pycache__/context.cpython-312.pyc | Bin 3705 -> 0 bytes .../__pycache__/session.cpython-312.pyc | Bin 7377 -> 0 bytes src/models/__pycache__/tools.cpython-312.pyc | Bin 2179 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 260 -> 0 bytes .../__pycache__/engine.cpython-312.pyc | Bin 13671 -> 0 bytes .../__pycache__/router.cpython-312.pyc | Bin 2475 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 415 -> 0 bytes .../agents/__pycache__/base.cpython-312.pyc | Bin 10132 -> 0 bytes .../agents/__pycache__/coder.cpython-312.pyc | Bin 1851 -> 0 bytes .../__pycache__/collector.cpython-312.pyc | Bin 1761 -> 0 bytes .../__pycache__/planner.cpython-312.pyc | Bin 4126 -> 0 bytes .../__pycache__/reviewer.cpython-312.pyc | Bin 1778 -> 0 bytes src/orchestrator/agents/base.py | 4 +- src/orchestrator/engine.py | 4 +- .../__pycache__/__init__.cpython-312.pyc | Bin 248 -> 0 bytes src/storage/__pycache__/redis.cpython-312.pyc | Bin 8817 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 275 -> 0 bytes src/streaming/__pycache__/sse.cpython-312.pyc | Bin 9035 -> 0 bytes 49 files changed, 749 insertions(+), 68 deletions(-) create mode 100644 .gitignore create mode 100644 mcp.json create mode 100644 mcp.json.example delete mode 100644 src/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/__pycache__/config.cpython-312.pyc delete mode 100644 src/__pycache__/main.cpython-312.pyc delete mode 100644 src/adapters/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/adapters/__pycache__/base.cpython-312.pyc delete mode 100644 src/adapters/__pycache__/claude_adapter.cpython-312.pyc delete mode 100644 src/adapters/__pycache__/openai_adapter.cpython-312.pyc delete mode 100644 src/api/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/api/__pycache__/routes.cpython-312.pyc delete mode 100644 src/context/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/context/__pycache__/compactor.cpython-312.pyc delete mode 100644 src/context/__pycache__/engine.cpython-312.pyc delete mode 100644 src/mcp/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/mcp/__pycache__/client.cpython-312.pyc create mode 100644 src/mcp/config.py create mode 100644 src/mcp/manager.py create mode 100644 src/mcp/registry.py delete mode 100644 src/memory/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/memory/__pycache__/store.cpython-312.pyc delete mode 100644 src/models/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/models/__pycache__/agent.cpython-312.pyc delete mode 100644 src/models/__pycache__/artifacts.cpython-312.pyc delete mode 100644 src/models/__pycache__/context.cpython-312.pyc delete mode 100644 src/models/__pycache__/session.cpython-312.pyc delete mode 100644 src/models/__pycache__/tools.cpython-312.pyc delete mode 100644 src/orchestrator/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/orchestrator/__pycache__/engine.cpython-312.pyc delete mode 100644 src/orchestrator/__pycache__/router.cpython-312.pyc delete mode 100644 src/orchestrator/agents/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/orchestrator/agents/__pycache__/base.cpython-312.pyc delete mode 100644 src/orchestrator/agents/__pycache__/coder.cpython-312.pyc delete mode 100644 src/orchestrator/agents/__pycache__/collector.cpython-312.pyc delete mode 100644 src/orchestrator/agents/__pycache__/planner.cpython-312.pyc delete mode 100644 src/orchestrator/agents/__pycache__/reviewer.cpython-312.pyc delete mode 100644 src/storage/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/storage/__pycache__/redis.cpython-312.pyc delete mode 100644 src/streaming/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/streaming/__pycache__/sse.cpython-312.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4da07d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Environment variables +.env +.env.local +.env.*.local + +# Python +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +dist/ +build/ +.venv/ +venv/ + +# Node +node_modules/ +npm-debug.log* + +# OS +.DS_Store +Thumbs.db + +# IDE +.vscode/ +.idea/ +*.swp +*.swo diff --git a/README.md b/README.md index ce29043..cc9355a 100644 --- a/README.md +++ b/README.md @@ -227,20 +227,93 @@ mensaje → planner → [step₁ → step₂ → ... → stepₙ] → reviewer ## MCP (Model Context Protocol) -Cliente stdio que se conecta a un servidor MCP al arrancar: +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_SERVER_COMMAND=node -AGENTIC_MCP_SERVER_ARGS=["mcp-server/stdio.js"] - -# Variables del MCP server (se heredan al subproceso) -ACAI_WEB_URL=http://localhost:8080 -ACAI_WEBSITE=mi-sitio -ACAI_PROJECT_DIR=/ruta/al/proyecto +AGENTIC_MCP_CONFIG_PATH=mcp.json ``` -El cliente descubre tools automáticamente vía `tools/list`, y los agentes las usan durante la ejecución. Los resultados de tools **nunca** entran al contexto como raw output — se resumen como artifacts. +**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 @@ -261,8 +334,9 @@ Variables de entorno con prefijo `AGENTIC_`: | `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_SERVER_COMMAND` | — | Comando del servidor MCP | -| `AGENTIC_MCP_SERVER_ARGS` | `[]` | Argumentos del servidor MCP | +| `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 | @@ -302,8 +376,11 @@ agentic:memory:_type:{type} → Set de doc IDs por tipo │ │ ├── base.py # ModelAdapter interface │ │ ├── claude_adapter.py # Anthropic Claude (streaming) │ │ └── openai_adapter.py # OpenAI GPT (streaming) -│ ├── mcp/ # MCP client -│ │ └── client.py # stdio transport, tool registry +│ ├── 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 @@ -328,6 +405,8 @@ agentic:memory:_type:{type} → Set de doc IDs por tipo │ └── 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 diff --git a/mcp.json b/mcp.json new file mode 100644 index 0000000..2a212c6 --- /dev/null +++ b/mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "acai-code": { + "command": "node", + "args": ["mcp-server/stdio.js"], + "env": {}, + "timeout": 30, + "startup_timeout": 10 + } + } +} diff --git a/mcp.json.example b/mcp.json.example new file mode 100644 index 0000000..de634f0 --- /dev/null +++ b/mcp.json.example @@ -0,0 +1,19 @@ +{ + "mcpServers": { + "acai-code": { + "command": "node", + "args": ["mcp-server/stdio.js"], + "env": { + "ACAI_WEB_URL": "http://localhost:8080", + "ACAI_WEBSITE": "mi-sitio", + "ACAI_PROJECT_DIR": "/ruta/al/proyecto" + }, + "timeout": 30, + "startup_timeout": 10 + }, + "filesystem": { + "command": "npx", + "args": ["@modelcontextprotocol/server-filesystem", "/tmp"] + } + } +} diff --git a/src/__pycache__/__init__.cpython-312.pyc b/src/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 4b00cdb48a2e9e233e9b1f2ef3dcdaf007354c5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmX@j%ge<81omxbGePuY5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!a?=kjPAw|d&&n@K zNz6;hOsvv($xklLP0cGQ)-Nc^PcKT$O-#D`Ev2%Lv59AjU^#Mn=XWW*`dy@W?9v diff --git a/src/__pycache__/config.cpython-312.pyc b/src/__pycache__/config.cpython-312.pyc deleted file mode 100644 index 1d2b6afd8d99d64b544e141c09aa0278bbfd3d9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2812 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 Jb)r*u^aHx=b`}5t diff --git a/src/adapters/__pycache__/base.cpython-312.pyc b/src/adapters/__pycache__/base.cpython-312.pyc deleted file mode 100644 index 761cccaf5a1c471fb9e579da4bfcbd1bea95e643..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3390 zcmbVO&2JmW6`x)1l1nZr$<|ja%gs8r6S1*L)N1Rb37kr*>pHfXqHZoUgvDxiC~dtT z%FHqei5`eX0d&-%K#n~dc7Pmu=>HG|2tXg?xx{Tzw7oG=i=;mFz1bB-$qLYB1^xIw zX5Y-d&-t`cDG_)!KK#s&3WWR>nf*sk31#n(Kzu|z(j=bZseuxzO;tfU7vw^%sfBt| zm%J9_!$PwV8cjp;dQc2Y%~EJKO@+{cO0E+xzeT*l12wh%Als{1&a?(-i_d5)nYIL4 z^BL`!v|9#Ug=!CSsmbPeXRJEjejT{mu}1^T@tn>$4x?-Y{VXj zi=LA>Zs2e(^p;Npl;xJ9j;Jj0PUPNzot-3RZhFKNv-2u@3Gg2gN}39IT74V*+st`t zQ}c3E_cZ(v;PR5wGp-=He8w4)D`Z?zat3lLp(ScQEIv@uEt_Sp1U607b;{L>Fz+Oc zI^oJjJG$4cFIn7=*1;n#@>Yw*q2*ZcsjvhTRw(_+DBlEU(3&732TGxXmlH>nlQ<4+ z*9ijK_e4>0krPreCP{~_w?i5wTvS?qW@GS~V%)YzLI~1v zkg^Gc69^{}paO`{a91`1g`Hj#r$7NyX6-%jFt(0bPw<JkFR zo4troLy(cKBXuOm3n)XtpbUe2_Q}*t?-$!{Uz^#Tnd`l|lf$|_HQ&3rGY!@H*t+nE z_sDt;iuXoed+pegMVp6$tbqU?WiJ9e7zj8Hs8%FJ7@>#0~ac-qLS zJk`9Sr*CP)_X-`gS`uX$-lcR{cKK2^;zq_M&dJdk$!n#O1<5t+_$^I0z z*5YJiWPU&kwGLIjl@3ragmxNk<^kyjyeNEt-oR#XlPKVkj`DHtJ*BF4&)ktS=}6y5 z#}={rex} zn;scVkYf2id-(eAR89GxPhGoC6U%{u$-1Dd4{Lv%9&L)*XRt-s=EzZveG}d3Lx+pC@ka3NUR}7x6F+pIKg$Y*gSMkY-j0$DYOVY4(#T4n&a#tLRrW)$ zCNz|BG`#Q1vV1eE^X{o5X9S~}>gVtdKX7Xv-71e$|H=f}UJl<=QG4?LwJ>{}g|AdKv-%}c>3qTy zH>wkIgyKc4)yAd`cdHyIM9x`r8CC~F4+wL3H>RBBQ7lJoKIv@2rBY64RzxvoLON4r z&y~J8>@Q)w4n+fZzN_pyELPy3-vb!r6h(PVmj6ax_&YiG1zGrlTmV0*=GUsByfPpF q;26~$NOndl3d+=g0Avber!ob+Q5e1f)W4?ADCM2Se-rqT4)`ysO-OeD diff --git a/src/adapters/__pycache__/claude_adapter.cpython-312.pyc b/src/adapters/__pycache__/claude_adapter.cpython-312.pyc deleted file mode 100644 index 37df90b986371536f0440b55ef9071ec09a8f3a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6959 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 O+85vamf%-g?|%VcryYC% diff --git a/src/adapters/__pycache__/openai_adapter.cpython-312.pyc b/src/adapters/__pycache__/openai_adapter.cpython-312.pyc deleted file mode 100644 index 502e05199af0425bbc39c8019e37325ddb53f6d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7370 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 diff --git a/src/api/routes.py b/src/api/routes.py index d2db28a..1012e96 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -13,6 +13,7 @@ from pydantic import BaseModel, Field from ..models.context import MemoryDocument, MemoryType from ..models.session import SessionState, SessionStatus +from ..orchestrator.engine import OrchestratorEngine from ..streaming.sse import EventType logger = logging.getLogger(__name__) @@ -28,6 +29,10 @@ class CreateSessionRequest(BaseModel): project_profile: dict[str, Any] = Field(default_factory=dict) immutable_rules: list[str] = Field(default_factory=list) metadata: dict[str, Any] = Field(default_factory=dict) + mcp_env: dict[str, str] = Field( + default_factory=dict, + description="Per-project env vars for MCP servers (e.g. ACAI_WEB_URL, ACAI_PROJECT_DIR)", + ) class CreateSessionResponse(BaseModel): @@ -59,32 +64,43 @@ _deps: dict[str, Any] = {} def set_dependencies( storage: Any, - orchestrator: Any, + model_adapter: Any, + context_engine: Any, + memory_store: Any, sse_emitter: Any, - context_engine: Any = None, - memory_store: Any = None, + mcp_registry: Any, ) -> None: _deps["storage"] = storage - _deps["orchestrator"] = orchestrator + _deps["model_adapter"] = model_adapter + _deps["context_engine"] = context_engine + _deps["memory_store"] = memory_store _deps["sse"] = sse_emitter - if context_engine: - _deps["context_engine"] = context_engine - if memory_store: - _deps["memory_store"] = memory_store + _deps["mcp_registry"] = mcp_registry def _get_storage(): return _deps["storage"] -def _get_orchestrator(): - return _deps["orchestrator"] - - def _get_sse(): return _deps["sse"] +def _get_mcp_registry(): + return _deps["mcp_registry"] + + +def _build_orchestrator(mcp_manager) -> OrchestratorEngine: + """Build an orchestrator with a session-specific MCPManager.""" + return OrchestratorEngine( + model_adapter=_deps["model_adapter"], + context_engine=_deps["context_engine"], + mcp_client=mcp_manager, + memory_store=_deps["memory_store"], + sse_emitter=_deps["sse"], + ) + + # ------------------------------------------------------------------ # POST /sessions # ------------------------------------------------------------------ @@ -97,8 +113,17 @@ async def create_session(body: CreateSessionRequest) -> CreateSessionResponse: immutable_rules=body.immutable_rules, metadata=body.metadata, ) + # Store mcp_env in session metadata for reconnection + if body.mcp_env: + session.metadata["mcp_env"] = body.mcp_env + await storage.create_session(session) + # Start per-session MCP servers with project-specific env + registry = _get_mcp_registry() + if registry.has_config: + await registry.create_for_session(session.session_id, body.mcp_env) + sse = _get_sse() await sse.emit( EventType.SESSION_CREATED, @@ -126,7 +151,16 @@ async def send_message( if not session: raise HTTPException(status_code=404, detail="Session not found") - orchestrator = _get_orchestrator() + # Get or create session's MCP manager + registry = _get_mcp_registry() + mcp_manager = registry.get_for_session(session_id) + if not mcp_manager and registry.has_config: + # Reconnect MCP (e.g. after server restart) + mcp_env = session.metadata.get("mcp_env", {}) + mcp_manager = await registry.create_for_session(session_id, mcp_env) + + from ..mcp.manager import MCPManager + orchestrator = _build_orchestrator(mcp_manager or MCPManager()) if body.stream: asyncio.create_task(_execute_and_persist(orchestrator, storage, session, body.message)) @@ -225,6 +259,10 @@ async def delete_session(session_id: str) -> dict[str, str]: if not deleted: raise HTTPException(status_code=404, detail="Session not found") + # Stop session's MCP servers + registry = _get_mcp_registry() + await registry.destroy_for_session(session_id) + sse = _get_sse() sse.cleanup_session(session_id) @@ -373,3 +411,28 @@ async def delete_knowledge(doc_id: str) -> dict[str, str]: if not deleted: raise HTTPException(status_code=404, detail="Document not found") return {"status": "deleted", "id": doc_id} + + +# ------------------------------------------------------------------ +# MCP Management +# ------------------------------------------------------------------ + +@router.get("/mcp/status") +async def mcp_status() -> dict[str, Any]: + """Status of all MCP servers across sessions.""" + registry = _get_mcp_registry() + return registry.get_status() + + +@router.post("/mcp/reload") +async def mcp_reload() -> dict[str, Any]: + """Hot-reload MCP config template (does not affect running sessions).""" + registry = _get_mcp_registry() + try: + registry.load_config() + return { + "status": "reloaded", + "servers": registry.server_names, + } + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) diff --git a/src/config.py b/src/config.py index d8e7f00..dd47241 100644 --- a/src/config.py +++ b/src/config.py @@ -42,7 +42,8 @@ class Settings(BaseSettings): working_context_max_items: int = 20 # --- MCP --- - mcp_server_command: str = "" + mcp_config_path: str = "" # Path to mcp.json; empty = legacy single-server mode + mcp_server_command: str = "" # Legacy: single server command mcp_server_args: list[str] = Field(default_factory=list) mcp_timeout_seconds: float = 30.0 mcp_startup_timeout_seconds: float = 10.0 diff --git a/src/context/__pycache__/__init__.cpython-312.pyc b/src/context/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 7559ba60b29f93c467ff787160156245a572aea4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 311 zcmX|6Jxc>Y5Z%4IBqrg&%0g@eL9WUE0kMg;X~gE5W!YVhmHjw&H{fYy;cxIa_*+Cs zeGm|=?1Xey&Rvd7@#eiZk74E`&u6Hs*H85f?YkEPBtzd_0C__K39OO9CB_|3bYjvn z1)hqmO3JJm72_Wa(E_*m(%C2<~+{Z$<8d^npA$FPIgIJ&_y7*GAeHNbOzanPFTxOIQV-;&)Am(vaXe z;rqJ?rOK*E>7s7hqbuXYTFYxchxOeD;RN6}#TakV`4(M%%}+OHi_MI@O#Bo^{|8J~ A2><{9 diff --git a/src/context/__pycache__/compactor.cpython-312.pyc b/src/context/__pycache__/compactor.cpython-312.pyc deleted file mode 100644 index 720b58a20d682092fe697e9f427f578d29e74fb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11322 zcmd5?dvF`adA|eh@FoBf07XKgbP`EPpd`q$Wm}?US=3viL^+b2*t8T3ggcQSL4e)^ zB@qE*I`vFoTOE@lIYeT+VcJegR?e8|D{PyYI*(30)5ib?UBYK&qB!of`lrK~&NTKv z?QidJAV@({I+H(g5_k7*zy0?0?eBXm{<)^c#^Cwp#J@(L78vH2^hJL-t-vm}unaT7 zh)g#lvZ5iz4j8%(EG-*j#%`mUbKRVp^W8jhE@m1qcbh@Oi>8=mz}jt9%jTGEz}{^i zaCAFZ#=uBipI`56DVLl#&>wxeT~@6ZcefkV?!FD$_sUeXl&R}PmuP*F>-I>lzCb07 zRu*kKCGGnNBi6jgh|U`Zt+spBu(6qud@T%Vz*t?Py~Jr-6;6&NPR}q`H(h3~7@uJ- zvjKPNpAIDAN$E;bh$IFE!;xe(5pNgpPEZyuB9XTWL>fq3l8}i*aWNc^3~v<%!qIpV ze`pq$L5w#!XtSN|m2^f4B1C1hkq1E$y%3Xxi}=(pNrTaNkC0RewVp3WlfB@SO!P}} z;lhyEBPDH@d!uMEh~^Txq&FBy$VnkOFqk08a2%yLI1YsAkb}0{bk+e@v4`XFL{j}I zcj4XG5g)!ymkecB2T4ZhoX!jl41~#Wz@Ruw(?27rQ&;Mizn>i*loWd;w&Q^Z`a>7O zvJ@%pw7SM7q(+8e7muPa!K4|P9cGhdiezc07^aU`>6ayGMl`%-)OSpib*X=)2zZ#F_7u#h0jlIe+mmPZ|_SGY`7Q=r?wwVM1}?=Z1eU( zlIS7ffp7#1zdhW8Z_&t^VL2%cY?n!7J0yZ~Laqu_f&>SL6_+F@qu47cq>f%D9`w_I zKe-vnDDw-Kr|9wCbk$woJ@0AGdzvQ?&YsSBnhTzN1y@JT(y<6KCe`-T@X9^&q%RFiU$RvPtZq_M{no7eqq}%c$=S_QaTKI*hfv5RUY>3t!zSM0&$S76v7v z?NIPGUAEg4e;`xFf$$YlgEDa=(N>hy(RVs&H4$jVDkP)K$F91u9oHk-mTS?;9pmwW zt2Jk7C0^7GJ%%Ky?>D3x5-%q)3`rC|8ogc;i0~lxDXiEAG-EJfKTZ>XA}Wrg)>m6^)6le)N^G12v^5 z&FWG)!lv1S%=tA8Gh(QaT0@_>6FA zuFtuGAzqX82&qpW-L@4^|7P$pS}`7siRtsLsKzU}-eYw}_=E5Z@v5t9f%lqDz>E6#| zDhFSyZ(x_rksdgpGP_9aI`s%5U-_J2^qE-n1q`sH1!NWAu2}ZO!UGq?@ZQuz|DW-f z8g)=^79^w0WXGQ!|KsB-T%4uqq+c3V%tX2bg)H6vUp%Ug4>%M@NQ6l?7>h=rSXI5E zm{oj?C&>n|QjGG@fC^tgG8j#YqqKgZB>&)!`0tj?TTFfrEJS8HCX z$@_z|T&8Bu@;E-Zykp`-Vm$GlvvqoN(btr{Fn%s`xajiDyEf!q8|F6dn%{UdzwzjM zuA@b_Z($ANZpgS68kxqX`G&T9L)+9NZ||MnTWDy{9J{%?ZEAO6^~0GHMX!I}8_0VD zQz!D?U715g^zz)x!^QP&W2WoQ;^v)Wra5;j_+@?L&T$^YsIAY|&o>71`0oym@kMvT zgk#)s-MO%?a)f`rd0~S(08QOg8lDugRdw?KmhZ0W#!!Yq9*|mA9%x&7pg0X_{zj>0 z#009!UdmKuKIWuZZ`(&@ZpD)}z7D4;&8aD?rUua<8vV@c%$w#u`wDWr>d>h7P)!-> zG~mSfnGs9UrSnN!C>#UC`80Za(*W2;DOp>tFR#E`rW+-7se!|+JN zgv3yCaEKaaFhjy|VTFSMk4Tqz>KV~idIMSLI`qXur-i|=EW@5l_L9UjcY86t=zV_PZG9ZK@Viwo_W~iHkzO3eTtH-Gri|Vv{9VhJ8fK=44pzr-hm_ z1wH>bztLn&Qp^Ae>J2Hp>h39aIWa^c)KE{v6dSd4fe3Iw(1kNp@(e>7sWWt8C>n!% zQR3i*U35_*q2>LUd&6>XEP6pP$-UtRAAW?;tx&AJ(iJh<1LPo7*Qy3{z^Va_mG&Kq zhX*9ZhD8f$i$?ZQItrBJ5KRu!WIs)wK%yF3`f8U9HJ>)HXiA$+iBD?d9Vkl^Yt7fSPDW?^vriT34rI)?8rIA=?8rClmDwZ7%$v;DccHu_$- zs@Smp<|==-{o3=NdTegn0%Nnlyt9f0hUyMP+?Mz=H@bf$+xKknlf$^o=q9f3n@dp zkg^GB;V=ydwF{pc9uvB5Q_&it$w#B#RZWs1OoS2On|@cdI!Qg{;R2YErSZrLYYUjQ zV|xJg+9{^zS>gH;(YT5OiV0q=6c<6L8Xng2Bor%k&{X$Z;bYObBr7J(c2$g+vce-8 zL55|ja7tL9nHqlG)X>+ZgjrEZPHnjZ4(?8T35@0cMl#C$3Wu_>=<(fJ)0SJk1&C1V z&z>G%n=yUpS(7YuHfJ@5~mf7?0xc+Pu_s{OiG zhoSbN=hsYeZ(FCWGtEB;d_OR|fA$MA+X|cakzIx)N6ItJ!V_nfvhhReMwZ6PVP>qyq?JHjadul6WQ*Sj-MQ;Ru z_|WXo-?!)64!z6f+m7V|$KTzaYwDhJKl=g97a!wo0^GU1uMWR7ob?r4>vEQL8kSU^ zc&hSMEcq0L%7}`tct;F9@O|*7A&N*5Wy#yBh7r}zdNtYC`_NC&y^I8$bW_@J30Qaa zK-zHsGv8M>6fm*U19Jz6Ngud37daq#D~c?lvD*rPO9I+qDY=b zsU*sT`cgEmfGC^^(M=pz#WWO;J~t$3Mm5`ehwzfEXp?HXLrN>H|1prry+}qG*t!0u z*L@kTxCX*l+!TOB);7W_cCVVSkK3~y-#+%uWAkga=GSa3tO-Jy&AWH!-Mb6!$1)v7 zmpjY7JW_OfCoJQZZ0&V>(Oo}LGhUMozwZ_ntT3Ztb-U{`HTRVWxQL}S!>G!SzH=jn zwBaE0sxbm7i5Si|Lpl(5sAg{JEwF!HP*gEBb&7Gr)m9F%k&@Jb|9POt2z+#6rJlOT z(nv%9qbh1U;gOfk((>@2Zz^_L0QMTH$zPh&sm@^ZEdEA}muZynKu1pxc2$mEl48SU zh^3BHyB4^t^R z2(eLfRjLTJQ356vB?+})$+JilHbyW@$eG%ZnpYYaYEeiU15K^HQ=D|vqr-g@H1cyu zMwwd>2G_W&;9j5Mf6>}jaPRugQF8i1f%po9Qked zeeFK{%uD=m6u)`*(W7?iw;2E5As9(LQuWb~@Cc7R1zclC0@x9R+S0rV-4-Z^YQSvP z42;US6>4 zHnbRXn+j;k`Q>NNT|OtD-_m|g-V)qG9>>R_HY8u({LvfN(jUDsk$$q{%$fAzj$T>pBuht)e`(pF0;kzvZ0V_H6sPZRe2(x7?;PMbM=W z8Hv(_E|g**@Y!&O66w{>ewfxokd)#xHw_~B6C?nG)lIN;+-P`i+ZK_Lj%iEZ3$v$+gGjp)$SvAo<-hOQl z#er3CJd{nozI&2N_z4FqPTti@L=bY<-zdAGP{mW<5JfTE3p4JCKU}pl1S4q)7s`={5lAhKR)~na zAT_2+V!wKMivTVH%N%(D;w$j||@l5h>GI8W!4=vTuUIPY#hdf;%aTrtgK#D5<_n zAeeG?N@yR& zB(P~FK^JYi92OM28nz9orU5xZS@DDLiewU_W!r!hj$=p{hhnNGE_qSNG~?kIh^hg` zvOv&3f=K=~5=2fcOzrAwXmJw`65N!G{id%W>%SJt*lu|mvw`gD$<7)3)X417ce>v> zk$dv#T1z2@IP4h@;nnr~igv+UzL`jbJ;I}_?$`IU0l3WJ| z#Z0eV34R-7KtPQiF$B`$!vSN30vV+}Y0|h)J9x+OCDr;{9lQ@|<8CCll`Z<#PK3ro z1>eSu?WWyXP){7vIs56Nr&0Yv1+(DnnzMHmJ%aj$?q0#!IcM)IdYaTPbfN|4(K-83 zv?{t9R3i<)-iW?4$3lp;8&4zdWN3(m>R^TIO2j49(CEwO-dI#lK3f|68SQr@=gI4+ zBGl~-*a%gd2<#+@raS+LR_&vSTIwj5)Hc+;M&CI^6X1zIgSV7V?V)4&ug%UAx(ei5 zG*KH;W*C#|wyTUQzp6^R{PlCI$`KT2B*)gj9E+bel5Q7-YWp}$|DcPpWg zVhe?E)ie~NWk)FV9K4@WjX4w&6OmAetfM^JY4RjZLNuWPDMG^ts{cT!O+ctfsKBl6 zh9)L{`i&-=@F)KS$&1Vf+`dmamTz5fw(t$bhOG-mtKFE11ugIfOBUNygEbu9AuzOd8I z*Q@;@SNlW0=(q7Liw%B$^};SMzpmJbt74kBZPW5co>21=k9D7JwegMG{OGLi+`Mnm zuMOl+kkPfhI0Wg#+7fzrv>>55mgwn$eMJbRG2tSPzEDM@xYb)fIjD!IW%XiAah5*A z_N0*+LZepN-MtmRlso};5+Rc+H_96a*rNYvOmTps{98Zbq%G-h&=ijTc2cn)Q-4c& zh>!$P%$LK28h+}2t=LHXwm}n;rKA}H;^nCS(g2kfn&K}I~cb4myGj6`rrC9 zrv5*e;Ln*SZdv`ARr6MV-s;cpDp*@aO$#PBdz$@_^Az2!W0rY$Yu?>DIZ$x#96h$k zar}N3+sfO&-uXgj&b?=jd;EXuzG`3_KVfM4n}u4&;`xN(!C}#0;5R}nK-={~SI+a~ gzjI$$u+y5)WqP81xTWJk=0^|u_q&WgcCtwS52xCBX#fBK diff --git a/src/context/__pycache__/engine.cpython-312.pyc b/src/context/__pycache__/engine.cpython-312.pyc deleted file mode 100644 index a6afedcdb1a6d0300a2f896b714507dfd878b13c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22637 zcmb_^dvIIVncv0xNq{842l%`sMUkMyheYa0JxocotQTdA@+PKMi$c5*1qvjXdqK;D zLD!pjcBn+NqLMVC)=mYbyK8FgPL!r?yfd9{61VBj>~sbY6Lii@AIASe6NGQDJ!#c_`ZDmzlPsE!*M^Si1rzkfNt$60=GGV z8{-6>ppWRL^kaG*g$)tIn1T7mF(Z6q#583dGqXHX#4=?avm$I3ED_t3eax=o^jyd= zS*>-D_e0M2bV_Wj4CSp6*OYtA&Fb1B@76)bFzR8D!uJS^;pc&Dnys-~*Ps;6qk zYNl$(YNvc-K9=W<)J@fo)lW5yH6UIlxFU^HO=C?{&120phU1*zev1>z-_xso@^c1% z#q2}Gjz*_rq1R&k@bpA@I>i6oo8RPPS3>-FXgVeaBYYUSkw|zVG(8^TqnBBpG$+ME zQ$2S3k=bxWka#I3&W^`s#gM?yh|#H;n8aU(&r2am3P-1rAB=^1_>s`7ArTFZM`neP z#EZded@LG`*rT(tnc0}s!@m%m79z+QM;pQM7^-gPgJLXv84+HZotg@Y;doF@pBB)5 zB!ptGhU{YK%A6pwtRDL_nxkJQJA%{GQFM)Vq!Ee>q07P9NK6Qi#}G9PP0u}}^+4Dv zg<`Sr^n`@4a|n}-y(mU6ha(}x-9zdSM%f7FkmNWMnu>~Zqp_$M@|$Frx`r=TjV?MZSE~7H{QTSu$|y}7i;mAup*z{G#3|3Omz|@^(v8x}$+q)B=_(68BbY&c zx!U4UWu>B`><&q>@Dxf1V$rLiX=$7mLHi6!K)1F9^>1?_ZcGQ*)(g5ZgN?Isf|p$7 zpHL+j-ZG6D)mXKbzTOvV1S3kBgj&J$mT}B1_yjY2OJOOYPOu=&T9~GVg?hn?Jew9L zUuY0)NV5x#f*rm?%M+Re2jWhlS#ZKH6L_HvzANO`O0e23g6l2gd+LnG%7s=yym72T zD%tF(bqvAMP3exETKmzFLz>UZD zv!dPnIrVt(>ha)5hJSDP93PyS2?gmC6^=ozG8`Pgg3cMx@mGS>_>35iis9HCkM0W2 zm0ID%)YNP&h#B$XEKMsYhWKFQT5wL%j;TOBU7n3ZSdEc!6_VI8Qy71`wLeDSHkZ)7 zrjzP)TujAl92Zlu4aZHYm{r{#=Dn|bPc50?0=ae*dc<<2V-BrG%&CP6YFV^2fofT` z*c|6K(1Ev41z%H-!!s{3{Wj49Po!#cB`r7*Um#ydwyBs0=gB<9j<`rek&ThC6qBui zEAVjcWhbko#{34^2uyTYtf!(*rCxvvh9sI6HvY5Iy)R24QRaI2iBMPQKI& zoH-!|r-I|a6}^C@=~#Gtl%d#ONgVH0$JwhuPS4C71`-HQhhu?2ym}pw_GsyTbSoW# z^Cq`y=jz&i$?5CtkGAf9Qq!EN*_p1{`H4Z_VP7!baIKoTnl=O+zu0%|Kb>FMcM{>U zY(vw6@gu8m4J~lvW^&c94E|SW%UiLAr#Kz9eS(`e=eInepUiE#R*B=ZEt|Ipx`gFb zPHah-Cr$Z$EtKFUb6cvVaR97@Uc0!omT<}GSxfw;E!pxdXrV+7 z{TQcRE?S8=3d}oVZCbg6IbloK6OQ+7@2LoE-igyumM|aVE`AARU5T=iQ16=DqP3Ct zC%5LqS}5U~?9{?}AE{mWm==n4Ya!(KXyLrC&7yR!U=-|i+~kgYc`YP31gExlLfJbe ztsNbALo#x)ey!Z(PA!!8wG{-G7|5q;p^`J!Pwv+8^M1mSC@Yv>Zm)ksnnfGyRp6*n zd!De1j)Yxs5y%N{<{E|aI&NAgRN%}w3fj+|2;{jE#&vl{tslx0D&KL;y8(lH^W)b- zIJf(?aNehW=gSl2C1J&_wT+85zr*EPD*8E)Z%GSXJqC=@kti>n>i`C;<9?SBQWr70 zU)VP}q=oXn;3+*fz?Bz${nq)J+eEjQD^~1p~-F#3zMMm0`Q4HkzH4(qt_xKVImZ`6qG_E zvONZ70ptfU4sMNm3Mf>G?7kKiuY%7Bs6@qhj#k4=XmN0Sb!+DkP%ifbcM+&@URQ9z z>n3s86@LPFV+|>2p!hA_Z5+G_Q2Rmqt5rb#dNN?G!aAdM+{7O)iWXp^g zdNmxnCema@I>XQYnnHx5{SMg%HV*9ur)Fdmg9zEqyufsDDkM7#x|c*kwz5kbG*)C5 zYpW5lAwl-8n}tdW%idYgqJRiCBg~MECJ!r;+Kxwql`xonHF5T3^r&tIPumbTQ=UGT}ixfv>WP+xa&TQ)Zv%h03A5FLM>Eq`m{!i522Q>ErB{(g@3@PB%(<+N1_uGAyGDl!O_YlA#`bW!e1fslxI;3i*!NAHfRaJJAR=L*(i^{M`L_vcqTmLCYr)(W=iPwJ);voQ(Mt}?zEaT<6wl7$pR8}p9GS%DD z)!SDpyB93rubhoZOWM(qb$V}}y?%D-Sf*i5x?#@;mLFEFG#r2AeBpD7tTa6L$T>s> z8kVl59b1W4mgyLL*fChp`_6R3&Ifxk{m0Y&$5$F&c;q~hAHn?-1qInKZ7U5&9yyQZi>L*% z<=#yBwsiTn}IJJX(>5B9Ej4lG#Fg0C@C zJCLp&c<{xQ+CvLvNb%NYJRNCI$FgO`)3;##Ma|aa{7TKP|6ur7McQiyAn!F!zt4`8 zTKj_Y6D#Lw$yPOH>pIA7>HfrK^4PJ^n&wZ6NqJFZOa z_H^y`Y(wMSlXp&L>*_OgUFo{6C%%TIm+roD=asjcvh@vj_ubi-Jf9q2I-IHBnXcdY zVBiDKO8vo)9p){rPdRg$d)2^I)_vmT>YDEQ?)cuW&sNpl9=kP`scKJGwP)*^?{?kk z$~JUnYdfAawIn;=o}%`@_;%y_p5-sR*O2z@da|Ye!I_mUM;;$LoqS>WrF*9y*wS0} ze=zXD(e#$*Qs>S;-m(83DY@^RH-4ylyzlux?N81>YTNbKLm$^TYwQb-)dtQ}bG!Ri zchZvKcc$^bvVXynwYqP*uDcdre`IZY;&3fo``Uq}Et$G)>AGzX9ow>fdp_`H_MJ@c zJDJ*x|EE%Yrx%XhJahfbQb(q4dm8_p-M_H9ZaS_zvd;0W)01&FrJYSnVp5mF>NzWJ zp1giC<7`Mf8=g4bi`K85$vSHvH+9@Nx@fyGvi#EH)?Nh59yyyHy!46NYO}9Za+NI$ zLrW=FEQS#tieffxK#I5_WMK|I!{@8k&BI9&jY9GlYaPo9(rxCbaAaVi~TUbsl zUy_p6O>o5|%P3^Q@!oSG$gnNHE&^iGHBdJZro7W?l4F&$3+91sPCie@sXYttWGq=uUyzOakdvfwY>xy^RihFm; zx?3b_XWa@BRa3A+SQjJ|f2BydM8Ueu>l1p>lz^a^DBV%WT(Clhcz?kPO+}>`FNR26 zz(km&N?X2hV4(&wIha6)<57+c<+q@x`lX=C62x4C;SyaLxm}5Cv zR9+_YzD1KaV0WzXpNwj1k;oi&Mp2AFt_uc3)1^SE6XjKwM5R77qD>W5)7=P>rNCdi z0?veZ6ooqhJst^;ts2RQs1?_XQjR$*J!zY zl{4Aw%1J1c&gQV~a~KH({l5b)fjHlgFhbO?o&rFqewHYwV*t_#>^td6a#DB3K#*Qg z(Rl#GNMi`p0@=BSU3$Q$*=UaxhRBZM0i`&@B(jB3;9&u%hU9{Bm8Li(%@}GaW&I`v z`TQY@h+ig$p`ouJB+=9v0JDt~-$FFrwjS%0Z1X=;;^0_vRbd-^jT8)9(Ik zb#u0+h5mc%S8ZJRz#6xHwF~hM;Q)qr_Ujb91N@>dg?R-1gmIoQOoouto`rDXEC5zU)Q zse4LE#}g*O63fZKAxp+e(f7W!0M2Y9@wzr%QKazan0~7>r||MKb~|1U9HXjQl_H<% zyLrDw$*e&pZ9C9YlovwUVU5lXp{s6wpt< z6a4Ud(MXR-(#~fB9Pu?c(7Z9Wjbz&5891^TgaUEo#>r@Sx)9x-ICJLZ^Fv2Y4+qY@ ze0q3PLAPe*w1%iUt-frU!D2|@PqD8Gwk0IRPOXA(#Sz4diG|YKe-_Fw9<^8w(OT#ObtqYbX?#A_4 zxpp(GTzJxRcmmcZgq2HDClZWR=JnHOfGcziS74gOUnwe3lhgwnKnC*r=y%>YZ`uSW z0K+qi#stielYIj-G#P;bN@0dP?J}>A<#0mGu8nMxuHT{$RH!-T))Ju+#H{ICPyLL< z``Y*y6&We{BqOztM9D6N#HQp^5*@?K;R!NX>4{ftsPUPxoA2uS%-GenpI0FHMf9XV zvQ>oG7y!sb7Yvrg>y%kQF}!&0?8)Jy=L7JcKXH0^BM1|DAe$nvw~=JS)zF+ch6Je? zg3lsx69`tPqe+4PfvUd==S?o_uFkl(K6G!*R-d?j{ApEZ%IjYk&Nj7Yns%j|c4e9d z(@lea+jQ`jebKP^vIeoSnC)Ow!HiKv@*tgs4hifu;^Op;U`fbVV0^8pJ{$t1HyU zj*^DjQ2K`2gp$cYWWEbQ|M}4QzCq(9i~HB$6!&e(Tk>}pgAr%EgszzGttAIf}QP|PaJ(g*+ z#_jYH_RTGhZBjC~4rt4b+4TR$*_3ZK8|aR$r7Rm@K3Z9abz665?!u^kG7I?lgZ@my})k;im(L#AYQ7L!}&RnIoKJQoMDQW04^*8>ExmTFm09SWod>ob> z*eL5NxKvPCSuY$(pbJ`PLP;DMnD^ZdlDC$X=!og^2y#zwy zaX&&`An-759KS*$gWyDvR0T}*K+GN=y+n6Abf}8*G!~k%E0iD)l3E%U!!u-tNt~u) zG0I1$A-`b#D$*-;h!~iZ7eJf|z6MDa6sxiQ{2M!VT~(D(Os9t z(4fW3ayGFn>f%A!fK!Gj1w3v*;b9EmVd8CY&SJVEKlExa9AO53Fh7&{PL+=hNq+H< zF$D1@Ifu#F3+EZrti6Vi-=nF*n5qW)X9-q_<5vRcZ%UGzH)zOm4F_ng#Raq>yU2KLu{yFHVXeGN9ibN23E~uWYh!XDnhr*w7!WIy`v#5dPsm}yBDyOzpe^tf z5d=}6#Uh9eOHS&43y5OrQ`~%tPO6fm5U+pr^{l4`%9h%OOwEpT&5j2hD>eHUoR8i0 zPoa_V9ZdTUuJ{gTJcm=x!%zL&7OYuo`AzqA_mXh;>Yb~ZroHK=z1ixPC$+7a+HL9D zZL21(dEXjmXtw`~Gx+SEa0Z(LjFYWmL#5i*RXrU%%qpwQw@)_AQ-$=utDw_SV_t=yGfF#3OGHWM;{cw0Gwsloz#{E^IW}zI6JL6O3W)(#f=Q>-E#CKCZsy z;|3b=h9&&i%=vdb^6tNHy=P47k~wi>p`2OB| z1IZo9AQU1;zJG10Vrl44%*%Thoavgwm~UBg*5&@DVe!N_Tq$>Rs(J4dcje-N6*r%9 zwImxUviEEAP#Eofdp_;no^o|B{{cz*4pLS3s>4h?>smQi!(V?M>r_F?9Pq45{Y%o_ zxjS=9S5v;ON6u|pvgdC7o%%=4b}e4{k+Wgdg4);>Cl-QLPWcW!avuI^-PT)XFb^*k zFc0sZxOXBsxGX&A|H0t*20w`X(2(jsmfAU-YJC2q^5bA8>N}pQ1E_rDY#^EZM)r4q z+P9wxB}*_Ct3!Iu`<(9At7bsluUDHToAMF`(A@ZR~cdEtuq4~%lB0tgTPHi#%L~o<8qwG|N`6oUzxy@Ee z=_otZZT`tFGr5DuT9EWNO*(R0n7f6!9ZsaIGom5hf$=+JGV@#TJ1AcKmEwWGe;BoL zlf)<%eVg+i^UzxmWe>|M(}W%x9UZ8dKG3?L=}XDuU?XZMrF{T>XF*O0&Kz`%bpsu< z6TH7|l2Clnm*?E)jd>YftWqmhic?6y8Zid?T=^0?8N>UILMr9PHAZ>h>I-(uo-D2@0fRY)xx>G}}ag>j$q*o8qX zFuN#}E8^RT#=F-`sW)5^xoNXo`48x__;;wsE{msbk9t*Pt9-XF+`5pd>P=Vm0`b2e z&-5Ni_a4D9uI_^_z8ZL6Uk>weV7hWIFm|@0_d#W&SW|EN2%+xS;Q zdl4+H0{tc?TBsz}L;rFVPH7h zVEk-)6^Q0WC(sHJGeuv%4p^`KU~)*6Y>Mw4Cet4X=2R-bfG1GRt9VA@)AJQN!x|IN zBkn^7hv)=;N{Dd^0(chq((H%#Kc%xV{^9qhqOvY1>w^*eP0M;Aguke)4q~QgK9t5~v3gd59*2j=Hi}bmmYybj*BZg$dIGQy;lnAO2jT$gW z*>EL#4fa$)2=*dTI5U*?rCAh~s2r$p*)TgDl#L|Or<09Q7)j>NT-aJ6jC6>%k#Y@B zir|K0S7vERd$temZ#n$qzxwgLPXC3Aug|x3{Ih2yFZ0{QQ}D!>;K;TdHv|bIjHkrB zp&DN8&5%h{jwJ#i3w3S`9^N3$h(uCjW`Y0>2u}?%!r7$I_(pmxgO@JUh7Dt!A*Kvt zXC7&=88T9}o#H!4*g&zC;vGanLjMM(Vc<~L_;&k(iAgZq9=hAIp4x@ePf4EHmUg#g z+}&w+x1vv5aqn6%D&<&CU)tTb;@-JneC%*%o#l&1$q0sQd@^-?>AJp^x}6~D($1X= z$9`!xSJ@YAt1b>m?&PhL--1C5Fxc(=xAv!+239<~7LE~VHSo2$rJ=9Ak=(Kze%QK) z2pt=YShoBNH)x`&78p2{S1mX<8eI_FVMBClGYsQENQQSwIAI|3=SdJxN;MQI{z_f= z+WDL}3HnmvPP9~l4A9v3&ux(|cv2n|eS(3^z(2R9g%J<1;Gjn?IPV;cLqJwAd}Bn4 zSxqtLQt;RZIH3-uEK1r9p3eTHqo{L4Nl{!VV!QDUD_H_}(Sr=Z_P$*cxk5xk*u)V( zI7-YTAqt7VG^gPd1!?5P2BiGo4zw~M!6+5P01hxzQL!F{6(_th5>5f4pONfZyhaXD zC+o;Ouvi?9q;8GqAySEJCNh5g2rGS`o|WoelK=>uY5`z95D89Q5`u@~+lub#JkMKT z&8!ywb2=cjU2k&9mgLZPwlJ_e5B2spkw^xc(2+|a99hO2{~U>opnwG@X2@s-MdN0a zqGAk+xbIPkXXN}DIgFzC3xs3`Bai|L(tsKHgrtVc)D=>rzyx6WZK$FidF{|AkrelB zI=34%lOLlD^lWx+VE2Og=ZswNRzX2g*Lc@|$A7o?PVY+HwuMt!U&GznJGIHScRTNO zu8_&XjT|@KTQ+G}+o0h$)hMr8rK)PXIrD6BNnV;7-E*hVDYL*7mo~>E0 zZ|U5vQ;Wltus`i-r-YYoom3JIKJ;{|3ActdhzD^bOC2-|NsWOnHqkl=cH|Kq zR`juX;FU%x;)?PnavFOaUEO4k_RlvXp2Uc%@^LLBzlOk zn@5?GE91?PPuq#lhdqq@i>k!j3@wovrAxpx(-i523cQo5#Fb(x1uv6zT90`jdZyCr z;YnC=Lc9rc?zB`TY)Gj_+ofT+;6>(*G4QN4CEp6>R=Du3V2syBG})?!@;=^?)yCSj zc*3qh()+$V&+4xmiMwEAqC6zv&qJ6UcXxH2)#TZve~vr5x<>JuXI5e{JHg;R|uYMNNzTJPIg$u}Azngdu!oqrhWq^(_UFmv{&ugkDpqQ6pX>OpCvudJE+I zJ=Ibl#IUysZZe3G0xD@45|q2nfg4L3uxtxW!-j^844Iz2h~1X$=Z8j51xC*gomVyW z4o3Xa{V82#TsPh=DZ`VE)SIjumvv$Bf3XSRHz}?Xjjc=s2CJ7s5rsh~oSVBKwJY)* z5{r#~RW_ZvQ{HXer;GBR;J|v-*^qTLLJ~WDXFAo`opSbMU3E(XcL(nbE+N*Lb=59; zGrqpGukQhF$bnStt_LsuTj_&=zuNbg`@a81YWMRg=W)$~#Rany#SV6*vK=-uQ{F+Ct!dWgspkHacOYBMCu`nqy4RHA`%>N=BxS7p z!~nCFRWIi(&p4VNI$$xQ2pUPwmy;XzK63VDA%A9u_{*cq=T~ZXZe}mj4+$nUR{zjZ zU$Qa(BWD+66tKL1RMozy)5MS>$Rp3v7e6ZBN`i{5kVrerZ|?i*zLIT_<<67(Ey?rm zzH;xCzpd}RWm(iM4rE)~lCez3p>)TghpmV1RQ>SSq8YC(?!JEK^-ObLy18%BsrHtw zu3f5LvL?;RFD>u5H=CSTKKH=6{Kf~PKlEmXUP=$WlzQ%5$~*e0L0?e`o}r-+97jo; zpWSKS?#+ar8>tM!>x|0yQv`lDOU&OOKg>Pf`<*Ou^d&8F3F#F6p?ZJ~~6&uJ&sgxgTs4L2tV?VyYS}QcV89I0V#PdV2x;Zm^=Ipsw6akt+ z;bSjTr`>SYnV5^CNZG`|CfBE;yL&<>aszjYaHGajk|g2-HO>msqtazef;m@)_{maZvk-A2fGflZb0S+1QipNPmJ~e~ zn1xw>Yg14Q%o_5NsT?r_?$D^L7IB6nWaRY)F11KbplSA;qu2NZdA+Ep3 zKVvTym=xEhNNh>UA^RZkD11W8piwaf>!lX1lan(B6B86=!d$BYi?=A@3OV0^gLiXy zT`AHiWV*D75e?v_o&v1|4cZl=hQ{|AYJa#l{=x}GM zsV~*IBjwxq$k`8GC{=gpofk38Vr?pwaJ zQnv#fnu_OlES*Q0*5rlc8MO70v!6sH&*^}EOX9nn<@u(^p^z^k5eLI}Bqn0NHKLCp zN%=}qiv_vr_G_idFUX~qi@t(fTKJ-`AeZC3 zRI%mI&l|77RzE&)G&*&Oei%%9ON$=_Vvk28m}0c#w`vs4XGi zJd$-+EM9o%<~1tj!gA%EK&EjZt(ak&PzX0pd1J=il6JQw+gIG3DQoAie_2LtFq6?A zwRw*W0I)D)?IX}<$L7Bwu$gHvOrJ`c41cZ!5ciGP0lkF26gvlOPr?Rm6a{`MStZ>H z^M+~PZ(jqYAv%F16$e8wy#teG;C6F-`3U_UfMU<1KGTP(ycnG8;lB`qFpwWPdtUoJ zD|$a3(;o5_SQ?$b65{DuJS>sevv@wN{>YcgQ86V%PO{E-LLCvrk-$$iO^1}<#v(i& zzRW|N$jYKVSOwT56+UV548Il{pQT4Hc>jmP5}G0Y%TA0G>{0S|Oi1+&T?>ZkC+U>t z0J`CcY5JwO9@$6(h5V0QU36mb8tEl?3Y$b65@W8^H%d&1uP`f4_V8c5Wh4R;8nkk; zd;zg|W6^<89z$#I%d6<1kPr$tiee1U=zUL}@`g9#8Ay8|Lf^e`?6KSPwA#PCCDXM( z-L*gEAH;*C16|p!9ht5}>8?YWt{2i>FQoh@Rt>tUQ@W20Tty=!f_z7&xj)_9pJ_gr zZa#R+`LupF^;u@tfJ}o_LKqVqhuQTETn_FTzHmcv99v z^Qk<_?~)oI7vH?;H_{Tw4L{C?ED#XeXe2Y_ zh~&(Y^Uuk7iyY!q#IxkQNDkQsFpZodrU;Kug<@Btf|#Uql1MO4NIXv=x;RBb2I9x$ zY$2zW90*GlhetsA6U^i-?qj{#WCicvvopJ`ch%xBH9z%qt(xHD&Z_aV6cB=T*P4ye zy14#5*@|%TboSwY)#v$f&A@zng7&Qvt~kk?KvG4X?j_wmZ88?Thp3} z0&COyO1Pc1rmEEe&Rn^wuQOF=U9GDI^0)Of|EbOYOB3R22Cc-}-W_Q3vd)1veLbrN z_^dzpt95Absk?2}1RpcNf>8vyjD%(ao=5GiExZ_venAFY|2p==Y2jI-x53O{ax8RTj9qaR`i9feC& z!pVN;QtDx^gcDqqIxqiwy ze#W)^jH~{8uJNg@ZlNY)t4rJJmiDgLw!CTn8Q1YMuIFdm)?ew(Iz4_GLZ|-~D(?F4 T+}>ZAk6U$vzvReeTkwAY?t^VI diff --git a/src/main.py b/src/main.py index 6d4a06a..8f3d583 100644 --- a/src/main.py +++ b/src/main.py @@ -1,7 +1,10 @@ """Agentic Microservice — FastAPI application entry point. -Wires together all components: Redis storage, model adapters, MCP client, +Wires together all components: Redis storage, model adapters, MCP registry, context engine, orchestrator, and SSE streaming. + +MCP servers are per-session: the global mcp.json defines WHAT servers +to run, and each session provides project-specific env vars. """ from __future__ import annotations @@ -20,7 +23,7 @@ from .adapters.openai_adapter import OpenAIAdapter from .api.routes import router, set_dependencies from .config import settings from .context.engine import ContextEngine -from .mcp.client import MCPClient +from .mcp.registry import MCPRegistry from .memory.store import MemoryStore from .orchestrator.engine import OrchestratorEngine from .storage.redis import RedisStorage @@ -34,8 +37,8 @@ logger = logging.getLogger(__name__) # Global instances (initialized in lifespan) redis_storage = RedisStorage() -mcp_client = MCPClient() sse_emitter = SSEEmitter(redis_storage=redis_storage) +mcp_registry = MCPRegistry() @asynccontextmanager @@ -45,11 +48,9 @@ async def lifespan(app: FastAPI): # 1. Connect Redis await redis_storage.connect() - - # Wire SSE emitter to Redis for event persistence (re-set after connect) sse_emitter.set_storage(redis_storage) - # 2. Initialize model adapter (based on configured provider) + # 2. Initialize model adapter if settings.default_model_provider == "openai": model_adapter = OpenAIAdapter() logger.info("Using OpenAI adapter (model: %s)", settings.default_model_id) @@ -57,36 +58,37 @@ async def lifespan(app: FastAPI): model_adapter = ClaudeAdapter() logger.info("Using Claude adapter (model: %s)", settings.default_model_id) - # 3. Initialize memory store (uses same Redis connection) + # 3. Initialize memory store memory_store = MemoryStore(redis_storage.client) - # 4. Initialize context engine (with memory store for knowledge base) + # 4. Initialize context engine context_engine = ContextEngine(memory_store=memory_store) - # 5. Start MCP client (if configured) - if settings.mcp_server_command: - try: - await mcp_client.start() - logger.info("MCP client started with %d tools", len(mcp_client.tools)) - except Exception as e: - logger.warning("MCP client failed to start: %s — continuing without MCP", e) + # 5. Load MCP config template (servers are started per-session) + if settings.mcp_config_path: + config_path = pathlib.Path(settings.mcp_config_path) + if not config_path.is_absolute(): + config_path = pathlib.Path(__file__).resolve().parent.parent / settings.mcp_config_path + mcp_registry._config_path = config_path + elif settings.mcp_server_command: + # Legacy: create a synthetic config from env vars + from .mcp.config import MCPConfigFile, MCPServerConfig + mcp_registry._config = MCPConfigFile(mcpServers={ + "default": MCPServerConfig( + command=settings.mcp_server_command, + args=list(settings.mcp_server_args), + ) + }) + mcp_registry.load_config() - # 6. Initialize orchestrator - orchestrator = OrchestratorEngine( - model_adapter=model_adapter, - context_engine=context_engine, - mcp_client=mcp_client, - memory_store=memory_store, - sse_emitter=sse_emitter, - ) - - # 7. Wire dependencies into API routes + # 6. Wire dependencies (orchestrator is created per-message with session's MCP) set_dependencies( storage=redis_storage, - orchestrator=orchestrator, - sse_emitter=sse_emitter, + model_adapter=model_adapter, context_engine=context_engine, memory_store=memory_store, + sse_emitter=sse_emitter, + mcp_registry=mcp_registry, ) logger.info("All systems initialized. Serving on %s:%d", settings.host, settings.port) @@ -95,7 +97,7 @@ async def lifespan(app: FastAPI): # Shutdown logger.info("Shutting down...") - await mcp_client.stop() + await mcp_registry.stop_all() await redis_storage.disconnect() logger.info("Shutdown complete.") @@ -106,7 +108,6 @@ app = FastAPI( lifespan=lifespan, ) -# CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], @@ -115,23 +116,19 @@ app.add_middleware( allow_headers=["*"], ) -# Mount API routes app.include_router(router, prefix="/api/v1") -# Health check @app.get("/health") async def health() -> dict[str, str]: return {"status": "ok", "service": settings.service_name} -# Root redirect @app.get("/") async def root(): return RedirectResponse(url="/dashboard/") -# Dashboard static files (mounted AFTER API routes) _dashboard_dir = pathlib.Path(__file__).resolve().parent.parent / "dashboard" if _dashboard_dir.is_dir(): app.mount("/dashboard", StaticFiles(directory=str(_dashboard_dir), html=True), name="dashboard") diff --git a/src/mcp/__init__.py b/src/mcp/__init__.py index aea2731..d6059b4 100644 --- a/src/mcp/__init__.py +++ b/src/mcp/__init__.py @@ -1,3 +1,5 @@ -from .client import MCPClient +from .client import MCPClient, MCPClientError +from .manager import MCPManager +from .registry import MCPRegistry -__all__ = ["MCPClient"] +__all__ = ["MCPClient", "MCPClientError", "MCPManager", "MCPRegistry"] diff --git a/src/mcp/__pycache__/__init__.cpython-312.pyc b/src/mcp/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 36a1c73b0d6b9d2e5f12deef73d4f7177854cffb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 241 zcmX@j%ge<81eL94GtGhYV-N=hn4pZ$VnD`ph7^Vr#vF!R#wbQch7_iB#weyrW=)ot zj6g|E##@}e&H>IjnW=dtMa)1EKTYOaY{>}zE%x~M#GIV?_>~NwLB{^_(hn_8Eh^T} z$}dVu%uC5ktkQSMPcF>`8c?iXP?Voul$e{CoSC1epO_95%S;ZgEG|jS)h{ke*3V5Y z(2tML%*!l^kJl@xyv1RYo1apelWJGQ2{anypkjU?@qw9;rDG4&6rfB6gq{ E08ZOLI{*Lx diff --git a/src/mcp/__pycache__/client.cpython-312.pyc b/src/mcp/__pycache__/client.cpython-312.pyc deleted file mode 100644 index 3c76a86bb9489102fc8081b9d7ee0c80e2b92923..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13675 zcmcgzeQ+B^mY>m#zCSI=@`o&E{1rz|EZcE{jS~kF+X*3wH;}L#zRl_s&BVyal9`c{ zSU9y@pspM)Y~o{goz%j`>_-6wdl#y<3aaj|iU47`y4or!Hb~5Rt8%-0RmXn-K-y?G_5Im*;*R>usl~-{XyX>6HS#`Y%ve zpm=JG;%QzNrzdq|IvUGNoEc+C&W^Dp*N^E*ZWuF=+&E^0oQ<0%&0}Whqvs8A%cOP8 zO3KE#ZPGqwrzssJ=qDP~flImIe4SSA##}mTAH|!VrFgSY^*U3ex(U?+RO{=yBF!_y z1Z~s*26u;s4s)9hC3zvv4J8vP;dF{SEGARQXfht;qVbrJNO8Y=`Ujkp;$ulJB}Nj` zR8mZZES5u&L}Xl$IBEL$l$eYPlElShCxqxsG%jr69z61`(awht4{@UK~U3sd?x|Snaqh%)JYEt_Zj#6?&VOh$YEa&bq&Q>n zqgTHR(y&0y(x++3MN=uQ$wYDaXap$CQellctNVN1tS(WTGO8`g`kGW)DOVRR@F*%0 z;K$rCDvIQ~3wmf{%(8d%aII?&x6lLShg>l#(909%C_v9 zW6#_3fu>BLbN=WrxBa^**MB(Me|V|)A*itAYd74ey$_q-muFxP}xR9PtXS_X&(VxU#eHaSR)|}s=bm{4ZJ8OPugfQsafxYVTwShVI zc{|ZY(V{wq2P8{MSyo%%W`Rm+b+c5Z1*I;EBh+j3C}>}nk3~}=2Yp|o#a759WWXtt zq$l4b?JUg@A@pD~6t}0TKR7)XhZhdaA6Rm7854Ksx{Iz1Q8oI7*F zIk4z`CGc|KDxYZ@$e0H1e0;pg`re<_y zZ8b%swxlSYsXR?Ut)%8)dhr5PYE?=UHKC~#m4Yg&($tTG>Zru^C-g52DT2(9!-S@W zO8h1?^;4q#fAoOob(cm|OFa7``+fQuiq|~_^YJ6y0`m`gJt|P`9e6$5X){zdSG6jS zQVwJIOLuBXHPh;sELYFw^4V`xdz5nE*i^bxOT2+M#sE|DX5R9mj<>$Zs2CF3H><5m z`5QL1UVBqUdDAoWH^`KImR9?crPfyBs+M?1j8faI=f6g}{_^*8mN@>^Z={1B;1R69 zW_9ZP!!woET%~p7N(I&4z^g+omvWVp`qsEM`>2cb`Nlr#{Oj;L1HZj3RGD0(B3gH~ zQsTgxXX#qk0B-pzbu2Rbs6y9Ws&tW=2W|5s@@4xdJ+ACtwQ-5hS^5-pnt6;mMgM|1 zMF-vK-Xmym38Y7irr7*){{j60J|;zz0QZSoAcdxp@o18Ox?+c-S>?dmpNNf5ivanE zhw?;hY6|QxxGFHpXu6(^hJ?~!16-Q~|AVZoM~5aM8z2K;gye!KiugH>$`;}xMB=fu zkhW~?-ny-`r>C=fd)ixs=$(_XsF=jZiA9C9z9-Zj>PAzA$X_MRln^fYd|~0V5S1+@g3KP?e|SX16{y%jw1~2`*pWab3jt2ycrrO9q7IQw zry_uKU^LkdC@#T6J#iXE)N!&~p_Na9?1U!hFe3UQu#AZd34#)2+fl{8B+i^Ucn8yuYfyR=;dIxYF2`YuuS_ z+?ltzE*v^{sK6LESZ>;@E{vTU`%DkzTL#KgpL4fn-K{xyFzXJkz^$h(>uJk*It!cfmYw{@e~-|413@EQrhy83HCdZ(nI>$u)Fk8@jHtZ#my|E_Lr-YS=?STftCg z-c7G??K!SH%XI^roU1wOYF^x#Z|}{wZp?4q{o#1F`P-iv*{$wXimmb#EWk8Z+nKHH zyk$2wIbbxuWA4B$7iITcIB@Pjrnd9dra6^I6c4Yy_$OK zsW+cm>K*#XHeA?E!+U>OsE6nIQ^7~s572)q7=Xo>w;YtKCg*I)I$J)c7<^w~aq!&1 zOkLlytv~PfEd=L-FKo`)0$E#N#m!}!?pt!-pRwIvFkl$w+euxw(EILTuUosYJV5X3XRq(*hw=x#^u7W1gKfK@{Hv`xX#cBwXe{@W z6k0-g=Z^hm=EFYvfdRvZ{Z&x@Pcs88*T82`v6>2I0KC!hjmr89XG|#d$TIspJh7Y;`>#kCt zl9yfyu6+&1>UHekR^`YuZ?LMFzH`2K01IVaK2<4{H-Ub`x=ZCgOdSJ7dWN2*E5S6i zl}MlWLW>IUUTr~B=i6BP$`wM16R4`!U8R1|r*&N=+}^!U5veyETgL*ynd@Wdb~HW5LMtdGV4 zL&#<@Vw151m{oBvl36r{;sGqW4=aJygURF*(^HB;W1on{<03+J*>ExvhuA8($jO9& z?1YF&Y#{id_^D*(1cX^pITVeVcnHerQam$+ul5Acq#@0cp&Jho1gkS=ZMb1=00X5c z#+*dv)GdpYye6eeF3{d&bocN*WB=;B$ioox$Fg_tY#5&JX518*g|v z=DoEGhvpCEyzN!|MyU<|S=k3rt>uO(i1y_85oNr^+w{bD`%9)qXwY@)o{6u`}Zt#5Qf(3!u6D*wu<1+P;!b!(^Tf?d_r8 zbdEGo*C=|Vj=e^+Sk~Kse9gVbFw#%GS4)rF!@gI?VmYu4D&BYRHbQp2j)u0^8$2Vu z%=In?DO+hQ_mc8GG<3M$-@7-&e6U#uwI6iSSPo%r6mg4sp&$MX#s&TczU*D&0@jq) zuM`6~3yKC7JQ|;M*GUM4l=aZf(m$qN(mbBCH29gCr&DRE16S)hmT(IZ1$tHoKC(_# zq0p{EjNlEuS`RALKs;&Hzhq{aBNQkN!y2VgAugaOin4bw;yduB$|FLJ`6a#T8?Ezc zEa}Zd5r{4qH!+flb$Ws$0qA1jFO;rB4N?qE$0QCAEmsU0rX7zYo=7B50Rw=u1Dp!% zqre?jb018RY=$tRvTs0C&S=_j6xwp_X?J@G&U29yph!5GmVzb`oj7INfWy-Ln4pdk z_hC|p3F?_3tEdYzj!iZ;#P0%GHWcwNN&(>EA|N*7GZEBHyp&p~79KOiB% z7!-rcd*OS}eUIR(olEY)jBW6y)q839%7M!VvetH>FYdY;yz0+14KBHNWo)|&tj+9L z@i*uEo3sASWf(Ev(3WrN_)O0r6lDO4ngEJkv0t{|vKkryE*gA}xxEDk<*lDPRB*!G zmw-ykk)Hzw%e!kY)m^e?TDD*1-7d{0+ucW93(>o`v)8)xSl&hh`Pz0G%YEH@+L?FT8KA5|ZMXzrS*aU?<7l}nhPNiP z#Rol#*XzX`Y(B&U&%hF^WYK4J6Pis`dqNORS8>tlDxo!=hHwt32fP3m^Z_n_NM=x% zzW-q%H7zQ#$nn~anFO9Gh&uRblaUno?XgtkcwE4H4)(xc2Qk7&ra=0n5I9*xWUag) zMa9?@?zhRdSYm2A6_%op3zLzcfmkZ>F&JAmM5ZvPPWp&EP(%R|%~LCT0+}VA8cv!f zklt}2MdUGD)-c@&Ypp1omFj;29HfUK0q1Q?XwHge3Zle0@5ZcmabvoZrxr+t8KW(3R&}b6iiB>jAHbb9_Ouc4vX1s)Gd^*}d945&<1X|iY7yDMFl-x>X^zLNUY>J77o%4%?y zL6`$|6>#`0t4a|lWx71PNjwUJMlGu9$|FovKhF>gIK5^iv%5HnSB1KCEnJW628Q2yN8i|?~lkf!M6eO~x7$*tCFh;d0i*Xg%pm38=5y48qi^MaC>QC2{7RQ=^ z%bUd2Tn z3M^yp&ev^N*m+Zli72r1(!{H+AKAJJR?55?{CUdDeOamgD+$H(?C$O%hQ79y-qXll z>t(Uruiw*5T^pqLaO|~R43yuc8-e~VM`O9Uafo8xqZpv9K~HG$$bub(g64E1Leag* zoWb3XqS}ON?V8n;DF{5Xb{WhtAhvqqT(IeF>xHg7w8d>oB;hoKa#(^BPEQF@I1CXC zi5P_dl{g7mNd(}$gLo1v6cb*m5NCiYt+CwV13*e+FtDf>?~JQ`h@H^%DaPAE)dq?m z*~sEYhUkyev-Z>7Fm>FPa8aD~x6v=rE$lxV+URI`L_(X)Z-Szt4<;5kt-mU1NXeSg zyxelwdUrhtTMWey$LrEC4A|Dcc*IJ26RfSa8(w;9Rhz0~5wEmVt(7XhS0(+b^7{s{ z>UtDaM!7O8sszB4M2ji$b2vvsV&9?k285%<{cVLtAZg@eBo;@@F4LA#K#^oO6FpV} zC1n zsFg%W$|I|b!>=5A`B1L4??!7Mfs%Wc+(Q}L5Nz*WNS{kzYF~0TFCNW0gP?f8!_z#z zoT&-)&0^~-n_u4i)6Qj6ci!Q?FnMlLk>Q-9J?j7!6wH}|H%!6X67Jib-NONzd$*b1 zvx$AT#ZU>^5!LV_$oSnHtG`N`=^1dGKo)VWth>a^e}#-{u2+dkne-^OnN>IBW@*?I z);4S_$v3Y#b~KfE1aRnGr2TG^3+qfL(dVB8=TNiq6{Bw2ctpvfhqCS}DOs|rD$N&t z{xT0JrgSc6h6d$318$*9Wj3Lme^DtE1#V#jY-E+@1GhfbU4XfCcv!Dq+lD03qu;8i z0>;1%d~#Kau)!D%>G4oX+oA+o?V53$R1T$_*BnsNd#W_v+Ba3{#LyViJ$R%qmY7aX zOAyDx(<#ya?VcK2Mdjob*XL5k~0GVvUCh=H85OD`Z+y#kj;Nj#BY7aO77@HEmhctaW z8G*ArP8@HTR4d^xJue)e9*6y(#{r?isk@?zQ+(Sa*wZ|6Iw~k0hgndIs*4N9i6B&F z!W54Gh?T752rwx>paHu=5uPBm6{wNiU_dHqrv4kP{jlXzU~A3IxTnzhPn~cK=gP6m z$8v$5Y@jC>7{~?&mI6C-H9Ie|pVatQy!8vC^P`!T!Da8RytnSAf5Vj>mv{VlFy{|t z{h=j)_eIkuH5>BYh62l01y&lHuS{H?c(rz^aqGo>V2G=#bFS8`t99|wWmjjR3AS4n zI$!84gs4FC-0<^<3Y}E#;Fq_26l|*0Y|hqf{+!YQ7Z~*5{6RQP^7aGo9DOJH_UN+r zz$Y+-9xDHQ3p^OuDM1VeK`wLECpOoLvlffnV9z9HYslIfGJ&lh+j=3~W#0B@z+=i_ zUrDIb&$f=(=vR7&P1Ln|ddSOO^AAG#-MZ=_AN6i10Oj{=CTQ}Wm&USh%dnn#U#Ele z`z(!RJ%eSFXLyi#->-uY@qQqHZ91(@J4fy3c5L^ zoyp_bSvV|=Tq{1Yw*Da;Ww>igql!8pET{QpDh-bhz>`?v!rgMxdVHVX~MJ*=T#{sI`J7nE^+PxLe{Fw3Y2}Rv5Oki^@X* zI~JrVVK7Buorjhx*e3YKFe#0L|0O;J73=W!^FSgoKNWAU)meYW9>0bJLJ(E83;uck zrK3w#&2xtTwpLv{vB^$<7LLsyTf8R=p4Z;Iv--kQ=bk!$Chzjybowr} zXPr$@R8+x=w>e)A3gOj9vu)dRZTDu|?tOdXQrqrK>z;R-^Zw?=K8UF0{9Rdp*H!b9 ze;`x80~T`K;3~sZ`@UfGRc;8MRn_ENO<5QCW(b;Ief(|RQuB_FTs!mj>Pt0QJNE}= z_cZU?_^R<=t*=?HKCrZD@Cyb*XL`!fq(nc;R};!OS(AAveEpgn*K8`TN{D@dHYY0# zR`U9bhjpdO&KY)=1#D4U3h2(VAmf#Kv87&L(?Va8ci!-Z@%19UsEo{JS#&zjKf+Qo zrl2{!?Qlet1m)ZcIgwFv<1m__9&9fL6qMucArX(_D8Vv{gud`#0vOgKBuXYTaDo-Y z4TEW=z?f`RkUR<1!f|x*2Uv~jMdTm}>J|9?Ehx)oF>zf>N!?>Y;iM?S9 zd}gNHej-6Z{U}~Ro7jfWX8~i)&2oT11aTu0TzKT=Z-IJDCAS0lw(SJqhLlR$1SIha zCPC~|jYmnb&ld1@w2m0&#%QO^B6kS3FJjxeQd^9&m;P+vSh0o79tV2yMtm9iKuGiN zAd?wjP6Ft+9VI64HwniGXYps)v^a{Fyv5p9$AAwB@%$7Tk~k0PQr7t{_2fBNMFHYK zO0Oan1)_`>p}IvhnIc*Q5tX@Y42St-G#nN?@XiG8;glezW)wN?!DfdsIfTh^Oi(tI z4TT4=gg`(<3?-ua6H&TEJ0=cHoS2{sKoZ2t(wC4tOMSxL5ApmBE%~N4h_B{1ZOOL< z3wF1@@n)63pobi=hI2!~h$R!{ZY-FwWTEOe7OYsZQH~bU9yFN6vFb$HCd%OWTu1Ah zRv9Q2umoHb+76AjW7V-mznfnD4qc~jDD3j-9r?P30)u(5kK{MaHLH5KU+p&Q{e{MA zeRtkhS70!2=^;630r!QFS-+W7Vvd!N-!$7-^^mXnjQWmMcR=5~ilf}>x9J_qtl)$M zz2eUy!#^ldLU-^9Kx-CvZ3red;t?${iDCoNpTyRN)C`_^6H!me2IWMYa;)A)Lh@3G z#LJ0tkj-H44w6$|%10*%{QJnuj0yQHYd@82+Nu0?%P#R3(1eDc^bjOqHE8-X9ZR!c z*eJT?_mtx|l=U~5nxXJp%J)B1;I~xprn!EuCTFhCn(Ht1FPU4PHrz7MwE5y=w None: + self.name = name self._command = command or settings.mcp_server_command self._args = args if args is not None else list(settings.mcp_server_args) self._timeout = timeout or settings.mcp_timeout_seconds @@ -64,7 +66,7 @@ class MCPClient: logger.warning("No MCP server command configured — skipping start") return - logger.info("Starting MCP server: %s %s", self._command, self._args) + logger.info("Starting MCP server [%s]: %s %s", self.name, self._command, self._args) self._process = await asyncio.create_subprocess_exec( self._command, *self._args, @@ -287,5 +289,5 @@ class MCPClient: name=name, description=t.get("description", ""), input_schema=t.get("inputSchema", {}), - server_name="mcp", + server_name=self.name, ) diff --git a/src/mcp/config.py b/src/mcp/config.py new file mode 100644 index 0000000..2df047f --- /dev/null +++ b/src/mcp/config.py @@ -0,0 +1,55 @@ +"""Multi-MCP configuration — parses mcp.json files (Claude Code format).""" + +from __future__ import annotations + +import json +import logging +from pathlib import Path +from typing import Any + +from pydantic import BaseModel, Field + +logger = logging.getLogger(__name__) + + +class MCPServerConfig(BaseModel): + """Configuration for a single MCP server.""" + + command: str + args: list[str] = Field(default_factory=list) + env: dict[str, str] = Field(default_factory=dict) + timeout: float = 30.0 + startup_timeout: float = 10.0 + + +class MCPConfigFile(BaseModel): + """Root config — mirrors Claude Code's .mcp.json format.""" + + mcpServers: dict[str, MCPServerConfig] = Field(default_factory=dict) + + +def load_mcp_config(path: str | Path) -> MCPConfigFile: + """Read and validate a mcp.json config file. + + Returns an empty config if the file doesn't exist. + Raises on parse/validation errors. + """ + config_path = Path(path) + if not config_path.is_file(): + logger.warning("MCP config not found at %s — no servers configured", config_path) + return MCPConfigFile() + + try: + raw = json.loads(config_path.read_text(encoding="utf-8")) + config = MCPConfigFile.model_validate(raw) + logger.info( + "Loaded MCP config from %s — %d server(s): %s", + config_path, + len(config.mcpServers), + ", ".join(config.mcpServers.keys()), + ) + return config + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON in {config_path}: {e}") from e + except Exception as e: + raise ValueError(f"Invalid MCP config at {config_path}: {e}") from e diff --git a/src/mcp/manager.py b/src/mcp/manager.py new file mode 100644 index 0000000..5b66632 --- /dev/null +++ b/src/mcp/manager.py @@ -0,0 +1,289 @@ +"""MCPManager — aggregates multiple MCP servers with namespaced tools. + +Sits between agents and MCPClient instances. Routes tool calls to the +correct server, handles hot-reload, and isolates failures. +""" + +from __future__ import annotations + +import asyncio +import logging +from pathlib import Path +from typing import Any + +from ..models.tools import ToolDefinition +from .client import MCPClient, MCPClientError +from .config import MCPServerConfig, load_mcp_config + +logger = logging.getLogger(__name__) + + +class MCPManager: + """Manages multiple MCP servers with unified tool access. + + Exposes the same interface as MCPClient (tools, is_running, + call_tool, get_tool_definitions) so the orchestrator doesn't + need to know if it's talking to one server or many. + """ + + def __init__(self, config_path: str | Path | None = None) -> None: + self._config_path = Path(config_path) if config_path else None + self._clients: dict[str, MCPClient] = {} + self._tool_index: dict[str, str] = {} # namespaced_name → server_name + self._single_server_mode = False + + # ------------------------------------------------------------------ + # Public interface (mirrors MCPClient) + # ------------------------------------------------------------------ + + @property + def tools(self) -> dict[str, ToolDefinition]: + """Combined tool registry across all servers.""" + result: dict[str, ToolDefinition] = {} + for server_name, client in self._clients.items(): + for tool_name, tool_def in client.tools.items(): + ns_name = self._namespace(server_name, tool_name) + result[ns_name] = ToolDefinition( + name=ns_name, + description=tool_def.description, + input_schema=tool_def.input_schema, + server_name=server_name, + ) + return result + + @property + def is_running(self) -> bool: + return any(c.is_running for c in self._clients.values()) + + def get_tool_definitions(self) -> list[dict[str, Any]]: + """Aggregated tool definitions for model adapters.""" + definitions: list[dict[str, Any]] = [] + for server_name, client in self._clients.items(): + if not client.is_running: + continue + for tool in client.get_tool_definitions(): + ns_name = self._namespace(server_name, tool["name"]) + definitions.append({ + "name": ns_name, + "description": f"[{server_name}] {tool.get('description', '')}", + "input_schema": tool.get("input_schema", {}), + }) + return definitions + + async def call_tool( + self, namespaced_name: str, arguments: dict[str, Any] + ) -> dict[str, Any]: + """Route a tool call to the correct MCP server.""" + server_name, raw_name = self._resolve_tool(namespaced_name) + + client = self._clients.get(server_name) + if not client: + raise MCPClientError(f"MCP server '{server_name}' not found") + if not client.is_running: + raise MCPClientError(f"MCP server '{server_name}' is not running") + + return await client.call_tool(raw_name, arguments) + + # ------------------------------------------------------------------ + # Lifecycle + # ------------------------------------------------------------------ + + async def start(self) -> dict[str, Any]: + """Start all configured MCP servers. + + If a config_path is set, reads from it. Otherwise uses any + manually added clients. + """ + if self._config_path: + config = load_mcp_config(self._config_path) + for name, server_cfg in config.mcpServers.items(): + if name not in self._clients: + self._clients[name] = self._create_client(name, server_cfg) + + # Detect single-server mode for backward compat (no namespacing) + self._single_server_mode = len(self._clients) == 1 + + results: dict[str, Any] = {} + start_tasks = [] + + for name, client in self._clients.items(): + start_tasks.append(self._start_client(name, client)) + + completed = await asyncio.gather(*start_tasks, return_exceptions=True) + for (name, _), result in zip(self._clients.items(), completed): + if isinstance(result, Exception): + results[name] = {"status": "failed", "error": str(result)} + else: + results[name] = result + + self._rebuild_tool_index() + + total_tools = len(self._tool_index) + running = sum(1 for c in self._clients.values() if c.is_running) + logger.info( + "MCPManager started: %d/%d servers running, %d tools available", + running, len(self._clients), total_tools, + ) + + return results + + async def stop(self) -> None: + """Stop all MCP servers.""" + stop_tasks = [client.stop() for client in self._clients.values()] + await asyncio.gather(*stop_tasks, return_exceptions=True) + self._clients.clear() + self._tool_index.clear() + logger.info("MCPManager stopped all servers") + + async def reload_config(self) -> dict[str, Any]: + """Hot-reload: re-read config, start new servers, stop removed ones.""" + if not self._config_path: + return {"error": "No config path set"} + + config = load_mcp_config(self._config_path) + new_names = set(config.mcpServers.keys()) + current_names = set(self._clients.keys()) + + to_add = new_names - current_names + to_remove = current_names - new_names + to_keep = new_names & current_names + + summary: dict[str, Any] = {"added": [], "removed": [], "kept": []} + + # Stop removed servers + for name in to_remove: + client = self._clients.pop(name) + await client.stop() + summary["removed"].append(name) + logger.info("Removed MCP server: %s", name) + + # Start new servers + for name in to_add: + server_cfg = config.mcpServers[name] + client = self._create_client(name, server_cfg) + self._clients[name] = client + result = await self._start_client(name, client) + summary["added"].append({"name": name, **result}) + logger.info("Added MCP server: %s", name) + + # Check if kept servers need restart (config changed) + for name in to_keep: + new_cfg = config.mcpServers[name] + client = self._clients[name] + if self._config_changed(client, new_cfg): + await client.stop() + new_client = self._create_client(name, new_cfg) + self._clients[name] = new_client + result = await self._start_client(name, new_client) + summary["kept"].append({"name": name, "restarted": True, **result}) + logger.info("Restarted MCP server (config changed): %s", name) + else: + summary["kept"].append({"name": name, "restarted": False}) + + self._single_server_mode = len(self._clients) == 1 + self._rebuild_tool_index() + + return summary + + def add_client(self, name: str, client: MCPClient) -> None: + """Manually add a client (used for legacy single-server mode).""" + self._clients[name] = client + self._single_server_mode = len(self._clients) == 1 + + def get_status(self) -> dict[str, Any]: + """Return status of all MCP servers.""" + servers: list[dict[str, Any]] = [] + for name, client in self._clients.items(): + servers.append({ + "name": name, + "running": client.is_running, + "tools_count": len(client.tools), + "tools": list(client.tools.keys()), + }) + return { + "config_path": str(self._config_path) if self._config_path else None, + "single_server_mode": self._single_server_mode, + "total_servers": len(self._clients), + "running_servers": sum(1 for c in self._clients.values() if c.is_running), + "total_tools": len(self._tool_index), + "servers": servers, + } + + # ------------------------------------------------------------------ + # Internals + # ------------------------------------------------------------------ + + def _namespace(self, server_name: str, tool_name: str) -> str: + """Build namespaced tool name. Skip prefix in single-server mode.""" + if self._single_server_mode: + return tool_name + return f"{server_name}.{tool_name}" + + def _resolve_tool(self, namespaced_name: str) -> tuple[str, str]: + """Resolve a namespaced tool name to (server_name, raw_tool_name).""" + # Direct lookup in index + if namespaced_name in self._tool_index: + server_name = self._tool_index[namespaced_name] + raw_name = namespaced_name + if not self._single_server_mode and "." in namespaced_name: + raw_name = namespaced_name.split(".", 1)[1] + return server_name, raw_name + + # Try splitting on first dot + if "." in namespaced_name: + server_name, raw_name = namespaced_name.split(".", 1) + if server_name in self._clients: + return server_name, raw_name + + # Fallback: search all servers for the bare name + for server_name, client in self._clients.items(): + if namespaced_name in client.tools: + return server_name, namespaced_name + + raise MCPClientError( + f"Tool '{namespaced_name}' not found in any MCP server. " + f"Available: {list(self._tool_index.keys())[:20]}" + ) + + def _rebuild_tool_index(self) -> None: + """Rebuild the unified tool index from all clients.""" + self._tool_index.clear() + for server_name, client in self._clients.items(): + for tool_name in client.tools: + ns_name = self._namespace(server_name, tool_name) + self._tool_index[ns_name] = server_name + + @staticmethod + def _create_client(name: str, cfg: MCPServerConfig) -> MCPClient: + return MCPClient( + name=name, + command=cfg.command, + args=cfg.args, + timeout=cfg.timeout, + startup_timeout=cfg.startup_timeout, + env=cfg.env, + ) + + @staticmethod + async def _start_client(name: str, client: MCPClient) -> dict[str, Any]: + try: + await client.start() + return { + "status": "running", + "tools_count": len(client.tools), + } + except Exception as e: + logger.error("Failed to start MCP server [%s]: %s", name, e) + return { + "status": "failed", + "error": str(e), + } + + @staticmethod + def _config_changed(client: MCPClient, new_cfg: MCPServerConfig) -> bool: + """Check if a server's config has changed (needs restart).""" + return ( + client._command != new_cfg.command + or client._args != new_cfg.args + or client._timeout != new_cfg.timeout + ) diff --git a/src/mcp/registry.py b/src/mcp/registry.py new file mode 100644 index 0000000..5e6d047 --- /dev/null +++ b/src/mcp/registry.py @@ -0,0 +1,135 @@ +"""Per-session MCP registry. + +Each session gets its own MCPManager with project-specific env vars. +The global mcp.json defines WHAT servers to run. The session's mcp_env +defines the project-specific variables (ACAI_WEB_URL, etc.). +""" + +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Any + +from .client import MCPClient +from .config import MCPConfigFile, load_mcp_config +from .manager import MCPManager + +logger = logging.getLogger(__name__) + + +class MCPRegistry: + """Manages per-session MCPManager instances. + + Uses a global mcp.json as template. Each session gets its own + set of MCP subprocesses with session-specific env vars merged in. + """ + + def __init__(self, config_path: str | Path | None = None) -> None: + self._config_path = Path(config_path) if config_path else None + self._config: MCPConfigFile | None = None + self._sessions: dict[str, MCPManager] = {} # session_id → MCPManager + + def load_config(self) -> None: + """Load the global MCP config template.""" + if self._config_path: + self._config = load_mcp_config(self._config_path) + logger.info( + "MCP registry loaded template: %d server(s) — %s", + len(self._config.mcpServers), + ", ".join(self._config.mcpServers.keys()), + ) + else: + self._config = MCPConfigFile() + logger.info("MCP registry: no config path, no servers") + + @property + def has_config(self) -> bool: + return self._config is not None and len(self._config.mcpServers) > 0 + + @property + def server_names(self) -> list[str]: + if not self._config: + return [] + return list(self._config.mcpServers.keys()) + + async def create_for_session( + self, + session_id: str, + mcp_env: dict[str, str] | None = None, + ) -> MCPManager: + """Create and start MCP servers for a session. + + The global config defines the servers. The mcp_env overrides + are merged into each server's env (so ACAI_WEB_URL etc. are + project-specific). + """ + # Clean up existing if any + await self.destroy_for_session(session_id) + + if not self._config or not self._config.mcpServers: + # No MCP configured — return empty manager + manager = MCPManager() + self._sessions[session_id] = manager + return manager + + manager = MCPManager() + + for server_name, server_cfg in self._config.mcpServers.items(): + # Merge: server-defined env + session-specific env + merged_env = {**server_cfg.env, **(mcp_env or {})} + + client = MCPClient( + name=server_name, + command=server_cfg.command, + args=server_cfg.args, + timeout=server_cfg.timeout, + startup_timeout=server_cfg.startup_timeout, + env=merged_env, + ) + manager.add_client(server_name, client) + + results = await manager.start() + self._sessions[session_id] = manager + + logger.info( + "MCP started for session %s: %s", + session_id[:12], + {k: v.get("status") for k, v in results.items()}, + ) + + return manager + + async def destroy_for_session(self, session_id: str) -> None: + """Stop and clean up MCP servers for a session.""" + manager = self._sessions.pop(session_id, None) + if manager: + await manager.stop() + logger.info("MCP stopped for session %s", session_id[:12]) + + def get_for_session(self, session_id: str) -> MCPManager | None: + """Get the MCPManager for a session, if any.""" + return self._sessions.get(session_id) + + async def stop_all(self) -> None: + """Stop all sessions' MCP servers (shutdown).""" + for sid in list(self._sessions.keys()): + await self.destroy_for_session(sid) + logger.info("MCP registry: all sessions stopped") + + def get_status(self) -> dict[str, Any]: + """Global status of the registry.""" + sessions_status: list[dict[str, Any]] = [] + for sid, manager in self._sessions.items(): + sessions_status.append({ + "session_id": sid[:12], + "running": manager.is_running, + "tools_count": len(manager.tools), + }) + + return { + "config_path": str(self._config_path) if self._config_path else None, + "template_servers": self.server_names, + "active_sessions": len(self._sessions), + "sessions": sessions_status, + } diff --git a/src/memory/__pycache__/__init__.cpython-312.pyc b/src/memory/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 3661d7a291bddbb3ed688838d66ca75f00cf5f5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245 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 diff --git a/src/models/__pycache__/__init__.cpython-312.pyc b/src/models/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 74fb6df68dddcaf53bc3c3a61cb55485041be5d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 636 zcmZ9Jzi-qq6vyrSxFmN;4v-Kt3sNLR_yf>M)s?ZRB4zOsjN?0n+Hqw&Qp1MC|G>`1 z-+~xAVhP>A#D?g`0=C<7Qayb1-t+VK{qUEfm;-yhefZk{g#i3;&dmv~jmy{8_y{`C zK?XQPkOwG$&JS3~BNXu%#e9ZlJ{Gcsrzqta%J>}3eJo-No}=8`F)Mh9O1?x(2)3Y` zeS+xDlvL?YTOU+`$s4LvFNQ{AO-*)!)u}hD(3ER2XnL-XF*(IsYRV(Zg`5rr895#F z|NDj#Z58Z`{9tP@j-}Z2jGBAR_{Ikg>82lgmt!&?cqtgIpjjSB-ET0_&B!^HlWnIs z5rREGr)2c$rE~YstAz}0+cI*99cB)RL+X$@xE;%dL+(&myr}M)L|sj~moN#rn(&sE ziS4+9z9nfgeBG|{>L%8 zVkg4dNop=S%2eI9ZgXw>Xz?qA5bnU^ d9avw0^$x5q!0J1Av;$9nNBJUnpUC?*>mP{zuz&yn diff --git a/src/models/__pycache__/agent.cpython-312.pyc b/src/models/__pycache__/agent.cpython-312.pyc deleted file mode 100644 index 500fa55da1565cf041a0b966b7a3b686bae83cfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2204 zcma)7&2JM&6rZ)%>-E}>lNbnwLL3rQT$NbrmkOmpX<{HWaZm!Sx)oZjcP3%Uewf)^ zf~~4@1(i5eK2)OT-Vhur^}_#9RYGzO&PbJzDpb)^tE88n`ey8WajGT1ee>Sz+c)q1 zes6xsW;F!ApTGOn{!>QicfQ#9C3=dq^))Du5Joz}5{@~N8`EPF*W*sym33L@vXgKX zT@iZ1NxG`8x+y&+p%}W1u<`)mB$1!QdoguQSXHp5h`MV{3#$g!^ge5*ktt=HXQx+* z7Z&T(U$GrhG(B7lqM9j;n5@{I9ooKEbbU;mVDc6JPDx^E)ARh$oq%yvPML zKJ7J1F_!wo49Eh1i6t)C#KBfC!=9A-kF6poj}Sq+1Z9X}Nsr^0F5|eKz_PC3gr00D zr6fykec>C`(xIEy_}s?>NNbE|rqIHd!aMT2h&67@p}8 z!(f_Wz*!=P>lwqi6PZrmlUZDvxl&oazBGM(afv0b%}-aWl_i##S)7GOYG!eMzB0pI z>Qd#ixyt92rN?NCALQSw3`r!Eq1|P&Yr;n(q#bv@Ulu`BtSnc)T^&%@w}6LXYn3 zK2Y918lu-Fo;~VmZLE89AY` zVsd0OnkmN)!Y|>Cn=ETOj=xT@5&FIpRGS)Di(eJ1zDJlUdSlqQnOSjsGu*MKp?7rx z0E{SUYBsQ^|AOY4_aM+3@qz>Wh{2(uC(;%l3Xjk#^x;Qo8I@wyQiA1d*NsB628g5p z%Yfw|@!No5egMu%Hn8UT>kh%I#HazQm=>DBnh^l~2+OVebj|iw4a@gJaxbJK@QI!f z*eJk~sTUm6tzmPjd3b--CwI&zVN;L<(E>m#W14^!8|;TP0!T&>JwtOm`XCoWAXw5c zux|mAa)z;QA{Ykc(U{EZOi%Ic<6KN|ahi*>T!>V6v!opBLgYaSz5wwp+LWhWX!-k# zT`hZmuA4v7k&kSSPW*IzWAuD$p?$6+pYI+YZC!bIyCaWwixaI++i!H_iSCK9*44Jw zk;gVqz4>=cDQMeqbzFHaA8Om%NKGg`-tZ3|=o9pa|D>&lApZ|<`fO45u?40vR(Ci6 zOxp9Qd{XZ+DYcJDSzW^e;L8an4eDvZ(ILT=Vc^Q}a#RzIo82AA&GXYm8hIYa&ECL< zj;+|M5kQmA>~%Z5WqX29`-kQvJpspKsvr@dThtsTX%B6*2+rmJwk^Ssw~H{(Z?cT- z)uYe|VD!2s%lc8cw|vD>&EmiVmf{3}(N+&GF}VheVndYNiEK&&!>q#>?wSrmER6-* z>paV9>2*}mNr*?wT=Y=HbD{6^^#@#>7Ry(^N;k*gZVbntK>@l@#pg4cG!`} zyCVgFR(?Y+^pL~jS4&xuDZf!xB0dWM_8dV>CU6x}xb_iK!bTm~N%@`6)OrK1htRgD z0!wkY-nbXY7l|J{3=p?>S546wc!Q7R;6o69#U)AFMDM;tr(U9?zo8?4pwrt*LORw? eZXDUj2e-J!G^8W!TWmCui diff --git a/src/models/__pycache__/artifacts.cpython-312.pyc b/src/models/__pycache__/artifacts.cpython-312.pyc deleted file mode 100644 index efe4a23e02e02a389dd95598d33aa127260d0f72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1489 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 YJ0~>YgANQk*^K(y#`S+IFvz?92by4(Z2$lO diff --git a/src/models/__pycache__/session.cpython-312.pyc b/src/models/__pycache__/session.cpython-312.pyc deleted file mode 100644 index 1145a362905c316eb178c63076d9af8ac1436395..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7377 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 diff --git a/src/models/__pycache__/tools.cpython-312.pyc b/src/models/__pycache__/tools.cpython-312.pyc deleted file mode 100644 index 62313b68fc499d50c87c022843bfa4d23eaa6bcc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2179 zcmbtV-D@0G6u+}OyR+Z{M26|iO*&yd);lxN zT|`zIp-+;x>Yq^RLmw3c5dp$6F$b|fcO6N7?C^mT|LMRw6MnM=WKXSSgx(5O43H<-}|nW-~OiH=7r;S(wf3 zn=RDx#X^02(f1vden`zIw0%!CJWI9cvhCSIb$yFELFo@%zbNsn;dy>&peZQEc*-(D z8rm-9Da5+(QE*ZgLN? zm?dd(E2ha-TuWH8mb4O@QcD&U-iJq?>6~~0juQo3sZ!6fy%nyo$n#K0o4#9hXhplM`5Ly;wfGC443LU&*(Z_GjdSQ>H7PT;p|$J8#9&Z`N|wuZZ1_SD5Ni4 zzjov5%;L;6PtIPPzY3$WIDc*C`qJWkvW*vccBQ-&P!^O|eP%&4ZKGbE_RYwpUKo_C z%wJ)KYnTwhvatelwz*IXLh62SP9euufWqqm8*XbPqM= zTls;;EU2H%vEv=@1L57}hO~VH%+JHS1kA$#QwZ>Run0*;DMo05CcjKP*iDS4SV{0w zgcop8c&1USWr|r|z;sOSQKmk&VBV�W1nkX5WHT0I5`C4N_p-a;S=zEAe;`GA?7B zdA99YMONLHco)#mh@4PgHq6jxwPyv(Hp6$p{}mPJ^d8UIUNs8!Ky1Sa&qBD@C==`T)41>y~dqZo71SAANP5rO_TC?%Uq%>25Rt@ivllwh4?<7`_>^qP zr6-yEo%vQze^c(?8lCv6e{*!Iado5Al&4xp2ODp1^li$6?NxAr|IWuRVEMoKps7{7 z>7h@{Kv$40ks~XkWktraB7YezC$yZ<@>7~8S~i+n8e2u%Z_h^ zK)Q&DZFM*JS9;=vNZXge{~ifXS;kcT$bkTsI`b#t&j1 z^up%QV&lq2swpqF#wMUJ%T0M=>%`c@#my7v8kg^SoASAKxbPsAVwbooJ_O5A7%_MV z7*K$Sgtc=eQ+UuA=9nkLS``S)6jV~x8Z>!m;w*(fh&YMph`u52dhq^`;r3%Q#omNJ zkX^wfz@KqRlD5dH-^s{Ra`G`b_Lv-bO2&7R3F+A9CwB-8JKag?_{KXs1csg7oHV&H K{}+Kl+~gli%_6}7 diff --git a/src/orchestrator/__pycache__/__init__.cpython-312.pyc b/src/orchestrator/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index b1c2d3796ac990e164cb89c77cca156b3d8c8c8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 260 zcmX@j%ge<81oK+YW_kkY#~=<2FhLog#ej_I3@HpLj5!Rsj8Tk?3@J?Mj8ROL%$h7O z8G(|TjJJgRi;^=^i%W_UOY)0c^U^c(Qj3^@Qhu7ux7bn<{9Ekt@rgM(@$oAeK7-8v z6|5gxoLW?@pOs&fl9-p0nOLRolAm0fo0?ZrtY1)+pI(%ho0y!LpQoRg4iw8w4z4UN zNzK(SE=tzVN3v5tK0Y%qvm`!Vub}c4hfQvNN@-52T@feH4v-Uz1%Si{W=2NFy9{a% PxaBW!DKxSdu>%DG%$P{Z diff --git a/src/orchestrator/__pycache__/engine.cpython-312.pyc b/src/orchestrator/__pycache__/engine.cpython-312.pyc deleted file mode 100644 index 1302896c5d1c080427deb778bde065b99e2b8faf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13671 zcmc&bZEPFImAhPie~Y51Pg1LIQL-udD~=t-iDb!&8-Gh_61FZ&bJsE%eyF=E%TlN| zXoEmTeX!l;A{$K+8!a09;zJd*Kn>iXmo`P~yIyc&q?F8_g7~gE-~!waI`*NBb2!|4 zvs{vrnK&)(a4Tr`&CJ`GH#6_OdGqG+w>F!Zf~RBQt*E<)qJEDV_0cK-eY>Bgs0E6n zf)qz{nmC=%1T{3mOq>ZaA;jNA961t!+p%3awy)JG@7=uQD^_(GYN|=LY0vqF& zgf(a-uqkd!*n@Tgo8yjzGw39+C0>k|#ZhD2kq5%Lbs8E;B>gWiNM=p*^6cyodcvWb>p3(33Ut%(6J9Qdz z2ZhK9UX+BeloHsH!2lXA|LQlI2hFktr#fO0w}(YBFFp?}?|Tcd`jy z6vGpM`@(nF$#^(P03kIcDMiXxJS$54q)6(-sYD_yL}!)ysYxzeK!wivaKii{lIEvn zOE{TKNnz|!JmA;JMnsgN30^j0I-5%JfW-_ZXFkNy0Bqf#;`sO=7oL=O0rEzXm!xQN zLIl{hJC&69Q<5?=C@}5ceQ0+)$|ohrSoZUYlrS>}W5-5KgA-7GBo*iVjBFi)5dslo zKnpM1%5qbpY#tAb&tT1z2qc=v#zsaGQ3<-2O(Q3vjq#aD{zF^CYD3lg4Y1vHHEre?o!BtQnMgi#CF95JW@l=oo8PjldkTFq}|2Pw;c^+h~yp}U_ z43z2Cm1&_&&sjJfybYX{)5F`yo7Q02IK%VW*9!Cm%`-;7U3QhTtg^1NJBNknNnRve zdweP$FX!6maXvB=iSz7n;P5ajD$A-ea3G>rhYN2L{KKf}+aR_u;xwJ6LdEK|266?d zqNp?jxq>`FP7Arhd_hhJxq^10D2Zq=Y$j$cHdX{AhYG6WEW>xIb)~mdt-=-X-D+Lw zE!C=U1^ha-uJo1~RanK3;@IqJ=?sY60l!1mB~a>QLqriUnMp(@WqpDOl+1`CFWBG{ z*>Gsz;DG}pM}&IJ>2@C+2Dk=c)9!=&_KoZwKX^ny5@q9&kuUBYc@nCT0@6#1pk8n}LE*gvhpRRR{r|2GN!+Ku(BPBvi!pX32B+aNiR^ zr`Q)u30yeIMZ>dw!>Pzr0wh@M1L>R)!ijJMlt3S-x}+41jLm?MB>F@l(w8bzoP7!f zfyo)!7z#y`Q7IIfZM>IH18QXp&>+Skd5+3EDNozH_KZ1K+W{|YuG$MPW6skGFH5f4 zhq*gO%I3*hy&0=_rFq+O^Y&G1&Dn#e53YDOEqk{>CTnfZSesW`b}Y9%M6$k&)wjZK zUuJjYO}Ym2y!J2bx1m2OQe;xd7~%h0$nH5PeVn3k6Q$=sxTPYS$BHbDUn@_Vis@9i z^af$ks2hw_6sj2ZvF^E6#QPTX_bl}N60^X(pwm%f)OPBOwvCdCU&M;SQ!Jv|Dd8tH zB^8A!RurKU4#%YFSOxJ>DWpNTW3Cdu3JHys%VWs1>mE+4Zn8j)N3D!i5UMmCLTMlm z%wCfUm)^1F60AZJt3s99)Nrxdzzl6PBBkY)AV06)3o0*)e7Q#NJvFs>tn<06zDv4 z`-2@5HvfqerJb{+*U;vhj85`#unEELQ*=EZKecT6>C>K@7i0Dlr-7CBD?c zW1wr}uv_zjkP<`|v}}aOU3+FHdpryV5mzu9!vfl%!ZT0+U$Ejc(aA}k+fbwgZB%tT z567p&Ga@VS(v*+{Lm39EG|(>nM}$YDI`N>93IuVR0r2TbXQ}4#V~Y zwvcQK2~)|CiYcJ&BYT0k&^;pvi9GDJoG4qzmFXG5m6P@H)WigIupPb>cH-yO$dn+! zP8pJ5#Igk!FQhD~fI3>%jT||0@QADziRmn$c9FHXF$<`Qfg(a~NZ`e(xCFK|OxYx! z1Y{G=o&qv(VDRe%I1GYyQJ5d$LDCZGmF7juk+9TQ6;Cx?0C(>vm=8 zcHPl24fa1!jN6{47#k*T`^T__J8QF!wv40gBg$;rL$6w0XZM}nw^aY|_37**hck~H zUa=m@IXr;#wxcZv$j0%z>QCv(SCMdtu}G zjjPW31;=?uu3_WS&aY;i;ap>DwsB*oabw;hvrn=+O z#8Thza`njD){#|59g+oxg)3`q%2=D0ynAnK&pv)M^Z3yf>sLOFuAf#f9{!1E#rfcU zN$dHkczx4PPG+3DK0kHW#Kldoom{Sd;2rA&pX6(Ry#EqourS|bMtkTN9vswBKc@#7 z?cXr~{X#=S;V&2(Vck&gXczNlKfQa4{>@Fb0RPIUf$CqGjR?DjdPZBBUk}iG9?}2$ z;bwq;(?mnXZ@fA{`c112;V$fCjk$!T$hXj3dJ0Sr#ax1o6bjcrvBK6{UM|5FtUR%z zaio^OiGi+UD5c@7#c3KSjv?E8B~wbfW}An8U(zV16xnPor5;o$t+}RCH_SPvY(pW@4x+);;Rn($oYsXMlgBv_(aMFDe*%>S%_k zbM(>wrl>E2`8;QuGp9|`R`tu+11eN{Kl5c!p><`H?^j2v|n?tQ?sWhrWX|q~CXQ|9D>EbBntbdmD zJf`+ldZ$gCbr!N^EBl$NleWaZQ2JVh(&pGc6)wFw+soR@(s}JOWDlgp8ar5OMTOFI z86O-{OH1!-cGaxAM;>fxLu{;ssX}R+I^%1OMB{3G=?yDZl;-~>4(L@|PupTot8nR! zo=$Zodck1CFE(!l5!rK5=h<<+|>3XypnUluGMdRl2G&mvZ%=!HG_ghs3>fss18zyiDC& zpEjh6+%V@#8$}J*l%zTDT=k!&2PP}@P}H)2H>S1j1S}m0Sg!A+7%&^?i*>8R#A~=E)Q5RH4!v93VyA^JlGgg*G{3*|Au! zQo@-&r>z9`*h_1V-bHOzPIqyn1xHxnxUK)Xo*cK~T{>=)`Np}|oOw3_H&CdPrm1!F z|4;Xw{~C2agLNoal52GP@e+M1^na<_Ighf|Yjn4<;$BqPht;(pnm{G7?0p~mxYAnJ zs&i6t&Ffx9cVCgiN?onnqt~rDah$*kf~@4gg|dDvHD{*RtzGwh*RG*lc3}T&ROT*9 z&pC^|Y0g}(*jop2*R)Ix0{wgOHm7vH$1AyMA&mYH_Sg)&?yyRZ|K#Y*5*GnnegzJE zcmta8YskTzoR0;xDe;;sbu0#VO>ki{TTSkg0KMRN*~x-iqw1bQI6>p}65y$)c{E{X zI}wpp?v*%pIx3xDJ5=8dUbf(I92;!hA3jy)?NIIn;UWxf9ytMz6n-su_~G_Rwn%XG z7biDSqO2oMciF=6Vnm3N!#@n?aln^N=%yg;6fPi=l1xSKz;Ru6NU2o3oKMQk1TO)s3G~afAfhT{S;e_x8i1=2{?w$fYj#Tox5^qHQ(z#TiiG3hu0Zjl z|A3}M+&UCL3#DnZzq@T)bfuOwVW!Oc9c_~xqD`sN6K$cE{#fPyKzRM#8f z;AoQLPYF0(GLuYA3pxT#NfB@udq)q949mKc z;rJ9Ud=aY+iZ_s#UExDwsiyiM&< z@jALfVYu!~9E?bwK zSilvkNM=C`(NO`_gsc@HdO&yrK}D4mT$vh(Auy_pDccl|RQ;SrB@595w&T$x=s$9? znUn~liRfP;&dhZQ5kCRX@3ZXv1djtO@hiXtij>(YNA=kgr%#+soKBG2!mf<9tLT=y z-t|WN8(Xe#%vc}G)%vov{!FbO9Epx5a3ng+;7Bx?^CYnok7i58(Q;*U$&0Dke zTj#%!t81MfKC^eVp*`CW$TS2X^Z1=Q%H%j}Ic>>Vx2{?p=gi+en6o<3aeBvqgxD#6 z@4O+`+CFdnpq9PlezpE`eYUpu^yvKHyp(e|Uwkaa^Y*3YffeV& zva{x9XV?7jinVP`f2+RESK=4r*B<=wz}10e--AHI!4Imv7r5o>w)x>4cuw7&8F%L; zY1zGDe&psFA1o-TjI;Mb}eSep>glrq`R6+jpJQt~gtA zU7KJ1#^rA;&b-mO+%@usbc0#y+=n%7H+vuW@gr9sS$b&Sjbkglhi@~QHrKnohc6vD zH?*+tyZe@U58uKfXxRaswrqISaoMqGxqjq#?4#!%BQw*Tad&6keHnM(qVZ?;*X`GZ zH|VAQp?BQ7KVqnsK|239P1SA4Cuv+X+p5JrFP)t^J@f5d7Y5(9crSOYS&6*HXgf^j z8k?0#x)#j_9?b+EU2YsaXUug5vYiiPIv;?k$@!XLQo6Roq;%n=w7AaM@-E8c%o@EJ zqj$yFe6y}8Ti2JV>svHj_b%6s&fDH???2zT7@gPOw7b>_f|>qY59JQB4HHjDlfC!N8<}0tbAeZcaE{?#Uc=h+AUEwGwA`@mF36a({v*l+l2yD!2rSYo*3BRq1@C=x|6IM-+b@iGS>(H zMxSv$Qd9!>zd6`6^v0$)+LrqEELV@dV;%h@UyTIa%@5JQz8~l7fIC0=$OR1Z$2)e) z?fr-903VnHG#o2Sy)39*j{E3Cc`NXQ*yKwOQ!JMym@$mJk*Pr;6cw^I>Co}E?pl!0v zn=;LtU}UxJpvr22S+rG3@=4wS)ZNW@1NYp8n;Iln1Kd}nw|@~R-I{CZSqeOUWAMh7 zr40v{*h4SbKFRA4?{3}=>;8#Y118!x2hC5xMTC_;?9l$g){F3tEr)BUUv8ieyR^UD z$O8PPZTMlRxM8Np&H5V_EyB+B@fyvIt@OA}e`DJa1(|=?Rz2?2|JFEYfXqKSwgT2$ z+HFs6q2Ag-KiRK+Yo`|BT|@Z!l7@b&OS{Aj+8~p0(a=ez#x!nVGQJ`0xQ1D7rN=t- z%Wd@t571Dt{ICvCS2Q|+R}9$3ij9VjR-8IaYfMjW)UEi2c0Jjx%Qn$ZHXE~E9m1`8 zK+SgRpgOyeLAaj=I#@*H(@^N#H1J#TN`bFM4v>dd%0v#!9~uE32w^M;!ZEvmNh zHJ1!awcWX9HrM3C)Ki!9cyCpiP?wlYW??fl7{QUKj}ZVkDA2dR0Q?IJQt?tN zP1gh3oJJ~sS!udUkw0a4*#5ew6y#QhXI+N`S*ds}RfdQ5__{|zW1c(^uBK4RXfQ|* zg7#v?D=HNSE{v#3*Hutj;Zg@m8%kfQ5R?|(YHArpU87&qD>+~=&1}!!B(fG9`5@XA z*sNeelLI@3FHFEGJjtTb7=SQ01DxwIB2++6l&}F4^p*<&OwhCKcL*5&QaWu2NUnem z69ES)bYbGb1WjWB$re~lS}a~Dsea- zmkEcngu~$~Pg8bGE;{drsiPpvw8aZdkv`*3Yhu{@ zx(DLZKoy|^{2s*tCMc+4JQEzmhqb5{EB3A)*@fCqX;}|(TtouaI%v}xp^7uaQ2DNV zAZ`z41s`x}qGB}7Zrz}JWWDW4aQ@L5OD%tjW8QYo!~LbgC(aGtR<(cfN~N? z?Ko>{Yinc2$hXyymud5?1&b6%iFsCIHqSc7(Lbaf-nanxL?pmf&Wt*U@`z3f-@uQx zu~ana*9+&cb^%Lt=tdER?_j|ROwb2V7TKxj*l$DOY)7TD(LE6@p4tWeJbz&?cq2SLSxwOGI8edrzM09YF}UBJ-R`mCiXV`;kZ^ope`XRX2$%!Pp~w#8_s`_ZMY z!KM1473=PQ>FAk1dG^`U&tBNJ>}Xjs4leZ%LP|9D0l!tz3#d&M^G3ZFwEM!E&$f%o`9g zQZ>H32_Z98+nTo^WTiZ8-iDB!GTU!E5Zc+YI2{ zB6!Luaa{g@@K^A{c|1M^%mVW$pym+?0d*;WY076Zq^ z5Lk%JDeJ+3aXdPqxUJMM?SK*_EtnAlW3>ZHthRu>s|IWbNWiv$ObkUBVJ-$r4mR0d zC{n||2qP0;8kz*MP5CVe&=$Y|bQA!^$!8^Phyn7u3uULocwX6AEHAL9vUk>EoxM<3 za0MwlEt){!VZ?1x45)4!Wt$~Y>w5B23PJI#nZRkfPYIEG6-gPygs{{#gwPBlY$h8A z6b9ZU{2ae2V9-S1Jpo2G{O+N`M|V`{kY;IgUxIsG5>d$sfs L{Sif8gpB_I-#FN% diff --git a/src/orchestrator/__pycache__/router.cpython-312.pyc b/src/orchestrator/__pycache__/router.cpython-312.pyc deleted file mode 100644 index 4bca5e071800dae0d6eee88de95df4f5e853b479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2475 zcmaJ?Uu;{|89(>`wVl{WTaw~zp7s)3{g)eu=X_Z-_dxxUvr z=Q>U-M;M6-XR6dnFqM~~(kGyM*vlk%MB)JnA;qL3-C2>QF(!3SSzAqvr+w!-ZYipE zEFGWk&-uRd{rla|1_$E^)<1vyxtlZ)`dSeE1+-YwtxsUTh+ae(6%dxN>`7I*AWPz` zcuGORD%L!;sueWgsaW^)s!=c`BqO3M4n;=p*+}e))MFIlL|YuWM;H-hgpJD*$oAe> zW=68~ePyxBlB*PwI8IVHaRgNGARc&KEex$HnPmM}z&KteerVC49ujK(z5SNOh)0}| zSz(1()Gb#+i`5tGNUIc3i`Y)ZVj-zzZwQYWi6?B|4?;V11D~C~A@`WE$nH$w5zsKt z*lcMQ?qmkJUbew9<%Sb@!Vy;~u`xHuO3e!>;W{HWbt+sfxjyD5c9|0_3w_(Sz11e+ z3D;jHEOfyR;U*=0edgFckJo)nC<|>LH&c`>yQE=}6&KX~a%PCf+-l7uRfw2t4eEjc z-2wOTuPxMF4|CNCFyTtcUEz8)z;0<(xG3397*MX*7<0W|!;xpm2%+L+V6sfSpvF~# z-4HBRY`;u+%nAIGTdq^mgqT9BUdzO}-g^@_mu=6D>}XDfI7?iI7=<@AgxI)d*M%v= z^;u2i$Rwa&7tVB>F~XQgA6Tc=W*WOr`0|}>AGAD(_GK2*`FWl!&P<=0Dn2)L_ND2W z{H(LnXQFOM^s^%R`Cq_&!?=iAXfYDpyDfsw57(6)#+npEq1KnSq<$Z-$t`&n?L(p7 zmrWq5Na!r`)dpHo&Z33{-ps!A?*ISwri9US3hg{PFFUe}9C;r63x8O8Q$A;?$k!a0 zMMqwf8`1td8WVI1c{L=-S!F7Q?Fre8;N=3eIh*rI)Uj-MkB;88F;rG%- zv<^KavI#Lsr{6~znM)MV!vPRsOlVu}?_S8klPSwBf-eZkwVSzo;M4^Guw0D>WolP# z2Y@RV<-m1jS0S`&j!`EU01Ck@!iWbn*Tctb4dCF2XIB@neYE+bh*^xV@Q4>Uw#SZU z`_e=302YJo+cvs1_ulc#XKw)^;8KCuo8vPAaB-AeYjQuEv>++_ne}M&{$KCMnbS8! zYh@BV9z88eF4w%EK`0e7$K!JV31o^=K&AvH%%N^gU@$!>q#A%*m2stN1Cn_`xeO4h zx_&9(+KK7>)J(?Ys(3jd>`oB@h@k^`T$n0Gez>_)CdTEZ23HqEIvxk>OpLB*2w2DH z&!gAeO$L1y7!L`x|G)-YwoSBaWNUc5J3PKMJlP$d+#JrdfAiH7PjBvf=A-1F z)JysI#@~h0f3NO)rv3Ck(vNPXv)y#|%k*RG$&T8o{B>8l^VGG}@P)z`sfVr&ja&%d z9JnNPhsNI;__v03k8LYx>}R**Xdu;D-Wxqp$ zzx;5|*5Q-g!zVx8_vzyshfi-je`aIum5q73JGXGvbfV|q*3rNN=bv1EazjnCT@d;o z1`ePPVuzGJONW$?$KuByQ8Ow%4gxBGQG5%+I`yM!W`?;@EaJc^7J0l_EY(3mibX2W zmm8vfFduvXK1sx5*{CCWU69;iiI0LPeq=dTCgG`G&CO!bx2wX`QDG`}V*1pnsS{_W zXXr6On=?~$C#PPTn(1{-s#yHq#~dz+Qqsr7_6xB^r8bX%&$>sB(lfvlgUTwfLB%AA zqhsHohrdS2Z_vb7X#d}0>CWg@EZvQz-+p24H<6mwx8p#+G5y*! nP$NBROC9N|BR7%qpuRnt%F6BJ#?XOVC?(0aCw?rAMKAdu6!e^# diff --git a/src/orchestrator/agents/__pycache__/__init__.cpython-312.pyc b/src/orchestrator/agents/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index cc0d921a25754f773dd29e72116cb3f7f82d416a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 415 zcmYLFF-rq67)^S2y|#F|xjBe9bTNOR2vRpeL~*$#ge25Raw*AGshfkp!BKGYw}`V4 zxJ4&7p_`LmuC0E<%X_@*}n?Vg%$72_&#a2A3E|-q9VC zlu64Iotmu7TAu2z$;%w02@=^eE_?ItF#p03F~nixOtY$z?r0*b8uVan#lPAdTdgIp z?Vn$q%Nr$c*Da|_AjhQ>A`d!dLv`SOv^*J1y{u%@!J633C6fVyA9fD(0+sVJ_aZExhuf>1wm{%a&y)a?)Ck%hJ4*OqvhvUdonK zY7LrTP%v$kyP~oe7nXqyaS#_(h5<45SJI`;qb;Uuxgc|Eug2P;&3{bnB5}3>`@VB| zNm-`XZrHJP&UfD5`ObIG`F-Ev&ny-rf$-s9{~{V~B;+@kQ35Ly%)KvwSSB19Cmh46 z;!Hv{u40f@$JOI%nzG|8O*P{hnrg?jkg{=ILO-rg7{(3IUc+hQ#)N6ylrWE*6P9rc zEz`xV3EQ}h()zePQ88XYX+zwRaE?1EZH&7TmE)C^HpQzF)#KHaHpkrw&$x%umUvB~ zcDy!GH(p2c)_8rQVZ0&X9rrRsMIvnME6T!3X{7N@M$U~lsmKAs*{Y_P&ta(=L zubA7iFC;{K!u09TWF(pPh2tSX@STsQ&-jEhAwI(SA~TWjbUKH0ECa%9GCZ7)@S${y|3r;V)sp#0ii^YtxX=_-NG6)wpGr&=O0H2Odgu>~u=|m#L&tj9Yh#&xmW58$xn6w;ErQ$=%iX`VlS!^^dKr7?e z*w9cSnnrF7L+4(mLas}0a91FRE3?Qe0 zT;YR)oECB#Wtdsel~KP|(oM&gr_8Jg6^!O7KDgjC$T~#KEYSR z7?OoH4~7cLV=Z(GMyQ5JM(7<3Qw5RC@*qJ0^+PhjP{D}2ivlj%Cx3D{<#7r30{Ly1rrl%|({64CJ3tdNc*dIdh*o8rS~B0`#Kw_Gjs zo(9PWre-C5Fc?in)4|}JqqrFXrK}bh5CRarMDh;e_AaoC=3HeHB*t8IE6q9UA<^fm zu+WrqHY~7zrN0L~NVq6y$l!_gGH%(bmXO1Q!EGZkY_=)HHufsiWtdoTn@Y`OZq&jtnka->Opf8Q6P%)E2l~Po) zn7Nc!C{Qr%b@iJC{vP2)54~(k+fu502J?n=g;H96{FpL**3T~!x$)ta%!8Xg^oPi0KyYvwGl!zSR<_h_$J^P^?y7NsoK zrckByF_P;jwNR+?++=i|{<6BfCSNxw>)xzC41v))m61y+e=cK*^_21o#TgYH7b4IT zzuZls{CJ#-GhJ5GUG;710NFnxtu2rCC_f9xzoLcK>4rRpHZiOnPEri!lY1>YaMe#hDQJ)~^nH^;Dhdarl@I&=^#$P>m{<-O-FMTHB zqnB&D@DRRCO$Cg`!aePKE|Q++lY%eg<0Ha!JniG6;j|1*&qij2-98EhlId<=sDS%~ zZeJSkU9bpQg+Ot%QHl6jNhip7P}0!pB}1{RWI3PW&qkAz!6;nzLX_AEncPal`d+}W zdKrgAIe;9YnP56~Hj)%16SSQ|usaQi6dS?tFtQ+NkP$)B0*?T5{c7G1a*{NFe9|F+ zH|faK8;oQua1(@4*<>hzL#U!0k5Wd~CS_0ph?K?Q_^nXR<0m5N={l!{h-9A%@j`?P z7F$dDNRkVp==>~?r>bP2jd6V1na5)gI7dmzl9iZ~bE;E5oQF@vBL!>*kXy|U;FPq3 zjdm!B*5AnE9VJ=gQ6UY}%g|fkw_~A(4<#ofJboFH<@9tk&IJp=O-6N&;A8|k#p9{- zz&nmC@Oqr88zVfAc|3A?ycs1e04}f*kmaBvU>?sWz7DHd1bMs@Nz=fAq0!^P!J(1k z1N>L81s)BO8YYv>l>acyiQ+aNKYDZ|ICgyCx#L5Fl9kq#L}-}+Bqy8!@q~m_QetBm zYvQR8CrGwKV@F2^BVjyfOoY=c6<7;*4;c^7sBRS1

>1%U=Een?4 zx;z*CSy#)#;9Ym!74s!?*4;0<`*Ut@uBv7^ej%Q#Y0fot%ud6Gx5|e4Ci|L77pdQdKO0RdYiAzU7E{! zw}{>?IZxdc?hxID{Ge)q|otA3^O z_1deoqN{spI9JnfrQ=e^m7YsIx803dcaP}qx!dZ0{l%*VWFR2A1G&bw*S%N0H(EsZ-duBMVLBb1 zj>YL%oDL&1OqX@G-*mRWF_LrnR%+K>J*$=3z9A9*yM`cl*XhnW+eByEwa%N)Z8z$& zyN1PG!ymNW+VyOnRaZ9U4jx~7{u?(BhAyX3qSv}tzbSU^zvUfVn~Yv$K`EZtvox~S zxK(s*%efk|u2#|2x)Q(T+L~)_T^iXi`Yk6k@Z2?fS9Xc!?lrUbQ!Q~dd~C10rzO>m zOR8K`TefMN*t9KI2U_D>sk#({uCZyQe`Vt8?v?Iqr&m4MzG1O%cx}s}HSgh1 zHR?9UABeib`B^2*pZ}_gRJP{NsffEP=V{7$1F!>jBS_I;%6cy8R=lJ4`_gVw~ra=(!Sfj&j*?J9PAMX`(70@641X_twy?LU?)`kw1OGw)Bn`5 zkAchwPG)4A{sY&50Wu#pct>`!A3oI%^ha&GA^w?NH*+G{lSwRZ2@{C77`QV&xFCDpV;|=APF9KqWE20GmTO zGm;-gUrE{4WlYF(gx~R)*68ur6mWG4ge)30iYbRiG2XG8LPoE^#(6_h%dr_1Kts(B z)L%?22~ zsZ5ROx?Ta%^JZ9m(bO!pjkT9(g?h|r=Eq4Ei~%-=kZn`Qm=(z~WP{EjK-v^C+F;Qc zkTI7vAhp4w;h!-=`{GW*y2=3GCWmETT5uLio zpKk)Dj&Qqut%C30Uiuy+l2$HyXc$cd;kdt9VyCBJMh(W>B=wod43FnHkN!bP_uTWN zqr;;Iz@myKB9bAIg2knh(J+r^oMem&L4G=!1Ur!5f&~U@Fd(#+oPh7a^BYbF(dR6$ zas$$ah4N=`9%}Sd7KA@1TLT+~QZSpSm%3;-@ndwh{YQ@+8yNzVgi3}#i{m|yH70Hv ztW4ajgh0(6ei-Yh)nlc$OtC9GSp0tSv6Oej8q18LzE2P`wcwPcQz zHea^%DDFq4_y%^+fIjePNh|9QKZ#{}g&VqJNv~Lc5)%>dRxMc)NKtEoKMv({)g?>f zA#3S1s1!~D7@+pVeLHbhFLz$({G0Bqqe*l$t!!P{x8`VH(A{m0XDk_()7p&`c--0Hu3K*<8oAWy}ug+v`O*d^#E3>PEZy$W?;M=2b zjf(C2a)9w?F3n`UeWJH-)qmr}y7xfVb6`oEt8dKKZx`#guTHGjKXt))*W0=hT{*e7 z<=|TV@RISjV60d#S=Zei*S3r9EvtiK^%DyR0XXh_I=kc0%^io9CbLzYVpV6hs{dwH z|Jtz^77pa9YnL+@GTG`~V)ZUGYie6_wN2UD9>Yrm?%e=O-BuWguD$C*SJu%j zI=ZiYHQTdS?AiN{d);v;Yd*AQK9n<8X3ce?x$ffEZkgMD>+R2a_lVv-3&Xd}b-DK5 zZ2L~JeJAExaxLAMHr{nqEq7e#Snj#dbKCC8+B-yh$KATtT!TO7ZO?jpMQ`sayXsu? zZU@v_;Q*_lu32Udj1Z>}j1VW-IeLo~j1Y*riQTiNcjr$rq~;*=Am2nRhnX(`(^{+# z?z^D!Gh#GYZzE&7MDH%j*zJbKmB!a57KRs(-Uj>U>sPuYD;bH4NJKUazNgGDKVL57U7$S7;E{sAKZ_mt89 z$a{dF{}j%Fl6`x?dC1SaTQy)g&_#aS%p7Q8f84UK8!|t!GY8t)pH%EKL+1T@=767l zzkx-%Nd;ZrZ()#br?H>LU4}z#>i4(pYdqAb{-}~Uw*E#qRgwAtj&MZ=Fd6n*35Mu*S4(Nj$PWd6uQP-YkR}6di?Dd z-+D2-{iwM8=*^C!-(@cD5^cxU^v53Dv>g-h-hS8S8`#OdyM=}LA*I5{IjU46plb9< zaHGmWB?gX}EV`rTnKZ7Kh76Mi#32U-5%V+fKn2PG4sf_>=G7ZotJ6hgQd+}SyE5un zxjBnYXHHF>?aZP-ql%$bxG6lQznWu}SxVS@@h~{9ReS}fDf`4&oO6u)&-gJzlrf=Y z=>ZWeY?-++;@8e~4$1ev5B+S4*V#A0rxJ*RWc@9mE9x%=Yym$PfEe)t8Y2>8^c(oY zNCCizCgDa+hM@s^P9+xI+Vb_!#!}HFe+=7cxakCV2fqb0fUB1wk%!iRyEiRJEOk=j zrh&p1G!~CH`SE`Mzn4H6xbiz_BD@L_`0TXA>A^ep;)!djbw?N6t)|MQ$;;_mrWSBP zEDZh1?OkGTJL@lUe>eC`r~exFqcd-uSv|KNc>0~T^}s=J#w?Fq7+G<>?!M~2*0$QT z-njjjt{r&|@%X?s)6uiEZF$#)T^FBUcQ&t?2mi6C2;a%A=9UqdxOqjKC5lybmK2MvWNZr@*6m$fN%fu?hOSC3Y0CDDw$+ z@oTB8mB%+@{snBMj)FgR8K^I~oQR~)q&OaR;0;2BDj#E&*bpN$HHCkH=oRuY z+n#5w8t>hT`n(2GPz85OUWb&Pc-ry?q>QApA#Xy;Osbpm7No4C%9pnxWhX}Ky$Yn- zNKIW{mt-_XIvf%(o~v3eg8_~1d+;g^rQc8wFd7eSg+!qhCH2JBls8Zk{HXrCiIQew ztiESK^4>Elo#t5vemS9ae5NvJ>h7t5`V=X7t++ye3x5R?6y(G-KFEN?@Q9N?C>`Qn zg!McYC9$CvK2t2g&xptSQ?e9(W+DVm!;d;-C8W<1cw9-!Z%+d9Z%?RZ(jTA%Wtu!I2o z)`flwvO!>gyK02OeEu@7?Inz;CR{}dKRK!$l75e@a!>O=f-*ew1pHLM`^YdKlO6v~ zntnq}zarLOVQl&}Y5g79FOvOtNY@?Ge228(Asu%}?;X@ XEy>A>v6ra7@yR*-}T%<%XN%$Bg63QWSSI{BEmt&zw#Ifu|h-*fZnV0j#yE~hi zU4K<6+)#xADS{3Nv70EH`rkSLv1Xg~<1i*RTt5bw?TNP-X}?e6T%oA-Y6d%yR- z?{wM>$6t4TFyA&9`-Ofc1OI76hb#EL%QPD@9qC#UrL|!#V!E#5q@KpZIBg6Y_}|dY zq?xvcEu6Q8Q+ifU?Z(6D8vB6h>0PGV(7IO-F4?fXJ=5!yZ{pvA)!?`sL00geZ-2&N z3shNv=O)b)NO4&f#%A0Xkb7QwlZ|*`ZQ}cn$X73_W@MJxv6X*B0eZD+_C0dA?CcGb zKs6oyR%`-~qVbvO_oauhTHdcQw&*Zmneng!9M6rMOr^H2&##u;*@UA3+$3nyaeP2} zuHhMw%Ix3I5TDGnEn@WUOpcrmrnA8pgTGrP?F*5{vOXAD1*nh zd1|r}!+J|wE09}P&?$bagz;fydLHd9f=v-RWXcjQZ9Se|1GR<|2MKJ*4D&=@madS! zE_W2!Z{>+ma(pvxvJ4b>=?rdBeuJR`^x(^+Ag^N_#19g>dTH_U^2#Dn06v%d-&njW z5q1s1yY{C_>ni^ z5g1mR@5PQfo1>dojfE>07gwqi;UiW*UcI__SuC$yxx9RJj~x;Re||K$=E3>FP3ttu z!^oY%MXO5cOn!i1LZ=|L4#nvRC`?OLy1wL|9(9dGC5#sBNN{iR-9O$kA15Ta@e zkz%o9){Pcv{gd5GZ-=;axc>CmC0)!Cw>ZFyX--H;UJzG8`1s}cI~DE z%wHDr)0=VUahamsC@HJx7A4NASqu)?{sY_3q%tOjIdblZzd+A>v6ra7@`&#>akwCVI5gH|t&_(v1Ag&?=3eFJ)`AcV0zxZnsuI4-l;#AnLKo^P*LWy59a+v!2bvZb5L)LM#L@|4seOf@MEt$eug6#>z9Tvh{3x_hiFt9Ityq;kxIEJ?pUZfExa)c<1j*k+Y zu#hPtBZ5;R31}o(6Um70c{gw3hl$cM3qt_UlsAVL8N)<|ThWeCvv?y@A;B!cjN|b5 zh(UAaw6&QeXfgfiKRGJ7%qBJvt z?LAmCyv4#T0m0>Dhs_!lT1G=?cps!wmQaw4h%!RUD>xQ3+KfP_orbr2}AvP3~ zI|QU8ONG?n?Hq(qr2;hIwTJDc)lS8|&lkM9lR z1-3ZbA;1g;hE=9;Dn)vHq#`Yp_r8x;iIyhLSQrBB7+36e6b%Z`?)dorYmUOxD{=7V zeM<$1Erq~aahq4jP)d}A9>pnVuQKC>z^}_;stO3{Tr9)l5NSg`J2Y@^Ft**%>eZh* zx;)zT5G~Lm%#X-`2!LKM}i}(H;vnpcZmZ6V(qC)Dg=YyF&J(q!$cJuUW}mAU@08gv2bb zt9{^7Y*fXm&g+D8vCC+nh2Sb5gR7KD0z3q*G1-IjK1)=7ZKy3!fCHkbnyxh6MH*-G z+QQ1>VtZkIrISxxE`zm4YwPW$V70Tdw7UKcx-jnl^LgttV4P}g3rV3VMC3{9{;)n& z3qbWGiEX}G#B_luT(fvvN`@@~{4rc7&}#|n=* zk6y`p&%JZbx#xY3e-{pm2-ZK}`n@(EM(9&^aF^EsxFq@bR ziKOIk(USgh^#_uQvqqt0nAntz^Ld=Lv`YYLMaz)zWt=Tr%6T12C7?4?j~TxtN1K3& zVJVho6wOgM^Ml2y$Lz7BhlU0j>v3XCXga1r`*h0CagwK@G&W73NdqaSfzyh4F>T=z z6&*;i%I0IH6mSe4R7`BsmVfO*XqbRZ<)UQYV2Np=O=wv~HAqT2U6zQUOMszb(^iIQ z2-r*?PzMw#vLn+0P$Udmmb2R3UloDIBIT@ACcr5rirwAP(V~5o1u?NHepwnUD!MkU zNLXKGHzldsrMvH|Shrc16w@%JtWnGoYymVS8AQcJU}>g+!y!dgB!&ZTpU093ve^JF5}PHQ)oekSoFJ8{<%)^~257KWblV2NtwExc zh*8o&h5?XF#bg(3x(@CI#)d@jB@Itv!g!qm?gl(ArH>Au96vS8tOZxf-Cr2GW0S*7 zK!^vxF+m7b5r^=lGS)8v&De>tXQno!W9hNc0r83`NnOm^T?5i<087%9F6J7dl~~BY zt$TF50EV;;)U`|Mvc;FdpO~T9qF0_tUUB8_s_wG+rcx=`&$0vHGS?{(xh8wJ3qr;s zTGsI-=*%J(0vlqTSPEFx>~iQPlx&0I0?u9l{{bC~`K$&G1f6wBDc-%MYo-PiVpSAJ z$G}-xy*vf%Al$%13ef;{qKt`R6EHKRvMgEL#cBy82=`BqT5+{!G%_L zm9u>ADTh>#%EPblE_cU!+wa_`QPMX`{mj}B^|`livh~{o8VW!jd1+KD6r2>6Nm<7x zjY3?oESF)oGcIqTfu}5d*^*6GZNO_u zfiNuzQ;GV=PYjNZ4xgf}4M0wxNuM4*DUY8TJ2`&(XXpu2{PQz?-+)Xq`|^-$3S^Bk z-8W=p%gh^Q9|)8qNT>p=k zV$XdkkVbpZEcfP6H?p9#IV;Z+Yu&OW_hQ1+gUEPQw7T4zJWJnoOKq-*t{=Fyeto`m z6EDzt`#QF8EOKg#8osl^<(}iRo->eBRq@+my0K4kljt&k22FDB^OIaMKE2J&aS1v* zBtJAC71}iv(W2xIOlFES0e?0ov9by^xMV^Pu*)ix3I5!lcZ;rb23i-PVvIj%Nlfv3KKBeUTJH7TFN7WFA5 zHWv|=--M+$w20x+p`)WCG?Xn90+mr_uaJ~2I5ZZ(rcql~8BPTH7lsSgl!sv0Rq-$( z2BCrDc*=o%x{=FaLj98pDQd+W4L+NMlTB1Gu|7flFd(zpmHJ?=DC-tsp+o~rT5tei zxxam z9<^t6~c_DqjbNB3#zqWSFWv*xD1By$)so#XDwBERS%gW#IFbV;pL3bqIfa;eNF5 zGb9Eh4?`_)jec+R{-y)JY5qg#&TIL@HT^fIrP%7gzjzj0DMpC7zy(C z_6>SR1pdSQ92`D8;Drw#30{Cd3i1Gt!W?uE_z+g#Se&t>pN_!fX}#vA{#h6b0KAY++nkt#m?;)kDI5_Q0RXQtBG|Sm<97 zc6%cZ8v6)YdmJ`r|tGuR8|WPreCw zY{W2`63aZtaUY|hzoD*AQPZDLL+N^qu|Dwwx!_4T5#iyy^Fz~ ftG;DnLr3p@)cRysl=H3h^mCEfeNPc=wt@Z&K8!Q) diff --git a/src/orchestrator/agents/__pycache__/reviewer.cpython-312.pyc b/src/orchestrator/agents/__pycache__/reviewer.cpython-312.pyc deleted file mode 100644 index 487971dfd5bb83e81fa2578e14187690eef9d0e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1778 zcmY*Z&2Jk;6rZ&>wzG+GBv1k=s3Te-9Hq9PoC*TDsiBt0hwM;t|^%SfC!#hdA?Op9Eau9pbuDkx49no9(=PHCxHbTg$&MihsIQ;Ld+8pXEQj-*P! z(L=^19ZN-}Q4*e7vqHB#@4^MzOSDmG5Wq2^y-S5!Mk9Mq8L`a*gCzpvX%KQOMFLZX zQ7v?M|A(Z7H#dX|x5I$ZKq@7m3-V=b;&CKQXi`oOXpjTO?e!jq{o(=iZbc$ydYNve zBY;WAp*Um!kVS?SUS6Vr2;N&483IBK&=o@m%Pmosd(WWAYVQj@mI)Vh(Ce;l^t*+_ zV|M?085JRaC{@giq`0GVuRIt)cmn8J1HjcrV8aA5CRL2HE8bfzy0p|qms-*^E+#)*h6}5=7Ky$Fj=IG zBLrYcqi3S?0+8&LK$C)4M((G$Lz&qM@azAB$M3gRHSH)oz%Ke2b^JWt!E@MRADCO;{|lx<1fG z#8!7aHd&oTk=z&DH&RAAo59f-&?4h76SG^8?}|icb7$Io4aaN+H(g~rkFvpMwSMMuSxwmbtf;Q-X)AA9#RmHJxPe5&r53+>-m2Lpy8PF5>)N8Q3h)*wLx=kL= zEIh2w|5iW$YyJFjec|E!^6~u3$1g1$zqs&Esk-PrZZu{qk4R&xqR!#Lb0j$yQG zlOrO?&_Tm3ac5XQC^gGjy?ib*>!-kYrUUg7@x>P=j+ xc+ diff --git a/src/orchestrator/agents/base.py b/src/orchestrator/agents/base.py index 3da4724..4cf5da1 100644 --- a/src/orchestrator/agents/base.py +++ b/src/orchestrator/agents/base.py @@ -10,7 +10,7 @@ from typing import Any, AsyncIterator from ...adapters.base import ModelAdapter, ModelConfig, StreamChunk from ...context.engine import ContextEngine -from ...mcp.client import MCPClient +from ...mcp.manager import MCPManager from ...memory.store import MemoryStore from ...models.agent import AgentProfile from ...models.artifacts import ArtifactSummary @@ -29,7 +29,7 @@ class BaseAgent: profile: AgentProfile, model_adapter: ModelAdapter, context_engine: ContextEngine, - mcp_client: MCPClient, + mcp_client: MCPManager, memory_store: MemoryStore, sse_emitter: SSEEmitter, ) -> None: diff --git a/src/orchestrator/engine.py b/src/orchestrator/engine.py index 909d47f..c5b23f1 100644 --- a/src/orchestrator/engine.py +++ b/src/orchestrator/engine.py @@ -13,7 +13,7 @@ from typing import Any from ..adapters.base import ModelAdapter from ..config import settings from ..context.engine import ContextEngine -from ..mcp.client import MCPClient +from ..mcp.manager import MCPManager from ..memory.store import MemoryStore from ..models.agent import AgentRole from ..models.session import SessionState, SessionStatus, TaskStatus @@ -34,7 +34,7 @@ class OrchestratorEngine: self, model_adapter: ModelAdapter, context_engine: ContextEngine, - mcp_client: MCPClient, + mcp_client: MCPManager, memory_store: MemoryStore, sse_emitter: SSEEmitter, ) -> None: diff --git a/src/storage/__pycache__/__init__.cpython-312.pyc b/src/storage/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index ccec8cd21defe49dd6e9fd05241560b9f736ac53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmX@j%ge<81QKm$Gi`wMV-N=hn4pZ$VnD`ph7^Vr#vF!R#wbQch7_iB#weyrW=)ot zj6g|E##=l=sVSMo!6o@ciRr0D%s@duP3BvyMIccy_ZEA6d}2;ceEdp=&me<;`RRui zrxq3KXXO{AB<7`LCRXXYlYN|rxzvWCMIX*=jj7Y%qz)E4z4UNNzK(S zE=txfhS;MYAD@|*SrQ+wS5SG2!zMRBr8Fniu80$8Jjhwa0zl#eGb1D8T?Y9F+>#f# Lq#D_a*nxroOaMa# diff --git a/src/storage/__pycache__/redis.cpython-312.pyc b/src/storage/__pycache__/redis.cpython-312.pyc deleted file mode 100644 index 956679ca345e7c8d74cd0860fae42b846d4c4a5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8817 zcmd5?X>1%vcJ7|;p0hba%}~50wMZQtiW=&mL|JQF9!fGLQOiwC1+Xb7gVA)8YHAL; zdPd|>B%o-81?f1Dm^~;Vi&)7o3X-u(vPhf&IjFxmz{xlmx}!yuECR$pevHix)++>Y z-mC7OgB;pO76DQut6x{Ws_J_6y;rYZ@vrOZ+zg~|F8!_g?+py|Q+%-!ZxHPI4}n-> zWM-6+S=kn66Sh$si!>MKMmhTAM|pViaUo$JwbMEw?npRCoe9^d%j9(@JfkAecG(fH zOL#}Uly=5_iTcrcma#DkKha?gUVbY6$E@)-8n7{gjO@C@$nM8Bv#cS@1wEPH0G78D zS=A&hog&e3MT$qW3X#TAMA8&ZQ&UN4QX!hErIln%3Ax=vN)~uYCYH_+<)mAZZcY+q zOr4%PY4V@EsmgOwIsMD6?@1#@S0m|YT9Gb{d~0~Cb|<4Gt&T-wX-&d*=c3v*DK#dA z&C-!fA`vB7>1tN87$d5@WoOC_B?&!2!p<(jB(SrQk+Tx5pF&R!aRJ6NN!c2Z5s%5+Hp#{B8X(Rf^fxv4jll5o$T(cGWnk_1`Z6HO*l zAR-t<8;18lRLdq~u~agxOs5mkBrFdJ+H@|Q%<6SvtUI4pNE8~Zjc>NVbDRJVm)l_Q11FZk9!OdRoD*O7>?~YI!-Ji7ucj_X=?uY^1nSE3yQZ=3{k*p#9Lc=jr2k1E%%GuW6ugy%Q-SN0YJ|o#{Q3ie(bW(mP2~<0P7h z#sDI{0Ec8+jg4eMHxs=YiS?Q~+)MinO=fjxB%&tObR;sfol3=&RLCmc0b^>7klkX| z{5$9Q_Z`ohb`_cq<(dx7^S>poLk$x%tq9T#O5ximEjN@7F)YAmmYJYc)!D6g(drBeN<=awZote% zOYvRjCqeUddptD`c0%XXi-N=pj|2O7~Tg(fO z?{5D>|NN=vjcrTG#pLt)=0bf(uD)Y!_uiigT$6YH%=?2yClhE|+P}Desb{h0yP<+8 zt%}lf(SLVnVQ97a$dAtz`rgd-z4;H~V6l!7+lp?cZud*g4`bX8HwN+UbK@+(aT zfUd$C;)h}_3B*@njdX5-ixL8%12a|Nf~m$umdP@(o_1VMQU~lon<036G!{>RPVR)V zN=dpwzb#=lRSC0PaRA!{A*1qKLwUZ}zvgeG@(L}6^8Sv3*pU-EiX0;b*2IpN8p88- z_c8VfdzAmsacr|pU{>tVC+DxEIGDcx9N=1UK5@Pm@bNCLz)0FZA`N^=*zXS zv}`TY)T_n%iISBpb3d^?HZ=;Y>IUuE;u~sMSm3NROHSsk8GZG9H;+yA_B80O!?4A} zy2JEEAF;YknMSvWy0V%sP~QwpYBx+vcbnzlLz2B%z=8jy`x0Q8;}JQNn2bzlsia|O z0~T*O9gk>Wy_2#A`*>8AA8`~kq=)tZkkC*{;QtxG@y6)J0Bz_u%(PWOsJikEXsG=* zWQMu$`tPa>>V4P4{zB)mT<5X8_xL=w2Bp^*uHQ{9q}D{=niwdEojI{HFYbNrYby8- zt`Fj%&_vgj#<-tPF>$#rS z*Et)uvANuzVOI1&>o1CKAYW>G;InRrTL<>AEA8xn#INjl4d`dB>_7+qtj&&e2Mc+X zcZIm5C|?HR>rig3KSgw~Y6B8QaN}8bR=m7&CH==P>h8>?`B*+^WI1CJ=AGvs_i)uK3K!OPc5A;#O zL30%jDr$zHjW!6`1`wRgdqc4N+gp|{FJ4}XEJhxVq{~gZ^eF=`z>W-|EV@u=NOJ8sX*&;|l*>DJ_&R40|VnpAW<>`n?C{OK0P zYeY%t4uwW16rI1CO2sRU5uOSr72N>`jZ7R|nregOHM%OavC|^^te-vY=AWGi0A1xmmOQWbU@|3h2lU2NOGyAaHFhD0;n^(c zSc|<07;Jej0(hA|5QT)#GFGLfhb1&tT}vp5s}K(`S33?dEXzrCm{nD7Y8`S|>#5DO zR&6p>bupN(b^nS8ZE=+vB#C0=#3W83L>Gg=w$w?(C zM=0k^OBEt3tFA*c?RO!gfGpb4Up3s{lgrb2?+NgIn%b6bF5WCOb>*76mIF`ZA5A=( z_-K1RbSmHUMq%3<^Y#~>ZTEily>H+N`@rIXf`3oWzvtn|ldeMdo4M{c^ZvntIJhbf zBHQWrUM;b8=KP%xPcJttlf3^(K|GQZkKlp4x#(g1VfG97mb!Dsof@$HF}T>k zw<*RlWq2B**sJoQ?Qs%KLU`&D)B>DRQCWixt^!UFq9tja1H2fR;z)E#XNiXUHN}*L zDmQw}#*R5t9epWROhF?J_pPlF)zG{&v^Z2~*q3YA_fT6t`}AnOp}*kmpXWZ=-<|gz z_&x36?(gwS?nU?g!{4uasKM!K)p-EG;XCnC_Z|2J4P)Hy3-4wBm<{jb|HKK@U$%$g zc+|!NxgrUWZ?cM<4I&s9{tvC949DqN+Y{lji8xq$du6z5j(0KTwVvgzb^VZsKq-q> zitO0}XSMvP(lKh|IAELXSv%OIlkn&4#D4ewult3${D8wSZa;&8+$s2(=H6kZSi{~$ zr$M0M*t;1pT2eGADbuldM#IY%!#gV7-Qbl#)VhIzD=iaO5(M5giQel-Q)>E}6wRbl z2dNRvVoVmo_;{Zr$x2klJ0J~1yXk8RhIV6cxG(h+BXDg6T_sc*nw!@~RpUVD9I ziQe=WqnbhdkQHyhp}MRWRSrr~4K9)BEamD>usir9j5cr0fJ`K+YKnC3(xqQHI~`Ld zF-+ancj%QKsbj4dq4YpM6bhjjc=9 z7q1r@yK;@N`{WxB7U~b?>JNf3sBbR1m0!gi_?3eWq>r(f_wOE%xM$7mK%4VfivwwigK{HO%@aTL zH{&OG=@g<{tTZbPr|`Hy6u7EQ>O8)I?>GUB1ov8#p7B&P0>3i|RS*bE6N593oIG7# z8vdwg&r=tGAO|g-37GIP?s<*zp)EQJWiEoNZ;kCjm-I3Wb|q-9;CmZbX1uZ*qjfzJ z+<0E87}!QnMg&jW5Xvie|Lahob80e8@Ct(@FvIE2G|*wH`@0fk)gG`IRRfP#w+mF= zvR=l+)s{yl&pyD;m3Om~8{ zfE)F6mOA)`3x-}ec@48um|ei^TbSYAMXq9ocATh?Jz{7qnZ_0gdZsjM1w#9C$nG$o z@P~fJ+l0gGPN#5=U2m`nop42C_kM2Ugr;>4s3N8OtZ-=E>k{1Sje^jy?zIC|?+~PQ zzfIUttal4X*IIWJIehNxqt6$vx^)5Gi>=HzUMq5f&_g-!xi4tE_x01~3)hZy0m_Sw zjJ@SW=x~v15JHq0pIxwj!t;fz{c{1H_)RUJ*?=-;hLZ|zC-1@of`+54}|4*2~E6MW%noo_0*T63<}`zP|QJ-6&Xx6iR`=yL{h HI>vtkq`;d& diff --git a/src/streaming/__pycache__/__init__.cpython-312.pyc b/src/streaming/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 82807bdc2df621144e1c348d2fa14ec7947aaee6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 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%0D9MJ7&1rdc+}&M_%pj;)7x#_T`i z0Ld7JTpS1Vz<{~Vo7P)hj;UhMdGOQgkIXStM(EVnl=U*2;bu5Mq!3vD3RfK6x0(Xh ze-BvyN-9Rhgc>dN5ElGf<~nbn4){ylw~+(j#g7fV==^_^$TTt5Krzdly)>RVqeSJ{ z840nZ5)J6UQ$;Jg-7uj~#-xNuyCH)FIb1phf;h}N_Y06sht5ixR>4U{NcBe5$^_uD zN7JbkkYI2$5yRAHf2H>maC|V6Z2`|;iDhZ(8*@|Xj5;chz@7x$@( z308!SFrb-?Stl9YGpz}73*pLY!ioe>78M#5LZ(-rHi)TZ9rUWe261RIeO`iqx(6pi z|4HL1sK`$u0bRq|Y?uv_*}5pRzIE7Aq5d9@8LI${hwR?}x30f(y&Ju~>-ao>*U<#X z^7NIb^Nv7qX8_<}Zc>-I=%Ue1pGj?JsdY+de>@lBvPF z`_8sNVcVYkwmrp`wyU-)w$~hYgsy^cI4>MtB_fsz2Sd*Y^7y?zF~jS8 z0g`q!FB}6D+5&jkpZE8#_;-Ei+f`}+_OD8t{!b78T8V&cUmDyL+{@f(VutsS8=D_I z(oJrO}81_KBV7lYQ%OIZFe6A{`a~VpuE>TXc``1Z}pmh=~h3$4tr~W#rB@> z26waX?`DB@1886<^D+iiqp+hoXn?5#Vr`KFG+d}9!+o5+msrjBf!Sr&2Lu2#CK-O? zVEW-n^}-KVz4@x<6lWMFm)NLjl0;3X!4RS;ua`+QjQFDIqK^aKS$3R&Aw2?yv|`4= zXbQ8eTFH{eF$%3IkSVRe2)fK7Gt2&nz4)wtt-9w?NZR64rU^AmYzCdpF`5c0`?Y1~ zj=HpG<#e(fW5>XyJ`b*N?crB&$HtFl}xE33_inp(@C|vzyJz0~(T<*#hThj7zs4*uOSVPZpE#~Po^R+~ zw&HpB4>Mn^nTf9rU9#g?+ zUowMxxSv~~^*7sqcB5tUkigv-V6nZ20rHK#wxKrm#-Zk+7WQT%5A>TYJWy`7S)qN; zQG%nutUBQ+eN#fU{$Z&qcR5xyp|?>l&mdM3>EM$HuY)ASVHA)0eW_s9<#q31pXo+l;}FZ=G%-*I82Fg3=Z_CT zfB6fB{>`)l2=YO!5ZGycc^xNLL(Oj5@U0y)*r(z6ZycKSVVwWe#uWFxY%*Poy(WJx zAI4?$Vs#v=XRwN4mBeZatD5}vAkwfp0YBwgs4#zhbdBTq`&XSN{{E7+lOJJd{u&!# zRWN@|n|l75Htm?ScF^W=W*fh+=x-~raJ_5WvdY7C_4|yA?_0ai%J=HYb9^M^2ynsp z+5Kz={~`ji?FePMitWKJ3uXZ@%hs5$B*XN6MzE$r`EQT6i%|do