Skip to content

fix(agent-harness): enforce required structured-output block every turn (#4117)#4900

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/4117-required-structured-output
Jul 16, 2026
Merged

fix(agent-harness): enforce required structured-output block every turn (#4117)#4900
senamakel merged 3 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/4117-required-structured-output

Conversation

@M3gA-Mind

Copy link
Copy Markdown
Collaborator

Closes #4117

Redo of the closed #4387, rebuilt against the current harness (post-#4399) and resolving the streaming blocker that stopped the original.

Approach

Opt-in RequiredOutputContract { block_key, required_keys } on AgentConfig. When active, the turn engine validates the final reply and repairs an omitted/invalid block before accepting the turn.

  • Validate — reply must contain a leading JSON object carrying every required key with a non-null value (reuses the harness JSON extractor, so fenced/inline/whole-object replies are recognized). Blank block_key is inert.
  • Repair — one bounded corrective re-prompt. Reconciled with streaming: when the invalid reply was already streamed to the client, the repaired block is append-synthed so the final reply stays consistent with what the user saw. This fixes the streaming/return divergence (sanil-23's blocking review) that closed fix(agent-harness): enforce required structured-output block every turn #4387.

Tests

6 turn tests (streamed append-synth, reprompt-failure→synth, preview-is-a-prefix) + 12 unit tests over validate/repair/reconcile — targeting the changed lines for the ≥80% diff-coverage gate.

Notes

Built on current main; all AgentConfig literals repo-wide use ..Default::default(), so the new field is additive. No AI attribution.

@M3gA-Mind
M3gA-Mind requested a review from a team July 15, 2026 12:44
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e1de111e-b2d9-4e91-858f-192bcf9c7995

📥 Commits

Reviewing files that changed from the base of the PR and between d320c43 and f86450c.

📒 Files selected for processing (8)
  • src/openhuman/agent/harness/mod.rs
  • src/openhuman/agent/harness/required_output.rs
  • src/openhuman/agent/harness/session/turn/core.rs
  • src/openhuman/agent/harness/session/turn/session_io.rs
  • src/openhuman/agent/harness/session/turn_tests.rs
  • src/openhuman/config/mod.rs
  • src/openhuman/config/schema/agent.rs
  • src/openhuman/config/schema/mod.rs

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3a2c28a7c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

"[agent_loop] required output block `{}` recovered via re-prompt (append)",
contract.block_key
);
repair_text

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Append only the repaired block for streamed replies

When a streamed reply omits the block, the original answer has already been sent to the client, but this branch appends the entire corrective re-prompt output. The re-prompt explicitly asks the model to reply again and continue with the answer, so a compliant model will return the JSON block plus the full answer, causing the user-visible and persisted reply to duplicate the answer after the appended block. For streamed repairs, append only the extracted required JSON block or use a block-only repair prompt.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 4539106. Confirmed legit: repair_instruction asks the model to lead with the block and continue with its answer, so the streamed append path was concatenating the whole re-prompt reply (block + restated answer) after the already-streamed prose, duplicating the answer in both the live stream and the persisted reply.

Fix: in the streamed path we now append only the extracted required block (find_required_block on the re-prompt reply, serialized), falling back to a synthesized block when the re-prompt doesn't recover one. The original prose is preserved and precedes the block; nothing re-streams the answer. Added a regression test (turn_appends_only_block_not_restated_answer_when_streamed) that returns block + restated answer from the re-prompt and asserts the answer appears exactly once.

Redo of the closed tinyhumansai#4387 against the current harness. Add an opt-in
RequiredOutputContract (block_key + required_keys) on AgentConfig; when active,
the turn engine validates the final reply and repairs an omitted/invalid block
before the turn is accepted.

- Validate: reply must carry a leading JSON object with every required key
  non-null (reuses the harness JSON extractor; blank block_key is inert).
- Repair: one bounded corrective re-prompt, reconciled with streaming — when
  the invalid reply was already streamed, the repaired block is append-synthed
  so the client's final reply stays consistent with what was streamed (fixes
  the streaming/return divergence that blocked tinyhumansai#4387).
- Tests: 6 turn tests (streamed append-synth, reprompt-failure->synth, preview-
  is-prefix) + 12 unit tests over validate/repair/reconcile.

Closes tinyhumansai#4117
@M3gA-Mind
M3gA-Mind force-pushed the fix/4117-required-structured-output branch from f3a2c28 to 9c2c4e9 Compare July 15, 2026 12:52
…tinyhumansai#4900)

Codex review: in the streamed (append) repair path, the whole corrective
re-prompt reply was appended after the already-streamed prose. Since
repair_instruction asks the model to lead with the block *and continue with its
answer*, a compliant re-prompt returns block + restated answer — so the
user-visible and persisted reply duplicated the answer after the block.

Append only the extracted required block (via find_required_block), falling back
to a synthesized block when the re-prompt doesn't recover one. The original
streamed prose is still preserved and precedes the block; no restated answer is
re-streamed or persisted.

Add a regression test where the re-prompt returns block + restated answer and
assert the answer appears exactly once (and the streamed continuation carries
only the block).
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

W5 sweep — pushed 4539106

Codex review (1 item, P2): Append only the repaired block for streamed repliesfixed. The streamed repair path appended the entire corrective re-prompt reply after the already-streamed prose; since repair_instruction asks the model to re-emit the block and continue with the answer, that duplicated the answer. Now appends only the extracted required block (fallback: synthesized block), preserving the original prose ahead of it. Added regression test turn_appends_only_block_not_restated_answer_when_streamed.

CI: rustfmt --check clean on both changed files. Coverage: the changed streamed-append lines are exercised by the new + existing streamed tests. No frontend touched.

Comment-only per role — not approving/merging.

@senamakel
senamakel merged commit 66e46a2 into tinyhumansai:main Jul 16, 2026
18 checks passed
senamakel added a commit to M3gA-Mind/openhuman that referenced this pull request Jul 18, 2026
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.

[Harness] Required structured output (JSON 'thoughts' block) frequently omitted

2 participants