From dca1755b198e34b2b0e5451a457053c7cde0c1d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 16:23:58 +0000 Subject: [PATCH 1/3] =?UTF-8?q?docs:=20prompt-packs=20plan=20=E2=80=94=20d?= =?UTF-8?q?eclarative=20extensions=20and=20a=20self-extending=20Chat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Design doc for the lowest tier of the extensibility ladder: extensions as prompt packs (prompt + tool grants, inert data) executed by the existing UI, with Chat as the conversational authoring environment via save_pack / test_pack tools. Includes the runtime tool vocabulary table and a coverage analysis showing ~80% of the sketched concepts (interface-concepts.md, interaction-concepts.md) reduce to packs given ~10 generic tools; the rest need own rendering surfaces and stay in the hosted-tool tier (tool-launcher-plan.md, now cross-referenced). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EUSXFcRA7ox4u9x1QENyHw --- docs/prompt-packs-plan.md | 250 +++++++++++++++++++++++++++++++++++++ docs/tool-launcher-plan.md | 5 + 2 files changed, 255 insertions(+) create mode 100644 docs/prompt-packs-plan.md diff --git a/docs/prompt-packs-plan.md b/docs/prompt-packs-plan.md new file mode 100644 index 00000000..00233ad2 --- /dev/null +++ b/docs/prompt-packs-plan.md @@ -0,0 +1,250 @@ +# Prompt Packs and the Self-Extending Sidebar + +Design exploration for the lowest tier of the extensibility ladder: extensions +as **declarative prompt packs** rather than hosted code, and Chat as the +environment where writers and domain experts author them — conversationally, +without a PR and without being software engineers. + +> **Status:** Proposal. Nothing here is implemented. Companion to +> [tool-launcher-plan.md](tool-launcher-plan.md), which covers the hosted-tool +> tier (grants, rooms, manifests); this document covers what should *not* need +> any of that machinery. +> +> Background reading that frames the goal: Dubakov, +> [*Malleable software: solid bases, custom code*](https://www.mdubakov.me/malleable-software-solid-bases-custom-code/); +> Morrell, [*Extensible software in the age of LLMs*](https://jeremymorrell.dev/blog/extensible-software-in-the-age-of-llms/). + +## The extensibility ladder + +Today the project has two rungs: *use the app* and *PR the monorepo*. The +merge gate on the second rung is doing work it shouldn't — it is the only +thing standing between "trusted" and "untrusted" extensions, so every +extension costs a review and a deploy. The plan is three tiers, with trust +carried by the *shape* of each tier rather than by review: + +1. **Prompt packs (this document).** Inert data — a prompt plus a small set + of tool grants — executed by our UI under the user's own session. No + third-party code runs anywhere, so no sandbox, no token handoff, no merge. + A domain expert (writing-center director, WAC instructor, clinician + teaching care-plan writing) can author one, with or without LLM help. +2. **Hosted tools** ([tool-launcher-plan.md](tool-launcher-plan.md)). Real + code on the author's own origin, talking to the platform through scoped + grant tokens. For anything that needs its own rendering surface or + transport (mindmap canvas, voice, a custom editor). +3. **Platform PRs.** Reserved for new *primitives* — a new tool in the + vocabulary below, a new pack surface, a new scope. This is the one tier + where the merge gate belongs, because primitives encode policy. + +The platform succeeds when things migrate *down* this ladder: a concept that +today would be a feature branch should become a pack. + +## What a pack is + +A pack is a small JSON document. Format sketch (field names provisional): + +```jsonc +{ + "pack": 1, // format version + "id": "advisors-questions", // slug, unique per owner + "name": "Advisor's Questions", + "hint": "The three questions Prof. Okafor would ask", // navbar-style hint + "surface": "button", // where it mounts — see below + "prompt": "You are helping a writer anticipate their advisor's...", + "tools": ["view", "offer"], // granted subset of the runtime vocabulary + "provenance": { + "author": "user:…", // or "builtin" + "created": "2026-08-31", + "origin": "chat" // authored in conversation vs. imported + } +} +``` + +Execution: the pack's `prompt` becomes the `instructions` of a tool loop (the +manual loop pattern proven in +`frontend/src/pages/my-words/interaction/liveResponder.ts` — tools carry +schemas but no `execute`; the app routes each call), restricted to the tools +the pack declares. The loop runs through the existing LLM proxy under the +user's own session; `pack_id` joins `client_id` in the usage and log +attribution. + +**Surfaces** — where a pack mounts, which also bounds what it can do: + +| Surface | Mounts as | Loop shape | Existing precedent | +| --- | --- | --- | --- | +| `button` | a card/button on Draft (or a Revise action) | one-shot: context in, one `offer`/`annotate` out | the five Draft prompts in `frontend/src/api/prompts.ts` — already pack-shaped data | +| `persona` | a selectable mode in Chat | full conversation with the pack's prompt + tools | `CHAT_INSTRUCTIONS`; my-words walkthrough strategy | +| `lens` | a review pass over the document | iterate the doc, emit `annotate`/`highlight` calls | Revise's feedback flows | +| `ritual` | a prompt at session open/close (or on request) | short `ask_writer` exchange, `remember` the answer | Tidelines' sixty-second reflection; Post-Game Review | + +The built-in pages keep their identity; packs *add rows* to them. First +refactor: the Draft buttons and (per +[#597](https://github.com/AIToolsLab/writing-tools/issues/597)) the Revise +grid become built-in packs read from data, so the runtime has first-party +consumers before any user-authored pack exists. + +## The reframe that makes 80% possible + +A pack that is *only* a prompt + output format covers the Draft buttons and +little else. The leverage is in the **runtime tool vocabulary**: if the tools +carry the generic capabilities — read the document, point at a passage, offer +cards, ask the writer, remember something — then the pack itself stays a +prompt, and the prompt is the only part the extension author writes. The +tools do the heavy lifting; the pack supplies judgment about *what to say*. + +This is also where the lab's values are enforced structurally rather than by +review. `docs/design/interface-concepts.md` closes with the observation that +the verbs that survived sketching were *ask, quote, notice, gather, +resurface, narrate, believe, doubt, offer, fade* — never *write* or *fix*. +The tool vocabulary below is that verb list made executable. The covenant +("what the AI never does") holds for every pack anyone ever authors because +**there is no silent-write tool to grant**: the strongest document verb is +`propose_edit`, which is confirm-gated by construction. Curating this +vocabulary — not reviewing individual packs — is the maintainers' job. + +## Runtime tool vocabulary + +Tools available to a pack's loop, gated by the pack's `tools` list. Grouped +by what they touch. "Precedent" = where the mechanism already exists in this +repo; "Needs it" names concepts from +[design/interface-concepts.md](design/interface-concepts.md) and +[design/interaction-concepts.md](design/interaction-concepts.md) plus +existing pages. + +### Document (routed to the doc authority / `EditorAPI`) + +| Tool | Essential schema | Who needs it | Precedent | Notes | +| --- | --- | --- | --- | --- | +| `view` | `{} → numbered paragraphs` | nearly everything | my-words `liveResponder`; [#583](https://github.com/AIToolsLab/writing-tools/issues/583) proposes moving it into Chat | Paragraph numbers are loop-internal coordinates, never shown to the writer (my-words rule). | +| `highlight` | `{phrase} → ok \| not_found` | Say-Back, Prior Self (*quote*), Table Read, Marginalia, doctext-style citations | my-words `highlight`; `EditorAPI.selectPhrase`; `docTextLink` | Phrase matching is the known-fragile part: [#589](https://github.com/AIToolsLab/writing-tools/issues/589), [#591](https://github.com/AIToolsLab/writing-tools/issues/591), [#592](https://github.com/AIToolsLab/writing-tools/issues/592). Fix once, every pack benefits. | +| `annotate` | `{phrase, note, kind} → ok` | Marginalia, Reader's Reply, Table Read, any `lens` pack | Word comment API; Google Docs comments blocked on a Drive scope ([#580](https://github.com/AIToolsLab/writing-tools/issues/580)) | Falls back to an in-pane annotation list where the host can't do margin comments. | +| `propose_edit` | `{find, replace, rationale} → accepted \| rejected \| not_found` | Blind Rewrite, rewording flows, my-words ops | my-words `str_replace`/`insert`/`move` + propose strategy; rooms plan's patch-with-confirm | **Always** confirm-gated. The `rationale` renders on the confirm card — the writer sees why, not just what. | + +The cursor-context (`DocContext`) and the writer's brief are *injected* into +the loop's first message rather than fetched by tool call — every pack wants +them, and `buildMessages` already does exactly this for Draft. + +### Writer interaction (rendered by the host page) + +| Tool | Essential schema | Who needs it | Precedent | Notes | +| --- | --- | --- | --- | --- | +| `offer` | `{items: [{label, body, kind?}]} → chosen \| dismissed` | every Draft-style pack; Repertoire (*offer, then return*); Predict-the-Reader | Draft's suggestion cards | `kind: "insertion"` items get Draft's insert-at-cursor affordance; default kind is just *shown*. The choice returns to the loop, so a pack can follow up. | +| `ask_writer` | `{question, choices?, free_text?} → answer` | Charter (*negotiate*), Say-Back, Earn-the-Answer, Post-Game Review, Dojo, Stuckness-aware questions, any Socratic pack | my-words walkthrough strategy | The tool that makes packs *dialogic* instead of oracular. A `ritual` surface is essentially one `ask_writer` plus one `remember`. | + +Plain prose from the loop renders as a chat/panel message through the +existing ``/`DocTextMarkdown` path — narration needs no tool. + +### Memory and history (the new platform primitives) + +| Tool | Essential schema | Who needs it | Precedent | Notes | +| --- | --- | --- | --- | --- | +| `remember` | `{key, value, scope: "doc" \| "user"} → ok` | Charter (criteria + grades), Intent Ledger, Repertoire, Tidelines (exemplars), Loom (threads) | doc scope: `documentSettings` / the brief. user scope: **new** backend KV, namespaced `(user, pack)` | The single most enabling addition. Without it every stateful concept needs a hosted backend, i.e. Tier 2. | +| `recall` | `{prefix?, scope} → entries` | same as `remember` | — | Both scopes surface in an inspectable "what this pack remembers" UI — memory the writer can read and delete, matching the erasure story. | +| `read_history` | `{kinds?, since?, limit} → own pack's logged events` | Tidelines, Post-Game Review, Repertoire's return-visit | JSONL logs already keyed `(user, client_id)`; consent levels in `src/consent.ts` | Read-back is scoped to the *pack's own* events and filtered to the user's consent level. Depends on the consent gate being default-closed ([#615](https://github.com/AIToolsLab/writing-tools/issues/615)). | +| `get_revisions` | `{since} → doc snapshots/diffs` | Tidelines, Post-Game Review, Prior Self (within one doc), Inkwell-lite | none — **new capability**: periodic snapshotting by the doc authority | The hardest row on this table; consent-heavy (it is a copy of the document over time). Defer until a pack demands it, but design the log envelope so it can. | +| `search_corpus` | `{query} → passages from the writer's provided corpus` | Prior Self (*retrieve, contradict*), My Words | `my-words/corpus.ts` | Requires a corpus-upload story first; the tool itself is then small. | + +### Authoring tools (Chat only — never grantable to a pack) + +This is the self-extension loop: Chat gets these tools in its ordinary +conversation, so "make that a button" is something the writer can just say. + +| Tool | Essential schema | What it enables | Notes | +| --- | --- | --- | --- | +| `save_pack` | the full pack object (zod-validated) `→ ok \| validation errors` | reifying a conversational behavior into a persistent pack | Validation errors bounce back as tool errors and the model retries — the schema is the reliability mechanism. Upsert by `id` covers editing. | +| `test_pack` | `{pack} → transcript of a dry run against the current document` | rehearsal before saving | Catches packs overfitted to the conversation they were born in. The transcript is shown to the writer, not just the model. | +| `list_packs` / `get_pack` | `{} → summaries` / `{id} → pack` | editing, composing ("like my Say-Back pack but for abstracts") | Also how Chat answers "what packs do I have?" | + +The saved pack lands with `provenance.origin: "chat"` and its prompt is +displayed in full on its settings card. **Legibility is the trust story**: a +pack is inspectable in a way code never is, and the writer who authored one +by talking can read exactly what they made. + +## Coverage: which concepts become packs + +Checked against the current concept inventory. Tools listed are beyond +`view` + injected context, which everything uses. + +| Concept | Surface | Tools needed | Verdict | +| --- | --- | --- | --- | +| Draft's five buttons | button | `offer` | pack today — they are already data in `prompts.ts` | +| Revise's feedback grid ([#597](https://github.com/AIToolsLab/writing-tools/issues/597)) | lens | `highlight`, `annotate` | pack | +| First Read, Say-Back, Reader's Reply, Predict-the-Reader, Who Is Served? | persona / lens | `highlight`, `offer`, `ask_writer` | pack — several were "prompt-only" as their cheapest honest prototype already | +| **Table Read** | persona | `annotate`, `ask_writer` | pack | +| Earn-the-Answer, Stuckness-aware questions, Dojo | persona | `ask_writer`, `offer` | pack | +| Marginalia | lens | `annotate` | pack | +| Blind Rewrite | button | `ask_writer`, `propose_edit` | pack | +| **Charter** | persona + ritual | `ask_writer`, `remember`/`recall`, `highlight` | pack (its cheapest test was literally "WoZ in Chat") | +| Intent Ledger | ritual | `remember`/`recall` | pack | +| **Repertoire** | ritual + button | `offer`, `remember`, `read_history` | pack | +| **Tidelines** | ritual | `remember`, `read_history`, `get_revisions` | pack, gated on the history rows | +| Post-Game Review | ritual | `read_history`, `get_revisions` | pack, same gate | +| **Prior Self** | lens | `search_corpus`, `highlight` | pack once a corpus service exists | +| **Loom** | — | needs a full-window custom editor | Tier 2 (hosted tool) | +| Skim View / Reading Playback | — | custom rendering/animation | Tier 2 | +| Inkwell (provenance ink) | — | deep editor instrumentation | Tier 2 (an honest subset via `get_revisions` might pack) | +| Mindmap prototype | — | own canvas | Tier 2 (the launcher plan's motivating case) | +| My Words voice | — | realtime audio transport | Tier 2 | + +Score: **~14 of 19 concepts (~75–80%) become packs** given the vocabulary +above — and the concentration is instructive. `offer` + `ask_writer` + +`highlight`/`annotate` alone cover roughly ten of them; `remember`/`recall` +adds the stateful four; `read_history`/`get_revisions`/`search_corpus` are +each demanded by only two or three concepts but are the ones nothing else can +substitute for. The honest 20% is anything that needs **its own rendering +surface or transport** — which is exactly the boundary where the hosted-tool +tier begins, so the two plans meet cleanly: a pack that outgrows the sidebar +graduates to a manifest and a grant token, not to a monorepo PR. + +## Security and consent posture + +- **Packs are inert data.** No pack-supplied code executes; the UI executes. + The threat model is therefore prompt-shaped, not code-shaped. +- **A shared pack is an untrusted prompt.** It runs with the *user's own* + session and only the tools it declares, so the blast radius is: it can read + the doc it was invoked on, say things, and propose confirm-gated edits. + There is no network tool and no cross-pack `recall`, so no exfiltration + channel exists to grant. Import shows the full prompt and the tool list as + the consent screen — the same move as the launcher plan's manifest consent, + minus the token. +- **Attribution:** `pack_id` alongside `client_id` on `llm_usage` and the log + envelope. Per-pack quotas can reuse the per-tool quota machinery when it + lands (launcher plan Phase 3). +- **Memory is visible and erasable.** `remember`ed state appears on the + pack's settings card and is covered by the existing erasure flow. +- **Content in tool results obeys the consent gate** exactly as log payloads + do (`src/consent.ts`) — `read_history` returns what the user's level + permits, nothing more. + +## What this buys the research program + +`pack_id` on every event makes the packs themselves a study instrument: +which packs do writers create, keep, iterate, share, abandon — and what do +domain experts build when building costs a conversation instead of an +engineering collaboration? That is an end-user-programming-of-writing-support +study the current architecture is one attribution column away from +supporting. + +## Phasing + +1. **Packs as data, first-party only.** Extract the Draft buttons (and the + Revise grid, [#597](https://github.com/AIToolsLab/writing-tools/issues/597)) + into built-in pack files; a small runtime executes `button` packs with + `offer`. No user authoring yet — this proves the format on code we already + trust. +2. **Chat gets `view` + `highlight`** ([#583](https://github.com/AIToolsLab/writing-tools/issues/583), + already filed) via the shared loop runner lifted out of my-words. +3. **User pack storage + paste-to-import.** The backend KV for packs + themselves; an import screen that shows prompt + tools. Draft renders the + user's `button` packs below the built-ins. +4. **Self-extension:** `save_pack` / `test_pack` / `list_packs` in Chat; + provenance cards. +5. **`ask_writer` + `annotate` + `persona`/`lens`/`ritual` surfaces** — the + dialogic tier. +6. **`remember`/`recall`** (user-scope KV) — the stateful tier; Charter + becomes buildable as a pack, which is a good acceptance test. +7. **On demand:** `read_history`, `get_revisions`, `search_corpus`, + `propose_edit` — each lands when the first concrete pack needs it, not + before. + +Steps 1–4 are the minimum for "extensions prompt themselves into existence"; +5–6 are where the coverage table's 80% actually arrives. diff --git a/docs/tool-launcher-plan.md b/docs/tool-launcher-plan.md index e3544427..3a3cf5c3 100644 --- a/docs/tool-launcher-plan.md +++ b/docs/tool-launcher-plan.md @@ -5,6 +5,11 @@ writing tools (first-party prototypes now, community research tools later), without merging them into the add-in or supporting them as first-class features. +> See also [prompt-packs-plan.md](prompt-packs-plan.md): the tier *below* +> hosted tools — extensions as declarative prompt packs that need none of the +> grant/manifest machinery here. Most sidebar-shaped concepts belong there; +> this plan covers what outgrows it (own rendering surface or transport). + > **Status:** Phases 0 and 1 are implemented; Phases 2+ remain proposal. > Implemented so far: > - **Phase 0** — the proxy and `/api/log` already require a Bearer session and From 724600c94eace3ae9aaa2b047a4b6502b1edc596 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 16:40:34 +0000 Subject: [PATCH 2/3] docs(prompt-packs): replace bespoke memory API with a files+git workspace; add MCP analysis The memory/history tools (remember, recall, read_history, get_revisions, search_corpus) were reinventing a filesystem and git. Replaced with a per-(user, pack) virtual workspace: files, grep, and git history, whose idiom the model already knows and whose contents the writer can read, export and delete. Names just-bash / just-git as candidates, with the undici network dependency flagged as due diligence rather than assumed away, and states the covenant boundary explicitly: the document is not a writable file in the workspace, and the workspace has no network. Adds a build-vs-reuse section (W3C Web Annotation TextQuoteSelector for the phrase-anchoring issues, OAuth device flow as precedent, XMPP's model without its wire) and a section on the three roles MCP could play here: outbound client to third-party servers as the cheap unlock, MCP-shaped internal tool definitions, and an inbound server as the powerful but values-fraught case that tests whether the covenant belongs to our UI or to the platform. Also sharpens the ask_writer rationale to floor control, non-chat surfaces, and typed study data, rather than the ability to ask at all. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EUSXFcRA7ox4u9x1QENyHw --- docs/prompt-packs-plan.md | 346 ++++++++++++++++++++++++++++++-------- 1 file changed, 276 insertions(+), 70 deletions(-) diff --git a/docs/prompt-packs-plan.md b/docs/prompt-packs-plan.md index 00233ad2..0b63c104 100644 --- a/docs/prompt-packs-plan.md +++ b/docs/prompt-packs-plan.md @@ -22,15 +22,17 @@ thing standing between "trusted" and "untrusted" extensions, so every extension costs a review and a deploy. The plan is three tiers, with trust carried by the *shape* of each tier rather than by review: -1. **Prompt packs (this document).** Inert data — a prompt plus a small set - of tool grants — executed by our UI under the user's own session. No +1. **Prompt packs (this document).** Inert data — a prompt plus a declared + set of tool grants — executed by our UI under the user's own session. No third-party code runs anywhere, so no sandbox, no token handoff, no merge. A domain expert (writing-center director, WAC instructor, clinician teaching care-plan writing) can author one, with or without LLM help. 2. **Hosted tools** ([tool-launcher-plan.md](tool-launcher-plan.md)). Real code on the author's own origin, talking to the platform through scoped grant tokens. For anything that needs its own rendering surface or - transport (mindmap canvas, voice, a custom editor). + transport (mindmap canvas, voice, a custom editor). See + [Reuse over invention](#reuse-over-invention) — much of this tier's + bespoke manifest/protocol design is a candidate for MCP. 3. **Platform PRs.** Reserved for new *primitives* — a new tool in the vocabulary below, a new pack surface, a new scope. This is the one tier where the merge gate belongs, because primitives encode policy. @@ -67,6 +69,11 @@ the pack declares. The loop runs through the existing LLM proxy under the user's own session; `pack_id` joins `client_id` in the usage and log attribution. +Note that this shape — a prompt, a tool allowlist, a name and a hint — is +very close to an **MCP prompt** (`prompts/list` / `prompts/get`). See +[What MCP would actually look like here](#what-mcp-would-actually-look-like-here); +the pack format may not need inventing at all. + **Surfaces** — where a pack mounts, which also bounds what it can do: | Surface | Mounts as | Loop shape | Existing precedent | @@ -74,7 +81,7 @@ attribution. | `button` | a card/button on Draft (or a Revise action) | one-shot: context in, one `offer`/`annotate` out | the five Draft prompts in `frontend/src/api/prompts.ts` — already pack-shaped data | | `persona` | a selectable mode in Chat | full conversation with the pack's prompt + tools | `CHAT_INSTRUCTIONS`; my-words walkthrough strategy | | `lens` | a review pass over the document | iterate the doc, emit `annotate`/`highlight` calls | Revise's feedback flows | -| `ritual` | a prompt at session open/close (or on request) | short `ask_writer` exchange, `remember` the answer | Tidelines' sixty-second reflection; Post-Game Review | +| `ritual` | a prompt at session open/close (or on request) | short `ask_writer` exchange, write a note to the workspace | Tidelines' sixty-second reflection; Post-Game Review | The built-in pages keep their identity; packs *add rows* to them. First refactor: the Draft buttons and (per @@ -87,9 +94,9 @@ consumers before any user-authored pack exists. A pack that is *only* a prompt + output format covers the Draft buttons and little else. The leverage is in the **runtime tool vocabulary**: if the tools carry the generic capabilities — read the document, point at a passage, offer -cards, ask the writer, remember something — then the pack itself stays a -prompt, and the prompt is the only part the extension author writes. The -tools do the heavy lifting; the pack supplies judgment about *what to say*. +cards, ask the writer, keep notes — then the pack itself stays a prompt, and +the prompt is the only part the extension author writes. The tools do the +heavy lifting; the pack supplies judgment about *what to say*. This is also where the lab's values are enforced structurally rather than by review. `docs/design/interface-concepts.md` closes with the observation that @@ -103,21 +110,23 @@ vocabulary — not reviewing individual packs — is the maintainers' job. ## Runtime tool vocabulary -Tools available to a pack's loop, gated by the pack's `tools` list. Grouped -by what they touch. "Precedent" = where the mechanism already exists in this -repo; "Needs it" names concepts from -[design/interface-concepts.md](design/interface-concepts.md) and -[design/interaction-concepts.md](design/interaction-concepts.md) plus -existing pages. +Tools available to a pack's loop, gated by the pack's `tools` list. "Precedent" += where the mechanism already exists in this repo; "Who needs it" names concepts +from [design/interface-concepts.md](design/interface-concepts.md) and +[design/interaction-concepts.md](design/interaction-concepts.md) plus existing +pages. ### Document (routed to the doc authority / `EditorAPI`) +This group is genuinely ours to build: it is the live document in a real +editor, under the covenant. + | Tool | Essential schema | Who needs it | Precedent | Notes | | --- | --- | --- | --- | --- | | `view` | `{} → numbered paragraphs` | nearly everything | my-words `liveResponder`; [#583](https://github.com/AIToolsLab/writing-tools/issues/583) proposes moving it into Chat | Paragraph numbers are loop-internal coordinates, never shown to the writer (my-words rule). | -| `highlight` | `{phrase} → ok \| not_found` | Say-Back, Prior Self (*quote*), Table Read, Marginalia, doctext-style citations | my-words `highlight`; `EditorAPI.selectPhrase`; `docTextLink` | Phrase matching is the known-fragile part: [#589](https://github.com/AIToolsLab/writing-tools/issues/589), [#591](https://github.com/AIToolsLab/writing-tools/issues/591), [#592](https://github.com/AIToolsLab/writing-tools/issues/592). Fix once, every pack benefits. | -| `annotate` | `{phrase, note, kind} → ok` | Marginalia, Reader's Reply, Table Read, any `lens` pack | Word comment API; Google Docs comments blocked on a Drive scope ([#580](https://github.com/AIToolsLab/writing-tools/issues/580)) | Falls back to an in-pane annotation list where the host can't do margin comments. | -| `propose_edit` | `{find, replace, rationale} → accepted \| rejected \| not_found` | Blind Rewrite, rewording flows, my-words ops | my-words `str_replace`/`insert`/`move` + propose strategy; rooms plan's patch-with-confirm | **Always** confirm-gated. The `rationale` renders on the confirm card — the writer sees why, not just what. | +| `highlight` | `{phrase} → ok \| not_found` | Say-Back, Prior Self (*quote*), Table Read, Marginalia, doctext-style citations | my-words `highlight`; `EditorAPI.selectPhrase`; `docTextLink` | Anchor with a W3C **`TextQuoteSelector`** (exact + prefix/suffix), not a bare needle — see [Reuse over invention](#reuse-over-invention). Fixes [#589](https://github.com/AIToolsLab/writing-tools/issues/589), [#591](https://github.com/AIToolsLab/writing-tools/issues/591), [#592](https://github.com/AIToolsLab/writing-tools/issues/592) once for every pack. | +| `annotate` | `{selector, note, kind} → ok` | Marginalia, Reader's Reply, Table Read, any `lens` pack | Word comment API; Google Docs comments blocked on a Drive scope ([#580](https://github.com/AIToolsLab/writing-tools/issues/580)) | Same selector model. Falls back to an in-pane annotation list where the host can't do margin comments. | +| `propose_edit` | `{selector, replace, rationale} → accepted \| rejected \| not_found` | Blind Rewrite, rewording flows, my-words ops | my-words `str_replace`/`insert`/`move` + propose strategy; rooms plan's patch-with-confirm | **Always** confirm-gated. The `rationale` renders on the confirm card — the writer sees why, not just what. | The cursor-context (`DocContext`) and the writer's brief are *injected* into the loop's first message rather than fetched by tool call — every pack wants @@ -128,20 +137,98 @@ them, and `buildMessages` already does exactly this for Draft. | Tool | Essential schema | Who needs it | Precedent | Notes | | --- | --- | --- | --- | --- | | `offer` | `{items: [{label, body, kind?}]} → chosen \| dismissed` | every Draft-style pack; Repertoire (*offer, then return*); Predict-the-Reader | Draft's suggestion cards | `kind: "insertion"` items get Draft's insert-at-cursor affordance; default kind is just *shown*. The choice returns to the loop, so a pack can follow up. | -| `ask_writer` | `{question, choices?, free_text?} → answer` | Charter (*negotiate*), Say-Back, Earn-the-Answer, Post-Game Review, Dojo, Stuckness-aware questions, any Socratic pack | my-words walkthrough strategy | The tool that makes packs *dialogic* instead of oracular. A `ritual` surface is essentially one `ask_writer` plus one `remember`. | +| `ask_writer` | `{question, choices?, free_text?} → answer` | Charter (*negotiate*), Say-Back, Earn-the-Answer, Post-Game Review, Dojo, Stuckness-aware questions | my-words walkthrough strategy; MCP **elicitation** is the same primitive | See below — on the Chat surface this is not about *being able* to ask. | Plain prose from the loop renders as a chat/panel message through the existing ``/`DocTextMarkdown` path — narration needs no tool. -### Memory and history (the new platform primitives) +**Why `ask_writer` rather than just talking.** On the `persona` surface the +model can obviously ask a question in prose, so the tool has to earn its +place on three other grounds: + +1. **Floor control as a mechanism, not an instruction.** The loop *blocks* on + the answer. A model that asks and then answers itself is + [#425](https://github.com/AIToolsLab/writing-tools/issues/425), and + Earn-the-Answer is a concept whose whole point is that the tool cannot + proceed. Prompting for that is a request; a suspended loop is a + guarantee — the same move as `propose_edit` being confirm-gated. +2. **Non-chat surfaces.** `button`, `lens` and `ritual` packs have no + conversational UI. A session-close ritual is not a chat; this is the only + way it can ask anything. +3. **Typed answers.** `choices` renders as the Charter's ●◐○ grader or a + scale and returns a *value*, not prose to re-parse — and logs it as a + schema'd event, which is the difference between hand-coding transcripts + and having study data. + +### Workspace: files and git, not a bespoke memory API + +An earlier draft of this document proposed five tools — `remember`, `recall`, +`read_history`, `get_revisions`, `search_corpus`. They collapse into a +**per-`(user, pack)` virtual filesystem with git history**: + +| Bespoke tool it replaces | Standard equivalent | +| --- | --- | +| `remember` / `recall` | write / read a file | +| `search_corpus` | `grep` | +| `read_history` | read the pack's log file | +| `get_revisions` | `git log -p`, `git diff` | +| Inkwell-lite (provenance) | `git blame` | + +Three reasons this is better than the API I would have written: + +- **The idiom is already in the weights.** A model writes shell and git + fluently; it writes `remember({key, scope})` only as well as our + documentation explains it. A bespoke API is a small language the model + learns at inference time from a paragraph we wrote. This is Morrell's + argument made concrete, and it is the single strongest reason to prefer a + standard here. +- **It is *more* legible for consent, not less.** "What does this pack + remember about you" becomes a directory the writer can read, export and + delete; erasure is removing a namespace. That beats an opaque KV + inspector. +- **It unlocks cases no bespoke tool had.** A scratchpad; *pasting in real + feedback from a human reader*; working across several documents. The Table + Read and Reader's Reply concepts get much stronger when they can run + against an advisor's actual comments in `feedback/round2.md` instead of a + simulated reader. + +**Tools:** start with a small file set — `ls`, `read_file`, `write_file`, +`grep`, `git_log`, `git_show`/`git_diff` — and treat a real `bash -c` as a +later upgrade if packs turn out to need pipelines. The value is in the +*idiom* being familiar, which the small set already delivers. + +**The covenant boundary.** Handing packs a shell-shaped surface only stays +compatible with "the values live in the vocabulary" if the boundary is +explicit: + +- **The writer's document is not a writable file in the workspace.** Mount it + read-only if useful (`doc.md`, regenerated per turn); every mutation still + goes through confirm-gated `propose_edit`. +- **The workspace has no network.** A pack can compute over its own notes and + nothing else. +- Scope is per `(user, pack)`; there is no cross-pack read, so no + exfiltration channel exists to grant. + +So the shell is powerful over scratch and memory, and powerless over the +document and the outside world — which is exactly where the covenant needs to +hold. + +**Candidate dependencies** (verified to exist; *not* yet vetted for adoption): + +| Package | Version | License | Notes | +| --- | --- | --- | --- | +| [`just-bash`](https://github.com/vercel-labs/just-bash) | 3.4.2 | Apache-2.0 | "Simulated bash environment with virtual filesystem", vercel-labs, very active. **Due diligence:** it depends on `undici`, so it may ship network-capable builtins — the no-network boundary above must be verified, not assumed. | +| [`just-git`](https://github.com/blindmansion/just-git) | 1.8.0 | MIT | Pure-TS git over a virtual filesystem, zero dependencies, keywords include `agent`/`sandbox`. | -| Tool | Essential schema | Who needs it | Precedent | Notes | -| --- | --- | --- | --- | --- | -| `remember` | `{key, value, scope: "doc" \| "user"} → ok` | Charter (criteria + grades), Intent Ledger, Repertoire, Tidelines (exemplars), Loom (threads) | doc scope: `documentSettings` / the brief. user scope: **new** backend KV, namespaced `(user, pack)` | The single most enabling addition. Without it every stateful concept needs a hosted backend, i.e. Tier 2. | -| `recall` | `{prefix?, scope} → entries` | same as `remember` | — | Both scopes surface in an inspectable "what this pack remembers" UI — memory the writer can read and delete, matching the erasure story. | -| `read_history` | `{kinds?, since?, limit} → own pack's logged events` | Tidelines, Post-Game Review, Repertoire's return-visit | JSONL logs already keyed `(user, client_id)`; consent levels in `src/consent.ts` | Read-back is scoped to the *pack's own* events and filtered to the user's consent level. Depends on the consent gate being default-closed ([#615](https://github.com/AIToolsLab/writing-tools/issues/615)). | -| `get_revisions` | `{since} → doc snapshots/diffs` | Tidelines, Post-Game Review, Prior Self (within one doc), Inkwell-lite | none — **new capability**: periodic snapshotting by the doc authority | The hardest row on this table; consent-heavy (it is a copy of the document over time). Defer until a pack demands it, but design the log envelope so it can. | -| `search_corpus` | `{query} → passages from the writer's provided corpus` | Prior Self (*retrieve, contradict*), My Words | `my-words/corpus.ts` | Requires a corpus-upload story first; the tool itself is then small. | +Both are young (first published Dec 2025 and Mar 2026), so treat the +maturity risk as real: the workspace should sit behind our own thin tool +interface so the implementation underneath can be swapped. + +**Where it runs: server-side.** Both packages are dependency-light TypeScript +and could run in the pane, but the backend already exists, a server-side +workspace persists across devices (which the rooms/phone story in the +launcher plan wants), and a server-side sandbox is a real boundary rather +than a browser-side fiction. It also keeps taskpane bundle weight flat. ### Authoring tools (Chat only — never grantable to a pack) @@ -159,61 +246,178 @@ displayed in full on its settings card. **Legibility is the trust story**: a pack is inspectable in a way code never is, and the writer who authored one by talking can read exactly what they made. +## Reuse over invention + +The test is not "does a standard exist" but: **is the idiom already in both +the model's weights and the domain expert's head?** Files, git and grep pass +twice over. Build bespoke only for (a) the writer's live document, (b) the +covenant, (c) the research instrument. + +| Concern | Verdict | +| --- | --- | +| Storage, scratch, corpus search, revision history | **Reuse** — virtual filesystem + git (above) | +| Tool protocol; hosted extensions | **Reuse — MCP** (`@modelcontextprotocol/sdk`). Retires much of the bespoke manifest design in the launcher plan; see below | +| Asking the writer a question | **Reuse the shape** — MCP elicitation is `ask_writer` | +| Identity / auth | **Already reused** — OAuth 2 device flow (RFC 8628) via Better Auth. Evidence the instinct works | +| Phrase anchoring ([#589](https://github.com/AIToolsLab/writing-tools/issues/589), [#591](https://github.com/AIToolsLab/writing-tools/issues/591), [#592](https://github.com/AIToolsLab/writing-tools/issues/592)) | **Reuse** — W3C Web Annotation `TextQuoteSelector` (exact + prefix/suffix) is specified for exactly this; `diff-match-patch` for fuzzy location. Currently hand-rolled three ways | +| Annotations / marginalia | **Reuse** — same W3C Web Annotation data model | +| Multi-surface transport (rooms) | **Steal the model, not the wire** — see below | +| Document access, `propose_edit`, consent gating, log tenancy, pack semantics | **Build** — Office.js/Docs reality, the covenant, the study instrument | + +**On XMPP.** The mapping is uncannily good: MUC is the launcher plan's rooms, +presence is its presence, pubsub is its `broadcast`, and federation is the +multi-institution research case. But adopting it means running +Prosody/ejabberd, bridging over BOSH or WebSocket, XML payloads — and we +would still define every message body ourselves. That is a server and an ops +burden for a small lab in exchange for a vocabulary we can copy for free. +Recommendation: **name the primitives after XMPP's model** (presence, MUC, +pubsub) so the protocol is conventionally shaped, and spend the standards +budget on MCP, where the actual interop lives. + +## What MCP would actually look like here + +MCP is worth more than a line in a table, because in this setting it has +three distinct roles and they have very different risk profiles. + +**The mapping is closer than it first looks.** MCP's primitives line up +against this design almost one for one: + +| MCP primitive | Our equivalent | +| --- | --- | +| **Tools** | the runtime tool vocabulary above | +| **Resources** (read-only, URI-addressed) | the document, the brief, workspace files — `thoughtful://doc/current`, `thoughtful://scratch/feedback.md`. A better fit than tools for injected context | +| **Prompts** (server-exposed, user-selectable, templated) | **a prompt pack** | +| **Elicitation** (server asks the *user* a question) | `ask_writer` | +| **Sampling** (server asks the client for a completion) | our LLM proxy relationship, inverted | + +That third row is the striking one: a pack is very nearly already an MCP +prompt, which means the pack *format* may not need inventing — and, more +importantly, neither does pack **distribution**. A writing center publishes +an MCP server exposing its prompts; an instructor's students connect to it; +the packs appear in their sidebar; when the center improves a prompt, +everyone gets the fix. Versioning, updates and provenance all come along. +Paste-a-JSON-blob handles none of that well. + +### Role 1: Thoughtful as MCP *client* (outbound) — the real unlock + +A pack declares a third-party MCP server alongside its prompt. "Check my +citations against my Zotero library", "pull the rubric from our course LMS", +"look it up in the library catalogue" stop being things we have to build. +The domain expert composes an extension out of a prompt plus somebody else's +server, and nobody hosts anything for us. + +This is the cheapest large win on the whole ladder, and it is strictly +additive: it needs no changes to the covenant, because third-party servers +supply *information*, not document authority. The work is a consent screen +naming the server and its tools, per-server scoping, and the usual +untrusted-content discipline — tool results from an external server are data, +never instructions, and must not be able to redirect a pack's loop. + +### Role 2: platform tools *defined* in MCP shape (internal) — cheap, do early + +Define the document/workspace/writer tools with MCP-shaped schemas even while +they run in-process in the taskpane. This costs almost nothing (it is a +schema convention) and means one set of definitions serves the in-pane loop, +the outbound case, and Role 3 later. + +**What does not fit:** the doc authority is a webview with no inbound +address, so document tools cannot sensibly be *served* over a network +transport — MCP over stdio is irrelevant here and MCP over HTTP would have to +reach back into the pane, which is the switchboard problem the launcher plan +already owns. Keep document tools in-process; MCP is for the outward-facing +surfaces. + +### Role 3: Thoughtful as MCP *server* (inbound) — powerful, values-fraught + +Exposing the writer's document and workspace as an MCP server means their own +Claude, ChatGPT or Cursor could reach it: "my advisor's feedback is in my +Thoughtful scratchpad." That is a genuinely new capability and the most +obvious "extended in ways we hadn't thought about" surface. + +It is also the sharpest values question in this document. An external agent +holding `propose_edit` is an excellent pipe for precisely the +write-my-paper-for-me workflow the lab exists to resist — and unlike a pack, +an outside client is not running under our UI, so none of the confirm-gating +or covenant lives there. The scope grant is the only thing that travels. + +The defensible position is that **the outbound server exposes reading and +asking, never writing**: `view`, resources, `grep` over the workspace, +possibly `annotate`; never `propose_edit`, never silent document mutation. +Read access still deserves a consent screen and its own `client_id` for +attribution. Worth prototyping precisely *because* it forces the question of +which parts of the covenant are properties of our UI and which are properties +of the platform — the answer should be "the platform", and this is the test. + +### Costs, honestly + +- Multi-tenant hosted MCP with per-user scoped tokens is newer and rougher + than the local single-user case; expect OAuth-per-server plumbing. +- MCP has no notion of "this field is document text, strip it at the + `usage` consent level" — our consent gate (`src/consent.ts`) has to wrap + every boundary, in both directions. +- An MCP client in the pane is just JSON-RPC, but bundle weight in a taskpane + is a real budget. + ## Coverage: which concepts become packs -Checked against the current concept inventory. Tools listed are beyond -`view` + injected context, which everything uses. +Checked against the current concept inventory. Tools listed are beyond `view` +plus injected context, which everything uses. | Concept | Surface | Tools needed | Verdict | | --- | --- | --- | --- | | Draft's five buttons | button | `offer` | pack today — they are already data in `prompts.ts` | | Revise's feedback grid ([#597](https://github.com/AIToolsLab/writing-tools/issues/597)) | lens | `highlight`, `annotate` | pack | | First Read, Say-Back, Reader's Reply, Predict-the-Reader, Who Is Served? | persona / lens | `highlight`, `offer`, `ask_writer` | pack — several were "prompt-only" as their cheapest honest prototype already | -| **Table Read** | persona | `annotate`, `ask_writer` | pack | +| **Table Read** | persona | `annotate`, `ask_writer` | pack — and much stronger with real reader feedback in the workspace | | Earn-the-Answer, Stuckness-aware questions, Dojo | persona | `ask_writer`, `offer` | pack | | Marginalia | lens | `annotate` | pack | | Blind Rewrite | button | `ask_writer`, `propose_edit` | pack | -| **Charter** | persona + ritual | `ask_writer`, `remember`/`recall`, `highlight` | pack (its cheapest test was literally "WoZ in Chat") | -| Intent Ledger | ritual | `remember`/`recall` | pack | -| **Repertoire** | ritual + button | `offer`, `remember`, `read_history` | pack | -| **Tidelines** | ritual | `remember`, `read_history`, `get_revisions` | pack, gated on the history rows | -| Post-Game Review | ritual | `read_history`, `get_revisions` | pack, same gate | -| **Prior Self** | lens | `search_corpus`, `highlight` | pack once a corpus service exists | +| **Charter** | persona + ritual | `ask_writer`, workspace files, `highlight` | pack (its cheapest test was literally "WoZ in Chat") | +| Intent Ledger | ritual | workspace files | pack | +| **Repertoire** | ritual + button | `offer`, workspace files + log | pack | +| **Tidelines** | ritual | workspace, `git_log`/`git_diff` | pack, given doc snapshotting | +| Post-Game Review | ritual | workspace log, `git_diff` | pack, same gate | +| **Prior Self** | lens | `grep` over corpus, `highlight` | pack once a corpus lands in the workspace | +| Citation / source checking *(new)* | lens | outbound MCP (Zotero, catalogue) | pack — enabled by MCP Role 1, previously not on the list at all | | **Loom** | — | needs a full-window custom editor | Tier 2 (hosted tool) | | Skim View / Reading Playback | — | custom rendering/animation | Tier 2 | -| Inkwell (provenance ink) | — | deep editor instrumentation | Tier 2 (an honest subset via `get_revisions` might pack) | +| Inkwell (provenance ink) | — | deep editor instrumentation | Tier 2 (an honest subset via `git blame` might pack) | | Mindmap prototype | — | own canvas | Tier 2 (the launcher plan's motivating case) | | My Words voice | — | realtime audio transport | Tier 2 | -Score: **~14 of 19 concepts (~75–80%) become packs** given the vocabulary -above — and the concentration is instructive. `offer` + `ask_writer` + -`highlight`/`annotate` alone cover roughly ten of them; `remember`/`recall` -adds the stateful four; `read_history`/`get_revisions`/`search_corpus` are -each demanded by only two or three concepts but are the ones nothing else can -substitute for. The honest 20% is anything that needs **its own rendering -surface or transport** — which is exactly the boundary where the hosted-tool -tier begins, so the two plans meet cleanly: a pack that outgrows the sidebar +Score: **~14 of 19 concepts (~75–80%) become packs**, and the concentration +is instructive. `offer` + `ask_writer` + `highlight`/`annotate` alone cover +roughly ten of them; the **workspace** (files + git) subsumes what was going +to be four more bespoke tools and covers the stateful and longitudinal +concepts; outbound MCP adds a category that was not previously imaginable +without us building an integration. The honest 20% is anything needing **its +own rendering surface or transport** — exactly where the hosted-tool tier +begins, so the two plans meet cleanly: a pack that outgrows the sidebar graduates to a manifest and a grant token, not to a monorepo PR. ## Security and consent posture - **Packs are inert data.** No pack-supplied code executes; the UI executes. - The threat model is therefore prompt-shaped, not code-shaped. + The threat model is prompt-shaped, not code-shaped. - **A shared pack is an untrusted prompt.** It runs with the *user's own* - session and only the tools it declares, so the blast radius is: it can read - the doc it was invoked on, say things, and propose confirm-gated edits. - There is no network tool and no cross-pack `recall`, so no exfiltration - channel exists to grant. Import shows the full prompt and the tool list as - the consent screen — the same move as the launcher plan's manifest consent, - minus the token. + session and only the tools it declares. Blast radius: read the doc it was + invoked on, compute over its own workspace, say things, propose + confirm-gated edits. No network tool, no cross-pack workspace read. +- **Import is the consent screen**: full prompt, tool list, and any + third-party MCP servers it wants, shown before first run. +- **Tool results from third-party MCP servers are untrusted data**, never + instructions — the loop must not let them redirect the pack or escalate its + tool grants. - **Attribution:** `pack_id` alongside `client_id` on `llm_usage` and the log - envelope. Per-pack quotas can reuse the per-tool quota machinery when it - lands (launcher plan Phase 3). -- **Memory is visible and erasable.** `remember`ed state appears on the - pack's settings card and is covered by the existing erasure flow. + envelope. Per-pack quotas reuse the per-tool quota machinery when it lands + (launcher plan Phase 3). Note [#578](https://github.com/AIToolsLab/writing-tools/issues/578) + — grant tokens minting fresh grants — as the analogous mistake not to + repeat with pack scopes. +- **Workspace state is visible and erasable**: a directory the writer can + read, export and delete, covered by the existing erasure flow. - **Content in tool results obeys the consent gate** exactly as log payloads - do (`src/consent.ts`) — `read_history` returns what the user's level - permits, nothing more. + do (`src/consent.ts`), which depends on that gate being default-closed + ([#615](https://github.com/AIToolsLab/writing-tools/issues/615)). ## What this buys the research program @@ -229,22 +433,24 @@ supporting. 1. **Packs as data, first-party only.** Extract the Draft buttons (and the Revise grid, [#597](https://github.com/AIToolsLab/writing-tools/issues/597)) into built-in pack files; a small runtime executes `button` packs with - `offer`. No user authoring yet — this proves the format on code we already - trust. + `offer`. Define tool schemas in MCP shape from the start (Role 2). No user + authoring yet — this proves the format on code we already trust. 2. **Chat gets `view` + `highlight`** ([#583](https://github.com/AIToolsLab/writing-tools/issues/583), - already filed) via the shared loop runner lifted out of my-words. -3. **User pack storage + paste-to-import.** The backend KV for packs - themselves; an import screen that shows prompt + tools. Draft renders the - user's `button` packs below the built-ins. + already filed) via the shared loop runner lifted out of my-words, with + `TextQuoteSelector` anchoring that also closes + [#589](https://github.com/AIToolsLab/writing-tools/issues/589)/[#591](https://github.com/AIToolsLab/writing-tools/issues/591)/[#592](https://github.com/AIToolsLab/writing-tools/issues/592). +3. **User pack storage + paste-to-import**, with the consent screen. + Draft renders the user's `button` packs below the built-ins. 4. **Self-extension:** `save_pack` / `test_pack` / `list_packs` in Chat; provenance cards. -5. **`ask_writer` + `annotate` + `persona`/`lens`/`ritual` surfaces** — the - dialogic tier. -6. **`remember`/`recall`** (user-scope KV) — the stateful tier; Charter - becomes buildable as a pack, which is a good acceptance test. -7. **On demand:** `read_history`, `get_revisions`, `search_corpus`, - `propose_edit` — each lands when the first concrete pack needs it, not - before. +5. **`ask_writer` + `annotate` + the `persona`/`lens`/`ritual` surfaces** — + the dialogic tier. +6. **The workspace** (server-side VFS + git, behind our own tool interface). + Charter becomes buildable as a pack, which is a good acceptance test. + Document snapshotting as commits unlocks Tidelines and Post-Game Review. +7. **Outbound MCP** (Role 1) — packs may declare third-party servers. +8. **On demand:** `propose_edit`, corpus import, and — as a deliberate, + separately-argued step — inbound MCP (Role 3, read-only). Steps 1–4 are the minimum for "extensions prompt themselves into existence"; -5–6 are where the coverage table's 80% actually arrives. +5–7 are where the coverage table's 80% actually arrives. From 9a25a82593efb5d8b522afaaeaaa21f395ce8468 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 16:44:23 +0000 Subject: [PATCH 3/3] =?UTF-8?q?docs(prompt-packs):=20correct=20the=20distr?= =?UTF-8?q?ibution=20story=20=E2=80=94=20collections=20we=20host,=20not=20?= =?UTF-8?q?servers=20they=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The doc's founding question is whether a domain expert who is not a software engineer can extend Thoughtful, and its own distribution story failed that test: 'a writing center publishes an MCP server' assumes a public HTTPS endpoint, multi-tenant auth and someone on call — more engineering than the monorepo PR the ladder exists to avoid. Adds a 'Who can actually publish?' section with the principle that LLMs made authoring cheap and operating no cheaper, a table of what skill each distribution mechanism actually demands, and the rule that anything asked of a domain expert must be something we host. Names collections (shared sets of packs on our infrastructure, Google-Docs-shaped) as the real distribution primitive, with author-hosted static URLs as an option rather than a requirement, and states what hosting distribution obliges us to take on — provenance, reporting, moderation — since that cost is what made self-hosting tempting to write down. Repositions MCP accordingly: the writing center is a consumer of servers others already run, never an operator. Collections added to the phasing. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EUSXFcRA7ox4u9x1QENyHw --- docs/prompt-packs-plan.md | 98 ++++++++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/docs/prompt-packs-plan.md b/docs/prompt-packs-plan.md index 0b63c104..c9ada987 100644 --- a/docs/prompt-packs-plan.md +++ b/docs/prompt-packs-plan.md @@ -246,6 +246,73 @@ displayed in full on its settings card. **Legibility is the trust story**: a pack is inspectable in a way code never is, and the writer who authored one by talking can read exactly what they made. +## Who can actually publish? + +The founding question of this document is whether a domain expert who is not +a software engineer can extend Thoughtful. That question has to be asked of +every mechanism the document proposes, *including its own distribution +story*. An earlier draft failed it: "a writing center publishes an MCP +server" quietly assumed a public HTTPS endpoint, multi-tenant auth and +someone on call. + +The corrective principle: **LLMs made authoring cheap and did nothing to make +operating cheap.** A writing-center director can now produce a working prompt +in an afternoon; they still cannot run a service, and no model will page them +at 2am. So an extensibility ladder built for this era should push +*operations* onto the platform and leave only *authoring* with the extender. +Any mechanism that fails this test is a mechanism for us and for Tier-2 +developers, and should be labelled as such rather than offered to a domain +expert. + +| # | Mechanism | Skill demanded | Who hosts | Verdict | +| --- | --- | --- | --- | --- | +| 0 | Author a pack in Chat | conversation | us | the target | +| 1 | Share a pack with a colleague | send a link or a code | us | the target | +| 2 | Publish a **collection** others subscribe to | naming, curation, writing a description — editorial, not technical | us | **the target; this is what "a writing center publishes" has to mean** | +| 3 | Host pack files yourself at a URL | static hosting (GitHub Pages, a university web directory), git | them | fine as an option; required of no one | +| 4 | Run an MCP server | server code, TLS, OAuth, uptime, incident response | them | out of reach — and unnecessary, see below | + +The rule that follows: **anything we expect a domain expert to do must sit at +level ≤ 2, and level 2 must be something we host.** + +### Collections: the actual distribution primitive + +A collection is a named set of packs with an owner and a visibility setting — +Google-Docs-shaped sharing, not app-store-shaped publishing. It lives on our +infrastructure as ordinary data, so: + +- publishing is clicking Share and naming the thing; +- subscribing is opening a link; +- an update propagates to subscribers **with the change shown**, because a + pack is a prompt that will run against someone else's document and silent + edits to that are not acceptable; +- provenance and version history come from the collection record rather than + from the author's infrastructure. + +Level 3 stays available for anyone who wants to own the canonical copy — a +collection can point at a static URL or a git repo of pack files, which costs +GitHub Pages rather than a server. It should never be the only path. + +**What hosting distribution costs us.** This is not a free win, and the cost +is exactly why self-hosting was tempting to write down. A shared pack is an +untrusted prompt that runs against someone else's document, so hosting +distribution makes us a distribution platform: provenance display, a report +path, a policy on what a public collection may contain, and some answer for a +pack that is hostile rather than merely bad. One-to-one sharing is +low-stakes; a public gallery is a different product with different duties. +Sequence accordingly — private sharing and named collections first, anything +resembling a directory only once we are willing to own moderation. + +### So what is MCP for? + +Not distribution. **The writing center is a pack author, not a server +operator**, and in the MCP relationship it is a *consumer*: one of its packs +can declare a server somebody else already runs — Zotero, a library +catalogue, an LMS vendor, a campus system maintained by IT. That is Role 1 +below, and it asks nothing of the pack's author beyond naming it. Where no +such server exists, the honest answer is that the integration waits for +someone to build it, and that someone is a Tier-2 developer. + ## Reuse over invention The test is not "does a standard exist" but: **is the idiom already in both @@ -291,12 +358,14 @@ against this design almost one for one: | **Sampling** (server asks the client for a completion) | our LLM proxy relationship, inverted | That third row is the striking one: a pack is very nearly already an MCP -prompt, which means the pack *format* may not need inventing — and, more -importantly, neither does pack **distribution**. A writing center publishes -an MCP server exposing its prompts; an instructor's students connect to it; -the packs appear in their sidebar; when the center improves a prompt, -everyone gets the fix. Versioning, updates and provenance all come along. -Paste-a-JSON-blob handles none of that well. +prompt, so the pack *format* may not need inventing. + +**Pack distribution is a different question, and MCP is the wrong answer to +it.** Serving prompts over MCP means running a live server with a public +endpoint, auth and uptime — more engineering than the monorepo PR this whole +ladder exists to avoid, demanded of the person least able to supply it. Packs +are static data; distributing them needs no server. See +[Who can actually publish?](#who-can-actually-publish). ### Role 1: Thoughtful as MCP *client* (outbound) — the real unlock @@ -443,14 +512,19 @@ supporting. Draft renders the user's `button` packs below the built-ins. 4. **Self-extension:** `save_pack` / `test_pack` / `list_packs` in Chat; provenance cards. -5. **`ask_writer` + `annotate` + the `persona`/`lens`/`ritual` surfaces** — +5. **Collections** — share one pack with a colleague, then named collections + with subscribe and update-with-diff. Hosted by us (level ≤ 2); a + collection may optionally point at an author-hosted static URL. Public + directories deliberately excluded until moderation is owned. +6. **`ask_writer` + `annotate` + the `persona`/`lens`/`ritual` surfaces** — the dialogic tier. -6. **The workspace** (server-side VFS + git, behind our own tool interface). +7. **The workspace** (server-side VFS + git, behind our own tool interface). Charter becomes buildable as a pack, which is a good acceptance test. Document snapshotting as commits unlocks Tidelines and Post-Game Review. -7. **Outbound MCP** (Role 1) — packs may declare third-party servers. -8. **On demand:** `propose_edit`, corpus import, and — as a deliberate, +8. **Outbound MCP** (Role 1) — packs may declare third-party servers. +9. **On demand:** `propose_edit`, corpus import, and — as a deliberate, separately-argued step — inbound MCP (Role 3, read-only). -Steps 1–4 are the minimum for "extensions prompt themselves into existence"; -5–7 are where the coverage table's 80% actually arrives. +Steps 1–4 are the minimum for "extensions prompt themselves into +existence"; step 5 is what lets anyone but the author benefit from one; 6–8 +are where the coverage table's 80% actually arrives.