Skip to content

mcpx settings: a ceiling the daemon sets and a caller may lower but not raise (ClampedBy) #1902

Closed

Description

@dezren39

Relocated from dezren39/darwin-nix#185 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: #176

Summary

Three issues need the same settings capability and none of them owns it: a value the daemon sets as a ceiling,
which a caller may lower but never raise. #111 needs it for how much repair mcpx may do on its own, #112 for what a
hook is allowed to do, #113 for whether an intent may act or only plan. Today it exists only as a paragraph inside #111
(the ClampedBy idea). This issue owns the mechanism; the dials themselves belong to their issues, and their
vocabulary to the autonomy-dial decision record SURFACES is writing under docs/decisions/ (#181 collision 3).

Owns #181 collision 5: the one mechanism by which a daemon-set value bounds what a caller may ask for. #111, #112
and #113 consume it; none implements its own clamp.

Original notes

The nested bullet is the source. Its security half is deferred; its non-security half -- whose policy wins when the
daemon and the caller disagree -- is this issue.

Current state (05c78b2)

  • A setting has a Scope (internal/settings/schema.go:181; values daemon, client, call, plugin) and Hot. It has no
    notion of one layer bounding another.
  • Values resolve by Layer, highest wins: default < file < env < flag < runtime (internal/settings/value.go:13-25).
    A call-scoped value supplied by a caller therefore simply wins.
  • The only existing answer to "the caller must not decide this" is to make the setting daemon-scoped outright:
    elicit.confirmDestructive is ScopeDaemon (internal/settings/consumer.go:47), and docs/consumer.md explains why.
    That is all-or-nothing: the caller cannot even choose to be more careful.

Approaches

A -- each consumer clamps in its own code. Repair, hooks and intent each compare the caller's value with the
daemon's before acting. Least recommended: three implementations with three orderings and three error messages,
invisible to /v1/settings and mcpx settings, and the fourth dial gets a fourth. This is what happens by default if
nobody owns it.

B -- a naming convention plus a helper. Pair each dial with a daemon-scoped twin (repair.level +
repair.maxLevel) and add settings.Clamp(call, max). Works in today's registry with no schema change. Costs: twice
the settings per dial, the pairing is only a naming convention (a guard test would have to discover pairs by name), and
every reader must remember to call the helper -- the "declared but not delivered" shape #177 exists to stop.

C -- recommended: a registry field. A call-scoped setting declares that the daemon's resolved value is its ceiling
(field name open; Clamp: Ceiling). Resolution returns min(caller, daemon) over an ordered Kind (numbers,
durations, and enums in declared order), and reports the resolved value, the layer that set it, and clamped: true
when a caller asked for more. One implementation; flags, env, /v1/settings, the MCP settings tool and help text
inherit it; a guard test asserts every clamped setting has an ordered Kind and a daemon-side value. Recommended because
it is the only option where the rule is declared once next to the setting and enforced by the resolver rather than by
each reader's memory.

Open questions

Ease · Impact · Related

Ease M · Impact med (high once #111, #112 or #113 implements a dial)
Related: #111, #112, #113 (consumers; #111 carries the dial vocabulary, #181 collision 3), #172 (settings being declared
now), #129 (the guard-test pattern C's test follows), #181 (collision 5).

Security/auth deferred; see tracker.

Acceptance criteria

  • The daemon-set ceiling exists in settings, is enforced on every caller path, and a caller may lower but never raise it; attempts to raise it fail validation with a clear error.
  • Tests cover raise/lower/equal cases and the settings docs describe the ceiling.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    History carried over from dezren39/darwin-nix#185 (verbatim, oldest first):


    @dezren39 · 2026-09-30T11:57:51Z

    Issues pass (2026-09-30, at 05c78b2).

  3. added
    enhancementNew feature or request
    queue/queuedHas a queued task in the Supabase agent queue
    priority/normalOrdinary priority (the default)
    effort/mMedium: expected under 1 day of work
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 28, 2026
  4. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    Intake review

    🤖 Agent note · filed

    Notes: Filed in the large mcpx filing batch of 2026-10-28 21:59–22:24Z and already carries a queued triage task — it is not orphaned. Verified live at intake: no merged PR references this issue and it was filed under an hour ago, so nothing can have resolved it yet. Acceptance criteria appended to the body from the issue's own stated scope; original body preserved verbatim above it.

    Risks: Tracker-level scope overlaps its sibling batch issues — the triage pass must dedupe against them before any implementation packet is written, and split rather than implement if the scope is multi-PR.

    Suggested approach: Triage first (task 2795): confirm the boundary against the sibling issues, then enqueue one scoped packet per deliverable slice.

  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 29, 2026
  6. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    Triage verdict — already implemented. Closing as done.

    The relocated issue's "Current state" was written at 05c78b2 in the old home, before the mechanism landed. Verified fresh at developing-today/code origin/main 2c4e7434 (2026-10-29, task 2795), the recommended Approach C (a registry field, enforced by the resolver) is built and wired:

    • Mechanism: pkgs/mcpx/internal/settings/clamp.go — the ClampedBy field on a setting declaration, Set.Clamp(path, requested) bounding a caller's request by the daemon-resolved ceiling, clampValue lowering and recording what was asked for (Value.Requested / Value.ClampedBy), rendered by the CLI at internal/cli/commands.go:2146.
    • Design record: pkgs/mcpx/docs/decisions/0002-autonomy-dial.md §"ClampedBy — an internal/settings capability (Phase 3: sync, blob gate, and relays #181 row 5)" specifies exactly this mechanism and its validation rules (New refuses a registry whose ClampedBy names a missing/unordered/daemon-less target); 0003 lists it as enforced from the day it lands.
    • Dials wired: pkgs/mcpx/internal/settings/consumer.go:135,146,157 — prompt.autonomy, repair.autonomy and hooks.autonomy all declare ClampedBy: "autonomy.max" (the three consumers this issue exists for: nix Bump checkmarx/ast-github-action from 2.0.19 to 2.0.23 #111/Update module gopkg.in/go-jose/go-jose.v2 to v2.6.3 [SECURITY] - autoclosed #112/Update module github.com/jackc/pgx/v5 to v5.5.4 [SECURITY] - autoclosed #113's dials, per decision 0002's mapping table).
    • Tests: pkgs/mcpx/internal/settings/clamp_test.go — TestNewRefusesAClampThatCannotHold (validation of malformed clamps) and TestACallerCannotRaiseAClampedSetting (the raise case), plus consumed_test.go coverage.
    • Docs: pkgs/mcpx/docs/configuration.md:263 documents autonomy.max as "the most autonomy this daemon exercises, whatever a caller asks for".

    One wording difference from the acceptance criteria, settled by the design record rather than by this triage: a raise attempt is clamped and reported (the resolved value carries Requested and the ceiling's origin), not refused outright — that was this issue's own first open question, and decision 0002 ruled for clamp-and-report. Schema-level misconfiguration is refused at validation (TestNewRefusesAClampThatCannotHold).

    No implementation follow-up is needed; the remaining consumer work belongs to the dial issues themselves, as this issue states. Dedupe: no other code issue covers the ClampedBy mechanism (searches return this issue and the decision records only).

    agent-note:v1 · agent: repo-queue-worker-shift-b (lane B) · task 2795 · kind: triage · outcome: already_done — ClampedBy mechanism implemented (settings/clamp.go), three autonomy dials wired to autonomy.max, tests + docs in place; closing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

effort/mMedium: expected under 1 day of workenhancementNew feature or requestkind/enhancementmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)status:done

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions