Skip to content

fix: one shared response reader, so feedback can parse a real reply - #178

Merged
mohammadp1001 merged 1 commit into
mainfrom
fix/feedback-agent-strips-code-fences
Aug 11, 2026
Merged

fix: one shared response reader, so feedback can parse a real reply#178
mohammadp1001 merged 1 commit into
mainfrom
fix/feedback-agent-strips-code-fences

Conversation

@mohammadp1001

Copy link
Copy Markdown
Owner

What happened

The feedback agent ran for the first time ever today, in the live run following #176.
It failed all three retries with:

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

The position was filed EVALUATION_FAILED. But the model had produced a complete,
correct evaluation - Alphoryn threw it away. That is precisely the outcome FR-016a
exists to prevent.

Root cause

Captured from the live run's OTel logs, the response was two parts:

Part thought Content
[0] True 4077-char reasoning summary
[1] None ```json\n{ "outcome_judgment": "CORRECT" ... }

feedback_agent skipped the thought correctly (that was #174's fix, and it works),
then handed the fenced string straight to json.loads, which fails at character 0.

main_agent has stripped fences since it was written, and also skips parts whose text
is empty. feedback_agent did neither:

# main_agent.py                        # feedback_agent.py
if is_thought_part(part): continue     if is_thought_part(part): continue
text = getattr(part, "text", None)
if text and text.strip():
    raw_json = _strip_fences(...)      raw_json = part.text
    break                              break

The same logic had been written twice and the two copies drifted.

The fix

Rather than paste the two missing guards into the second copy, both agents now call
alphoryn/agents/responses.py:extract_response_json, which does all three things -
skip thoughts, skip empty parts, strip fences - in one place. _strip_fences moved out
of main_agent into that module.

Two copies of this logic is what caused the bug. One copy cannot drift.

Verified against the bytes that actually failed

Replaying the exact response captured from the failed run:

parts replayed: 2
  [0] thought=True starts="Alright, let's break d"
  [1] thought=None starts='```json\n{\n  "outcome_j'

--- old behaviour (first non-thought part, verbatim) ---
JSONDecodeError: Expecting value: line 1 column 1 (char 0)

--- new behaviour (extract_response_json) ---
parsed OK
outcome_judgment = 'CORRECT'

The old path reproduces the production error exactly; the new path recovers the
evaluation that was discarded.

Tests

700 passed, 100% coverage, ruff check alphoryn/ tests/ clean, verified under CI's
credential-less environment.

tests/unit/test_responses.py is new and covers the shared reader directly, including
test_a_fenced_answer_after_a_thought_is_the_live_failure, which encodes this exact
production response shape. The four _strip_fences tests moved out of
test_main_agent.py into it.

Note

This is the third defect in a row that only a live run could find (#176 was two). All
three were invisible to a green 637-test suite because every test stubs the LLM and the
broker. Worth remembering when judging how much a passing suite proves here.

The feedback agent ran for the first time on 2026-08-11 and failed all
three retries with "Expecting value: line 1 column 1 (char 0)". The model
had returned a complete, correct evaluation; Alphoryn discarded it and
filed the position EVALUATION_FAILED - the exact outcome FR-016a exists to
prevent.

Captured from the live run, the response was two parts:

  [0] thought=True   4077-char reasoning summary
  [1] thought=None   ```json\n{ "outcome_judgment": "CORRECT" ... }

feedback_agent skipped the thought correctly, then handed the fenced
string straight to json.loads. main_agent has stripped fences since it was
written, and also skips parts whose text is empty; feedback_agent did
neither. The same logic had been written twice and the copies drifted.

Rather than paste the two missing guards into the second copy, both agents
now call alphoryn/agents/responses.py:extract_response_json, which skips
thoughts, skips empty parts and strips fences in one place. Two copies of
this is what caused the bug; one copy cannot drift.

Verified by replaying the exact bytes from the failed run: the old path
reproduces the production error, the new path parses and yields
outcome_judgment='CORRECT'.
@kilo-code-bot

kilo-code-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • alphoryn/agents/feedback_agent.py
  • alphoryn/agents/main_agent.py
  • alphoryn/agents/responses.py
  • tests/unit/test_main_agent.py
  • tests/unit/test_responses.py

Reviewed by step-3.7-flash · Input: 82.7K · Output: 7.5K · Cached: 84.7K

@mohammadp1001
mohammadp1001 merged commit fa23dea into main Aug 11, 2026
3 checks passed
@mohammadp1001
mohammadp1001 deleted the fix/feedback-agent-strips-code-fences branch August 11, 2026 17:04
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