docs(personal): restructure bourdain docs to separate system prompt

Refactor documentation to distinguish character reference from AI
system prompt. Removed user context and persona definitions.
System prompt instructions moved to prompts/personal/bourdain.md.
This commit is contained in:
2026-05-21 06:53:04 -04:00
parent d556ef9409
commit b7e0dc927f
22 changed files with 1851 additions and 2541 deletions

95
docs/tools/kairos.md Normal file
View File

@@ -0,0 +1,95 @@
# Kairos
> Calendar, events, tasks, and contacts — Robert's personal productivity system of record.
- **MCP server name:** `kairos`
- **Prompt snippet:** [prompts/tools/kairos.md](../../prompts/tools/kairos.md)
## What It Is
Kairos covers Robert's calendars, events, tasks, and contacts. It's the system of record for personal scheduling and relationship logistics — the layer that holds the actual calendar entries, the actual contact records, the actual task lists. Tools are async, use ISO 8601 for dates/datetimes, and record Prometheus metrics via `record_tool_call()`.
Kairos holds the **raw records**; Neo4j holds the **agents' interpretation** layered on top. For contacts that means Kairos has the phone numbers and emails, while Shawn's Neo4j `Contact` nodes hold the relationship strength, last-contact tracking, and notes. Both are legitimate; they serve different purposes.
## MCP Capabilities
Kairos exposes async MCP tools across three areas. Coverage is incremental — check `tools/list` for the current set; the table below reflects what's available now.
### Calendar (6 tools)
| Tool | Mutating | Purpose |
|---|---|---|
| `list_calendars` | No | List calendars with sync/capability metadata |
| `list_events` | No | List events with optional calendar/date filters |
| `get_event` | No | Fetch a single event with full detail |
| `create_event` | Yes | Create an event on a calendar |
| `update_event` | Yes | Partial update of an event |
| `delete_event` | Yes | Delete an event |
### Tasks (5 tools)
Note: "Projects" in Kairos aren't a separate model — a project is a `Task` with `task_type='PROJECT'`. Project features (subtasks, dependencies, milestones, Gantt dates, comments, attachments) live on the Task model.
| Tool | Mutating | Purpose |
|---|---|---|
| `list_tasks` | No | Filter by status, priority, calendar, parent, date, starred |
| `get_task` | No | Single task incl. subtask and dependency IDs |
| `create_task` | Yes | Create a task or subtask |
| `update_task` | Yes | Partial update |
| `delete_task` | Yes | Delete task (cascades to subtasks) |
### Contacts (8 tools)
| Tool | Mutating | Purpose |
|---|---|---|
| `list_contacts` | No | Search across name, email, phone, org |
| `get_contact` | No | Contact with phones/emails/addresses |
| `create_contact` | Yes | Create a contact |
| `update_contact` | Yes | Partial update |
| `delete_contact` | Yes | Delete contact (cascade) |
| `add_contact_phone` | Yes | Add a phone number |
| `add_contact_email` | Yes | Add an email address |
| `add_contact_address` | Yes | Add a postal address |
### Coverage roadmap (not yet exposed via MCP)
The Kairos web UI supports more than the MCP currently exposes. Known gaps:
- **Calendars** — create, update, delete, sync trigger
- **Tasks** — dependency add/remove, comments CRUD, attachments list/delete, alerts, templates, project helpers
- **Contacts** — phone/email/address update and delete, address book operations
- **Cross-cutting** — tags CRUD, notifications, sync observability (ICS imports, sync logs)
If MCP discovery doesn't surface a tool you expected, MCP coverage may not include it yet. Surface the gap rather than confabulating a workaround.
## Who Uses Kairos
- **Shawn** — heavy, primary. Contact records, calendar events, communication tracking. Shawn's Neo4j Contact/Event/Communication nodes hold the *interpretation* (relationship strength, follow-up state); Kairos holds the actual entries.
- **Watson** — read-heavy. Pulls contact context (who Robert is talking about) and event history (life events, important dates) to inform emotional/relational work. Writes only when adding life events Watson is tracking.
- **Cristiano** — calendar only. Match dates, tournament schedules. Reads to know what's coming up; writes when Robert decides to attend something.
- **Nate** — calendar. Trip windows, blackout dates, scheduling around travel.
Other agents may read Kairos when their work intersects with personal logistics, but the three above are the primary users.
## What It's Good For
- Looking up who someone is before drafting a message
- Checking the calendar for conflicts before committing
- Creating events, tasks, contact records that need to live in the canonical store
- Pulling contact context (timezone, notes, history) for any conversation about a person
- Task tracking that needs to persist (vs. transient `Task` Neo4j nodes for cross-domain context)
## What It's Not Good For
- Emotional/relational interpretation — that's Watson's Neo4j nodes
- Cross-domain linking — Kairos is its own database; the cross-references between contacts, trips, training, finance, etc. live in Neo4j
- Synthesis or analysis — Kairos returns records; you interpret them
- Engineering or work logistics — Kairos is personal-scope; work and engineering have their own tools
## Known Gotchas
- **Mutating tools touch real records.** A `delete_contact` is not undone by walking it back in conversation. Confirm before mutating, especially for cascades (deleting a task cascades to subtasks; deleting a contact cascades to phones/emails/addresses).
- **Dependency between Kairos and Neo4j writes.** When creating a `Contact` in Kairos, also update Shawn's Neo4j `Contact` node if relationship-interpretation fields apply (importance, last_contact, notes). Otherwise the two stores drift.
- **ISO 8601 dates and datetimes.** Always pass dates and datetimes in ISO format. Time zones matter — specify explicitly when not in Robert's local time.
- **Tasks don't have a separate Project entity.** Projects are Tasks with `task_type='PROJECT'`. Use `list_tasks` with appropriate filtering, not a hypothetical `list_projects`.
- **Coverage is incremental.** The set of mutating operations expands over time. Don't assume a missing tool means the operation isn't supported in the underlying system — it may just not have an MCP wrapper yet.

View File

@@ -1,46 +1,193 @@
# Mnemosyne
> Multimodal personal knowledge base — text, images, and graph-structured content.
> Multimodal personal knowledge base — Robert's curated content across many domains, retrieved through a content-type-aware MCP surface.
- **MCP server name:** `mnemosyne` (runs in the lab; FastMCP at `/mcp` on its own host)
- **MCP server name:** `mnemosyne`
- **Prompt snippet:** [prompts/tools/mnemosyne.md](../../prompts/tools/mnemosyne.md)
- **Project repo:** `/home/robert/git/mnemosyne` (full README, architecture docs)
- **Project repo:** `/home/robert/git/mnemosyne`
## What It Is
Mnemosyne is "the memory of everything you know" — a content-type-aware multimodal knowledge management system built on Neo4j vectors and Qwen3-VL embeddings. Unlike a generic vector store, Mnemosyne knows what *kind* of thing a document is (a novel, a textbook, an album, a journal entry, a business proposal) and adjusts chunking, embedding, and retrieval accordingly.
Mnemosyne is "the memory of everything you know" — Robert's content-type-aware multimodal knowledge management system built on Neo4j vector storage and Qwen3-VL embeddings. Unlike a generic vector store, Mnemosyne knows what *kind* of thing a document is (a novel, a textbook, an album, a journal entry, a business proposal) and adjusts chunking, embedding, and retrieval accordingly.
It is a **retrieval engine**, not a synthesis engine. It returns ranked chunks plus metadata; the calling agent does its own synthesis. Architecturally this is intentional — letting the LLM see chunks and pivot mid-search beats pre-digesting answers server-side.
It is a **retrieval surface, not a synthesis engine**. Tools return ranked evidence — chunks plus metadata. The calling agent reads the chunks and forms the answer, citing chunk UIDs back so Robert can trace what informed the response.
## Concepts
**Library** — the top-level container. Each library has a `library_type` that drives chunking, embedding, and re-ranking strategy.
**Collection** — a named group of items inside a library (a novel series, a multi-volume manual).
**Item** — an indexed document or file. Only items with `embedding_status = "completed"` appear in search results.
**Chunk** — a text segment of an item. `search` returns a `text_preview` (~500 chars); use `get_chunk` for the full text.
## Library Types
| `library_type` | Content |
|---|---|
| `fiction` | Novels, short stories. Cover art available. |
| `nonfiction` | General non-fiction prose. |
| `technical` | Manuals, textbooks, docs. Diagrams and code-like content. |
| `music` | Lyrics, liner notes, album artwork. |
| `film` | Scripts, synopses, stills. |
| `art` | Catalogs, descriptions, the artwork itself. |
| `journal` | Personal entries; temporal/reflective. |
| `business` | Proposals, marketing, sales, strategy. Commercial context. |
| `finance` | Statements, tax, market commentary. Quote figures exactly. |
**Scoping queries to the right library_type matters.** A search for "Stoic philosophy" against the `finance` library returns useless results.
## MCP Tools
### Recommended workflow
```
list_libraries
→ search(query, library_type=..., library_uid=...)
→ get_chunk(chunk_uid) # only when text_preview is insufficient
```
### `search`
Hybrid retrieval: vector + full-text + concept-graph candidates fused by RRF (Reciprocal Rank Fusion), with optional Synesis re-ranking.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `query` | str | required | The search query |
| `library_uid` | str \| None | None | Restrict to one library by UID |
| `library_type` | str \| None | None | Restrict by library type (table above) |
| `collection_uid` | str \| None | None | Restrict to one collection by UID |
| `limit` | int | 20 | Max candidates to return |
| `rerank` | bool | True | Apply Synesis re-ranking |
| `include_images` | bool | True | Include matching images in the response |
| `search_types` | list[str] \| None | `["vector", "fulltext", "graph"]` | Which retrieval strategies to run |
Returns:
```json
{
"query": "...",
"candidates": [
{
"chunk_uid": "...",
"item_uid": "...",
"item_title": "...",
"library_type": "...",
"text_preview": "... (~500 chars) ...",
"score": 0.92,
"source": "vector|fulltext|graph"
}
],
"images": [...],
"total_candidates": 42,
"search_time_ms": 85,
"reranker_used": true,
"reranker_model": "...",
"search_types_used": ["vector", "fulltext", "graph"]
}
```
### `get_chunk`
Full text of a single chunk by UID. Use when `text_preview` is insufficient.
| Parameter | Type | Description |
|---|---|---|
| `chunk_uid` | str | The chunk UID from a `search` result |
### `list_libraries`
Enumerate libraries the caller is authorized to read.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `limit` | int | 50 | Max libraries (capped at 200) |
| `offset` | int | 0 | Pagination offset |
### `list_collections`
Enumerate collections, optionally filtered to one library.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `library_uid` | str \| None | None | Filter to one parent library |
| `limit` | int | 50 | Max collections (capped at 200) |
| `offset` | int | 0 | Pagination offset |
### `list_items`
Enumerate indexed documents or files. Check `embedding_status` — only `"completed"` items appear in search.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `collection_uid` | str \| None | None | Filter to one collection |
| `library_uid` | str \| None | None | Filter to one library |
| `limit` | int | 50 | Max items (capped at 200) |
| `offset` | int | 0 | Pagination offset |
### `get_health`
Pallas-compatible health probe. No auth required.
```json
{
"status": "ok | degraded | error",
"checks": {
"neo4j": {"status": "ok", "duration_ms": 2.1},
"s3": {"status": "ok", "duration_ms": 8.4},
"embedding": {"status": "ok", "model": "...", "duration_ms": 0.3}
}
}
```
Neo4j or S3 failures → `error` (critical). Missing or unconfigured embedding model → `degraded` (non-critical).
## Authentication
All tools except `get_health` require a `Bearer` token in the `Authorization` header. Three credential types:
| Type | Issued by | Lifetime | Scope |
|---|---|---|---|
| **Opaque `MCPToken`** | Mnemosyne admin | Long-lived (optional expiry) | `allowed_libraries` list on the token row; per-tool ACL available |
| **Per-turn JWT** (`iss=daedalus`) | Daedalus chat | ≤10 minutes | `libs` claim (list of Library UIDs) |
| **Team JWT** (`iss=mnemosyne`, `typ=team`) | Mnemosyne | 10-year lifetime | Resolved live from `TeamWorkspaceAssignment` → Neo4j `Library.workspace_id`. Revoked via `active_jti` rotation. |
Every authenticated request resolves to a `resolved_libraries` list — the set of Library UIDs the caller may read. Tools enforce this list at the query layer. Empty list = authenticated but sees nothing (fail-closed). No auth = also fail-closed.
## Who Uses Mnemosyne
All regular agents have access via team-based authentication. Each team's token resolves to the libraries appropriate for that team's domain:
- **Personal team** — all personal-relevant libraries (fiction, nonfiction, technical, music, film, art, journal, business, finance). Each agent self-filters by `library_type` based on their domain.
- **Work team** — business-focused libraries; supporting reference (Ann reaches for nonfiction; Alan reaches for business strategy material).
- **Engineering team** — technical libraries and reference (Harper for build references; Scotty for runbooks and incident records).
Within a team, each agent is responsible for searching the right `library_type` for their work — there's no per-agent ACL inside a team token. Searching the wrong library type returns useless results, not an error.
## What It's Good For
- Searching the user's personal knowledge base across libraries (fiction, nonfiction, technical, music, film, art, journal, business, finance)
- Multimodal queries — find a book cover, an album sleeve, a screenshot, alongside text
- Searching Robert's curated knowledge across libraries — books, music, journal entries, business documents, reference material
- Multimodal queries — find a book cover, an album sleeve, a screenshot alongside text
- "Did I read something about X" / "what did I write about Y on what date"
- Pulling source material the user has actually curated, rather than guessing from training data
- Following graph relationships (Author → Book → Topic; Artist → Album → Track)
- Pulling source material Robert has actually curated, rather than guessing from training data
- Following graph relationships through the underlying Neo4j vector store (Author → Book → Topic; Artist → Album → Track)
## What It's Not Good For
- General web knowledge — that's Argos
- Anything not already in the KB — Mnemosyne only knows what's been ingested
- Anything not yet ingested — Mnemosyne only knows what's been indexed
- Synthesis or "give me the answer" — Mnemosyne returns chunks; the calling agent synthesizes
- Real-time information (status, news) — content is ingested, not live
## MCP Tools Exposed
| Tool | Purpose |
|---|---|
| `search` | Hybrid search (vector + graph + full-text), re-ranked |
| `get_chunk` | Retrieve the full text of a chunk by ID |
| `list_libraries` | What libraries exist (fiction, technical, etc.) |
| `list_collections` | Collections within a library |
| `list_items` | Items within a collection |
| `get_health` | Service health probe |
- Writing — Mnemosyne is a retrieval surface; ingestion happens through Daedalus and admin tooling
## Known Gotchas
- **It's retrieval, not answers.** A `search` call returns chunks; the agent then has to read them and form the answer. Don't expect Mnemosyne to "tell you" something.
- **Library type matters.** Searching the *fiction* library for technical content returns nothing useful. Use `list_libraries` first if uncertain.
- **Citations should be preserved.** Mnemosyne returns chunk IDs and source metadata — when synthesizing, cite back to the chunk so the user can verify and trace.
- **Empty results may mean the index isn't ready.** If `setup_neo4j_indexes` hasn't been run for a given environment, vector search returns empty results and the app logs a readiness warning. Surface that, don't silently confabulate.
- **It's retrieval, not answers.** Always cite `chunk_uid` so Robert can verify.
- **`library_type` matters.** Searching the wrong library type returns nothing useful. Use `list_libraries` if uncertain.
- **`text_preview` is ~500 chars.** Often enough for the agent to decide whether the chunk is relevant; not enough for synthesis. Call `get_chunk` for the full text only when you need it.
- **Only `embedding_status = "completed"` items appear in search.** A library with items in progress will show fewer results than `list_items` suggests.
- **Empty results may mean the index isn't ready in this environment.** `get_health` will report `degraded` if the embedding model is missing. Surface that, don't silently confabulate.
- **Fail-closed auth.** No token = no results. Empty allowed-library list = also no results. Distinguish "I searched and found nothing" from "I'm not authorized" — `list_libraries` returning an empty set is the tell for the latter.
- **`include_images=True` by default.** When images aren't relevant, set it to False to reduce noise and tokens.
- **Re-ranking has a cost.** `rerank=True` (default) gives better precision but adds latency. For exploratory queries, `rerank=False` is fine; for the query that produces the final answer, leave reranking on.

View File

@@ -1,14 +1,14 @@
# Neo4j Knowledge Graph — Personal Team
You have access to a unified Neo4j knowledge graph shared across fifteen AI assistants (9 personal, 4 work, 2 engineering).
You have access to a unified Neo4j knowledge graph shared across all assistants (10 personal, 5 work, 3 engineering).
## Principles
1. **Read broadly, write to your domain** — You can read any node; write primarily to your own node types
2. **Always MERGE on `id`** — Check before creating to avoid duplicates
3. **Use consistent IDs** — Format: `{type}_{identifier}_{qualifier}` (e.g., `trip_costarica_2025`, `recipe_carbonara_classic`)
3. **Use consistent IDs** — Format: `{type}_{identifier}_{qualifier}` (e.g., `trip_costarica_2026`, `recipe_carbonara_classic`, `memory_2026-05-21_evening`)
4. **Always set timestamps**`created_at` on CREATE, `updated_at` on every SET
5. **Use `domain` on universal nodes** — Person, Location, Event, Topic, Goal take `domain: 'personal'|'work'|'both'`
5. **Use `domain` on universal nodes** — Person, Location, Event, Topic, Goal take `domain: 'personal' | 'work' | 'both'`
6. **Link to existing nodes** — Connect across domains; that's the graph's power
## Standard Patterns
@@ -27,25 +27,30 @@ MATCH (a:TypeA {id: 'a_id'}), (b:TypeB {id: 'b_id'})
MERGE (a)-[:RELATIONSHIP]->(b)
```
## Your Team's Node Ownership
## Personal Team Node Ownership
| Assistant | Domain | Owns |
|-----------|--------|------|
| **Shawn** | General assistant (calendar, contacts, email) | Contact, Event, Communication |
| **Nate** | Travel & Adventure | Trip, Destination, Activity |
| **Hypatia** | Learning & Reading | Book, Author, LearningPath, Concept, Quote |
| **Marcus** | Fitness & Training | Training, Exercise, Program, PersonalRecord, BodyMetric |
| **Seneca** | Reflection & Wellness | Reflection, Value, Habit, LifeEvent, Intention |
| **Watson** | Relationship memory & emotional safety | Reflection, Value, Habit, LifeEvent, Intention, EmotionalMemory, RelationshipTheme, DialogueNote, DynamicPattern |
| **Bourdain** | Food & Cooking | Recipe, Restaurant, Ingredient, Meal, Technique |
| **Bowie** | Arts & Culture | Music, Film, Artwork, Playlist, Artist, Style |
| **David** | Arts & Culture | Music, Film, Artwork, Playlist, Artist, Style, Fashion |
| **Cousteau** | Nature & Living Things | Species, Plant, Tank, Garden, Ecosystem, Observation |
| **Garth** | Personal Finance | Account, Investment, Asset, Liability, Budget, FinancialGoal |
| **Cristiano** | Football | Match, Team, League, Tournament, Player, Season |
### Replaced agents
Watson replaces Seneca (as of 2026-04-28); Watson inherited Seneca's node types (Reflection, Value, Habit, LifeEvent, Intention) with a warmer, less goal-oriented framing. David replaces Bowie; David inherited Bowie's node types (Music, Film, Artwork, Playlist, Artist, Style) and added Fashion.
## Cross-Team Reads
- **Work team:** Skills, Projects, Clients (for context on professional life)
- **Engineering:** Infrastructure status, Prototypes (for automation ideas)
- **Universal nodes:** Person, Location, Event, Topic, Goal (shared by all)
- **Work team:** Skill, Certification, Project, Client (context on professional life)
- **Engineering team:** Infrastructure (services Robert depends on), Prototype (automation ideas)
- **Universal nodes:** Person, Location, Event, Topic, Goal (shared by all; carry `domain` property)
## Full Schema Reference

118
docs/tools/nike.md Normal file
View File

@@ -0,0 +1,118 @@
# Nike
> Live football (soccer) data — teams, players, fixtures, results, standings.
- **MCP server name:** `nike`
- **Prompt snippet:** [prompts/tools/nike.md](../../prompts/tools/nike.md)
## What It Is
Nike is the live football data source — backed by [TheSportsDB](https://www.thesportsdb.com/). All tools are **read-only**: team profiles, rosters, player info, fixtures, results, standings, match detail, live scores. Transport is HTTP Streamable at `/mcp/`.
Named for the Greek goddess of victory, fitting for a tool whose job is to track who's winning.
Nike sits next to Cristiano's Neo4j domain rather than overlapping with it. Nike provides the **canonical live data** — current standings, who's playing tomorrow, who scored on what date. Cristiano's Neo4j `Match`, `Team`, `Player` nodes hold the **interpretation** — what Robert thought of a match, which players he's following, tactical observations. The two stores serve different purposes.
## MCP Tools
All tools are read-only. The set below reflects what's currently exposed.
### Teams and rosters
| Tool | Purpose |
|---|---|
| `get_team_info` | Team profile — stadium, capacity, location, founded year, colors, short description |
| `get_roster` | Current squad grouped by position (Goalkeepers → Defenders → Midfielders → Attackers) |
### Players
| Tool | Purpose |
|---|---|
| `get_player_info` | Player profile — position, nationality, DOB, current team, status; with premium key adds height, weight, squad number, biography |
### Fixtures and results
| Tool | Purpose |
|---|---|
| `get_fixtures` | Recent results and upcoming matches for a team (filter: `all`, `upcoming`, or `past`) |
| `get_match_result` | Result of a match for a team on a specific date — score, venue, referee, attendance, status |
| `get_match_detail` | Full match detail — statistics, lineups, substitutes, minute-by-minute timeline (goals, cards, subs). **Premium required.** |
### Standings and live scores
| Tool | Purpose |
|---|---|
| `get_standings` | League table — points, goal difference, current form. League aliases supported (see below). |
| `get_livescores` | Live scores worldwide, grouped by league. **Premium required.** |
### Prompts
| Prompt | Purpose |
|---|---|
| `football_analyst` | Primes the assistant with football analyst context — platform description, followed teams, tool summary. Use at session start to skip manual setup. |
## League Aliases
`get_standings` accepts league aliases — useful when the underlying name is verbose.
| Alias | League |
|---|---|
| `MLS`, `Major League Soccer`, `American Major League Soccer` | MLS (ID 4346) |
| `EPL`, `Premier League`, `English Premier League` | Premier League (ID 4328) |
For other leagues, pass the full name and Nike will attempt to resolve it automatically.
## Season Format
Season format varies by league:
- **MLS:** single year — `"2026"`
- **European leagues (Premier League, etc.):** hyphenated season — `"2025-2026"`
Get this wrong and the standings call returns nothing useful.
## Premium vs. Free Tier
Nike uses a TheSportsDB API key set via the `SPORTSDB_KEY` environment variable. The free key is `3`. Some tools require a premium key.
| Tool | Free tier | Premium required |
|---|---|---|
| `get_team_info` | ✓ | |
| `get_roster` | Cached data only | Live V2 squad |
| `get_player_info` | Basic profile | Height, weight, number, bio |
| `get_fixtures` | ✓ | |
| `get_standings` | ✓ | |
| `get_match_result` | ✓ | |
| `get_match_detail` | — | ✓ (required) |
| `get_livescores` | — | ✓ (required) |
When a premium-required tool is called on a free key, it returns an error message rather than silently degrading.
## Who Uses Nike
- **Cristiano** — exclusive. Football is Cristiano's domain; Nike is his live-data source.
Other agents reference football work through Cristiano (via the messaging system) rather than calling Nike directly.
## What It's Good For
- Looking up the actual current standings before talking about a league
- Pulling fixtures for a team to know what's coming up
- Confirming a match result rather than relying on training data (which may be stale)
- Walking through a specific match in detail — lineups, timeline, statistics (premium)
- Live scores during active match windows (premium)
## What It's Not Good For
- Storage — Nike is read-only; what Robert thought of a match goes in Cristiano's Neo4j `Match` node, not back into Nike
- Cross-team or cross-domain reads — Nike is single-purpose for football
- Tactical analysis — Nike returns the data; Cristiano interprets it
- Anything beyond football
## Known Gotchas
- **Get the season format right.** MLS uses `"2026"`; European leagues use `"2025-2026"`. Wrong format → empty results.
- **Free tier limits.** `get_match_detail` and `get_livescores` return errors on the free key. Don't try to work around it — surface the limitation to Robert.
- **`get_match_detail` requires an event ID.** Workflow: `get_fixtures` first to find the event ID, then `get_match_detail` with that ID.
- **TheSportsDB data quality varies by league.** Top European leagues are well-covered; smaller leagues may have gaps or stale data. If something looks wrong, sanity-check against another source via Argos.
- **Rosters are cached on the free tier.** If a transfer just happened, the free-tier roster may not reflect it yet. Premium has the live V2 squad data.
- **Default `team_name` is `"Toronto FC"`.** Several tools default to TFC if no team name is given. Be explicit when asking about other teams.

117
docs/tools/orpheus.md Normal file
View File

@@ -0,0 +1,117 @@
# Orpheus
> Robert's Kawai piano — play music, manage the song library, review practice sessions.
- **MCP server name:** `orpheus`
- **Prompt snippet:** [prompts/tools/orpheus.md](../../prompts/tools/orpheus.md)
## What It Is
Orpheus is the MCP for Robert's Kawai piano. It turns the piano into something an assistant can actually *play* — converting ABC notation to MIDI, queuing pieces for playback, managing a song library, and capturing practice sessions for review. Named for the Greek musician whose lyre could move stones; the Kawai is the modern equivalent.
Music isn't something David just talks about — it's something he can bring to life. Orpheus is the difference between recommending a piece and demonstrating it.
## MCP Tools
### Playback
| Tool | Purpose |
|---|---|
| `play_abc` | Play music from ABC notation. Converts to MIDI and queues for playback. **The easiest way to play a piece.** Args: `title`, `abc`, optional `tempo_bpm`. |
| `play_midi` | Queue raw MIDI events. Each event is a dict with `type`, `time`, `note`, `velocity`, `channel`, `control`, `value`. Args: `title`, `midi_data`, `tempo_bpm` (default 72). |
| `play_song` | Queue a library song by ID. Args: `song_id`. |
| `stop_playback` | Stop whatever's currently playing. |
| `playback_status` | Returns `{is_playing, is_paused, title, playback_id, progress}`. |
### Library
| Tool | Purpose |
|---|---|
| `export_midi` | Convert ABC to MIDI and save to the song library for future replay via `play_song`. Args: `title`, `abc`, optional `tempo_bpm`, `save_to_library=True`. |
| `list_songs` | List all songs in the library — id, title, composer, genre, duration. |
### Sessions (review practice history)
| Tool | Purpose |
|---|---|
| `list_sessions` | List recent practice sessions — id, date, duration, note count. Args: `limit` (default 20). |
| `get_session` | Full session details including all raw MIDI events. Args: `session_id`. |
### System
| Tool | Purpose |
|---|---|
| `get_system_info` | Returns hostname, IP, instrument, uptime, DB path, version. |
## Canonical Workflows
### Play a piece from ABC notation
```
play_abc(
title="Gymnopédie No. 1",
abc="X:1\nT:Gymnopédie No. 1\nC:Erik Satie\nM:3/4\nL:1/4\nK:Dmaj\n...",
tempo_bpm=60
)
```
The simplest path — composer + piece + ABC notation → live piano playback.
### Save a piece to the library for later
```
export_midi(
title="Gymnopédie No. 1",
abc="...",
tempo_bpm=60,
save_to_library=True
)
# returns a song_id
# Later:
play_song(song_id="...")
```
Useful when David has converged on the right interpretation of a piece and wants it persistent rather than re-composed each time.
### Review a practice session
```
list_sessions(limit=5)
# returns recent sessions with date and note counts
get_session(session_id="...")
# returns full MIDI event detail — every note played, when, how hard
```
This is the diagnostic side — when Robert practiced something and wants to see what he actually played vs. what he intended.
## Who Uses Orpheus
- **David** — primary user. Music is David's domain, so the piano is most often his to play. Orpheus is how David can demonstrate rather than just describe.
Other agents may use Orpheus too — it just happens that David is the first one with it. If another agent's work has a legitimate reason to play something (a piece tied to a memory, a song for a mood), Orpheus is available.
## What It's Good For
- Demonstrating a piece David is recommending ("here, let me play it for you")
- Building a library of pieces Robert is working on or wants to revisit
- Reviewing practice sessions for analysis — what was played, tempo discipline, note accuracy
- Setting a mood — a piece in the background while doing something else
- Exploring music interactively — try a piece, adjust tempo, try variations
## What It's Not Good For
- Music *recommendations* alone — those don't need Orpheus; conversation is enough. Orpheus is for when David wants to *play* the recommendation, not just describe it.
- Recording — Orpheus plays back; the piano captures sessions separately
- Genre or artist analysis — that's interpretation work belonging in David's Neo4j `Music`, `Artist`, `Playlist` nodes
- Songs not in ABC notation or MIDI form. To play something obscure, you need the notation first.
## Known Gotchas
- **ABC notation is the path of least resistance.** Most pieces in the public domain (classical, traditional folk) have ABC available. For other pieces, you may need to transcribe or find a MIDI source first.
- **Tempo matters for piece identity.** Satie at 60 bpm and Satie at 90 bpm are different musical experiences. Pick deliberately and document the choice in the song-library entry if saving.
- **Playback is queued, not instant.** `playback_status` tells you what's actually playing. Don't assume a successful queue call means the piece is currently audible.
- **`stop_playback` is the kill switch.** Use it when something is wrong (wrong piece, wrong tempo, distraction) rather than waiting for it to finish.
- **`get_session` returns all raw MIDI events.** For long sessions this is a lot of data. If you just want overview metrics (date, duration, count), `list_sessions` is enough.
- **The piano is a physical object in Robert's space.** Playing something loud at 11pm has real consequences. Confirm before queueing a piece if context suggests it might be disruptive.

118
docs/tools/periplus.md Normal file
View File

@@ -0,0 +1,118 @@
# Periplus
> Maps, bookmarks, collections, and directions — Robert's canonical store for geographic places.
- **MCP server name:** `periplus`
- **Prompt snippet:** [prompts/tools/periplus.md](../../prompts/tools/periplus.md)
## What It Is
Periplus is the canonical store for **places** in Robert's life: addresses and points of interest, their actual coordinates, organized into collections (often one collection per trip or per category), with multi-hop directions between them. Backed by OpenStreetMap's Nominatim for place search and OSRM for routing.
Named for the ancient Greek *periplus* — the sailing manual that listed coastal landmarks in order. Same idea: the catalogue of places that matter, with the routes between them.
Periplus sits in the same relation to Neo4j that Kairos does for calendar and contacts: Periplus holds the **canonical geographic record** (the lat/lng, the bookmark, the collection); Neo4j holds the **interpretation and cross-domain linking** (what Robert did at the place, what restaurant Bourdain recommended, what species Cousteau observed there).
## ⚠️ Critical Discipline: Never Estimate Coordinates
> Models reliably misplace estimated coordinates — bookmarks for restaurants end up in the ocean.
When you need a place's coordinates, **call `search_places` to look them up.** Do not estimate or guess from memory. The typical workflow is:
1. `search_places("place name")` to resolve the place via Nominatim
2. Use the returned `lat` and `lng` to `create_bookmark` or other coordinate-consuming operations
This rule has no exceptions. Even for "obvious" landmarks where you think you know the coordinates, look them up.
## MCP Tools
### Search
| Tool | Purpose |
|---|---|
| `search_places` | Search OSM/Nominatim for addresses and places. Returns lat/lng + metadata. **The first step for any place not already bookmarked.** |
| `search_bookmarks` | Search saved bookmarks by name / tag / source / collection. Returns existing bookmarks before you re-create them. |
| `get_bookmark` | Fetch a single bookmark by UUID. |
### Bookmarks (creation)
| Tool | Purpose |
|---|---|
| `create_bookmark` | Create a bookmark from coordinates (name, lat, lng, optional description/address/source/tags). |
| `import_bookmarks` | Import a KML/KMZ/GPX/GeoJSON file from the server filesystem. |
### Geographic queries
| Tool | Purpose |
|---|---|
| `find_bookmarks_nearby` | Find bookmarks near a point, sorted by distance. Includes `distance_m`. |
| `find_bookmarks_in_area` | Find bookmarks within a bounding box (sw_lat/lng → ne_lat/lng). |
| `get_directions` | Multi-hop directions between waypoints (semicolon-separated `lat,lng` pairs). Returns distance, duration, GeoJSON geometry, step-by-step instructions. Modes: `car`, `bike`, `foot`. |
### Collections
| Tool | Purpose |
|---|---|
| `list_collections` | List all collections with bookmark and track counts. |
| `get_collection` | Get a collection with its full bookmark list. |
| `create_collection` | Create a new collection (name, optional description and tags). |
| `add_bookmarks_to_collection` | Add bookmarks to a collection (comma-separated bookmark UUIDs). |
| `remove_bookmark_from_collection` | Remove a bookmark from a collection (does not delete the bookmark). |
## Canonical Workflow — Find and Save a Place
```
1. search_places("Rideau Canal Ottawa")
→ [{lat: 45.4274, lng: -75.6919, name: "Rideau Canal", ...}]
2. create_collection("Ottawa Landmarks")
→ {id: "coll-uuid", ...}
3. create_bookmark(
name="Rideau Canal",
lat=45.4274, lng=-75.6919,
address="Rideau Canal, Ottawa, Ontario",
source="nominatim"
)
→ {id: "bm-uuid", ...}
4. add_bookmarks_to_collection(
collection_id="coll-uuid",
bookmark_ids="bm-uuid"
)
```
Steps 1 and 3 enforce the no-estimate rule: the coordinates passed to `create_bookmark` came from `search_places`, not from the model's head.
## Who Uses Periplus
- **Nate** — heavy, primary. One collection per trip, with the destinations, lodging, day-trip points of interest. Used alongside `get_directions` for itinerary logistics.
- **Bourdain** — restaurants, markets, shops. Collections organized by city or by type. Bourdain's Neo4j `Restaurant` and `Ingredient` nodes cross-link to Periplus bookmarks.
- **David** — stores, theatres, studios, apothecaries — the places where culture and good taste live. Collections by city or by type.
- **Cousteau** — site-of-interest bookmarks for nature observations (dive sites, garden suppliers, parks).
- **Other agents** read Periplus when their work involves a place; the four above do the writing.
## What It's Good For
- Resolving "where is X" with real coordinates
- Building a per-trip collection of places worth knowing about
- Multi-hop routing for actual itinerary planning
- Finding bookmarks near a point or in an area ("what's near the hotel")
- Importing GPS tracks, KML waypoints, or other geo data
## What It's Not Good For
- Estimating coordinates without lookup. (Documented above; restating because it's the single most important rule.)
- Cross-domain interpretation — Periplus holds the place; what Robert did or thought about the place belongs in Neo4j (Activity, Restaurant, Observation, etc.)
- Real-time location tracking — Periplus is for places, not for "where is Robert right now"
- Indoor navigation, building floor plans — Periplus is map-scale
## Known Gotchas
- **Never estimate coordinates.** Bears repeating because the failure mode is silent: a bookmark created with estimated lat/lng looks fine until someone tries to navigate to it and ends up at the wrong location. Always `search_places` first.
- **Nominatim's `display_name` is verbose.** Use it for verification but pass cleaner names to `create_bookmark` (`name` field).
- **`tags` is a JSON string, not a dict.** When passing tags to `create_bookmark` or `create_collection`, serialize the dict to a JSON string: `'{"category": "restaurant"}'`, not `{"category": "restaurant"}` directly.
- **`waypoints` format is specific.** `get_directions` expects semicolon-separated `lat,lng` pairs: `"45.42,-75.70;45.50,-73.57"`. Get this wrong and routing fails silently.
- **Search before create.** Before `create_bookmark` for a known place, run `search_bookmarks` to avoid duplicates. Bookmarks aren't deduplicated by coordinates automatically.
- **Collections are organizational, not exclusive.** A bookmark can belong to multiple collections. Adding to one doesn't remove from another.
- **`import_bookmarks` reads server filesystem paths.** The path must be absolute and accessible to the Periplus server, not the client.