diff --git a/Sources/Homelab/trellis-mcp-tool-surface-followups.md b/Sources/Homelab/trellis-mcp-tool-surface-followups.md new file mode 100644 index 0000000..7a9a5dc --- /dev/null +++ b/Sources/Homelab/trellis-mcp-tool-surface-followups.md @@ -0,0 +1,96 @@ +--- +created: '2026-05-23' +path: Sources/Homelab +project: trellis-mcp-tool-surface-followups +tags: +- mcp +- claude-code +- lovebug +type: project-plan +--- + +## Goal + +Make the Trellis MCP tool surface more ergonomic for in-session use without changing the underlying data model. Two changes: + +1. Rename `pause_thread` to `checkpoint_thread` so the verb matches the event it writes (and stops colliding with the `paused` status value). +2. Add optional `id` parameter to all tools that take `slug`, so threads can be addressed by their integer ID during a session. Slug remains canonical; ID is a convenience handle. + +Both changes are small, scoped server-side edits. No data migrations, no breaking changes if done with aliases. + +## Locked Decisions + +- **Slug stays canonical.** Names are durable, meaningful, survive migrations. IDs are database-implementation artifacts. The model is: slug for discovery and persistence, ID for session-scoped operation. Same pattern as `ps` vs `kill `. +- **ID stability is explicitly not a goal.** IDs may not survive a restore or migration. That's fine — you're not going to remember `232` next week either. You'll discover the thread again via `list_threads`, see whatever ID it has now, and use that. +- **Rename via alias, not hard cutover.** Add `checkpoint_thread` as the new canonical name, leave `pause_thread` as a deprecated alias that calls through. Drop the alias after a grace period (TBD — could be one trellis release, could be a calendar deadline). +- **No `id`-only tools.** Every tool that gains `id` keeps `slug`. If both are passed, prefer `id` and warn on mismatch. If neither, error. This preserves slug-first workflows and adds id as additive. + +## Open Items + +- [ ] Decide grace period for `pause_thread` alias before removal +- [ ] Confirm whether `parent_id` field in `list_threads` already returns the integer (it filters by it, but does it return it?) — affects whether `list_threads` needs response-shape changes +- [ ] Decide whether `start_thread` and `fork_thread` should return the new thread's ID in their response payload (probably yes — otherwise you have to immediately `get_thread` to learn the ID) +- [ ] Audit the database schema to confirm `threads.id` is the right field to expose (vs some other internal identifier) + +## Phases + +### Phase 1 — Rename pause_thread → checkpoint_thread + +- [ ] Add `checkpoint_thread` tool that takes the same args as `pause_thread` (`slug`, `state`) and writes the same `checkpoint` event +- [ ] Leave `pause_thread` registered as a deprecated alias — same handler, optionally logs a deprecation warning +- [ ] Update tool descriptions to reflect the rename +- [ ] Update any internal documentation / README references + +### Phase 2 — Add id parameter to read/write tools + +Tools to modify: +- `get_thread(slug, include_events)` → `get_thread(slug?, id?, include_events)` +- `checkpoint_thread(slug, state)` → `checkpoint_thread(slug?, id?, state)` +- `resume_thread(slug)` → `resume_thread(slug?, id?)` +- `update_thread(slug, ...)` → `update_thread(slug?, id?, ...)` +- `fork_thread(parent_slug, new_slug, ...)` → `fork_thread(parent_slug?, parent_id?, new_slug, ...)` + +For each: +- [ ] Make `slug` optional in the schema +- [ ] Add optional `id` (integer) +- [ ] Server-side: branch on which was provided; error if neither; warn if both disagree +- [ ] Update tool descriptions + +### Phase 3 — Expose id in responses + +- [ ] `start_thread` response includes `id` +- [ ] `fork_thread` response includes `id` of new child +- [ ] `get_thread` response includes `id` +- [ ] `list_threads` response includes `id` per row (confirm whether already true) + +### Phase 4 — Verification + +- [ ] In-session test: address a thread by integer ID end-to-end (start → checkpoint → resume) without typing the slug +- [ ] In-session test: cross-chat handoff using ID ("can you pull thread 232") +- [ ] Confirm `pause_thread` alias still works for any prior tooling that calls it + +## Notes + +**Naming context — why pause is wrong for the action:** + +The existing `pause_thread` tool writes a `checkpoint` event, not a status change. Per its own docstring: "the thread row is not changed except for `updated_at`." So the verb doesn't describe what's happening — checkpointing is happening; pausing is not. + +Worse, there's a `paused` value in the status enum (`active | paused | done | abandoned`). So you have: +- A `paused` status that means "this thread is on hold" +- A `pause_thread` action that does *not* set that status + +`checkpoint_thread` resolves the collision. Pausing-the-status remains a separate decision via `update_thread(slug, status='paused')`. + +**ID lookup context — why this matters ergonomically:** + +In session usage, slugs are friction: long, must be re-typed or re-quoted, easy to mistype. Integers are zero-friction: `pull thread 232`, `checkpoint 232`, `resume 232`. The slug stays useful for first-discovery ("which thread was the traefik debugging one?") but once you're operating on it, the integer wins. + +Pattern parallel: Linux processes. `ps` shows names for discovery; `kill 4729` for operation. Names for finding, numbers for doing. + +**What's explicitly out of scope:** + +- Changing slug semantics (still required at creation, still kebab-case, still the URL-safe identifier) +- Migrating existing data +- Changing the event log structure +- Adding any new event types +- The three other trellis follow-ups already tracked (retry/cleanup on embedding failure, semantic dedup before insert, revisit enricher pause-during-reflector) \ No newline at end of file