yoi/work-items/closed/20260603-001124-unify-reasoning-block-lifecycle/artifacts/implementation-report.md

2.4 KiB

Implementation report: reasoning block lifecycle

Investigation

Initial implementation unified reasoning persistence through BlockStop.reasoning, but OpenAI Responses text-bearing reasoning items still had two Thinking lifecycles:

  1. response.reasoning_text.delta streamed through the real reasoning content-part block.
  2. response.content_part.done stopped that block with no persistence metadata.
  3. response.output_item.done emitted a second synthetic metadata-only Thinking BlockStart/BlockStop pair.

That preserved persistence but changed live callback semantics: UI/trace consumers that listen to Thinking block stop callbacks could observe an extra empty Thinking stop after the real streamed reasoning block.

Fix summary

OpenAI Responses now defers the stop for reasoning content_part.done when the part is a Thinking/reasoning-text content block. At response.output_item.done, the provider finalizes the deferred existing block with ReasoningBlockData instead of creating a second synthetic live-visible block.

Thinking block handler scopes are also keyed by block index, so a deferred reasoning-text stop still uses its original streamed buffer even if another Thinking block (for example a reasoning summary block) starts and stops before output_item.done.

Metadata-only reasoning items with no reasoning content-part still emit a synthetic metadata-bearing Thinking block so encrypted/id-only reasoning can be persisted and round-tripped.

The fix preserves:

  • live reasoning_text.delta Thinking deltas;
  • OpenAI Responses id, summary, and encrypted_content persistence;
  • a single Thinking lifecycle for text-bearing reasoning items;
  • metadata-only reasoning coverage.

Validation

Passed:

  • cargo test -p llm-worker openai_responses::events::tests::reasoning --lib
  • cargo test -p llm-worker --lib
  • cargo check --workspace --all-targets
  • ./tickets.sh doctor
  • git diff --check
  • nix build .#yoi

Residual risk

The provider delays the stop event for OpenAI Responses reasoning text blocks until response.output_item.done so final encrypted/summary metadata can be attached to the same block. This avoids duplicate live stops but means the block stop is slightly later than the raw content_part.done SSE for reasoning text. This is intentional for the unified persistence model and covered by focused provider tests for the reviewed sequence.