Tree Memory
Query, explore, and write to Tree's (Your Rooted Personal Assistant) memory through the MCP server.
Instruction
The query or instruction to run is: $ARGUMENTS
If no arguments are provided, ask the user what they want to know or do with Tree's memory.
Memory modes — which tools exist
The server registers its tools ONCE at boot from memory.mode (ADR-006), so the tool set tells you the mode:
| Tool |
rag |
graphrag |
What it does |
search_memory |
✅ |
✅ |
Semantic + text search. Different signature per mode (see below). |
ingest_url |
✅ |
✅ |
Ingest a web page. |
ingest_file |
✅ |
✅ |
Ingest a local file's text. |
ingest_conversation |
✅ |
✅ |
Ingest conversation text. |
search_web |
✅ |
✅ |
Live web search (Bright Data SERP). |
scrape_web |
✅ |
✅ |
Scrape URLs to markdown. |
visualize_memory_embeddings |
✅ |
✅ |
2-D map of the memory's topics (clusters of chunk embeddings). |
query_memory |
— |
✅ |
NL → MongoDB aggregation for exact/structured answers. |
deep_search_memory |
— |
✅ |
Wide search, results written to disk + a YAML index. |
visualize_memory_graph |
— |
✅ |
Render the graph as interactive HTML. |
memory_dashboard |
— |
✅ |
Graph dashboard app. |
review_list_pending / review_confirm / review_reject |
— |
✅ |
Human review of flagged duplicate entities. |
Do not work around a missing tool. In rag there are no edges, so the seven graph tools are not registered at all and calling one returns the standard unknown-tool error. If query_memory is absent, the answer is search_memory — not a retry. visualize_memory_embeddings is the exception that proves the rule: the map has no edges, so it is registered in BOTH modes.
Reading Strategy
search_memory — Default for most queries
- Open-ended or semantic queries (find related things, explore a topic). Start here when unsure — the most forgiving tool, and the only reader present in both modes
- Parameters and result differ by mode:
|
rag |
graphrag |
| Parameters |
query, top_k (default 10) |
query, top_k (default 10), max_hops (default 1), max_results (default 10), visualize |
| How |
hybrid vector + text search (RRF) over child chunks, grouped back to their parent chunks |
the same seed search, then graph expansion over edges |
| Returns |
{"parents": [...], "outcome": "found" | "nothing_found", "search_mode": "hybrid" | "text_only" | "vector_only"} — each parent has content, heading_path, parent_id, score, matched_children and the document it belongs to (title, source_uri, date) |
nodes + edges of the matched subgraph |
| Present it as |
passages grouped by document title, quoting the matched children |
entities and their relationships |
query_memory — For structured/precise questions (graphrag only)
- Counts, filters, aggregations, specific lookups ("how many tasks does Paul have?") — translates natural language to MongoDB aggregation pipelines via LLM
- Use when
search_memory is too broad or you need exact answers
deep_search_memory — For broad exploration (graphrag only, progressive disclosure)
- Runs a wider search and saves all results to disk. Parameters:
query, top_k (default 50), max_hops (default 3), session_id (optional)
- Returns a YAML index with one-line summaries — NOT the full results. Scan each entry's
context, then Read only the file paths (file field, under directory) you actually need, and summarize for the user
Visualization
visualize_memory_embeddings — both modes. Use it for "what topics are in my memory", "show me a map", "how is my memory organised". It draws the embedding map: every child chunk as a point at its stored coordinates, coloured by its cluster, each cluster carrying an LLM-written label. Pass hulls=true when the user asks to outline the clusters. Two answers you must relay verbatim rather than paper over:
No clustering run found for this user — run make memory-run-clustering-pipeline to build the embedding map. — say exactly that and offer to run the command. Do NOT fall back to search_memory and summarize topics yourself; the user asked for the map.
- A first line reading
N of M chunks have no cluster assignment (or a stale one) — run make memory-run-clustering-pipeline — repeat that line, then the rest of the answer: the map is real but under-reports the corpus, and make memory-run-clustering-pipeline is the fix.
When the answer carries a file path plus a graphs:// resource link, the client could not render the map inline: share the path if it is on the user's machine, otherwise read the linked resource and save its text as a local .html file. Never re-author the HTML.
Graph visualization — graphrag only. Use visualize=true on search_memory or query_memory when the user asks to see a graph or map out connections between entities. In rag there is no graph to draw — present the retrieved parents as text, or draw the embedding map instead.
Search loop
- At most 3
search_memory calls per user question, and every retry must change the query materially — different entities or a different angle, never a synonym. Why: a synonym lands in the same embedding neighbourhood and returns the same parents, so the retry spends a call and buys nothing.
- Good:
"Prefect deployment slots" → "free-tier limit five deployments".
- Bad:
"Prefect deployment slots" → "Prefect deployment slot".
outcome: "nothing_found" → at most ONE materially different retry, then answer "Not in memory" naming what you searched. Never fall through to search_web unless the user asked for the web. In graphrag there is no outcome field: an empty [] from search_memory / query_memory, or No results found. from deep_search_memory, IS nothing_found for this rule. Why: nothing_found means the search ran and matched nothing (a dead leg reports itself in search_mode, not as an empty result), so a third phrasing is guessing — and answering from the web passes web text off as the user's own memory.
- Good: two
nothing_founds → "Not in memory — I searched the sourdough starter decision and bread baking notes."
- Bad: two
nothing_founds → search_web("sourdough starter"), presented as what memory holds.
- Stop as soon as the answer is covered — 3 is a budget, not a target. Why: every extra call adds latency and near-duplicate passages the user has to re-read, and a specific question is usually answered by the first call.
- Good: the first call returns the decision the user asked about → answer and stop.
- Bad: the first call already answers, and two more run "to be thorough".
search_mode other than "hybrid" → prefix ONE caveat line naming the leg that was unavailable, offer to retry later, then answer anyway. For text_only use exactly: Search ran text-only — vector search was unavailable; results may miss semantic matches (vector_only is the mirror case: the text leg was down). search_mode is rag-only — graphrag never sends it, so there is no caveat to print there. Why: those parents are real but partial, and an unflagged partial answer reads as a complete one.
- Good: caveat line, then the passages, then "I can rerun this once vector search is back."
- Bad: present the
text_only passages as all of memory, or refuse to answer at all.
- Budget the loop in calls, never in tokens or context size. Why: you cannot measure your own context spend, so a size cap is unenforceable guesswork, while "at most 3 calls" is checkable in the transcript.
- Good: "that was my third
search_memory call — I answer with what I have."
- Bad: "keep searching until the retrieved passages start to feel too long."
Writing Strategy
Use these tools when the user wants to add content to memory. All three exist in both modes; what gets written differs (rag: document + chunk rows; graphrag: those plus entities and edges).
All three answer the same receipt — {"source_uri", "duplicate", "document_id", "flow_run_id", "status"}, plus an echo of the url / file_path you passed:
duplicate: true — already in memory, NOTHING was submitted: document_id names the existing document, flow_run_id is null, status is "duplicate". Say it is already known, name its source_uri, and do not resubmit.
duplicate: false — ONE pipeline run was submitted: report the source_uri and the flow_run_id, and say memory is being written out-of-band. No counts of any kind come back — never report one. Confirm later with search_memory for the new title; a nothing_found right after a submit means "not written yet", not "nothing is there".
ingest_url — Ingest a web page
- Pass the URL; the router handles plain web pages, Substack articles and YouTube videos, and the tool returns as soon as the run is submitted
source_uri may differ from the URL you passed (a YouTube link canonicalises) — quote the source_uri
- Use when: user shares a URL and wants it added to memory
ingest_file — Ingest a local file
- The server never opens the path: read the file YOURSELF and pass its text as
content (convert non-text formats to text/markdown first)
- Pass the absolute file path — it is the dedup key (
source_uri is file://<path>) — and an optional title
- Use when: user wants to add a local file to memory
ingest_conversation — Ingest conversation text
- Pass the raw conversation text and optional title
- In
graphrag it also extracts people, tasks, episodes, preferences and their relationships
- Use when: the user asks to remember a conversation — whole sessions are persisted by the SessionEnd hook, not by you
Presenting Results
- Summarize results in a human-readable way — don't dump raw JSON unless the user asks for it.
- Group by type (people, tasks, episodes, documents) when presenting mixed results; in
rag, group the parents by their document title, and in graphrag highlight the relationships between entities.
- For deep search: present the index summary first, then offer to dive into specific entries.
- On
outcome: "nothing_found", say so and name what was searched (see Search loop).
Errors
Any tool may answer {"error_type", "retryable", "message"} instead of its normal result. Act on retryable, never on the code string: true (transient infrastructure — search_unavailable, pipeline_unavailable, storage_unavailable, network_error, …) means retry the SAME call ONCE, then relay message and stop; false (invalid_input, unsupported_url, configuration_error, …) means change the input or stop — retrying it unchanged fails identically. Always relay message when you stop.
Memory Reference
Node Types (both modes write document and chunk; the rest are graphrag-only)
- document — Source documents (articles, papers, files, conversations)
- chunk — Two subtypes:
parent (4096 tokens, what retrieval returns, never embedded) and child (256 tokens, the embedded search unit). Every chunk row carries parent_id (its parent chunk's id, or the document's id for a parent) and chunk_index.
- person, organization, location, event, object — POLE+O entities (
object subtypes include task, project, topic, software, …)
- preference — User preferences; fact — free-form propositions that fit no typed relation (island nodes, no edges)
Edge Types (graphrag only)
- part_of / next — structure: child → parent → document, plus sequential order between sibling chunks at both levels
- mentions (document → person), referenced (document ↔ document), has (person → preference)
- related_to — the umbrella for LLM-extracted domain relations, discriminated by
semantic_type (has_task, experienced_by, knows, employed_by, located_at, owns, uses, …)
- same_as (confirmed duplicate entities), superseded_by (bi-temporal supersession between contradictory preferences/facts)
Source types: substack (articles/RSS), web (generic pages), youtube (videos), huggingface (ArXiv datasets), file (via ingest_file), conversation (via ingest_conversation), latent (referenced but not yet fully ingested).
1---2name: tree-memory3description: Query, explore, and write to Tree's memory. Use when the user asks to recall, search, visualize, or ingest information (people, tasks, episodes, preferences, documents, URLs, files, conversations).4---56# Tree Memory78Query, explore, and write to Tree's (Your Rooted Personal Assistant) memory through the MCP server.910## Instruction1112The query or instruction to run is: $ARGUMENTS1314If no arguments are provided, ask the user what they want to know or do with Tree's memory.1516---1718## Memory modes — which tools exist1920The server registers its tools ONCE at boot from `memory.mode` (ADR-006), so the tool set tells you the mode:2122| Tool | `rag` | `graphrag` | What it does |23|---|---|---|---|24| `search_memory` | ✅ | ✅ | Semantic + text search. **Different signature per mode** (see below). |25| `ingest_url` | ✅ | ✅ | Ingest a web page. |26| `ingest_file` | ✅ | ✅ | Ingest a local file's text. |27| `ingest_conversation` | ✅ | ✅ | Ingest conversation text. |28| `search_web` | ✅ | ✅ | Live web search (Bright Data SERP). |29| `scrape_web` | ✅ | ✅ | Scrape URLs to markdown. |30| `visualize_memory_embeddings` | ✅ | ✅ | 2-D map of the memory's topics (clusters of chunk embeddings). |31| `query_memory` | — | ✅ | NL → MongoDB aggregation for exact/structured answers. |32| `deep_search_memory` | — | ✅ | Wide search, results written to disk + a YAML index. |33| `visualize_memory_graph` | — | ✅ | Render the graph as interactive HTML. |34| `memory_dashboard` | — | ✅ | Graph dashboard app. |35| `review_list_pending` / `review_confirm` / `review_reject` | — | ✅ | Human review of flagged duplicate entities. |3637**Do not work around a missing tool.** In `rag` there are no edges, so the seven graph tools are not registered at all and calling one returns the standard unknown-tool error. If `query_memory` is absent, the answer is `search_memory` — not a retry. `visualize_memory_embeddings` is the exception that proves the rule: the map has no edges, so it is registered in BOTH modes.3839---4041## Reading Strategy4243### `search_memory` — Default for most queries44- Open-ended or semantic queries (find related things, explore a topic). **Start here when unsure** — the most forgiving tool, and the only reader present in both modes45- Parameters and result differ by mode:4647| | `rag` | `graphrag` |48|---|---|---|49| Parameters | `query`, `top_k` (default 10) | `query`, `top_k` (default 10), `max_hops` (default 1), `max_results` (default 10), `visualize` |50| How | hybrid vector + text search (RRF) over **child chunks**, grouped back to their **parent chunks** | the same seed search, then graph expansion over edges |51| Returns | `{"parents": [...], "outcome": "found" \| "nothing_found", "search_mode": "hybrid" \| "text_only" \| "vector_only"}` — each parent has `content`, `heading_path`, `parent_id`, `score`, `matched_children` and the `document` it belongs to (`title`, `source_uri`, `date`) | nodes + edges of the matched subgraph |52| Present it as | passages grouped by document title, quoting the matched children | entities and their relationships |5354### `query_memory` — For structured/precise questions (graphrag only)55- Counts, filters, aggregations, specific lookups ("how many tasks does Paul have?") — translates natural language to MongoDB aggregation pipelines via LLM56- Use when `search_memory` is too broad or you need exact answers5758### `deep_search_memory` — For broad exploration (graphrag only, progressive disclosure)59- Runs a wider search and saves **all** results to disk. Parameters: `query`, `top_k` (default 50), `max_hops` (default 3), `session_id` (optional)60- Returns a **YAML index** with one-line summaries — NOT the full results. Scan each entry's `context`, then `Read` only the file paths (`file` field, under `directory`) you actually need, and summarize for the user6162### Visualization6364**`visualize_memory_embeddings` — both modes.** Use it for "what topics are in my memory", "show me a map", "how is my memory organised". It draws the **embedding map**: every child chunk as a point at its stored coordinates, coloured by its cluster, each cluster carrying an LLM-written label. Pass `hulls=true` when the user asks to outline the clusters. Two answers you must relay verbatim rather than paper over:6566- `No clustering run found for this user — run make memory-run-clustering-pipeline to build the embedding map.` — say exactly that and offer to run the command. Do NOT fall back to `search_memory` and summarize topics yourself; the user asked for the map.67- A first line reading `N of M chunks have no cluster assignment (or a stale one) — run make memory-run-clustering-pipeline` — repeat that line, then the rest of the answer: the map is real but under-reports the corpus, and `make memory-run-clustering-pipeline` is the fix.6869When the answer carries a file path plus a `graphs://` resource link, the client could not render the map inline: share the path if it is on the user's machine, otherwise read the linked resource and save its text as a local `.html` file. Never re-author the HTML.7071**Graph visualization — `graphrag` only.** Use `visualize=true` on `search_memory` or `query_memory` when the user asks to see a graph or map out connections between entities. In `rag` there is no graph to draw — present the retrieved parents as text, or draw the embedding map instead.7273---7475## Search loop76771. **At most 3 `search_memory` calls per user question, and every retry must change the query materially** — different entities or a different angle, never a synonym. Why: a synonym lands in the same embedding neighbourhood and returns the same parents, so the retry spends a call and buys nothing.78 - Good: `"Prefect deployment slots"` → `"free-tier limit five deployments"`.79 - Bad: `"Prefect deployment slots"` → `"Prefect deployment slot"`.802. **`outcome: "nothing_found"` → at most ONE materially different retry, then answer "Not in memory" naming what you searched.** Never fall through to `search_web` unless the user asked for the web. In `graphrag` there is no `outcome` field: an empty `[]` from `search_memory` / `query_memory`, or `No results found.` from `deep_search_memory`, IS `nothing_found` for this rule. Why: `nothing_found` means the search ran and matched nothing (a dead leg reports itself in `search_mode`, not as an empty result), so a third phrasing is guessing — and answering from the web passes web text off as the user's own memory.81 - Good: two `nothing_found`s → "Not in memory — I searched the sourdough starter decision and bread baking notes."82 - Bad: two `nothing_found`s → `search_web("sourdough starter")`, presented as what memory holds.833. **Stop as soon as the answer is covered — 3 is a budget, not a target.** Why: every extra call adds latency and near-duplicate passages the user has to re-read, and a specific question is usually answered by the first call.84 - Good: the first call returns the decision the user asked about → answer and stop.85 - Bad: the first call already answers, and two more run "to be thorough".864. **`search_mode` other than `"hybrid"` → prefix ONE caveat line naming the leg that was unavailable, offer to retry later, then answer anyway.** For `text_only` use exactly: `Search ran text-only — vector search was unavailable; results may miss semantic matches` (`vector_only` is the mirror case: the text leg was down). `search_mode` is rag-only — `graphrag` never sends it, so there is no caveat to print there. Why: those parents are real but partial, and an unflagged partial answer reads as a complete one.87 - Good: caveat line, then the passages, then "I can rerun this once vector search is back."88 - Bad: present the `text_only` passages as all of memory, or refuse to answer at all.895. **Budget the loop in calls, never in tokens or context size.** Why: you cannot measure your own context spend, so a size cap is unenforceable guesswork, while "at most 3 calls" is checkable in the transcript.90 - Good: "that was my third `search_memory` call — I answer with what I have."91 - Bad: "keep searching until the retrieved passages start to feel too long."9293---9495## Writing Strategy9697Use these tools when the user wants to add content to memory. All three exist in both modes; what gets written differs (`rag`: document + chunk rows; `graphrag`: those plus entities and edges).9899**All three answer the same receipt** — `{"source_uri", "duplicate", "document_id", "flow_run_id", "status"}`, plus an echo of the `url` / `file_path` you passed:100- `duplicate: true` — already in memory, NOTHING was submitted: `document_id` names the existing document, `flow_run_id` is null, `status` is `"duplicate"`. Say it is already known, name its `source_uri`, and do not resubmit.101- `duplicate: false` — ONE pipeline run was submitted: report the `source_uri` and the `flow_run_id`, and say memory is being written out-of-band. No counts of any kind come back — never report one. Confirm later with `search_memory` for the new title; a `nothing_found` right after a submit means "not written yet", not "nothing is there".102103### `ingest_url` — Ingest a web page104- Pass the URL; the router handles plain web pages, Substack articles and YouTube videos, and the tool returns as soon as the run is submitted105- `source_uri` may differ from the URL you passed (a YouTube link canonicalises) — quote the `source_uri`106- Use when: user shares a URL and wants it added to memory107108### `ingest_file` — Ingest a local file109- The server never opens the path: read the file YOURSELF and pass its text as `content` (convert non-text formats to text/markdown first)110- Pass the absolute file path — it is the dedup key (`source_uri` is `file://<path>`) — and an optional title111- Use when: user wants to add a local file to memory112113### `ingest_conversation` — Ingest conversation text114- Pass the raw conversation text and optional title115- In `graphrag` it also extracts people, tasks, episodes, preferences and their relationships116- Use when: the user asks to remember a conversation — whole sessions are persisted by the SessionEnd hook, not by you117118---119120## Presenting Results121122- Summarize results in a human-readable way — don't dump raw JSON unless the user asks for it.123- Group by type (people, tasks, episodes, documents) when presenting mixed results; in `rag`, group the parents by their document title, and in `graphrag` highlight the relationships between entities.124- For deep search: present the index summary first, then offer to dive into specific entries.125- On `outcome: "nothing_found"`, say so and name what was searched (see Search loop).126127### Errors128129Any tool may answer `{"error_type", "retryable", "message"}` instead of its normal result. Act on `retryable`, never on the code string: `true` (transient infrastructure — `search_unavailable`, `pipeline_unavailable`, `storage_unavailable`, `network_error`, …) means retry the SAME call ONCE, then relay `message` and stop; `false` (`invalid_input`, `unsupported_url`, `configuration_error`, …) means change the input or stop — retrying it unchanged fails identically. Always relay `message` when you stop.130131---132133## Memory Reference134135### Node Types (both modes write `document` and `chunk`; the rest are graphrag-only)136- **document** — Source documents (articles, papers, files, conversations)137- **chunk** — Two subtypes: **`parent`** (~4096 tokens, what retrieval returns, never embedded) and **`child`** (~256 tokens, the embedded search unit). Every chunk row carries `parent_id` (its parent chunk's id, or the document's id for a parent) and `chunk_index`.138- **person**, **organization**, **location**, **event**, **object** — POLE+O entities (`object` subtypes include `task`, `project`, `topic`, `software`, …)139- **preference** — User preferences; **fact** — free-form propositions that fit no typed relation (island nodes, no edges)140141### Edge Types (graphrag only)142- **part_of** / **next** — structure: child → parent → document, plus sequential order between sibling chunks at both levels143- **mentions** (document → person), **referenced** (document ↔ document), **has** (person → preference)144- **related_to** — the umbrella for LLM-extracted domain relations, discriminated by `semantic_type` (`has_task`, `experienced_by`, `knows`, `employed_by`, `located_at`, `owns`, `uses`, …)145- **same_as** (confirmed duplicate entities), **superseded_by** (bi-temporal supersession between contradictory preferences/facts)146147**Source types:** `substack` (articles/RSS), `web` (generic pages), `youtube` (videos), `huggingface` (ArXiv datasets), `file` (via `ingest_file`), `conversation` (via `ingest_conversation`), `latent` (referenced but not yet fully ingested).