Repository navigation
feat(operator-queue): atomic asks — authoring rules in the ask_operator contract, option and title limits (#3243) - #3253
Merged
Merged
Conversation
This was referenced Oct 5, 2026
…se) answer (#3242) The #2376 sink now handles SOMETHING_ELSE = "(something else)" before membership: accepted on any approval with an instruction in response_text, refused by name otherwise (instruction_required, reserved_value), and refused on platform-minted / gate approvals (not_off_menu). Every other unoffered string stays refused. validate_response_choice takes response_text keyword-only and required. - ask_service: gate refusal; ask_operator refuses the literal as an option - both writers map ReservedAnswerError to a named 422 - OperatorResponse.response_text bounded at 4000 - Workspace projection: decided_by_options boolean (T8 ruling) - recent answers render "Something else: <instruction>" - resume frame: platform sentence above the data fence Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e) (#3242) - platform prompt (both authored copies) + agent guide: the approval bullet and the queue-file write-back say what the reserved value means and that the instruction is in response_text; never list it as an option - test_1402 SENTINELS += "(something else)" - MCP: types.ts exports SOMETHING_ELSE; ask_operator, get_my_ask and respond_to_operator_queue descriptions name it; the respond tool keeps `error` and adds the backend's {status, code, message, offered_options?} (T5, additive) - py <-> ts parity test for the literal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- utils/operatorQueue.js: SOMETHING_ELSE (mirrored, parity-tested), offeredChips (drops the literal and the size-cap marker), decisionLabel, decidedByOptions; buildQueueResponse needs an instruction with the literal - QueueCard + QueueItemDetail: chips from offeredChips, a 'Something else' chip outside the v-for (hidden on gate approvals); typing with no pick arms it, the label and Send copy flip; Enter sends only after an explicit pick; maxlength 4000; Send rule is the shared builder - respondToItem returns true/false; the detail panel clears only on success - ResolvedCard + detail resolved view render 'Something else', never the literal Raw-colour counts unchanged (QueueCard 28/59, QueueItemDetail 26/55). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /m: a 'Something else' chip in the options group (reuses ops-option-btn, hidden on gate approvals) opens the form; the consequence line says none of the options will run; Send reads 'Send instruction' and stays disabled until the instruction is typed; maxlength 4000 - PortalAsks: the chip after the offered chips, hidden when the projection says decided_by_options (T8 ruling); typing with no pick arms it, placeholder and Send aria-label flip; Enter never sends the auto-armed state; submit() sends the armed option through the shared builder. Diff kept to the approval template, helpers beside pick(), the body of submit() and one import line (open PR #3181 edits other hunks) - portalAsksTestidPrefix: the id inventory gains the chip's id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document what #3242 built: the reserved (something else) decision an approval accepts besides its own options, with the instruction in response_text; the named 422s (instruction_required, reserved_value, not_off_menu, invalid_options at raise); the Something else chip on the desktop card and detail, /m and the Workspace, hidden on gate approvals on every surface (decided_by_options on the Workspace projection); the resume frame and contract text. Corrects every doc that still said an approval's answer must be one of its options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dead optionsOf import (C1, I1) (#3242) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-miss literals; public decided_by_options; learnings (I2 I4 I5 I6 I7 I12) (#3242) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tor projections; gate- is only the fallback (I3) (#3242) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lookalikes on the native raise (#3243) One pure predicate in operator_queue_choices (shared with the file path in the next checkpoint): at most 5 options, the reserved (something else) never counted; at most 60 characters per option; an option reading as the chip ("Something else", any case, parenthesised or not) refused as invalid_options. Agent raises also get a hard 120-character title (title_too_long). Checked after the replay lookup, so a pre-cap ask's retry still replays, and before the rate cap, so a refusal spends no token. Env-overridable, floored at load (2 options / 16 chars / 40-char title). Mutation: with the _refuse_over_caps call removed, 9 tests in test_3243_atomic_asks.py go red (TestNativeRaise refusals). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on the marker (#3243) A NEW file entry over the option caps (or a chip lookalike) is held as invalid_options, an over-long title as invalid_title — the same predicate as the native raise, after the id check and before the depth/rate caps, so no rate token is spent. The ids (<=10) and the limits ride on platform.ingestion beside `reason`, so a self-clearing queue_full cannot hide them; _marker_differs now compares the key set so a fixed entry's id drops off. Cap holds never increment `held` (no "runaway or compromised agent" flood alert). Rows already ingested are never re-judged. Also replaces the CP1a env test's module reload (it handed other suites a stale OperatorQueueSyncService) with a test of the loader itself. Mutation: with the hold disabled, 4 TestFileHold tests go red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and the prompt (#3243) ask_operator's description carries five authoring rules (one decision per ask; a one-glance title, at most 120; options name the choice only, with one bad-then-good example; at most 5 options of at most 60 characters, aim for under 40; context is labelled facts for people), the chip-lookalike rule, and the three new refusal codes with what to do about each. Field descriptions for title/question/options/context/proposal carry the per-field rule. The platform prompt and prompt.md gain a three-line "Write atomic asks" paragraph pointing at the tool, and the queue-file paragraph names the invalid_options / invalid_title holds. Agent guide: one pointer sentence. Sentinels: "One decision per ask", "invalid_options". A pytest pins the description and prompt to the phrases built from the Python caps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#3243) .env.example and both compose files gain OPERATOR_QUEUE_MAX_OPTIONS (5), OPERATOR_QUEUE_OPTION_MAX_CHARS (60) and OPERATOR_QUEUE_ASK_TITLE_MAX_CHARS (120). security.md §26, the operating-room flow (step + revision row) and the api-endpoints catalog name the new codes, where they run, and the file hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se so parity is green (#3243) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irst, fold NFKC/zero-width chip lookalikes, register the test (#3243) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion docs and add the §26.7 authoring-caps bullet (#3243) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cap (#3243) Claude Code shows a model only the first 2,048 characters of a tool description (#3234); ask_operator was 2,676 at 3c14c23, so its last rules (the refusal codes and their remedies) were invisible to every agent. The description is now 1,710 characters, ordered by what a model acts on first: what the tool does and the fire-and-park contract, then the five atomic-ask rules, then the reserved (something else) rule. Field detail moved into the parameter descriptions, which are not cut: the receipt fields, differs and idempotency (request_id); title_too_long and its fix (title); the bad-then-good option example, options_required, too_many_options / option_too_long / invalid_options with their fixes (options); field_too_large (question, context, proposal); role_unassigned (to); how an ask ends (expires_at); reask_requires_link (supersedes_expired). Tests: the published descriptions of ask_operator, get_my_ask and respond_to_operator_queue are <= 2,048 over a real listTools, and every moved phrase is published on its field; the unit test keeps ask_operator <= 1,800 for headroom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vybe
force-pushed
the
feature/3242-approval-something-else
branch
from
October 6, 2026 10:10
210fc6b to
efedeaa
Compare
vybe
force-pushed
the
feature/3243-atomic-asks
branch
from
October 6, 2026 10:10
9737639 to
33a0cc6
Compare
vybe
marked this pull request as ready for review
October 6, 2026 10:15
…sh-landed (#3253) — mechanical, per the merge-train note on the PR Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
Author
|
merge-train (2026-10-06): retargeted this PR to |
trinity-ability
approved these changes
Oct 6, 2026
trinity-ability
left a comment
Contributor
There was a problem hiding this comment.
merge-train: batch validated on train/20261006-1038 (#3270)
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.
Fixes #3243 — second of the chain #3242 → #3243 → #3247. Stacked on #3250 — base is
feature/3242-approval-something-else; the stack merges together (operator, 2026-10-05). Draft until the chain is in.What
Agents are taught to write short, atomic asks, and the platform refuses the ones that are not.
services/operator_queue_choices.py, one check for both create paths): by default at most 5 options (the reserved(something else)is never counted), at most 60 characters per option, and an agent-raised title of at most 120 characters. Nothing is truncated: a cut option changes what an approval means.ask_operatorrefuses with a code the agent can act on —too_many_options,option_too_long,title_too_long,invalid_options— each naming the limit in force and the remedy. The check runs after the replay lookup, so a retry of an older ask still replays.invalid_options,invalid_title) and an id leaves the list once the agent fixes the entry. Held entries do not count toward the flood alert. Asks already pending are not re-checked.ask_operatordescription carries five rules — one decision per ask; a title read at a glance; options name the choice only; few options; context is for people — and the platform prompt carries three lines that point at the tool. Detail that belongs to one field (codes, remedies, the bad-then-good example) lives in that field's parameter description.ask_operatordescription is 1,710 characters (was 2,676), under Claude Code's 2,048-character cap that dev's bug: two MCP tool descriptions run past Claude Code's 2,048-character cap, so their tails never reach the model #3234 census enforces, with a ≤ 1,800 test in this branch.OPERATOR_QUEUE_*knobs in.env.exampleand all three compose files, with floors so a skill-gate approval stays raisable.Rulings carried (orchestrator, on the operator's behalf — plan file)
Review + security
/review(claude-fable-5-1, report-only): MERGEABLE AFTER FIXES. One rule on both paths; a retry replays even when over the new limits; seven mutations all red; queue suites green twice in shuffled order. Fixed in9bcc39db(C1: the three knobs were missing fromdocker-compose.hosted.yml, parity test red),aea0d502("by default" wording and an env-independent guard; one refusal for a title over 300; NFKC + zero-width normalisation),3c14c23a(docs),97376394(the description budget — found by the next issue's plan, not by the review)./cso --diff: nothing supported.Tests
Reported by the builder on the fix tips: pytest 499 passed on the listed suites and 1,665 on the files that read
.env.example/ the edited docs; mcp-server 691 passed,tsc --noEmitclean. Not run here: the full Python unit island and the frontend suite (no frontend change in this PR) — CI.Before merge
backend-unit-testdoes not run here (andscreenshot-guardwas cancelled). The backend evidence is the builder's and reviewer's foreground runs listed above. The full suite runs once feat(operator-queue): approvals get a "Something else" answer (#3242) #3250 merges and this PR retargetsdev— read that run before merging.devcleanly as a stack (checked against 493d2a6).deploy_local_agent,chat_with_agentandget_objectiveswhen run against this stack's base; dev already shortened all three, so they clear once the stack meets dev.Handoffs
replacesclause to the same description and must stay under the ≤ 1,800 test.🤖 Generated with Claude Code