feat(tools): give every assistant the Daedalus workspace file tools
All 17 assistants can now read and write their Daedalus workspace. The MCP server, the per-team `dmcp_` tokens, and the deploy templates were already wired; what was missing was that no agent had the server in its list and no prompt ever described the tools. Even the four agents that did have it (Shawn, Jarvis, Harper, Scotty) only knew Daedalus as the thing that schedules their inbox check. Adds the tool docs (docs/tools/daedalus.md human reference, prompts/tools/daedalus.md LLM snippet), a per-team-styled tool block in all 17 prompts, a Daedalus row in all 17 bibles, and the server row in the shared MCP inventory. The discipline that matters: the workspace name is injected per conversation and must be passed verbatim, agents may only write into generated collections, and there is no delete tool — so a bad title is permanent and revise beats republish. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
107
docs/tools/daedalus.md
Normal file
107
docs/tools/daedalus.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Daedalus
|
||||
|
||||
> The chat platform the assistants speak through — and, via its MCP server, their persistent workspace document store.
|
||||
|
||||
- **MCP server name:** `daedalus`
|
||||
- **Prompt snippet:** [prompts/tools/daedalus.md](../../prompts/tools/daedalus.md)
|
||||
- **Project repo:** `/home/robert/git/daedalus`
|
||||
|
||||
## What It Is
|
||||
|
||||
Daedalus is the front door: the web and voice chat UI, the workspace/collection/file model behind it, and the scheduler that fires automations. Everywhere else in the estate Daedalus is an MCP *client* — it calls Pallas-hosted agents. The `daedalus` MCP server is the one place it acts as a **server**, and it exposes a single mount at `/mcp`.
|
||||
|
||||
For the assistants, the point is **persistence**. A conversation is ephemeral; a workspace file is not. The file tools let an agent list what Robert has uploaded, read it, publish its own finished work back, and revise that work later — which makes a workspace the assistants' shared long-term document memory, complementary to Neo4j (structured facts) and Mnemosyne (curated reading).
|
||||
|
||||
The same server also carries the **phone senses** (`read_phone_screen`, `recent_notifications`, `send_sms`), which only function during a live mobile voice session. Those are scoped to the mobile agent and are not part of the file surface every agent gets.
|
||||
|
||||
## Concepts
|
||||
|
||||
| Concept | What it means |
|
||||
|---|---|
|
||||
| **Workspace** | A named container for files, e.g. "Home", "Ouranos", "Amazon Connect". The unit of reach. |
|
||||
| **Collection** | A folder within a workspace, with a **provenance**. Every workspace has `default` (`user`) and `published` (`generated`). |
|
||||
| **Provenance** | `user` = Robert uploaded it, ground truth. `generated` = an assistant authored it, an opinion. |
|
||||
| **Scope** | The authorization realm. 1:1 with a Pallas team, M:M with workspaces — this is what decides which workspaces a team's agents can reach. |
|
||||
|
||||
**Provenance is the load-bearing distinction.** It is why agents may read everything in reach but may only *write* into `generated` collections — an assistant can never overwrite or forge one of Robert's own uploads.
|
||||
|
||||
## MCP Tools
|
||||
|
||||
All four take `workspace` as the first argument, accepting the workspace **id, slug, or display name**. Agents are told the display name, so that is what they normally pass; the resolver strips surrounding quotes, markdown emphasis, and case differences, because small models echo the injected name with decoration.
|
||||
|
||||
### `list_workspace_files(workspace, collection=None)`
|
||||
|
||||
Returns one line per file, newest first:
|
||||
|
||||
```
|
||||
- soil-moisture-notes.md (id=…, collection=published, provenance=generated, size=4213B)
|
||||
```
|
||||
|
||||
The `id` is what every other tool wants. Optionally filter to one collection slug.
|
||||
|
||||
### `read_workspace_file(workspace, file_id)`
|
||||
|
||||
Returns the file's text. Non-UTF-8 files come back as a description (`"…: 91234 bytes of application/pdf; not text-readable."`) rather than an error. Text over **200,000 characters** is truncated with a `…[truncated]` marker.
|
||||
|
||||
### `publish_generated_document(workspace, title, body_markdown, collection="published", as_of=None, sources=None)`
|
||||
|
||||
Writes a new Markdown document. The title is slugified into the filename. `provenance='generated'` and `author_agent` are taken from the authenticated identity, **never from the payload** — an agent cannot claim to be someone else or claim its output is a user upload. `as_of` defaults to now; `sources` is an optional list of references.
|
||||
|
||||
Publishing also triggers the Mnemosyne ingest hook, so a published document becomes searchable.
|
||||
|
||||
### `revise_workspace_file(workspace, file_id, body_markdown)`
|
||||
|
||||
Replaces the body of an existing generated file. Refused on `user` collections.
|
||||
|
||||
**There is no delete tool.** This is deliberate — an agent cannot destroy content. The consequence is that a badly-titled publish is permanent as a filename and can only be corrected in body text, so agents should treat the title as a commitment.
|
||||
|
||||
## Authentication and reach
|
||||
|
||||
One `dmcp_` bearer token per Pallas team, minted in Daedalus, gating the whole server. The token resolves to the team's `PallasInstance`; reach is then derived from that instance's **scope**:
|
||||
|
||||
```
|
||||
agent → team (PallasInstance) → scope → workspaces
|
||||
```
|
||||
|
||||
Both halves must be present. A team with `scope_id = NULL` reaches **nothing**, even for reads. Current filing:
|
||||
|
||||
| Team | Scope | Workspaces in reach |
|
||||
|---|---|---|
|
||||
| Iolaus (personal) | Personal | Home, Assistant, World Cup 2026 |
|
||||
| Mentor (work) | Work | Amazon Connect, Infrastructure |
|
||||
| Kottos (engineering) | Engineering | Ouranos |
|
||||
|
||||
Reach is checked on **every call**, so adding a workspace to a scope grants access immediately with no redeploy.
|
||||
|
||||
## Who Uses Daedalus
|
||||
|
||||
**Every agent on all three teams**, as the standard workspace read/write surface — the same way every agent gets `time` and `neo4j_cypher`.
|
||||
|
||||
The workspace an agent operates in is not a property of the agent; it is injected per conversation by Daedalus. The same agent speaking in the Home workspace and in the Assistant workspace files into different places. By convention Sophia's workspace is **Home**, but nothing is hardcoded.
|
||||
|
||||
## What It's Good For
|
||||
|
||||
- Persisting a finished piece of work — a memo, a plan, a research summary — past the end of the conversation
|
||||
- Reading what Robert has uploaded into the workspace under discussion
|
||||
- Dropping a long document from context and re-reading it on demand (context compaction)
|
||||
- Handing work between agents: one publishes, another reads
|
||||
- Making assistant output human-reviewable in the Daedalus UI
|
||||
|
||||
## What It's Not Good For
|
||||
|
||||
- Structured facts and relationships — that's Neo4j; a document is not a queryable node
|
||||
- Searching across everything Robert has curated — that's Mnemosyne
|
||||
- Scratch space — every publish is a real, undeleteable file
|
||||
- Storing anything non-Markdown; the publish tool takes Markdown text only
|
||||
- Deleting or tidying up — no delete tool exists
|
||||
|
||||
## Known Gotchas
|
||||
|
||||
- **Refusals are returned as ordinary tool text, not errors.** An agent that does not read the result carefully will carry on as though the write succeeded. They are logged server-side as `mcp_file_tool_refused` — grep Loki for that when a write silently did not happen.
|
||||
- **"Forbidden" is deliberately ambiguous.** A workspace outside scope and a workspace that does not exist return the identical message, so the tool surface can't be used to enumerate workspace names. When debugging, check the scope filing rather than assuming the name is wrong.
|
||||
- **The single commonest failure is a mangled workspace name.** The system line quotes the name for exactly this reason; earlier versions used markdown bold and small models echoed the asterisks.
|
||||
- **Agents may not write to `default`.** Attempts return the `provenance` refusal. `published` is the generated collection in every workspace today.
|
||||
- **`as_of` silently defaults to now**, which asserts the content is current. An agent summarizing stale material must set it explicitly.
|
||||
- **File-reach and knowledge-reach both derive from scope**, so they always agree. This was not true before mid-2026, when Mnemosyne reach came from a separate join table and could diverge from file reach.
|
||||
- **Phone senses share this server but not its auth model** — they need a live mobile session, not just a valid token, and return a plain "unavailable right now" string outside one.
|
||||
- **A stale API container serves stale tools.** Daedalus runs a bind-mounted backend; `docker compose restart api` is required after a pull, since `up -d` reloads nothing.
|
||||
Reference in New Issue
Block a user