Bug Description
The ReACT section generation loop in report_agent.py is vulnerable to a hallucination pattern where the LLM fabricates <tool_result> blocks in its own response, effectively "answering itself" with invented data before the real tool execution occurs.
This causes the report to contain completely fabricated usernames, quotes, engagement statistics, and analysis — all presented as if they came from the Zep knowledge graph.
Root Cause
In the ReACT loop (_generate_section_react), when the LLM outputs a tool call, the full LLM response (including any self-fabricated <tool_result> blocks) is appended to the message history before the real tool result is injected:
# Line ~1456 in report_agent.py
messages.append({"role": "assistant", "content": response}) # ← includes fake <tool_result>
messages.append({
"role": "user",
"content": REACT_OBSERVATION_TEMPLATE.format(
tool_name=call["name"],
result=result, # ← real tool result
...
),
})
What happens:
- LLM outputs:
<tool_call>{"name": "insight_forge", ...}</tool_call> followed by <tool_result>{"name": "insight_forge", "result": "...fabricated data..."}</tool_result>
- The parser correctly extracts the
<tool_call> and executes the real tool
- But the entire response (including the fake
<tool_result>) gets appended to message history
- The real tool result is added as a separate user message
- Now the LLM sees both the fake and real results in its context
- In subsequent iterations, the LLM treats the fabricated data as authoritative and cites it in the final report
Evidence
In a test report, Section 1 cited these entities with detailed quotes and engagement numbers:
@VibeCodeKing (312 likes, 87 retweets)
@SideHustleSara ("Mom of 3 | Wannabe app builder")
u/NomadLaunchpad, u/MobileFirstMike, u/ZeroToAppStore
None of these entities existed in the scraped data or Zep knowledge graph. They were all fabricated by the LLM inside <tool_result> blocks in its first response, then treated as real data throughout the rest of the section generation.
Proposed Fix
1. Strip fake <tool_result> blocks before appending to message history:
@staticmethod
def _strip_fake_tool_results(response: str) -> str:
"""Strip any <tool_result> blocks the LLM fabricated in its response."""
import re
cleaned = re.sub(
r'<tool_result>.*?</tool_result>',
'',
response,
flags=re.DOTALL,
)
cleaned = re.sub(r'\n{3,}', '\n\n', cleaned)
return cleaned.strip()
Then in the ReACT loop:
# Replace:
messages.append({"role": "assistant", "content": response})
# With:
cleaned_response = self._strip_fake_tool_results(response)
messages.append({"role": "assistant", "content": cleaned_response})
2. Add anti-hallucination rules to SECTION_SYSTEM_PROMPT_TEMPLATE:
5. [CRITICAL: Do NOT fabricate data]
- NEVER invent usernames, quotes, statistics, or engagement numbers
- NEVER include <tool_result> blocks in your response — only the system provides tool results
- Only cite entities, quotes, and numbers that appear in actual tool results
- If no relevant data exists for a claim, say so rather than making it up
Impact
This bug affects all report generation. Reports may contain plausible-sounding but completely fabricated entities, quotes, and statistics that cannot be traced back to any real data in the knowledge graph. This is especially problematic because the fabricated data is indistinguishable from real data without cross-referencing the entity pool.
Environment
- LLM: Claude claude-opus-4-6 (via Anthropic API)
- Zep Cloud knowledge graph
- MiroFish latest (as of April 2026)
Bug Description
The ReACT section generation loop in
report_agent.pyis vulnerable to a hallucination pattern where the LLM fabricates<tool_result>blocks in its own response, effectively "answering itself" with invented data before the real tool execution occurs.This causes the report to contain completely fabricated usernames, quotes, engagement statistics, and analysis — all presented as if they came from the Zep knowledge graph.
Root Cause
In the ReACT loop (
_generate_section_react), when the LLM outputs a tool call, the full LLM response (including any self-fabricated<tool_result>blocks) is appended to the message history before the real tool result is injected:What happens:
<tool_call>{"name": "insight_forge", ...}</tool_call>followed by<tool_result>{"name": "insight_forge", "result": "...fabricated data..."}</tool_result><tool_call>and executes the real tool<tool_result>) gets appended to message historyEvidence
In a test report, Section 1 cited these entities with detailed quotes and engagement numbers:
@VibeCodeKing(312 likes, 87 retweets)@SideHustleSara("Mom of 3 | Wannabe app builder")u/NomadLaunchpad,u/MobileFirstMike,u/ZeroToAppStoreNone of these entities existed in the scraped data or Zep knowledge graph. They were all fabricated by the LLM inside
<tool_result>blocks in its first response, then treated as real data throughout the rest of the section generation.Proposed Fix
1. Strip fake
<tool_result>blocks before appending to message history:Then in the ReACT loop:
2. Add anti-hallucination rules to
SECTION_SYSTEM_PROMPT_TEMPLATE:Impact
This bug affects all report generation. Reports may contain plausible-sounding but completely fabricated entities, quotes, and statistics that cannot be traced back to any real data in the knowledge graph. This is especially problematic because the fabricated data is indistinguishable from real data without cross-referencing the entity pool.
Environment