From ff8948349c6f1dcea6e9eb7c594b8718896db0d4 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 00:23:12 +0700 Subject: [PATCH 1/2] queen: no backlog dispatch while finished work waits in review (review_first card, gHashTag/t27#10555) Behind TRIOS_QUEEN_REVIEW_FIRST=on. After the owner's assignments are put first, the round counts the review queue with the card's in_review_queue (the sweep's own filter: started, finished, review_state NULL or 'wait', not reaped, issue open) and keeps only the candidates the card admits: send-backs and assignments at any queue length, backlog only when the queue is empty. When the rule held every candidate and capacity was not the reason, the tick's refusal reads " dispatches wait in review; ...". Card, wasm (310 bytes, two builds one hash) and gen.ts are tool-written; 38 hand-written TS lines added to queen-tick.ts, no new foreign file. Co-Authored-By: Claude Opus 5.5 --- .../services/queen-review-first-card.gen.ts | 47 +++++ .../server/src/api/services/queen-tick.ts | 38 ++++ trios/agent-server/specs/PIN | 3 + .../agent-server/specs/queen/review_first.t27 | 175 ++++++++++++++++++ .../specs/queen/review_first.wasm | Bin 0 -> 310 bytes 5 files changed, 263 insertions(+) create mode 100644 trios/agent-server/apps/server/src/api/services/queen-review-first-card.gen.ts create mode 100644 trios/agent-server/specs/queen/review_first.t27 create mode 100644 trios/agent-server/specs/queen/review_first.wasm diff --git a/trios/agent-server/apps/server/src/api/services/queen-review-first-card.gen.ts b/trios/agent-server/apps/server/src/api/services/queen-review-first-card.gen.ts new file mode 100644 index 0000000000..038e4dc973 --- /dev/null +++ b/trios/agent-server/apps/server/src/api/services/queen-review-first-card.gen.ts @@ -0,0 +1,47 @@ +// Generated by `t27c gen-ts` from review_first.t27. Do not edit. +// +// Every name, value and type here comes from that spec. Change the spec and +// regenerate; an edit made here is lost the next time anyone builds. + +export const KIND_BACKLOG = 0 satisfies number; +export const KIND_REWORK = 1 satisfies number; +export const KIND_ASSIGNED = 2 satisfies number; +// t27c gen-ts: fn kind_of was not emitted -- this backend lowers declarations, not bodies. +export const RS_NONE = 0 satisfies number; +export const RS_WAIT = 1 satisfies number; +export const RS_OTHER = 2 satisfies number; +// t27c gen-ts: fn in_review_queue was not emitted -- this backend lowers declarations, not bodies. +export const REVIEW_QUEUE_MAX = 0 satisfies number; +export const R_ADMIT = 0 satisfies number; +export const R_REVIEW_FIRST = 1 satisfies number; +export const REFUSAL_TAIL = " dispatches wait in review; the backlog is closed until the review queue is empty" satisfies string; +// t27c gen-ts: fn backlog_open was not emitted -- this backend lowers declarations, not bodies. +// t27c gen-ts: fn admission was not emitted -- this backend lowers declarations, not bodies. +// t27c gen-ts: fn admits was not emitted -- this backend lowers declarations, not bodies. +// t27c gen-ts: fn round_refused was not emitted -- this backend lowers declarations, not bodies. +// t27c gen-ts: a TestBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a TestBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a TestBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a TestBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a TestBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a TestBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a TestBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a TestBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a InvariantBlock was not emitted -- it is checked by the compiler, not by the artifact. +// t27c gen-ts: a InvariantBlock was not emitted -- it is checked by the compiler, not by the artifact. + +// Declaration order, which the spec's own laws depend on. +export const __STRUCT_ORDER__ = [] as const; +export const __DECL_ORDER__ = ["KIND_BACKLOG", "KIND_REWORK", "KIND_ASSIGNED", "RS_NONE", "RS_WAIT", "RS_OTHER", "REVIEW_QUEUE_MAX", "R_ADMIT", "R_REVIEW_FIRST", "REFUSAL_TAIL"] as const; + +// What this spec holds and this backend did not print. Empty is the whole +// story most of the time; an entry here is a promise the artifact does not +// keep, and reading it is how a tool tells a partial module from a complete +// one without parsing comments. +// +// A LIST, not a map keyed by name. `specs/numeric/formats.t27` declares four +// separate consts called `result`, one per test block, and under an object +// literal three of the four omissions vanished into the fourth -- a record of +// what went missing that itself went missing. A spec is free to reuse a +// name; this file is not free to lose the second one. +export const __NOT_EMITTED__ = Object.freeze([] as const); diff --git a/trios/agent-server/apps/server/src/api/services/queen-tick.ts b/trios/agent-server/apps/server/src/api/services/queen-tick.ts index 720367a96c..a97ea9f962 100644 --- a/trios/agent-server/apps/server/src/api/services/queen-tick.ts +++ b/trios/agent-server/apps/server/src/api/services/queen-tick.ts @@ -50,6 +50,7 @@ import { dispatchByActors, handRoundToBeeActors, } from './queen-bee-actors-round' +import { flag, loadCardWasm, u32 } from './queen-card-wasm' import { githubCiDeps, takeBackRefusedAcceptances } from './queen-ci-verdict' import { type ClaimLease, claimOf, type DispatchClaim } from './queen-claim' import { @@ -141,6 +142,7 @@ import { type ReviewCapacity, startReviewerActors, } from './queen-review-actors' +import * as RF from './queen-review-first-card.gen' import type { LaneGate } from './queen-review-lanes' import { LANE_GO, LANE_STOPPED } from './queen-review-lanes-card.gen' import { @@ -2026,6 +2028,39 @@ export async function runRound( const assigned = await pendingAssigns(pool).catch(() => [] as number[]) if (assigned.length > 0) candidates = manualFirst(candidates, assigned) + // REVIEW FIRST (review_first.t27, t27#10555, TRIOS_QUEEN_REVIEW_FIRST=on): no + // backlog issue starts while finished work waits in review; send-backs and + // assignments still do. The queue is the sweep's own filter. Off: as above. + let reviewFirst: string | null = null + if ((process.env.TRIOS_QUEEN_REVIEW_FIRST ?? '').trim() === 'on') { + const rf = loadCardWasm('queen/review_first.wasm') + const done = await pool.query( + `SELECT issue, review_state, (outcome NOT LIKE 'reaped%') IS TRUE AS kept + FROM queen_dispatch WHERE started = true AND finished_at IS NOT NULL`, + ) + const rs = (s: unknown) => + s == null ? RF.RS_NONE : s === 'wait' ? RF.RS_WAIT : RF.RS_OTHER + const open = (n: unknown) => flag(issuesNow?.has(Number(n)) ?? true) + const queue = done.rows.filter((r) => { + const row = [1, 1, rs(r.review_state), flag(!r.kept), open(r.issue)] + return rf.call('in_review_queue', ...row) !== 0 + }).length + const sentBack = new Set( + inFlight.rows + .filter((r) => r.review_state === 'sendBack') + .map((r) => Number(r.issue)), + ) + const kind = (n: number) => + rf.call('kind_of', flag(assigned.includes(n)), flag(sentBack.has(n))) + const before = candidates.length + candidates = candidates.filter( + (n) => rf.call('admits', kind(n), u32(queue)) !== 0, + ) + const held = u32(before - candidates.length) + if (rf.call('round_refused', u32(candidates.length), held) !== 0) + reviewFirst = `${queue}${RF.REFUSAL_TAIL}` + } + // Named, because RING-00 is asked about this exact array a few lines below // and "the same number" has to be the same number, not a second reading of // the same idea. @@ -2038,6 +2073,9 @@ export async function runRound( candidateBodies, tasks: openingBoard, }) + // The rule, not capacity, is why nothing starts: the tick says so. + if (reviewFirst && !choice.chosen && swiftCapacityAnswer(choice).answer) + choice.refusal = reviewFirst // TRIOS_QUEEN_DISPATCH_BATCH=on (t27#9021): one chooser call fills every // free lane with edits that meet no other (queen-batch-choose.ts); off, the // round asks queend again after every pick, as before. diff --git a/trios/agent-server/specs/PIN b/trios/agent-server/specs/PIN index c0542c46fc..8a3a8c610b 100644 --- a/trios/agent-server/specs/PIN +++ b/trios/agent-server/specs/PIN @@ -127,3 +127,6 @@ claim card: gHashTag/t27 specs/queen/claim.t27 (t27#8765, PR t27#8768 head d4ced claim: queen/claim.t27 sha256 f031594c8c0c5078a126ad4ed3ec0e3ef6b1179658572878fa24361c4b17cec9 claim: queen/review_valve.t27 sha256 5177806d264aa3611b5187a89b929c2231cbf7f95eb3cb78d246d913b658d8a6 claim: queen/claim.wasm sha256 08fc2b32d46742638c00d20fe1e64dd81289080dcfc8fa662972acc411d4884a +review_first card: gHashTag/t27 specs/queen/review_first.t27 (t27#10555, PR t27#10562), byte-identical; review_first.wasm = t27c gen-c + zig 0.16.0 cc --target=wasm32-wasi -Oz -DNDEBUG -mexec-model=reactor, exports every fn of the card: kind_of,in_review_queue,backlog_open,admission,admits,round_refused (reproducible: two builds, one hash); constants: t27c gen-ts > apps/server/src/api/services/queen-review-first-card.gen.ts +review_first: queen/review_first.t27 sha256 c0ec4028feccb9740ddd434f8a1ee8f4fce9b7a4ce2dd1664de504a0b5d06e88 +review_first: queen/review_first.wasm sha256 6c9fbeca0bc9dc98122ee4dcc8cdf1ad0c37c659625422828281309875555f2f diff --git a/trios/agent-server/specs/queen/review_first.t27 b/trios/agent-server/specs/queen/review_first.t27 new file mode 100644 index 0000000000..dfce350f1d --- /dev/null +++ b/trios/agent-server/specs/queen/review_first.t27 @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/queen/review_first.t27 -- no new work from the backlog while finished work waits in review (gHashTag/t27#10555). +; The owner's rule, 2026-10-18: the Queen takes nothing from the backlog while there is a queue in review. +; Measured 2026-10-18 17:09Z on api.t27.ai/queen/status: 69 of 70 workers busy; in the last hour 102 +; dispatched, 56 finished, 11 reviewed; reviewable (the review sweep's own queue) 29, unreviewed 362. +; The swarm starts about nine bees for every verdict, so the queue in review only grows, and each new bee +; holds files (claim.t27) that the bees already in review need when they are sent back. +; The rule: +; 1. the review queue is exactly the review sweep's own filter, the one the status page counts as +; `reviewable`: the dispatch started, it finished, its review_state is NULL or 'wait', it was not +; reaped, and its issue is still open (in_review_queue); +; 2. while that queue is longer than REVIEW_QUEUE_MAX (0: not while anything waits) a candidate from +; the backlog is refused, and the round says why (admission, R_REVIEW_FIRST); +; 3. work the review itself asked for is never backlog: an issue the review sent back is dispatched +; again at any queue length (KIND_REWORK), and so is an issue the owner assigned by hand +; (KIND_ASSIGNED, the supervisor's pendingAssigns); +; 4. the queue empties on its own: every outcome of the review that is not an accept has a clock +; (review_valve.t27), so a closed backlog always reopens without a person. +; The host counts the queue and names each candidate's kind; this file decides. Not here: the wiring into +; trios (queen-tick.ts candidate filter, vendored wasm and t27c gen-ts constants), how keys are shared +; between bees and the reviewer, the review verdicts themselves. ASCII only (L3). +; phi^2 + 1/phi^2 = 3 | TRINITY + +module QueenReviewFirst; + +; --- 1. What a candidate is --------------------------------------------------------------------------- + +/// KIND_BACKLOG: an open issue with no dispatch the review is working on. KIND_REWORK: an issue whose +/// last dispatch the review sent back (review_state 'sendBack'). KIND_ASSIGNED: an issue the owner +/// assigned by hand. The codes are mirrored by number: a new kind takes the next code, none moves. +pub const KIND_BACKLOG : u8 = 0; +pub const KIND_REWORK : u8 = 1; +pub const KIND_ASSIGNED : u8 = 2; + +/// The owner's hand wins over the review's send-back, and both win over the backlog. +pub fn kind_of(assigned: bool, sent_back: bool) -> u8 { + if (assigned) { return KIND_ASSIGNED; } + if (sent_back) { return KIND_REWORK; } + return KIND_BACKLOG; +} + +; --- 2. What the review queue is ---------------------------------------------------------------------- + +/// A dispatch row's review_state as the host reads it: RS_NONE is NULL (no verdict yet), RS_WAIT is +/// 'wait' (fewer verdicts than criteria, dispatch.t27 REVIEW_RULE), RS_OTHER is any written verdict. +pub const RS_NONE : u8 = 0; +pub const RS_WAIT : u8 = 1; +pub const RS_OTHER : u8 = 2; + +/// Whether one dispatch row is in the review queue: the sweep's own filter (trios queen-tick.ts +/// reviewFinishedDispatches, counted as `reviewable` by queen-public-status.ts). A row whose issue was +/// closed is never reviewed, so it never holds the backlog shut. +pub fn in_review_queue(started: bool, finished: bool, state: u8, reaped: bool, issue_open: bool) -> bool { + if (started == false) { return false; } + if (finished == false) { return false; } + if (reaped) { return false; } + if (issue_open == false) { return false; } + if (state == RS_NONE) { return true; } + return state == RS_WAIT; +} + +; --- 3. Admission ------------------------------------------------------------------------------------- + +/// How many dispatches may wait in review while the backlog stays open. Zero, the owner's words: not +/// while anything is in review. +pub const REVIEW_QUEUE_MAX : u32 = 0; + +/// R_ADMIT: the candidate may be chosen. R_REVIEW_FIRST: refused, the backlog is closed while the review +/// queue is longer than REVIEW_QUEUE_MAX. +pub const R_ADMIT : u8 = 0; +pub const R_REVIEW_FIRST : u8 = 1; + +/// The refusal as the round reports it, after the queue length: "29 dispatches wait in review; ...". +pub const REFUSAL_TAIL : str = " dispatches wait in review; the backlog is closed until the review queue is empty"; + +pub fn backlog_open(review_queue: u32) -> bool { + return review_queue <= REVIEW_QUEUE_MAX; +} + +pub fn admission(kind: u8, review_queue: u32) -> u8 { + if (kind == KIND_REWORK) { return R_ADMIT; } + if (kind == KIND_ASSIGNED) { return R_ADMIT; } + if (backlog_open(review_queue)) { return R_ADMIT; } + return R_REVIEW_FIRST; +} + +pub fn admits(kind: u8, review_queue: u32) -> bool { + return admission(kind, review_queue) == R_ADMIT; +} + +/// Whether a round that admitted no candidate owes the review-first sentence: some candidate was +/// refused by this rule and none was admitted. +pub fn round_refused(admitted: u32, refused: u32) -> bool { + if (admitted > 0) { return false; } + return refused > 0; +} + +; --- 4. Tests ----------------------------------------------------------------------------------------- + +test the_owner_hand_wins_then_the_review_then_the_backlog { + assert kind_of(false, false) == 0; + assert kind_of(false, true) == 1; + assert kind_of(true, false) == 2; + assert kind_of(true, true) == 2; +} + +test the_queue_is_the_sweeps_own_filter { + assert in_review_queue(true, true, RS_NONE, false, true) == true; + assert in_review_queue(true, true, RS_WAIT, false, true) == true; + assert in_review_queue(true, true, RS_OTHER, false, true) == false; + assert in_review_queue(true, false, RS_NONE, false, true) == false; + assert in_review_queue(false, true, RS_NONE, false, true) == false; + assert in_review_queue(true, true, RS_NONE, true, true) == false; + assert in_review_queue(true, true, RS_NONE, false, false) == false; + assert in_review_queue(true, true, RS_WAIT, true, true) == false; + assert in_review_queue(true, true, 7, false, true) == false; +} + +test the_backlog_is_closed_while_anything_waits_in_review { + assert admission(KIND_BACKLOG, 0) == 0; + assert admission(KIND_BACKLOG, 1) == 1; + assert admission(KIND_BACKLOG, 29) == 1; + assert admission(KIND_BACKLOG, 4294967295) == 1; + assert backlog_open(0) == true; + assert backlog_open(1) == false; + assert admits(KIND_BACKLOG, 0) == true; + assert admits(KIND_BACKLOG, 1) == false; +} + +test work_the_review_asked_for_is_never_backlog { + assert admission(KIND_REWORK, 0) == 0; + assert admission(KIND_REWORK, 1) == 0; + assert admission(KIND_REWORK, 29) == 0; + assert admission(KIND_REWORK, 4294967295) == 0; + assert admits(KIND_REWORK, 362) == true; +} + +test the_owners_assignment_is_never_held { + assert admission(KIND_ASSIGNED, 0) == 0; + assert admission(KIND_ASSIGNED, 29) == 0; + assert admits(KIND_ASSIGNED, 4294967295) == true; +} + +test an_unknown_kind_is_backlog_and_is_held { + assert admission(3, 0) == 0; + assert admission(3, 1) == 1; + assert admission(255, 29) == 1; +} + +test a_round_reports_the_rule_only_when_it_stopped_everything { + assert round_refused(0, 0) == false; + assert round_refused(0, 5) == true; + assert round_refused(1, 5) == false; + assert round_refused(3, 0) == false; +} + +test the_measured_round_of_2026_10_18 { + assert admits(kind_of(false, false), 29) == false; + assert admits(kind_of(false, true), 29) == true; + assert admits(kind_of(true, false), 29) == true; + assert admits(kind_of(false, false), 0) == true; +} + +; --- 5. Invariants ------------------------------------------------------------------------------------ + +invariant the_codes_never_move { + assert KIND_BACKLOG == 0 and KIND_REWORK == 1 and KIND_ASSIGNED == 2; + assert RS_NONE == 0 and RS_WAIT == 1 and RS_OTHER == 2; + assert R_ADMIT == 0 and R_REVIEW_FIRST == 1; +} + +/// The owner's bound: nothing may wait in review while the backlog is open. +invariant the_bound_is_the_owners { + assert REVIEW_QUEUE_MAX == 0; +} diff --git a/trios/agent-server/specs/queen/review_first.wasm b/trios/agent-server/specs/queen/review_first.wasm new file mode 100644 index 0000000000000000000000000000000000000000..da2ce9b2bee33790d5267265beff173f8d70ce7e GIT binary patch literal 310 zcmXYqPfNrw5XIl5|BZ_xD1w)O7e9e#b11T(qOCToaktH;*(wT>3LgB_t|{vr-n{v} zVFr!s2moEiHBd9}$=8XWD;NqA6?uqRL_~2y+t8Aj@Ws+1q;i_3oM+E@+NsVA?+m=s zz250&qdycVTkF29%nIy8_tl_YwR)@Us78h8_@Q}RZ_HC=wyH;5G;ODy)22s~&VJ{R zE(SA(1A|(PPPNF6z9|nsuEFjkZdmyKgK`0I2}!qy`83c8yntka1g`i61O! Date: Mon, 19 Oct 2026 00:27:18 +0700 Subject: [PATCH 2/2] queen: say plainly which choose answer lets the review-first refusal through (t27#10555) Co-Authored-By: Claude Opus 5.5 --- trios/agent-server/apps/server/src/api/services/queen-tick.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trios/agent-server/apps/server/src/api/services/queen-tick.ts b/trios/agent-server/apps/server/src/api/services/queen-tick.ts index a97ea9f962..74a2174d64 100644 --- a/trios/agent-server/apps/server/src/api/services/queen-tick.ts +++ b/trios/agent-server/apps/server/src/api/services/queen-tick.ts @@ -2073,7 +2073,7 @@ export async function runRound( candidateBodies, tasks: openingBoard, }) - // The rule, not capacity, is why nothing starts: the tick says so. + // answer is true unless queend refused for capacity: then the rule says why. if (reviewFirst && !choice.chosen && swiftCapacityAnswer(choice).answer) choice.refusal = reviewFirst // TRIOS_QUEEN_DISPATCH_BATCH=on (t27#9021): one chooser call fills every