Skip to content

Support training with streaming harnesses - #2369

Draft
hallerite wants to merge 1 commit into
mainfrom
codex/training-streaming-harnesses
Draft

Support training with streaming harnesses#2369
hallerite wants to merge 1 commit into
mainfrom
codex/training-streaming-harnesses

Conversation

@hallerite

@hallerite hallerite commented Aug 14, 2026

Copy link
Copy Markdown
Member

Summary

  • support renderer-backed training for harnesses that require stream=true
  • canonicalize Chat Completions, Responses, and Anthropic requests for rendering, then let each dialect serialize the completed response in its native JSON and SSE shape
  • open buffered streams immediately and emit dialect-valid, content-free progress events while generation runs
  • commit the authoritative renderer result before terminal events, preserving exact completion token IDs and logprobs

Why

Streaming harness calls enter the interception server's relay path. Eval clients can relay provider SSE, but the training client previously only implemented non-streaming get_response(), so those harnesses failed before generation.

Renderer generation remains intentionally buffered. The engine's completed result is still the source of truth for tokens and logprobs; Verifiers only adapts its delivery to the streaming protocols expected by harness SDKs.

Ordinary SSE comments are insufficient for clients that reset liveness only after decoding an API event. During generation, Verifiers therefore sends native no-content events: an empty Chat delta, Responses lifecycle events, or an empty Anthropic message delta. No fake model text or tool arguments are introduced.

Grok Build's newer content watchdog intentionally ignores lifecycle and empty-delta events. The companion Grok harness PR, #2366, sets that independent watchdog beyond Verifiers' maximum remote runtime instead of fabricating model tokens.

Scope

  • renderer output is buffered, not token-streamed
  • Chat Completions, Responses, and Anthropic are supported through the dialect interface
  • eval-provider streaming remains unchanged
  • request/response rewrites and trace commits happen before the client receives a terminal event

Validation

  • uv run pytest tests/ — 914 passed, 75 skipped
  • uv run pytest tests/v1 -m 'not e2e' — 70 passed
  • temporary protocol matrix validated text, reasoning, tool calls, and truncation across all three dialect parsers and the installed OpenAI/Anthropic SDK event models
  • exact Grok Build 1.0.3 behavioral smoke confirmed parsed Responses lifecycle events keep its pinned watchdog alive
  • changed-file pre-commit hooks passed
  • pre-push Ruff, format, and ty hooks passed

Live Prime requests reached the inference service, but the currently available model deployments did not provide a complete training smoke: attempts encountered a removed model, an unsupported generate route, missing generate logprobs, and a temporary 502. The deterministic protocol and full repository suites above are green.

The all-files markdown hook reports 321 pre-existing MD033 findings in the untouched verifiers/legacy/envs/experimental/composable/tasksets/swe/README.md.

The commit is signed with the contributor's verified SSH signing key.

Comment thread verifiers/v1/clients/train.py
@hallerite
hallerite force-pushed the codex/training-streaming-harnesses branch from 087942f to 80250d6 Compare August 14, 2026 22:09
Comment thread verifiers/v1/clients/train.py
@macroscopeapp

macroscopeapp Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new streaming capability for training harnesses, adding abstract methods to dialect base classes, new serialization/streaming methods across three dialect implementations, and substantial new server-side SSE handling logic. There is also an unresolved High severity finding regarding missing index fields on streaming tool calls. The scope of new runtime behavior warrants human review.

No code changes detected at e46f3e2. Prior analysis still applies.

You can customize Macroscope's approvability policy. Learn more.

@hallerite
hallerite force-pushed the codex/training-streaming-harnesses branch from 80250d6 to 2f63e73 Compare August 15, 2026 01:14
*,
include_start: bool = True,
sequence_number: int = 0,
) -> list[bytes]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Streaming tool calls lack index

High Severity

Chat stream_events copies the completed message into delta, so tool_calls entries have no streaming index. OpenAI-compatible clients require index on each delta.tool_calls item and will reject or drop those chunks. Previously this helper only rewrote plain-text replies; the new training relay now sends unrewritten tool-call completions through it, so streaming tool-using harnesses fail.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2f63e73. Configure here.

@hallerite
hallerite force-pushed the codex/training-streaming-harnesses branch from 2f63e73 to e46f3e2 Compare August 15, 2026 10:39
if response.message.reasoning_content is not None:
output.append(
{
"id": "rs_vf_intercept",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium dialects/responses.py:610

Serialized reasoning items are removed on the next turn instead of being replayed, so multi-turn Responses requests lose the model's reasoning history and receive a capability-removal notice. blocked_content_path rejects any reasoning item with an id but no encrypted_content, which is exactly the shape emitted here; omit the synthetic id or serialize encrypted replay state.

-                    "id": "rs_vf_intercept",
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/dialects/responses.py around line 610:

Serialized reasoning items are removed on the next turn instead of being replayed, so multi-turn Responses requests lose the model's reasoning history and receive a capability-removal notice. `blocked_content_path` rejects any `reasoning` item with an `id` but no `encrypted_content`, which is exactly the shape emitted here; omit the synthetic `id` or serialize encrypted replay state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

"id": response.id or "resp_vf_intercept",

Buffered Responses streams announce resp_vf_intercept in response.created but complete with response.id when a request_id is present, so one SSE stream contains two response identities. Clients correlating events by response ID can split or reject the result; keep the synthetic ID consistent during serialization.

-            "id": response.id or "resp_vf_intercept",
+            "id": "resp_vf_intercept",
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/dialects/responses.py around line 666:

Buffered Responses streams announce `resp_vf_intercept` in `response.created` but complete with `response.id` when a `request_id` is present, so one SSE stream contains two response identities. Clients correlating events by response ID can split or reject the result; keep the synthetic ID consistent during serialization.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e46f3e2. Configure here.

"sequence_number": sequence_number,
"response": response,
}
return f"data: {json.dumps(event)}\n\n".encode()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stream start IDs diverge

Medium Severity

stream_start and stream_heartbeat hardcode response IDs such as resp_vf_intercept and vf-intercept, while serialize_response prefers the generate request_id when present. After keepalives, terminal events can carry a different ID than the lifecycle events already sent, breaking clients that correlate the stream by response ID.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e46f3e2. Configure here.

@hallerite
hallerite marked this pull request as draft August 15, 2026 13:30
@hallerite

Copy link
Copy Markdown
Member Author

still a work in progress.

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.

1 participant