From 705b4f8cbe9bec9fc6bf0f39a8705b377d1d701b Mon Sep 17 00:00:00 2001 From: Robert Helewka Date: Mon, 4 May 2026 15:34:51 -0400 Subject: [PATCH] Docs: update --- docs/mnemosyne_integration.md | 154 +++++++++++++++++++++------------- 1 file changed, 97 insertions(+), 57 deletions(-) diff --git a/docs/mnemosyne_integration.md b/docs/mnemosyne_integration.md index da2909a..c60a5e6 100644 --- a/docs/mnemosyne_integration.md +++ b/docs/mnemosyne_integration.md @@ -1,102 +1,142 @@ # Mnemosyne Integration — Pallas Reference -This document summarises the Pallas-specific changes required for Mnemosyne knowledge integration. The full specification lives in [`daedalus/docs/mnemosyne_integration.md`](../../daedalus/docs/mnemosyne_integration.md). +This document describes how Pallas-hosted agents connect to Mnemosyne for workspace-scoped knowledge search. The full integration specification lives in [`daedalus/docs/mnemosyne_integration.md`](../../daedalus/docs/mnemosyne_integration.md). --- ## Overview -Pallas agents gain access to Mnemosyne's content-type-aware knowledge graph as a downstream MCP server. Agents can search documents, browse libraries, retrieve items, and traverse the concept graph — all via standard MCP tool calls. +Mnemosyne is a downstream MCP server like any other from Pallas's perspective. Agents declare `"mnemosyne"` in their `servers` list; the server URL and bearer-forward opt-in live in the project's `fastagent.config.yaml`. + +What makes Mnemosyne different from other downstream servers: + +- **Workspace-scoped search.** Daedalus mints a per-turn HS256 JWT carrying the workspace UUID and sends it as `Authorization: Bearer` on the `send_message` call to Pallas. Pallas captures it in `request_bearer_token`, and the fast-agent patch (`pallas._fastagent_patch`) forwards it on outgoing calls to Mnemosyne when `forward_inbound_auth: true` is set. Mnemosyne validates the JWT and scopes all Cypher searches to that workspace. +- **The LLM never sees `workspace_id`.** The scoping is claim-driven: Mnemosyne reads the JWT claims, overwrites any `workspace_id` the model may have produced in tool arguments, and enforces containment server-side. Pallas is transparent transport. --- -## Configuration Changes +## Configuration ### fastagent.config.yaml -Add the Mnemosyne MCP server: +Add the `mnemosyne` stanza to `mcp.servers`. The only Mnemosyne-specific flag is `forward_inbound_auth: true`: ```yaml mcp: servers: - # ... existing servers (argos, neo4j_cypher, kernos, rommie, gitea, grafana) ... mnemosyne: transport: http - url: "http://puck.incus:22091/mcp" + url: "https://mnemosyne.ouranos.helu.ca/mcp/" + forward_inbound_auth: true ``` +This is already deployed in `iolaus/fastagent.config.yaml`, `kottos/fastagent.config.yaml`, and `mentor/fastagent.config.yaml` and their Ansible templates in `virgo/ansible/`. + ### Agent Definitions -#### Research Agent (port 23031) +Add `"mnemosyne"` to the `servers` list of any agent that should be able to search workspace content. Sub-agents (e.g. `research`, `tech_research`) that are orchestrated by primary agents do not need it unless they independently issue search calls. -The `knowledge` agent in the research chain gains Mnemosyne access: +**iolaus** — all primary agents have Mnemosyne access: `shawn`, `david`, `hypatia`, `watson`, `nate`, `garth`, `bourdain`, `cousteau`, `marcus`, `cristiano`, `mikael`. + +**kottos** — `harper`, `scotty`. + +**mentor** — `alan`, `ann`, `jeffrey`, `jarvis`, `aws_sa`. + +Example (from `iolaus/agents/shawn.py`): ```python -@fast.agent(name="search", servers=["argos"]) -@fast.agent(name="knowledge", servers=["neo4j_cypher", "mnemosyne"]) -@fast.chain(name="research", sequence=["search", "knowledge"], default=True) +@fast.agent( + name="shawn", + instruction=_INSTRUCTION, + servers=["argos", "mnemosyne", "neo4j_cypher", "kernos", "time"], + default=True, +) +async def _shawn(): + pass ``` -The `knowledge` agent's system instruction should guide tool selection: +--- -> Use `mnemosyne.search_knowledge` for document content retrieval — it handles chunking, vector search, re-ranking, and content-type-aware context. Use `neo4j_cypher` for graph topology queries, relationship exploration, and data not managed by Mnemosyne. +## How Bearer Forwarding Works -#### Infrastructure Agent (port 23032) +1. Daedalus mints a per-turn JWT: + ```json + { + "iss": "daedalus", + "sub": "chat", + "ws": "", + "libs": [], + "iat": , + "exp": , + "jti": "" + } + ``` -No changes — Infrastructure does not use Mnemosyne. +2. Daedalus calls Pallas's `send_message` tool with `Authorization: Bearer ` in the HTTP request headers. -#### Orchestrator (port 23033) +3. Pallas's `MultimodalAgentMCPServer` captures the token via FastMCP's `get_access_token()` into the `request_bearer_token` context variable (see `pallas/multimodal_server.py`). -```python -@fast.agent(name="research_sub", servers=["argos", "neo4j_cypher", "mnemosyne"]) -@fast.agent(name="infra_sub", servers=["kernos", "gitea", "rommie"]) -@fast.orchestrator(name="orchestrator", agents=["research_sub", "infra_sub"], - plan_type="iterative", default=True) -``` +4. The fast-agent patch in `pallas/_fastagent_patch.py` (installed at import time in `pallas/__init__.py`) wraps `_prepare_headers_and_auth`. When a server config has `forward_inbound_auth: true`, the patch reads `request_bearer_token.get()` and injects `Authorization: Bearer ` into the outgoing HTTP headers for that MCP call. -### Registry Update +5. Mnemosyne receives the same token, validates the HMAC signature against its `MCPSigningKey` table, and scopes all search Cypher queries to `ws` from the claims. -Update agent descriptions to reflect Mnemosyne access: - -```json -{ - "server": { - "name": "ca.helu.ouranos/pallas-research", - "title": "Research Agent", - "description": "Web search via Argos, knowledge graph via Neo4j, and content library search via Mnemosyne", - "version": "1.1.0", - "remotes": [ - { "type": "streamable-http", "url": "http://puck.incus:23031/mcp" } - ] - } -} -``` +The `forward_inbound_auth` flag is **per-server** — other servers in the same agent (`argos`, `neo4j_cypher`, `time`, etc.) never receive the bearer. --- ## Available Mnemosyne MCP Tools -These tools become available to agents with `mnemosyne` in their `servers` list: +These tools become available to agents with `"mnemosyne"` in their `servers` list: -| Tool | Purpose | When to Use | -|------|---------|-------------| -| `search_knowledge` | Hybrid vector + full-text + graph search with re-ranking | Document content retrieval, question answering over stored knowledge | -| `search_by_category` | Search scoped to a library type (fiction, technical, etc.) | When the user specifies or implies a content domain | -| `list_libraries` | List all knowledge libraries | Discovering what knowledge domains exist | -| `list_collections` | List collections within a library | Browsing a specific knowledge domain | -| `get_item` | Retrieve item metadata + chunk previews + concept links | Deep dive on a specific document/item | -| `get_concepts` | Traverse concept graph | Exploring relationships between topics, people, places | +| Tool | Purpose | +|------|---------| +| `search_knowledge` | Hybrid vector + full-text + graph search with re-ranking, scoped to the current workspace | +| `search_by_category` | Search within a specific library type (technical, fiction, business, etc.) | +| `list_libraries` | List accessible libraries | +| `list_collections` | List collections within a library | +| `get_item` | Retrieve item metadata, chunk previews, and concept links | +| `get_concepts` | Traverse the concept graph | + +All tools are transparently scoped to the workspace by JWT claims. An agent in workspace A cannot retrieve content from workspace B regardless of what arguments it produces. --- -## Downstream MCP Servers (Updated) +## Downstream MCP Servers -| Server | Host | URL | Used by | -|--------|------|-----|---------| -| argos | miranda.incus | `http://miranda.incus:25534/mcp` | Research, Orchestrator | -| neo4j_cypher | circe.helu.ca | `http://circe.helu.ca:22034/mcp` | Research, Orchestrator | -| **mnemosyne** | **puck.incus** | **`http://puck.incus:22091/mcp`** | **Research, Orchestrator** | -| kernos | caliban.incus | `http://caliban.incus:22021/mcp` | Infrastructure, Orchestrator | -| gitea | miranda.incus | `http://miranda.incus:25535/mcp` | Infrastructure, Orchestrator | -| rommie | caliban.incus | `http://caliban.incus:22031/mcp` | Infrastructure, Orchestrator | -| grafana | miranda.incus | `http://miranda.incus:25533/mcp` | Infrastructure | +| Server | URL | `forward_inbound_auth` | +|--------|-----|----------------------| +| mnemosyne | `https://mnemosyne.ouranos.helu.ca/mcp/` | `true` | +| argos | `http://miranda.incus:25534/mcp` | — | +| neo4j_cypher | `http://circe.helu.ca:22034/mcp` | — | +| kernos | `http://caliban.incus:22021/mcp` | — | +| gitea | `http://miranda.incus:25535/mcp` | — | +| rommie | `http://caliban.incus:22031/mcp` | — | +| grafana | `http://miranda.incus:25533/mcp` | — | + +--- + +## Provisioning (one-time, server-side) + +1. On the Mnemosyne host, generate the signing key: + ```bash + docker compose exec app python manage.py seed_signing_key --kid daedalus-1 + # Copy the printed hex secret + ``` + +2. Set on Daedalus (`.env` or Ansible vault): + ``` + DAEDALUS_MNEMOSYNE_MCP_URL=https://mnemosyne.ouranos.helu.ca/mcp/ + DAEDALUS_MNEMOSYNE_SIGNING_KID=daedalus-1 + DAEDALUS_MNEMOSYNE_SIGNING_SECRET= + DAEDALUS_MNEMOSYNE_TOKEN_TTL_SECONDS=600 + ``` + +3. Restart Daedalus and the three agent deployments (iolaus, kottos, mentor). + +The OCI vault secret is `virgo-mnemosyne-signing-secret`; Ansible injects it via `mnemosyne_signing_secret`. + +--- + +## Degraded Mode + +If Daedalus's `MNEMOSYNE_SIGNING_SECRET` is blank or `MNEMOSYNE_MCP_URL` is empty, `mint_chat_token` returns `None`. Pallas calls proceed without a bearer; Mnemosyne search is unavailable but all other agent tools continue normally. No error is surfaced to the user.