Skip to content

fix: adversarial review remediation — release v0.1.5 (rounds 2 + 3)#8

Merged
ejwhite7 merged 2 commits into
mainfrom
fix/review-remediation
Apr 30, 2026
Merged

fix: adversarial review remediation — release v0.1.5 (rounds 2 + 3)#8
ejwhite7 merged 2 commits into
mainfrom
fix/review-remediation

Conversation

@ejwhite7

@ejwhite7 ejwhite7 commented Apr 30, 2026

Copy link
Copy Markdown
Owner

Summary

Two rounds of adversarial review remediation, shipping as v0.1.5. All 449 specs pass under Ruby 3.4.9.

Round 2 (commit eace165)

Addressed 12 defects from review v2: Anthropic \#{...} literal interpolation, thread-unsafe @_stream_* ivars in Anthropic streaming, error-body capture in on_data (Anthropic only — see round 3), compaction-poisoning-system-prompt role change, OpenAI tool_call_id fail-fast, Gemini streaming finishReason, README/CLAUDE.md event-key drift, dead parse_sse_events removal, faraday-net_http cap removal, Fallback no-longer-buffers happy path, :fallback_start event introduction.

Round 3 (commit b0f750e)

Addressed defects in round 2's fixes:

  • Streaming error_body was only wired through on Anthropic — Gemini and OpenAI now also pass override_body: to handle_error_response, so streaming HTTP failures actually carry the real server message in ApiError#response_body.
  • Compaction summary role still produced consecutive same-role messages when the first preserved message was :assistant. Role is now context-aware (:user if next is :assistant, otherwise :assistant). New specs cover both orderings.
  • :fallback_start was emitted but silently dropped by the agent loop — the loop now translates it into a new agent-level :provider_fallback event and clears the streamed-content accumulator.
  • Agent::Core#config: kwarg was decorative — round-2 CHANGELOG claimed it "flows through to provider construction" but it does not. Docstring and CHANGELOG corrected; per-agent config must be passed to the model factory directly.
  • JSON.generate(result.value) could crash on tools returning Time/Date/custom objects. Now wrapped in a rescue with to_s fallback.
  • tool_calls_made arguments leaked the raw string-keyed JSON.parse output while the tool block received symbol keys. Both now use the symbol-keyed form via the newly-public Tools::Executor.deep_symbolize_keys.
  • Added StreamEvent#fallback_start? predicate.
  • Removed two references to nonexistent BufferedStreamProxy class from CHANGELOG.
  • CLAUDE.md provider/extension guides updated to remove the dead parse_sse_events reference and the wrong event[:iterations] key.
  • README documents :fallback_start and :provider_fallback.

Other

  • .gitignore added (.DS_Store, *.gem, vendor/bundle).
  • .ruby-version pins to 3.4.9 (matches CI's 3.4 entry).
  • Version bumped 0.1.3 → 0.1.5. Skipped 0.1.4 — round-2 work was committed but never tagged or published; the v0.1.5 CHANGELOG entry covers both rounds.
  • REVIEW.md documents the round-3 review findings for traceability.

Test plan

  • bundle exec rspec — 449 examples, 0 failures, ~3 seconds
  • Syntax-checked all modified Ruby files
  • CI green on Ruby 3.2 / 3.3 / 3.4

🤖 Generated with Claude Code

Fixes the following issues identified during review remediation:

1. anthropic.rb: Fix escaped string interpolation in ProviderError message
   for malformed tool call JSON. Removed backslash-escaping so actual tool
   name and parser error appear instead of literal \#{...} text.

2. anthropic.rb: Replace thread-unsafe @_stream_* instance variables with
   method-local variables. All streaming state is now scoped to the
   method/block via a process_anthropic_stream_event helper.

3. agent/core.rb + all providers: Thread per-agent config to provider
   construction. BaseProvider accepts config: kwarg; providers use it
   instead of RubyPi.configuration directly.

4. All providers: Detect HTTP error status in on_data streaming callbacks.
   Accumulate error response bodies separately so ApiError contains the
   full error body even when streaming consumed the response.

5. compaction.rb: Change compaction summary from role: :user to
   role: :assistant to prevent consecutive user messages that Anthropic
   rejects (strict user/assistant alternation).

6. openai.rb: Raise ProviderError on nil/blank tool_call_id in tool
   result messages and assistant tool calls (same fail-fast as Anthropic)
   instead of silently sending 'unknown'.

7. gemini.rb: Parse actual finishReason from streaming response instead
   of hardcoding 'stop'.

8. README.md: Fix event key references (e[:iteration] -> e[:turn],
   event[:iterations] -> event[:result].turns).

9. CHANGELOG.md: Add comprehensive v0.1.4 entry documenting all fixes.

10. base_provider.rb: Remove dead parse_sse_events method (all providers
    now use real incremental streaming via on_data).

11. Gemfile + gemspec: Remove arbitrary < 3.4 upper bound on
    faraday-net_http.

12. fallback.rb: Stream events directly to consumer on happy path instead
    of buffering. Emit :fallback_start event on primary failure so consumer
    can clear partial state before fallback streams.

All 444 tests pass.
Addresses defects identified during round-3 review of the round-2 fixes
(commit eace165). All 449 specs pass under Ruby 3.4.9.

Critical:
- Streaming error_body recovery on Gemini and OpenAI: round-2 added the
  error_body accumulator on all three providers but only Anthropic
  passed it to handle_error_response. Gemini and OpenAI now also pass
  override_body so ApiError#response_body carries the real server
  message on streaming HTTP failures.
- Compaction summary role is now context-aware: round-2 changed the
  role from :user to :assistant to avoid consecutive user messages,
  but produced consecutive assistants when the first preserved
  message was already :assistant. The role is now :user when the next
  preserved is :assistant, and :assistant otherwise. New compaction
  specs exercise both orderings.
- :fallback_start StreamEvent now propagates to agent users via a
  new :provider_fallback agent event. The agent loop also clears
  the streamed-content accumulator on fallback so the recorded
  response reflects only the fallback's output.

Major:
- StreamEvent#fallback_start? predicate added for symmetry with
  text_delta?, tool_call_delta?, done?.
- Loop#act guards JSON.generate(result.value) against
  JSON::GeneratorError / TypeError so tools returning Time, Date,
  or other non-JSON-native objects no longer abort the agent.
  Falls back to value.to_s.
- Tools::Executor.deep_symbolize_keys promoted to a public class
  method so the loop can apply it once up front. tool_calls_made
  arguments now match the symbol-keyed shape the tool block
  receives, instead of leaking the raw string-keyed JSON.parse
  output through Result#tool_calls_made.

Documentation:
- Agent::Core#config: kwarg docstring corrected. The kwarg is
  informational; per-agent provider config must be passed to the
  model factory: RubyPi::LLM.model(:openai, "gpt-4o", config: cfg).
  Round-2 CHANGELOG claimed "now flows through to provider
  construction" — that was untrue and has been corrected.
- CHANGELOG: removed two references to nonexistent BufferedStreamProxy
  class. Restructured 0.1.4 entry to be accurate; added 0.1.5 entry.
- CLAUDE.md: "Adding a New LLM Provider" no longer references the
  deleted parse_sse_events helper; describes the on_data + override_body
  pattern. Extension example uses event[:result].turns instead of
  the nonexistent event[:iterations]. Event list updated.
- README: documents :fallback_start StreamEvent and the agent-level
  :provider_fallback event.

Tooling:
- .gitignore added (.DS_Store, *.gem, vendor/bundle, coverage).
- .ruby-version pins to 3.4.9 (matches CI's 3.4 entry).

Version bump: 0.1.3 -> 0.1.5 (skipping 0.1.4 — round-2 work was
completed but never tagged or published; the 0.1.5 CHANGELOG entry
documents both rounds).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@ejwhite7 ejwhite7 changed the title fix: adversarial review remediation round 2 (12 defects) fix: adversarial review remediation — release v0.1.5 (rounds 2 + 3) Apr 30, 2026
@ejwhite7 ejwhite7 merged commit d6a1df2 into main Apr 30, 2026
3 checks passed
@ejwhite7 ejwhite7 deleted the fix/review-remediation branch June 9, 2026 18:29
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