Repository navigation
Description
Design: mcpx sampling trust boundary — reviewable, validated, and honestly error-coded
Design doc: files/designs/code-mcpx-sampling-trust-boundary.md in the keep-the-four-repos-healthy goal workspace (developing-today/code design architect, pass 10, 2026-10-28).
Origin: dezren39/nix#256 — sampling relay: human review, validation of results, error mapping (8 conformance rows). This design scopes only its remainder (audit below); no Drew direction decision is needed — nix#256's acceptance is explicit and its scope is conformance-only (sampling is deprecated in 2026-07-28; standing instruction is keep-working, add-nothing-new).
Problem
mcpx has no model, so sampling is a relay with a trust decision inside it: an upstream asks for a completion, something answering for the user produces one, and mcpx hands it back as if the user had spoken. Verified on main 528886eb:
- A person cannot review a sampling request — the prompt is not shown.
sampleViaBroker(pkgs/mcpx/internal/daemon/hooks.go~L198-214) stores the request's system prompt as the broker question's Message (fallback: "<server>asks for a model completion") and the full request in the question's Schema field. Butmcpx elicit'srenderQuestion(pkgs/mcpx/internal/cli/elicit.go) renders Schema as a JSON Schema:describeSchemalooks forproperties, a sampling request has none, so the messages — the actual prompt — render as nothing and the answer example offered is'{}'. Routing never offers a person the question either:elicit.Route(internal/elicit/elicit.go~L502-503) sends every Sample question to the agent audience unconditionally. Denial exists (decline is conformance-tested), but the spec's review rows have no surface. Gaps:sampling-app-review-ui,sampling-app-view-edit-prompts,sampling-app-review-responses,sampling-client-user-approval-controls. - Nothing validates sampling messages or results, either direction.
sampleViaBrokerunmarshals onlymaxTokens/systemPrompt;messagesis never inspected — tool-result messages with non-tool_resultcontent, or atool_resultid no earliertool_useissued, pass through (register SMP-06 pairing). On return, the only checks are JSON-object / not-null (~L226-236); a missingrole/modelis then silently filled in ("unknown") instead of judged, and the result is never checked against its request (stopReason: "toolUse"/tool_usecontent against a tools-less request; era content shape per SMP-05).Client.answervalidates nothing either. Gaps:sampling-both-validate-message-content(client + server rows). - Every sampling failure is
-32603. The 2025-11-25 sampling error table assigns: user rejected →-1; tool result missing / tool results mixed with other content →-32602. mcpx produces neither: a decline becomeserrors.New("the sampling request was " + action), an expired wait becomes the rawAwaiterror, andClient.answer's handler wrap (internal/mcpclient/modern.go~L296-303) flattens every handler error to-32603. An upstream cannot tell "the user said no" from "mcpx broke". Gap:sampling-client-errors-user-rejected-and-tool-shape.
Already in place (not this design's work)
- Denial: broker decline/cancel of a sampling question works and is tested (
sampling-human-in-loop-can-deny). - The broker substrate is shared and sufficient: sampling questions are ordinary broker rows (
Mode: Sample), listed/shown/answered by the existingmcpx elicitcommands — the review gap is rendering + routing, not a missing store or UI. - Typed-error precedent exists (
rpcError/codeInvalidParamsin mcpclient; fault classification in mcpserver); the broker already refuses null Sample answers with the question left open. - Neighbours are distinct: mcpx: design — sub-capability gating (gate on sampling.tools / sampling.context / elicitation.form, not parent presence) #1753 gates which sampling fields may cross (its Phase 2 adds a request-shape
-32602before the handler — this design's-32602s are content errors decided by validation, and compose with it); design(mcpx): JSON Schema validation of tool input and output — one validator at the client choke point #1481 is schema validation of tool/elicitation payloads; design(mcpx): elicitation broker hygiene — owner-only store, bounded retention, redacted events, review-first answering #1675 is broker storage hygiene + review-first elicitation; the sampling rate limit is nix#257's.
The design (decisions recorded in the doc)
- One choke point, one validator. All sampling validation lives in one helper used by the broker path (
sampleViaBroker, in and out) — where legacy wire requests and moderninput_requiredanswers already converge — not copied into mcpclient/mcpserver separately. - Errors first (Phase 1). Decline/cancel of a sampling answer →
-1("user rejected", action named). Expiry/no-answerer stays-32603with an explicit expired message — nobody chose, so it is not a rejection. Tool-shape violations →-32602(raised by Phase 2's checks through Phase 1's typed error).Client.answer's wrap preserves a typed error's code. - Validation judges, doesn't repair (Phase 2). Request side, on open: messages decode per era (content single before 2025-11-25, single-or-array from 2025-11-25); tool-result messages hold only
tool_resultblocks, each id paired with a precedingtool_use— violations get-32602before a question opens. Result side, on answer: result must carryroleandmodeland era-legal content, andtoolUse/tool_useis valid only against a tools-carrying request — and the silentrole/modelfill-in is removed (it manufactures fields the answerer never supplied). A bad answer is refused at the broker step with the question left open where it arrived through the broker. Model-hint mismatch is not a failure (hints are expressly ignorable). - Review on the existing surface (Phase 3). No new UI:
mcpx elicitrenders a Sample question as sampling (server, system prompt, message list; full JSON onshow; a realCreateMessageResultanswer skeleton instead of'{}'). Review-before-delivery takes the honest form this architecture has — inspect/override while pending, results recorded and visible viaresult— stated plainly in CLI help; a genuine pre-delivery staging hold for agent answers is an open question in the doc, not a phase.
Phases (sub-issues of this issue)
- Phase 1 — typed sampling errors (
-1rejection,-32602tool-shape type, expiry stays-32603labelled). Independent. - Phase 2 — message + result validation at the broker (request pairing/era shape; result-vs-request; fill-in removed). Independent in code; its
-32602raising uses Phase 1's typed error (triage settles the seam if Phase 1 hasn't landed — not hard-gated). - Phase 3 — reviewable sampling in
mcpx elicit+ conformance-matrix/register truth-up + nix#256 completion evidence. Gated on Phase 2.
Open questions (kept in the design doc, NOT filed)
- Genuine pre-delivery staging of agent sampling answers (a latency-contract change; only on evidence inspect-while-pending is insufficient).
- Era threading to the broker: if the upstream's era can't reach
sampleViaBrokerwithout new plumbing, union-shape acceptance vs plumbing for a deprecated feature is Phase 2 triage's recorded call. - Whether a validated-bad request should also be recorded (count/log naming the server) for chronic-malformation visibility.
- Sampling as a client-hosted task (2025-11-25 only): untouched; would inherit the validator if it ever becomes real.
Acceptance (design level)
- Phases 1–3 landed. A person can read a pending sampling request's actual messages in
mcpx elicitand answer with a well-formed skeleton; a malformed request is refused-32602before any question opens; a malformed or request-mismatched result never reaches the upstream and missingrole/modelis refused, not filled; a decline reaches the upstream as-1and an expiry as-32603naming expiry. - The seven scoped
#256conformance gaps retired or explicitly recorded partial in the matrix; mcpx: sampling relay -- human review, validation of results, error mapping (8 conformance rows) dezren39/darwin-nix#256 carries completion evidence against each acceptance bullet and is closed iff all are met.
Acceptance criteria
- Phases 1–3 (sub-issues of this design) have landed: typed sampling errors, broker message+result validation, and reviewable sampling in
mcpx elicitwith matrix/register truth-up. - A declined sampling request reaches the upstream as
-1; tool-shape violations as-32602; an expired wait as-32603naming expiry — no sampling failure is flattened into an unlabelled internal error. - Sampling requests are validated before a broker question opens (era content shape; tool-result pairing per SMP-06) and results are validated against their request before relay (role/model present, era-legal content, toolUse only against a tools-carrying request); the silent
role/modelfill-in is gone. mcpx elicitrenders a Sample question's server, system prompt and messages, and offers a realCreateMessageResultanswer skeleton.- The seven scoped mcpx: sampling relay -- human review, validation of results, error mapping (8 conformance rows) dezren39/darwin-nix#256 conformance gaps are retired or explicitly recorded partial, and mcpx: sampling relay -- human review, validation of results, error mapping (8 conformance rows) dezren39/darwin-nix#256 carries completion evidence against each of its acceptance bullets, closed if and only if all are met.
Activity
developing-today commentedon Oct 28, 2026
CODE-763
dezren39 commentedon Oct 28, 2026
Design filed by the design architect (pass 10, 2026-10-28). Phases as sub-issues: #1835 (Phase 1, typed sampling errors), #1836 (Phase 2, broker validation), #1837 (Phase 3, reviewable sampling + truth-up, status/blocked gated on Phase 2). Triage packets enqueued to repo-health-code-pilot: task 2718 (#1835), task 2719 (#1836), task 2720 (#1837) — all verified queued with last_error=null. Design doc: files/designs/code-mcpx-sampling-trust-boundary.md in the keep-the-four-repos-healthy goal workspace; central register entry added (status: proposed). No user/review: nix#256's acceptance is explicit and conformance-only in scope.
dezren39 commentedon Oct 28, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: triaged (coverage pass)
Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 40 (priority/normal). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
Found via: coverage pass 2026-11-03
dezren39 commentedon Nov 3, 2026
🤖 Agent note · picked up
repo-worker-code, OpenChamber scheduled lane (lane335a5d1f-bfc1-4831-99ea-850c8939a727)main— confirm the fix path is scoped, name blockers and ordering, close if already handled. No implementation in this task.origin/maind3476bce7, labels synced, remaining work delegated to the phase issues.dezren39 commentedon Nov 3, 2026
✅ Triage verdict — epic is live, fully decomposed, and not superseded. Keep it open as the tracker; the only remaining scope is Phase 3 (#1837), whose gate cleared today.
repo-worker-code, OpenChamber scheduled lane (queue task 7749,repo-health-code-pilot)origin/maind3476bce7(2026-11-03), freshghstate for every named issue/PR.Current state of the phase set
mainpkgs/mcpx/internal/mcpclient/client.go:1729-1770—codeUserRejected = -1,UserRejectedError,ToolShapeError(-32602), expiry left-32603and labelledpkgs/mcpx/internal/elicit/sampling.go:158ValidateSampleRequest,:213ValidateSampleResult; wired atpkgs/mcpx/internal/daemon/hooks.go:329(request, before the question opens) and:394(result), plus broker accept pathinternal/elicit/elicit.go:507; the silentrole/modelfill-in is gone (no"unknown"assignment inhooks.go)status/blockedpkgs/mcpx/internal/cli/elicit.go:268still renders every question throughdescribeSchema(a sampling request has noproperties, so messages render as nothing and the answer skeleton is'{}'), andpkgs/mcpx/internal/elicit/elicit.go:831-834still routes everySamplequestion to the agent unconditionallyThe parent acceptance ("Phases 1–3 landed") is therefore not met, so the epic stays open — but no implementation packet belongs on this issue: its scope lives entirely in the phase children.
Revalidation checks (all fresh)
1834across issues and PRs return only the phase set (mcpx: Phase 1 of #1834 — typed sampling errors (-1 on rejection, -32602 tool-shape, expiry stays -32603) #1835/mcpx: Phase 2 of #1834 — sampling message + result validation at the broker (pairing, era shape, no silent fill-in) #1836/mcpx: Phase 3 of #1834 — reviewable sampling in mcpx elicit + matrix/register truth-up + nix#256 evidence #1837), the mcpx: sampling relay -- human review, validation of results, error mapping (8 conformance rows) #1955 relocation pointer, and unrelated#1834xnumbers. Sibling designs Design: mcpx upstream question rate limits — one limiter at the client choke point, keyed by the configured server #2333 (question rate limits), Design: mcpx elicitation era edges — downgrade rich enums for old hosts, complete the -32042 circuit, close the stale url row #2077 (elicitation era edges) and design(mcpx): completion, finished — one rate limit at the /v1 choke point, relevance-ranked local answers #2386 (completion at/v1) are adjacent, not duplicates.git ls-remote --heads originhas no live1834/1835/1836/1837branch; no open PR touches sampling review.dezren39/nix#256is CLOSED (relocated here; its scope is the mcpx: design — sampling trust boundary (reviewable, validated, honestly error-coded) #1834 set, recorded by the 2026-10-28 note on mcpx: Phase 3 of #1834 — reviewable sampling in mcpx elicit + matrix/register truth-up + nix#256 evidence #1837). No action needed on the nix side.held,user/owned,do-not-merge,do-not-auto-edit.Blocker and ordering (recorded)
main(lines above), and mcpx: Phase 2 of #1834 — sampling message + result validation at the broker (pairing, era shape, no silent fill-in) #1836 closed today.repo-health:v1:triage:developing-today/code:issue:1837, statusblocked) carries exactly that resume condition ("Resume when mcpx: Phase 2 of #1834 — sampling message + result validation at the broker (pairing, era shape, no silent fill-in) #1836 implementation merges: re-run triage, then enqueue Phase 3 implementation packet"). It is now resumable; it must be unblocked/requeued so mcpx: Phase 3 of #1834 — reviewable sampling in mcpx elicit + matrix/register truth-up + nix#256 evidence #1837's triage can enqueue the Phase 3 implementation.No follow-up implementation task is filed against #1834 itself; the work belongs to #1837 and its task 2720.