Skip to content

Majordomo 2/7: an MCP server exposing the tool vocabulary over the existing orchestrator paths - #569

Merged
AdamFrisby merged 5 commits into
mainfrom
codeybox/c4176ce1
Sep 25, 2026
Merged

AdamFrisby merged 5 commits into
mainfrom
codeybox/c4176ce1

Conversation

@AdamFrisby

Copy link
Copy Markdown
Owner

Automated via CodeyBox — work item c4176ce1c02d4ecf98fd8040985cb74e

Initiated by CodeyBox operator


Co-Authored-By: CodeyBox noreply@codeybox.invalid
🤖 Generated with CodeyBox

AdamFrisby and others added 5 commits September 25, 2026 04:32
…ng orchestrator services

An MCP endpoint (POST /mcp/majordomo, stateless streamable HTTP) publishes
exactly the eleven tools in the majordomo vocabulary and routes every call
through MajordomoAuthorization.Decide — the transport holds no policy of its
own. Reads are backed by the same store/status services the operator
endpoints read; mutations go through the existing create/patch/cancel/retry
command paths, with planning and commit split so dry-run and proposal modes
return the real change set without touching the store. Chain creation takes
structured item+edge arguments (never prose — the admin plan parser is
deliberately unreachable from this path) and persists atomically via a new
transactional IWorkItemStore.CreateAllAsync; a partially valid chain files
nothing. The endpoint accepts only the configured majordomo ApiClients
identity — the operator key is refused — and every call is audit-logged
before execution with tool, arguments, decision, outcome, and identity.

Shared composition review and the knob catalog move to a dependency-free
src/CodeyBox.Composition so the paste path and the structured path validate
through one code path; ValidateStructured adds the per-item entry point a
structured caller needs. New MajordomoServerOptions (CodeyBox:Majordomo)
carries mode, per-turn blast-radius cap, identity name, and turn window;
defaults ship proposed mode.

CodeyBox-Prompt-Revision: 1
Co-Authored-By: CodeyBox <noreply@codeybox.invalid>
StartupResume_AdoptionExit0_ButPushHangs_IsBoundedByResumeTimeout timed
out at its 15s wall-clock backstop under a saturated audit run: the sweep
spans three dedicated LongRunning threads plus pool continuations, and
real scheduling lag exceeded the slack. The bound under test never
changed - only the test's wall-clock budget did.

Both push-bound tests now follow the file's established FakeTimeProvider
pattern: a >5s configured timeout routes the resume wait through the
injected-clock WaitAsync branch, so resume/adoption/push thread
scheduling is effectively unbounded in wall-clock, and the hanging (or
blocked) checkpoint push is cancelled deterministically by Advance().
The clock is only advanced after CheckpointPushCalls observes the push
in flight, so the resume/adoption fake-time timeouts cannot fire early
and skip promotion. The sibling BlocksBeforeReturningTask test shared
the same fragile bound and gets the same conversion. WaitUntilAsync
gains an optional timeout for the in-flight poll; the outer WaitAsync
remains as a pure hang backstop so a real regression still fails.

CodeyBox-Prompt-Revision: 1
Co-Authored-By: CodeyBox <noreply@codeybox.invalid>
…indow

Audit-followup on the majordomo surface. Two behavioral errors and a set
of supporting/quality fixes:

- The turn-budget check raced: stateless HTTP serves calls in parallel, so
  concurrent mutations each snapshotted the same pre-call usage and
  jointly overshot the per-turn cap. Mutations of one identity are now
  serialized through measure -> decide -> commit -> record via a keyed
  gate, and a commit that throws after writes still charges the projected
  blast radius and emits an outcome audit record instead of riding back
  silent.
- cancel_work_item's blast radius was the declared 1 item while the real
  mutation cascades to every queued transitive dependent. The executor now
  enumerates the cascade before Decide and passes a projected count;
  WorkItemCancelPlan carries that set so the commit replays exactly what
  the proposal/dry-run reviewed (guarded writes still skip dependents that
  raced out of Queued), and the change set reports a CancelDependents node
  so review sees the full effect.

Supporting: polymorphic JSON for planned changes plus PlannedItemCount;
WorkItemPatch.HasFieldEdits derived from the same normalised array as the
no-change guard; reason/outcome wire strings gathered into one constant
file; handler dispatch moved from a branching ladder to a frozen table
verified against the vocabulary at registration; ResultText reads the
error text once; IValidateOptions + ValidateOnStart for the server
options; the authentication-disabled sentinel name is rejected as an
ApiClients entry; IWorkItemStore.CreateAllIsAtomic makes the batch-commit
atomicity contract checkable and CommitAllAsync refuses non-atomic
stores; the shared creation post-commit tail is no longer duplicated.

Tests: cancel cascade visibility in dry-run/proposal and execution,
cascade exceeding per-call and remaining-budget caps, concurrent-budget
overshoot, post-commit-throw accounting, projected-count authorization
units, and the structured-review refusal naming item+field via a refactor
node. Full solution builds warnings-clean; 92 majordomo tests, the cancel
REST-path suites, and the composition-review suite pass.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
CodeyBox-WorkItem: c4176ce1c02d4ecf98fd8040985cb74e
CodeyBox-Agent: devin/swe-2-high
CodeyBox-Prompt-Revision: 1
CodeyBox-Fallbacks: antigravity→devin (×2 Agent antigravity rate-limited by provider (transient rate limit; retrying after backoff): agent exited 1)
Co-Authored-By: CodeyBox <noreply@codeybox.invalid>
…write accounting

Address audit findings on the majordomo MCP surface:

- get_agent_capacity now feeds probe-resolution conflicts through
  ConflictUnknownSnapshot + the quota gate (fail closed, matching /quota
  and the dispatch router) instead of treating contested members as
  unmetered, and masks reset instants on depleting-balance pools before
  reporting quota_resets_at.
- WorkItemCommandOutcome carries WritesApplied set by the commit path
  itself; update_work_item and retry_work_item charge the turn ledger
  from that signal rather than inferring writes from the HTTP status or
  the plan shape.
- DurationMinutesConverter maps out-of-range/non-finite minute counts to
  JsonException (a bind refusal with field path) instead of leaking
  OverflowException past the audit record, and reads bare numeric strings
  as minutes rather than days.
- Parameterless tools refuse non-object payloads; refusal field names are
  translated through the wire naming policy; the advertised schema and
  the chain-node converter share derived wire names and fail loudly at
  registration when the generated shape drifts.
- DELETE cancel classifies before enumerating the cascade so refused and
  no-op cancels skip the whole-store scan; dead outcome checks removed;
  ledger docs match the faulted-commit charge rule.

CodeyBox-Prompt-Revision: 1
Co-Authored-By: CodeyBox <noreply@codeybox.invalid>
@AdamFrisby
AdamFrisby merged commit b414bd0 into main Sep 25, 2026
@AdamFrisby
AdamFrisby deleted the codeybox/c4176ce1 branch September 25, 2026 14:23
Audit("majordomo.tool_call")
.Information(
"Majordomo call {CallId}: {Identity} invoked {Tool} — decision {Decision}; args {Arguments}",
callId, identity, tool, decision, argumentsJson);
Audit("majordomo.tool_call")
.Information(
"Majordomo call {CallId}: {Identity} invoked {Tool} — decision {Decision}; args {Arguments}",
callId, identity, tool, decision, argumentsJson);
public static void MajordomoToolOutcome(
string callId, string identity, string tool, string outcome, string? detail) =>
Audit("majordomo.tool_outcome")
.ForContext("Detail", detail ?? "")
.ForContext("Detail", detail ?? "")
.Information(
"Majordomo call {CallId}: {Identity} {Tool} → {Outcome}",
callId, identity, tool, outcome);
.ForContext("Detail", detail ?? "")
.Information(
"Majordomo call {CallId}: {Identity} {Tool} → {Outcome}",
callId, identity, tool, outcome);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants