Majordomo 2/7: an MCP server exposing the tool vocabulary over the existing orchestrator paths - #569
Merged
Merged
Conversation
…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>
| 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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated via CodeyBox — work item c4176ce1c02d4ecf98fd8040985cb74e
Initiated by CodeyBox operator
Co-Authored-By: CodeyBox noreply@codeybox.invalid
🤖 Generated with CodeyBox