From e36fe254f0d2179ba93046096cf3670b4ccb5b10 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 5 May 2026 02:58:32 +0000 Subject: [PATCH] =?UTF-8?q?P0005:=20accept=20+=20execute=20=E2=80=94=20asy?= =?UTF-8?q?nc-by-default-for-long-running-tools=20principle=20(NEW)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Acceptance: - Flip docs/promotions/P0005 promotion_status: proposed -> accepted - Update tags array ("proposed" -> "accepted") - Fill Review Notes with operator decision (klappy, 2026-05-05) Execution: - canon/principles/async-by-default-for-long-running-tools.md (NEW): tier-2 - Frontmatter declares: derives_from partial-data-with-transparency, vodka-architecture, axioms; complements partial-data-with-transparency - Codifies the three-tool triad for any long-running MCP action: (...), get__status(id), cancel_(id) - Latency budgets: submit <=5s p99; status <=1s median; notification <=1s median; long-poll <=5s p99 - Action-side complement to partial-data-with-transparency-and-background-warm (which is the read-side rule) - Receipts: PTXprint v1.2 typesetting + AMS hosted /mcp Last of 8 stuck proposals. Sweep complete: P0009 (#167), P0001 (#168), P0008 (#169), P0007 (#170), P0006 (#171), P0003 (#172), P0004 (#173), P0005 (this PR). P0002 had already merged via #163->#165->#166. --- ...async-by-default-for-long-running-tools.md | 56 +++++++++++++++++++ ...async-by-default-for-long-running-tools.md | 16 +++--- 2 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 canon/principles/async-by-default-for-long-running-tools.md diff --git a/canon/principles/async-by-default-for-long-running-tools.md b/canon/principles/async-by-default-for-long-running-tools.md new file mode 100644 index 0000000..abb1f0b --- /dev/null +++ b/canon/principles/async-by-default-for-long-running-tools.md @@ -0,0 +1,56 @@ +--- +uri: klappy://canon/principles/async-by-default-for-long-running-tools +title: "Async by Default — Long-Running MCP Tools Return an Identifier, Never Block" +audience: canon +exposure: nav +tier: 2 +voice: neutral +stability: evolving +tags: ["canon", "principle", "mcp-server", "async", "long-running", "latency", "vodka-architecture"] +derives_from: + - klappy://canon/principles/partial-data-with-transparency-and-background-warm + - klappy://canon/principles/vodka-architecture + - klappy://canon/values/axioms +complements: + - klappy://canon/principles/partial-data-with-transparency-and-background-warm +status: active +--- + +# Async by Default — Long-Running MCP Tools Return an Identifier, Never Block + +> Any MCP tool whose work could exceed ~5 seconds wall-clock returns an identifier within that budget and places the long-running work behind a separate read tool. No tool blocks for the duration of work. + +## The Principle + +Three minimum-viable tools result for any long-running action: + +1. `(...)` — submit; returns identifier within ~5 seconds +2. `get__status(id)` — poll; returns current state, progress, and result-when-complete +3. `cancel_(id)` — request cancellation; returns ack + +Notification-style push (server-pushed events on supported transports) is additive. The polling tool remains the canonical floor so consumers on poll-only transports work too. + +## Latency Budget Recommendation + +- **Submission tool returns**: ≤ 1s median, ≤ 5s p99 +- **Status read tool returns**: ≤ 1s median (state read, never reaches the worker that does the work) +- **Notification delivery (when present)**: ≤ 1s median, ≤ 5s p99 +- **Long-poll fallback**: ≤ 5s p99 round-trip + +## Failure Mode — Blocking the Consumer + +A tool that blocks for 30 minutes ties up the consumer's MCP session, hides progress, breaks cancellation, and forces every consumer host to implement timeout/retry around it. Returning an identifier immediately keeps the wire predictable and the consumer in control of when to ask for results. + +The shape also keeps the *server* in control of how long the work continues if the consumer disconnects. With a blocking tool, the work dies on disconnect; with the async shape, the work continues, the cache populates, and the next consumer's request finds the result without re-running the work. + +## Relationship to Adjacent Canon + +`canon/principles/partial-data-with-transparency-and-background-warm` is the read-side complement: the user-blocking *read* path must not block on a corpus scan; return what's already observed, schedule the rest in the background, disclose what's missing. This principle is the action-side: the user-blocking *action* path must not block for the duration of the work; return an identifier, expose poll+cancel, let the consumer drive their own attention. + +Both principles share the underlying axiom: the consumer's blocking time is a budget the substrate must spend frugally. + +## Receipts + +- **PTXprint-MCP v1.2 typesetting.** `submit_typeset` / `get_job_status` / `cancel_job` triad. Worker → `ctx.waitUntil(fetch())` → Container → DO state. 30-minute jobs do not block the consumer's MCP session at any point. +- **AMS hosted /mcp.** `ams_send` returns on wire-accept, not peer-receive. `ams_recv` is the explicit poll path with a 5–10s long-poll cap. `ams_leave` is the cancellation path. Same shape. +- *(Future receipts: each compliant server adds one row — server, action tool, status tool, cancel tool, observed median submit latency.)* diff --git a/docs/promotions/P0005-async-by-default-for-long-running-tools.md b/docs/promotions/P0005-async-by-default-for-long-running-tools.md index 5ec1ddf..1e7e354 100644 --- a/docs/promotions/P0005-async-by-default-for-long-running-tools.md +++ b/docs/promotions/P0005-async-by-default-for-long-running-tools.md @@ -6,8 +6,8 @@ exposure: nav tier: 3 voice: neutral stability: evolving -tags: ["promotions", "proposed", "mcp-server", "async", "long-running", "job-id", "polling", "latency"] -promotion_status: proposed +tags: ["promotions", "accepted", "mcp-server", "async", "long-running", "job-id", "polling", "latency"] +promotion_status: accepted --- # P0005: Async by Default — Long-Running MCP Tools Return an Identifier, Never Block @@ -132,16 +132,14 @@ The principle is distinct from `partial-data-with-transparency-and-background-wa ## Status -`proposed` +`accepted` (2026-05-05) ## Review Notes -(To be filled during review) - -- **Reviewer**: -- **Decision**: -- **Date**: -- **Notes**: +- **Reviewer**: klappy (operator) +- **Decision**: `accepted` +- **Date**: 2026-05-05 +- **Notes**: Last of the 8-proposal sweep (P0001 + P0003–P0009 behind the just-merged P0002 chain). Created `canon/principles/async-by-default-for-long-running-tools.md` as a tier-2 principle doc. Action-side complement to `partial-data-with-transparency-and-background-warm` (which is the read-side rule). Codifies the three-tool triad (``, `get__status`, `cancel_`) and the four latency budgets. Receipts: PTXprint v1.2 typesetting + AMS hosted /mcp. ## Execution Record