-
Notifications
You must be signed in to change notification settings - Fork 0
fix: accept event-driven routing exit #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
a9e177d
e44e81c
b41b72a
602e3c1
cd0ba30
218e847
dbda45b
9261a82
f2c697c
46af136
0d59344
3bb9edb
b4bca5d
76e66bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,8 @@ | |
| * KD-8 safe:只看"有无机械出口信号",零意图分类器。 | ||
| */ | ||
|
|
||
| import { hasEventDrivenExternalWaitExit } from '../final-routing-slot.js'; | ||
|
|
||
| /** Routing-tool substrings that count as a legitimate exit (持球/群发传球). */ | ||
| const ROUTING_TOOL_SUBSTRINGS = ['hold_ball', 'multi_mention'] as const; | ||
|
|
||
|
|
@@ -23,6 +25,8 @@ function hasRoutingToolCall(toolNames: readonly string[]): boolean { | |
| } | ||
|
|
||
| export interface RoutingExitInput { | ||
| /** Stored output text. Only the final routing slot is inspected for structural external-wait exits. */ | ||
| readonly text?: string; | ||
| /** Line-start @cat mentions parsed this turn (parseA2AMentions). */ | ||
| readonly lineStartMentions: readonly string[]; | ||
| /** Tool names invoked this turn (scan for hold_ball / multi_mention). */ | ||
|
|
@@ -36,13 +40,15 @@ export interface RoutingExitInput { | |
| /** | ||
| * True iff the turn has a legitimate routing exit (传球 / 持球 / 升级). | ||
| * Mirrors the suppression set of evaluateVoidHold + F177-G hook | ||
| * (line-start @, hold_ball, multi_mention, targetCats, co-creator). | ||
| * (line-start @, hold_ball, multi_mention, targetCats, co-creator, structural | ||
| * 2b external wait slot). | ||
| */ | ||
| export function hasValidRoutingExit(input: RoutingExitInput): boolean { | ||
| if (input.lineStartMentions.length > 0) return true; | ||
| if (input.structuredTargetCats.length > 0) return true; | ||
| if (input.hasCoCreatorLineStartMention) return true; | ||
| if (hasRoutingToolCall(input.toolNames)) return true; | ||
| if (hasEventDrivenExternalWaitExit(input.text)) return true; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a guarded reply contains Useful? React with 👍 / 👎.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in dbda45b ( What changed:
Red -> Green coverage:
Validation:
[砚砚/GPT-5.5🐾] |
||
| return false; | ||
| } | ||
|
|
||
|
|
@@ -74,6 +80,7 @@ export const REMEDIAL_PROMPT = | |
| '请只补一个出口,不要重做刚才的工作:\n' + | ||
| '- 传球:另起一行,行首独立写 @句柄(如 @opus48)\n' + | ||
| '- 持球等外部条件:调用 cat_cafe_hold_ball\n' + | ||
| '- 事件驱动外部等待(已有结构化回调 + EYES>0):另起一行写 External Wait: event-driven (<id>)\n' + | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a first pass has useful text but no exit, this new remedial option asks the model to return only Useful? React with 👍 / 👎.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a guarded cat first produces useful content but no route exit, this new prompt can now make the remedial call answer with only Useful? React with 👍 / 👎.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in |
||
| '- 升级co-creator:另起一行行首写 @co-creator'; | ||
|
|
||
| export function buildRemedialPrompt(): string { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1732,6 +1732,7 @@ export async function* routeSerial( | |
| shouldRemediateRouting({ | ||
| needsGuard: needsServerRoutingGuard, | ||
| attempted: routingGuardAttempted, | ||
| text: '', | ||
| lineStartMentions: getRoutingExitLineStartMentions(), | ||
| toolNames: collectedToolNames, | ||
| structuredTargetCats: [...structuredTargetCats], | ||
|
|
@@ -1748,6 +1749,7 @@ export async function* routeSerial( | |
| noTextBlocksOverride = result.allRichBlocks; | ||
| if ( | ||
| !hasValidRoutingExit({ | ||
| text: result.storedContent, | ||
| lineStartMentions: getRoutingExitLineStartMentions(result.a2aMentions), | ||
| toolNames: collectedToolNames, | ||
| structuredTargetCats: [...structuredTargetCats], | ||
|
|
@@ -1799,6 +1801,7 @@ export async function* routeSerial( | |
| shouldRemediateRouting({ | ||
| needsGuard: needsServerRoutingGuard, | ||
| attempted: routingGuardAttempted, | ||
| text: storedContent, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this text makes Useful? React with 👍 / 👎.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in |
||
| lineStartMentions: routingExitLineStartMentions, | ||
| toolNames: collectedToolNames, | ||
| structuredTargetCats: [...structuredTargetCats], | ||
|
|
@@ -1816,6 +1819,7 @@ export async function* routeSerial( | |
|
|
||
| if ( | ||
| !hasValidRoutingExit({ | ||
| text: storedContent, | ||
| lineStartMentions: routingExitLineStartMentions, | ||
| toolNames: collectedToolNames, | ||
| structuredTargetCats: [...structuredTargetCats], | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| # Review Request: route guard 2b event-driven external wait | ||
|
|
||
| Review-Target-ID: fix-route-guard-2b-event-driven | ||
| Branch: fix/route-guard-2b-event-driven | ||
| Target: local branch based on `origin/main` at `8e412d2b` | ||
|
|
||
| ## What | ||
|
|
||
| The routing guard now treats a final-slot line of | ||
| `External Wait: event-driven (<id>)` as a legitimate 2b external-wait exit. | ||
|
|
||
| Changed paths: | ||
| - `packages/api/src/domains/cats/services/agents/routing/guards/routing-guard-remedial.ts` | ||
| - `packages/api/src/domains/cats/services/agents/routing/route-serial.ts` | ||
| - `packages/api/test/routing-guard-remedial.test.js` | ||
| - `packages/api/test/route-serial-routing-guard-remedial.test.js` | ||
|
|
||
| ## Why | ||
|
|
||
| Daily patrol found a real routing contradiction: when PR tracking had structured | ||
| callback coverage and EYES>0, the collaboration rule said 2b event-driven wait | ||
| means no `hold_ball`, but the server-side routing guard rejected the response as | ||
| "no legal route exit" unless it saw line-start `@` or `cat_cafe_hold_ball`. | ||
| That caused unnecessary remedial churn and local cat ping-pong even though the | ||
| next action was an external callback. | ||
|
|
||
| ## Original Requirements | ||
|
|
||
| Source: scheduled patrol in `thread_mqcj45byxoka2z7u`, wake | ||
| `2026-06-28 00:00 Asia/Shanghai`. | ||
|
|
||
| > 每轮必须先查真相源和证据,再给风险/价值判断与下一步动作。 | ||
| > 发现可执行事项后主导闭环:按家规走 feature lifecycle(定位真相源、立项、实现/协调、质量门禁、review、完成记录)。 | ||
|
|
||
| Observed incident source: `clowder-labs/clowder-ai#32` review/check wait path | ||
| where current rules selected 2b event-driven waiting but route guard demanded | ||
| `@` or `hold_ball`. | ||
|
|
||
| ## Tradeoff | ||
|
|
||
| This is intentionally structural: only a final routing slot line matching | ||
| `External Wait: event-driven (<id>)` counts. It does not classify natural | ||
| language like "I will wait for CI", so the F177/KD-8 guard remains mechanical. | ||
|
|
||
| The remedial prompt now teaches that exact outlet format, so future guard | ||
| patches can add the missing exit without redoing work. | ||
|
|
||
| ## Architecture Ownership | ||
|
|
||
| Architecture cell: routing / A2A guard | ||
| Map delta: none | ||
| Why: this extends the existing routing guard exit predicate and route-serial | ||
| input plumbing. It does not add a new Store, Queue, Router, Adapter, Dispatcher, | ||
| Binding, runtime service, or external contract. | ||
|
|
||
| Please check: | ||
| - diff matches `Map delta: none` | ||
| - the `External Wait` recognizer is structural enough and not an intent classifier | ||
| - route-serial passes the correct stored text at every guard check | ||
|
|
||
| ## Quality Gate Evidence | ||
|
|
||
| ### Red | ||
|
|
||
| - `routing-guard-remedial.test.js`: `2b External Wait event-driven 槽位 → 不触发 remedial` failed, returning `true` instead of `false`. | ||
| - `routing-guard-remedial.test.js`: `External Wait: event-driven(<id>) counts as a valid 2b external-wait exit` failed, returning `false` instead of `true`. | ||
| - `routing-guard-remedial.test.js`: prompt test failed because `event-driven` was missing. | ||
| - `route-serial-routing-guard-remedial.test.js`: event-driven external wait caused two Codex invocations instead of one. | ||
|
|
||
| ### Green | ||
|
|
||
| - `pnpm --dir packages/api run build`: passed | ||
| - `CAT_CAFE_DISABLE_SHARED_STATE_PREFLIGHT=1 bash packages/api/scripts/with-test-home.sh node --import $(pwd)/packages/api/test/helpers/setup-cat-registry.js --test --test-timeout=60000 packages/api/test/routing-guard-remedial.test.js packages/api/test/route-serial-routing-guard-remedial.test.js`: 33 tests passed | ||
| - `CAT_CAFE_DISABLE_SHARED_STATE_PREFLIGHT=1 bash packages/api/scripts/with-test-home.sh node --import $(pwd)/packages/api/test/helpers/setup-cat-registry.js --test --test-timeout=60000 packages/api/test/final-routing-slot.test.js packages/api/test/verdict-detect.test.js`: 57 tests passed | ||
| - `pnpm --dir packages/api run lint`: passed | ||
| - `git diff --check`: passed | ||
| - `pnpm check`: passed | ||
|
|
||
| ### Extra Gate Checks | ||
|
|
||
| - `node scripts/check-hotfix-pattern.mjs`: `{"hotfix":false,"matchedTerms":[],"matches":[]}` | ||
| - `node scripts/check-fallback-layers.mjs`: N/A, script is not present in this tree. | ||
| - `pnpm run check:architecture-ownership`: N/A, script is not present in this tree. | ||
| - `rg --files designs | rg '\.pen$'`: N/A, `designs/` is not present. | ||
| - Root artifact hygiene: | ||
| - `git status --short | rg '^.. [^/]+\.(png|jpe?g|webp|gif|webm|mp4|mov|wav|pdf|pen)$'`: no output | ||
| - `git diff --name-only origin/main...HEAD | rg '^[^/]+\.(png|jpe?g|webp|gif|webm|mp4|mov|wav|pdf|pen)$'`: no output | ||
|
|
||
| ### Dogfood-Your-Slice | ||
|
|
||
| Scope verdict: required. This is cat-visible routing behavior. | ||
|
|
||
| Dogfood path: the route-serial integration suite exercises a guarded Codex turn | ||
| whose final slot is `External Wait: event-driven (pr:clowder-labs/clowder-ai#32)`. | ||
| Before the fix, route-serial invoked Codex twice; after the fix, it persists the | ||
| original visible response with one invocation and no routing-guard failure. | ||
|
|
||
| ## Open Questions | ||
|
|
||
| ### Technical OQ | ||
|
|
||
| - Should we accept only the English `External Wait` template, or also add a | ||
| separate Chinese canonical template later? This patch keeps the existing | ||
| documented template only. | ||
| - Is line-level matching inside the final slot acceptable, or should the entire | ||
| final paragraph be exactly one `External Wait` line? | ||
|
|
||
| ### Value OQ | ||
|
|
||
| None. | ||
|
|
||
| ## Next Action | ||
|
|
||
| Please do a non-author review of `fix/route-guard-2b-event-driven`. If clean, | ||
| approve and include the focused validation you ran. If there are P1/P2 findings, | ||
| route back to `@codex` for receive-review. | ||
|
|
||
| ## Receive-Review Update | ||
|
|
||
| Reviewer found one P2 on current head `a9e177d8`: `External Wait: event-driven` | ||
| was accepted by `routing-guard-remedial`, but Phase H `validateRoutingSyntax` | ||
| still treated an inline mention in the same final slot as `invalid_route_syntax`. | ||
|
|
||
| Fix: | ||
| - moved the structural event-driven external-wait predicate into | ||
| `final-routing-slot.ts` | ||
| - made `routing-guard-remedial.ts` reuse that shared helper | ||
| - taught `validateRoutingSyntax()` to treat the same final-slot event-driven | ||
| exit as a legitimate syntax suppressor | ||
|
|
||
| Red→Green: | ||
| - `2b event-driven external wait exit suppresses inline mention syntax warning` | ||
| failed with `invalid_route_syntax`, now passes with `ok`. | ||
|
|
||
| Failure-mode sweep: | ||
| - Pattern: newly added legitimate route exit must be recognized consistently by | ||
| every mechanical routing guard in this PR. | ||
| - Scanned touched routing guard surfaces: remedial exit predicate, route-serial | ||
| guard invocation sites, Phase H final-slot syntax validator, verdict adjacent | ||
| tests. | ||
| - Result: shared helper now prevents remedial/Phase-H drift for this exit. | ||
|
|
||
| Additional verification after the fix: | ||
| - `pnpm --dir packages/api run build`: passed | ||
| - `CAT_CAFE_DISABLE_SHARED_STATE_PREFLIGHT=1 bash packages/api/scripts/with-test-home.sh node --import $(pwd)/packages/api/test/helpers/setup-cat-registry.js --test --test-timeout=60000 packages/api/test/final-routing-slot.test.js`: 23 tests passed | ||
| - `CAT_CAFE_DISABLE_SHARED_STATE_PREFLIGHT=1 bash packages/api/scripts/with-test-home.sh node --import $(pwd)/packages/api/test/helpers/setup-cat-registry.js --test --test-timeout=60000 packages/api/test/routing-guard-remedial.test.js packages/api/test/route-serial-routing-guard-remedial.test.js packages/api/test/verdict-detect.test.js`: 68 tests passed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a cat follows the L0 identity rule and signs after the routing line, e.g.
External Wait: event-driven (pr:35)followed by[砚砚/GPT-5.5],finalRoutingSlot(text)returns the signature paragraph, so this helper returnsfalse. That makes the new 2b external-wait exit still trip the remedial/verdict/void guards for signed outputs;verdict-detect.tsalready had to strip trailing cat signatures for the same final-slot failure mode, so this shared exit predicate should do the same before selecting the slot.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in
602e3c11d0be8df9fef1559f2c68db99ffdc449d.hasEventDrivenExternalWaitExit()now strips trailing cat signatures before selecting the final slot, andverdict-detectreuses the same shared stripper. Added regression coverage forExternal Wait: event-driven (...)followed by[砚砚/GPT-5.5]. Verification: build passed, expanded guard suite 123/123,git diff --checkclean,pnpm checkpassed.