Skip to content

pi: skip Oh My Pi request-control system rows in the dispatch check - #786

Merged
ualtinok merged 1 commit into
cortexkit:masterfrom
tsankotsanev:fix/785-omp-request-control-rows
Oct 26, 2026
Merged

ualtinok merged 1 commit into
cortexkit:masterfrom
tsankotsanev:fix/785-omp-request-control-rows

Conversation

@tsankotsanev

Copy link
Copy Markdown
Contributor

Approved issue: #785

Harness coverage (required)

For each entry select: covered / not applicable — surface does not exist there / NOT covered.

  • OpenCode 1: not applicable - surface does not exist there (the provider dispatch structure check lives only in packages/pi-plugin/src/pi-dispatch-structure.ts; no equivalent in packages/plugin)
  • OpenCode 2: not applicable - surface does not exist there (same as OpenCode 1)
  • Pi: covered (shared payloadStructure code path; Pi's providers do not emit these control rows, so its behavior is unchanged)
  • OMP: covered (the fix targets the rows Oh My Pi's Anthropic provider appends)
  • Rust module: not applicable - surface does not exist there (no dispatch structure check in crates/)

What and why

Oh My Pi's Anthropic provider (insertAnthropicControlMarkers in pi-ai/src/providers/anthropic.ts, 18.12.6) appends request-control system messages while it builds the request: tool_addition / tool_removal blocks when the session's recorded tool set differs from the current one, and empty effort-only controls (content: []). This happens after the context pass, so the prepared plan never contains them.

payloadStructure skipped system / developer messages only before the first conversation row, so a trailing control was counted as an extra row and every turn was refused with prepared turn structure ... expectedRole=missing actualRole=system comparison=count. Oh My Pi records the control only after a successful request, so an affected session could never recover on its own.

The fix skips system messages whose content is an array made only of tool_addition / tool_removal blocks (an empty array included). A system row carrying any other content (a string, a text block, or a control mixed with text) is still counted and compared, so unprepared system text keeps refusing.

Tests

New packages/pi-plugin/src/issue-785-request-controls.test.ts:

  • An Anthropic payload ending in a tool_addition control, a mixed tool_addition + tool_removal control, or an empty effort-only control has no mismatch against the plan without it. Without the fix this fails with row=4 expectedRows=4 actualRows=5 expectedRole=missing actualRole=system ... comparison=count, the reported refusal.
  • An unprepared system row with string content, a text block, or a control mixed with text still refuses, both trailing and mid-history.

Ran in packages/pi-plugin:

  • bun test src/issue-785-request-controls.test.ts src/issue-686-midhistory-system.test.ts src/issue-686-system-head.test.ts src/pi-dispatch-structure-diagnostic.test.ts: 20 pass, 0 fail.
  • bun run typecheck: clean.
  • bunx biome check on both changed files: clean.
  • bun run test: 3483 pass, 53 skip, 7 fail. The same 7 (index-env-guard, tag-snapshot-hydration-pi, context-handler-lkg-debug-assertions, lkg-fence-restart-regression x3, held-head-replay-witness) also fail on unmodified master in this environment.

The live behavior was also checked by applying the same change to the published 0.49.23 dist: the previously refused session replies normally and new sessions are unaffected.

Oh My Pi's Anthropic provider (insertAnthropicControlMarkers in
pi-ai/src/providers/anthropic.ts, 18.12.6) appends request-control
system messages while it builds the request: tool_addition/tool_removal
blocks when the session's recorded tool set differs from the current
one, and empty effort-only controls. That happens after the context
pass, so the prepared plan never contains them.

payloadStructure skipped system/developer messages only before the
first conversation row, so a trailing control was counted as an extra
row and every turn was refused with "prepared turn structure ...
expectedRole=missing actualRole=system comparison=count". Oh My Pi
records the control only after a successful request, so an affected
session could never recover on its own.

Skip system messages whose content consists only of tool_addition or
tool_removal blocks (an empty array included). System rows carrying any
other content are still counted and compared, so an unprepared system
text row keeps refusing.

Refs cortexkit#785
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

#785 has the design-approved label; this PR can be reviewed.

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