Repository navigation
Description
Relocated from dezren39/darwin-nix#77 on 2026-10-28 (placement audit): mcpx's single source lives in developing-today/code (pkgs/mcpx); re-home tracked at #309. Original body below; issue references inside it are historical (dezren39/nix numbering).
Parent: #181
Summary
Four protocol gaps left after #61, each named in docs/protocol.md §6 and none tracked
elsewhere. Re-verified at 05c78b2: all four are still true, and §6 still lists them, which
is the right place for them to live — a gap written down in the document that describes the
surface is worth more than a gap in an issue. What this issue adds is the ordering and the one
real design decision among them.
Original notes
Not from the user's notes. Filed from the protocol work in #61:
Follow-ups from the protocol work (#61), each named in
docs/protocol.md§6 but not tracked.
- Questions raised inside
mcpx_execcannot be answered inline. Correlation identifies a call, and a script makes many upstream calls, so questions from a script go to the broker rather than back to the MCP client asinput_required. A script-level correlation (the run id) would fix it.- Inbound
notifications/cancelleddoes not interrupt the upstream call. A client cancelling a longtools/callstops waiting; the upstream server keeps working.notifications/tasks/statusfrom upstream is dropped. mcpx neither forwards nor records it.pool.callTimeoutis not call-scoped. The most obviously wanted per-call setting:--pool-call-timeouton one command should bound that command's call, but a daemon started with a different value ignores it. Needs threading throughinternal/pool(chore(deps): bump rustix from 0.37.19 to 0.37.25 in /sources/pavex/examples/realworld #56 left it, reasonably).
Current state (at 05c78b2)
docs/protocol.md §6 "What is still missing" (:368-382) lists all four, plus two the notes did
not mention: url-mode elicitation raised by mcpx itself, and OAuth for remote servers ("declared
and not performed"). The last one is a #177 instance — a declared capability that is not
delivered — and should be cross-linked there rather than quietly carried here.
| Gap | Verified at 05c78b2 |
|---|---|
| Inbound cancellation does not interrupt the upstream call | notifications/cancelled is sent by mcpx as a client (internal/mcpclient/client.go:715) and accepted by the test servers, but nothing routes an inbound one to an in-flight upstream call. internal/mcpclient/http.go:112 documents the decision: protocol-level cancellation was removed because pool.callTimeout was the bound that mattered. §6 states what is needed: "the request id plumbed through the pool". |
notifications/tasks/status is dropped |
The string appears nowhere in internal/. §6: "mcpx polls instead." |
A question from mcpx_exec cannot be answered inline |
§3.4's correlation identifies a call; §6 restates the consequence. Unchanged. |
pool.callTimeout is not call-scoped |
internal/settings/registry.go:57 (Default: "120s"), read at internal/config/poolsettings.go:43-44 into the pool's configuration. There is no per-call override path. |
Approaches
Three of the four have no real choice; one does.
pool.callTimeout per call — no real choice, and it is the one to do first. Thread an
optional per-call deadline from the request through internal/pool and let the pool's configured
value be the default. The only decision worth making is whether a caller may raise it or only
lower it, and that decision is not this issue's: it is #185 (ClampedBy — a daemon sets a
ceiling, a caller may lower but not raise). Take #185's answer. A daemon operator who sets 120s to
protect the machine should not be overridden by a script asking for an hour; a script that wants
30s should get 30s. Note the interaction the code already documents: internal/mcpclient/http.go
removed protocol-level cancellation because pool.callTimeout was the bound. Making the timeout
call-scoped therefore makes the cancellation gap below more visible, not less.
notifications/tasks/status — no real choice. Record it and expose it. mcpx already polls;
the notification is strictly better information arriving earlier. It lands in the event store
(#6), which is where anything with a seq belongs, and #6's gap semantics apply — a saturated
consumer must see a gap marker rather than silently miss a status change. Do it with or after #6.
Inbound cancellation — no real choice about whether, a real one about scope. A client that
cancels and an upstream server that keeps working is a resource leak with a user watching it. The
plumbing is what §6 says: carry the inbound request id into the pool so the in-flight call can be
found and cancelled. The decision is whether cancellation is best-effort (send
notifications/cancelled upstream, stop waiting, let the server finish in its own time — which is
all the MCP spec promises) or enforced (also tear down the pooled instance when the scope makes
that safe). Best-effort is correct by the spec and cheap; enforced is what a user means by
"cancel" when a runaway tool is eating the machine. Recommend best-effort by default with the
teardown available where the scope is caller-private — which is the same isolation question #190
and #191 are about.
The exec correlation gap — the one genuine design decision.
A — least recommended: give a script's questions a synthetic call id and answer them as if they
were a call. Cheapest, and it looks right in a demo. It breaks as soon as a script makes two
concurrent upstream calls that both ask: the client is answering a question it cannot attribute,
and a wrong answer to an elicitation is worse than no answer. It also silently changes what §3.4's
correlation means, which is a documented wire contract.
B — keep questions from scripts on the broker, and make the broker reachable from the client.
Status quo plus a way for an MCP client to poll and answer broker questions in-band. No contract
changes, works today via /v1/elicit and mcpx_ask_*. The cost is that a modern client which
implements input_required correctly still gets a worse experience from mcpx_exec than from
mcpx_call, for no reason the client can see.
C — recommended: add a second correlation level — the run — and declare it in §3.4. A script's
questions correlate to the run id, which the exec surface already has and already exposes
(execsvc.Result.runId). A question raised inside a run is answerable inline by whoever invoked
the run, because there is exactly one of those, and the run id disambiguates concurrent upstream
calls without pretending they are one call. This is what the original note proposes and it is
right. It costs a documented change to §3.4's correlation rules and a revision-gated behaviour
(only clients that understand the run-level correlation get inline answers; the rest fall back to
B's broker), which is the pattern internal/mcpserver/revisions.go already implements for
FeatInputRequired.
Recommended because it is the only option that makes mcpx_exec a first-class citizen of the
question protocol rather than a permanent exception, and because the fallback to B is free.
Open questions
- Does the run-level correlation need to nest — a script that runs a recipe that runs a script?
§3.4's rules should state the nesting or forbid it explicitly. - Cancellation and tasks overlap: cancelling a call that was promoted to a task
(/v1/tasks/{id}/cancel) already exists. Is inboundnotifications/cancelledon a task-backed
call the same path? If so, the plumbing is smaller than §6 suggests. docs/protocol.md§6 also lists "OAuth for remote servers. Declared and not performed".
That is a declared-but-undelivered capability and belongs in feat(nix): Android SDK + JDK 21 in the system and dev shells, and the OpenChamber release signing key #177's inventory. It is out of
scope here; it should not stay only in a §6 bullet.- Is
pool.callTimeout's interaction withmcpx exec's own timeout defined anywhere? Two
timeouts with no stated precedence is how the next "flag accepted and ignored" (Update Rust crate petgraph to 0.6.4 - autoclosed #74) happens.
Ease · Impact · Related
Ease M overall — S for tasks/status, M for the call-scoped timeout, M for cancellation
plumbing, M–L for the run-level correlation and its revision gate.
Impact med-high — the call-scoped timeout is the most obviously wanted per-call setting in the
product, and a cancel that does not cancel is the kind of thing a user tells other people about.
Related: #181 (parent), #185 (ClampedBy — decides whether a caller may raise the call
timeout), #6 (event store — where tasks/status records belong, and whose gap semantics apply),
#112 (exec lifecycle hooks — shares the exec Frame ordering contract; #111 owns the rule for
adding a frame kind, collision 7), #177 (OAuth "declared and not performed" belongs in its
inventory), #78 (daemon state and caches), #56 (the settings pass that deliberately left the
per-call path alone), #61 (closed — the work these follow).
In flight: the PROTOCOL architect is working on these as this is written
(internal/mcpclient/**, internal/mcpserver/**, internal/pool/**, docs/protocol.md).
Re-check state before starting; parts of this may already be merged.
Security/auth deferred; see tracker.
Activity
developing-today commentedon Oct 28, 2026
CODE-791
dezren39 commentedon Oct 28, 2026
History carried over from dezren39/darwin-nix#77 (verbatim, oldest first):
@dezren39 · 2026-09-30T07:28:47Z
Integration pass. This issue has no category tracker -- it predates the build-out and is a dependency of several parts of it. Placed in the proposed order at #181.
@dezren39 · 2026-09-30T12:18:58Z
Issues pass (2026-09-30, at
05c78b2). Restructured to the backlog template; the originaltext is quoted verbatim under Original notes and nothing was dropped.
notifications/tasks/statusappears nowhere ininternal/.pool.callTimeoutisinternal/settings/registry.go:57(default120s), read atinternal/config/poolsettings.go:43-44, with no per-call override path. Inboundnotifications/cancelledis sent by mcpx as a client (internal/mcpclient/client.go:715) butnothing routes an inbound one to an in-flight upstream call — and
internal/mcpclient/http.go:112records why: protocol-level cancellation was removed because
pool.callTimeoutwas the boundthat mattered. Making the timeout call-scoped therefore makes the cancellation gap more visible.
docs/protocol.md§6 carries two gaps this issue did not name, one of which belongselsewhere: "OAuth for remote servers. Declared and not performed" is a declared-but-undelivered
capability and belongs in feat(nix): Android SDK + JDK 21 in the system and dev shells, and the OpenChamber release signing key #177's inventory, not in a §6 bullet.
call id breaks on two concurrent asking calls; the broker-only status quo makes
mcpx_execapermanent second-class citizen; a run-level correlation declared in §3.4, revision-gated the
way
FeatInputRequiredalready is, is recommended. The other three are marked "no realchoice" with the one decision each carries.
ClampedBy): whether a caller may lower theceiling only, or raise it. That is Phase 3: sync, blob gate, and relays #181 collision 5 and it is not this issue's to settle.
Parent: #181and an Ease/Impact/Related footer added.internal/mcpclient/**,internal/mcpserver/**,internal/pool/**,docs/protocol.md). Re-check before starting.@dezren39 · 2026-09-30T12:26:18Z
Issues pass (2026-09-30, at
05c78b2) -- for whoever lands this. This issue is in flight, so its body was not edited. These came out of re-checking every other issue against currentmain; each is verified at05c78b2unless marked.samplingto every upstream server whenever an elicitation handler is installed (internal/mcpclient/modern.go:54-56). The daemon always installs one, so every upstream is told it may sample, even when no caller can answer.rootsis declared unconditionally (modern.go:52) and is deprecated by the same SEP as Sampling. Bump build/buck/preludes/buck2-prelude fromd956689to52604b1#110 carries the decision about both declarations.docs/protocol.md§2.2 (:96-110) still listsloggingas declared for three revisions and says push "depends on a notifier". Since Phase 2: delegated Manage cannot add members #171 neither is true (Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159 item 18).@dezren39 · 2026-09-30T14:21:56Z
First bullet (questions raised inside mcpx_exec answered inline by the MCP client) implemented in #236: correlation by run id, one attribution rule shared with single calls (every in-flight call on the key must belong to the run, counted by the pool), both eras. Remaining bullets: inbound cancellation now reaches the upstream call on stdio and HTTP (#198); upstream notifications/tasks/status and call-scoped pool.callTimeout remain open here.
@dezren39 · 2026-10-01T21:58:15Z
Inbound
notifications/cancelledfrom an upstream server is now handled by mcpx as a client (branchfix4/client-gaps, 316f1e7, combined fix4 PR): requests the server sent mcpx (elicitation, sampling, roots) are tracked by id; a cancellation naming one ends the handler's context (so a question relayed to the host is withdrawn) and no response is sent. The reason goes toOnWarning→server.warningin the lifecycle log. Gap records removed for clientcancellation-receiver-should-stop-free-no-response,cancellation-processing-should-ceaseandcancellation-log-reasons; those 13 cells now run and pass, and fail withcancelAskedremoved ("answered=true stopped=false").Still open here: server-side
cancellation-log-reasons(reason reachesServer.OnCancel, which nothing outside tests sets), and the other rows in this issue's body (tasks/status, exec questions, per-call timeout).@dezren39 · 2026-10-28T20:45:58Z
Triage verdict (lane A, task 2049, 2026-10-28): CONFIRMED at current HEAD, with one sub-item partially advanced — routed code-side; kept open under #181.
Evidence (owning repo developing-today/code @ bfcfc15; the mcpx source left dezren39/nix on 2026-10-19, #468):
pkgs/mcpx/docs/protocol.md§6 "What is still missing" still lists these gaps at HEAD — the document that describes the surface still names all of them.notifications/tasks/status: still dropped. The string appears only in the vendored spec schema (internal/mcpspec/schema/2025-11-25/schema.json) and conformance data (internal/conformance/requirements.json) — no handler anywhere ininternal/.pool.callTimeout: still not call-scoped. It is read once from settings into the pool configuration (internal/config/poolsettings.go:55-56, registry default atinternal/settings/registry.go:107); there is no per-call override path.notifications/cancelled/ closed stream cancel the upstream request); the remaining cancellation gap is the ask path, where a call runs as a daemon task and keeps running. The inline-answer gap persists in its current form (-32021deliberately not raised; broker-fallback question is ⚡ Bolt: Optimize search match counting using pre-computed highlights #252 item 1).Routing: no follow-up packet from this triage — this is protocol design work in developing-today/code (re-home tracked by #309, OPEN), and its own sequencing stands: the callTimeout item takes #185's
ClampedByanswer, and the tasks/status item lands with or after #6's event store. No counterpart for #77 exists in code yet, so #77 stays open under tracker #181.dezren39 commentedon Oct 29, 2026
🤖 Agent note · picked up
dezren39 commentedon Oct 29, 2026
🤖 Agent note · triage verdict — RESCOPED: one item already landed, one partially landed, one deferred to #1554, one follow-up enqueued
Verdict: the issue's snapshot is stale in the way its own "In flight" warning predicted — the protocol work has since landed parts of this. Per-item dispositions below; only the call-scoped timeout is enqueued. No duplicate issue exists for any item (dedupe searches return #1864 alone, plus #1554 for the tasks overlap noted).
Verified fresh against current main
82b019a0, chieflypkgs/mcpx/docs/protocol.mdas rebuilt (§3.4, §3.5, §6 "What is still missing", dated 2026-09-30) plus code reads:mcpx_exec) runs as an interruptible call (/v1/askkindexec); its run id is chosen and registered before the script starts; the generated client sends it asX-Mcpx-Runon every/v1/call, so upstream calls join the ask table as members of the run; a question on a key whose in-flight calls all belong to one run is that run's — parallel calls included; ambiguity (two runs, or a run plus anyone else) goes to the broker, and run membership is removed when the run ends. That is Approach C's design, including the broker fallback. Current §6 no longer lists this gap. No packet; nothing left of this item except this note. (The nesting question the issue raised is answered implicitly by the as-built rule — membership is per run id, and a nested run is a different run sharing a key means ambiguity → broker. If that is not the intended semantics, it is a §3.4 documentation point, not an implementation gap.)notifications/cancelled(stdio, legacy HTTP session) and a closed stream (2026-07-28 HTTP, sessionless legacy) cancel the upstream request." What remains is exactly the issue's open question: "A call on the ask path runs as a daemon task and keeps running (§3.5)." Since ask-path calls ARE daemon tasks and/v1/tasks/{id}/cancelalready exists, the remainder is plausibly the smaller plumbing the issue hoped for — but that equivalence must be confirmed by whoever implements it, so no packet is enqueued from triage: the item stays open here, rescoped to ask-path/task-backed calls only, with the direct-call part recorded as done.notifications/tasks/statusdropped — STILL OPEN, deferred to design(mcpx): tasks, finished — one task store for MCP and /v1, and a client that can call task-required tools #1554 by design, not neglect. Current §6 still lists it ("notifications/tasks/statusis dropped; mcpx never task-augments a call upstream…"). The issue's own approach says record-and-expose lands in the event/task store — and the one-task-store design now exists as design(mcpx): tasks, finished — one task store for MCP and /v1, and a client that can call task-required tools #1554 ("tasks, finished — one task store for MCP and /v1…", open). Recording task status into a store that is being redesigned would be throwaway work; this item should land with or after design(mcpx): tasks, finished — one task store for MCP and /v1, and a client that can call task-required tools #1554. Cross-linked there by this verdict's reference; it stays listed on mcpx: protocol follow-ups -- inline answers from scripts, cancellation, task status, call-scoped timeout #1864 so it is not lost if design(mcpx): tasks, finished — one task store for MCP and /v1, and a client that can call task-required tools #1554's scope shifts.pool.callTimeoutnot call-scoped — STILL OPEN; follow-up enqueued (the issue's "do first" item).internal/config/poolsettings.go~L55-56 still readspool.callTimeoutonly from settings into the pool's (per-server) configuration;internal/config/config.go'sCallTimeoutis a per-server config field throughout (merge at ~L855) — no per-request/per-call override path exists, so--pool-call-timeouton one command still cannot bound that command's call. (One facet did land since the snapshot: §3.5 —pool.callTimeoutnow pauses while a question is pending on the instance. That is not call-scoping.) The follow-up carries the issue's settled rules: thread an optional per-call deadline throughinternal/pool; the daemon's configured value is the ceiling — a caller may lower it, never raise it (the ClampedBy rule this issue adopts); and the precedence between the call-scoped timeout andmcpx exec's own timeout must be written down in the same change (the issue's open question — two timeouts with no stated precedence is how the next "flag accepted and ignored" happens).Dedupe: per-item searches (call-scoped timeout; notifications/cancelled upstream; tasks/status; run correlation) return #1864 as the only scoped issue, with #1554 the owning design for item 3's store. Nothing closed as a duplicate.
This issue stays open as the home for items 2 (remainder) and 3; items 1 is done and 4 is queued.
dezren39 commentedon Oct 29, 2026
🤖 Agent note · triage complete
pool.callTimeoutimplementation enqueued to repo-health-code-pilot as task 3082 (small_fix, dedupe_keyrepo-health:v1:fix:developing-today/code:issue:1864); this issue now carriesqueue/queuedfor it and stays open for the ask-path cancellation remainder and the design(mcpx): tasks, finished — one task store for MCP and /v1, and a client that can call task-required tools #1554-deferred tasks/status item.136 remaining items
dezren39 commentedon Nov 4, 2026
agent-note:v1
state: parked
queue: repo-health-code-pilot
task: 9002 (small_fix, worker repo-worker-code, lane aea12c82-c5af-4e5a-9b66-f8e360f87177)
by: openchamber-scheduled-lane
at: 2026-11-04T07:14Z
Implementation and targeted tests are complete on branch
queue/9002-1864-ask-cancelin/home/user/git/fleet/9002;go test ./internal/mcpserver -count=1andgo test ./internal/cli -count=1pass. Parked at the run budget before commit/PR. Resume: assess the recorded transport-boundary cancellation race, rungit diff --check, commit/push, open the PR, and verify CI. Queue resume is scheduled in 20 minutes.dezren39 commentedon Nov 4, 2026
agent-note:v1
event: handed_off
task: 9002
lane: code-m (worker repo-worker-code)
execution_lane: opencode
note: Re-claimed by lane code-m at 2026-11-04T07:35Z; execution-lane assignment is opencode, so the task is handed off — the laptop opencode claimant launches the bound session. Resume state preserved in task 9002 checkpoints: branch queue/9002-1864-ask-cancel at laptop /home/user/git/fleet/9002, stage targeted_tests_passed, no commit/PR yet; remaining: assess the recorded transport-boundary cancellation race, commit/push, open PR, verify CI.
dezren39 commentedon Nov 5, 2026
agent-note:v1
event: handed_off
task: 9002
worker: repo-worker-code (lane code-f)
stage: handed_off
summary: Task 9002 re-claimed by lane code-f (attempts 16/16); execution-lane assignment again returned assigned_lane=opencode with session_id null, so this lane hands off per the runbook. Resume state for the claimant: laptop worktree ~/git/fleet/9002 on branch queue/9002-1864-ask-cancel — implementation and targeted suites complete per prior checkpoints; remaining: assess cancellation race, commit/push, open PR. Flagging the attempts cap: if this claim's lease expires unlaunched, the task hits max_attempts and will need curator/monitor attention.
dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off
opencode, so this task is handed off to the opencode claimant, which launches the bound opencode session. Checkpointed stagehanded_offon task 9002.dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off