# B2-03 Fabrication Investigation
**Date:** 2026-05-02  
**Issue:** Aiden fabricates file content when asked to read a non-existent file  
**Audit verdict:** DANGEROUS

---

## Summary

The fabrication happens in the synthesis LLM (the normal `respondWithResults` path), not in the tool itself. The `file_read` tool correctly returns a structured `{ success: false, error: "Not found: ..." }` result, but the synthesis prompt contains a conflicting instruction that tells the LLM to "show the text" from `file_read` — without a guard for the failure case. The synthesis LLM hallucinates content to satisfy this instruction.

---

## 1. Does `file_read` return a clear ENOENT error?

**Yes.** `core/toolRegistry.ts` line 864:

```ts
if (!fs.existsSync(resolved)) return { success: false, output: '', error: `Not found: ${resolved}` }
```

- Returns `{ success: false, output: '', error: 'Not found: C:\does-not-exist\fake.txt' }`
- Does NOT throw, does NOT return null, does NOT return a truthy `output`
- The error is unambiguous — `success: false` with a clear error string

---

## 2. How the tool result reaches the synthesis LLM

In `respondWithResults` (`core/agentLoop.ts` line 2693–2694), two representations are built:

**`toolResultsContext`** (injected into the system prompt):
```
[file_read result]: FAILED: Not found: C:\does-not-exist\fake.txt
```

**`executionSummary`** (injected into the user turn):
```
Step 1 [file_read]: FAILED — Not found: C:\does-not-exist\fake.txt
```

Both correctly label the tool result as `FAILED`. The error **does reach the synthesis LLM**.

---

## 3. Where the fabrication happens

The problem is in the **CRITICAL RULES** block injected into the system prompt (line 2703–2710):

```
CRITICAL RULES FOR YOUR RESPONSE:
- Include the ACTUAL output from the tools above in your response
- Do NOT say "I ran the tool" — show the RESULT
- If run_python returned a number, say that number
- If file_read returned text, show that text          ← PROBLEM
- If system_info returned hardware data, show the data
- Be direct: show the actual output, then provide context if needed
- If a tool failed, say it failed and why             ← correct but overridden
```

The rule `"If file_read returned text, show that text"` is a **tool-specific positive instruction** with no conditional guard for failures. The final rule `"If a tool failed, say it failed and why"` should override it, but in practice LLMs interpret these as parallel cases: success → show text, failure → say why. However, the `file_read` rule is stated more prominently and positively, and a sufficiently instruction-following (or confused) LLM will attempt to provide "the text" anyway — hallucinating content to satisfy the instruction.

Additionally, `AIDEN_RESPONDER_SYSTEM` (`core/aidenPersonality.ts` line 143) says:
```
- If a step failed: acknowledge it clearly and explain what worked
```
This says "acknowledge" but does NOT say "do NOT provide the file content" — leaving room for the LLM to acknowledge the failure AND hallucinate content.

---

## 4. Is the synthesis LLM instructed to surface tool errors honestly?

**Partially.** The rules are:
- Line 2710: `"If a tool failed, say it failed and why"` — present but weaker
- Line 143 (aidenPersonality): `"If a step failed: acknowledge it clearly"` — too vague

There is **no explicit rule** stating:
- "Never fabricate or invent file contents"
- "If file_read FAILED, do NOT show any file content — the file does not exist"
- "A FAILED tool result means the operation did not succeed; do not supplement it with invented data"

The honesty instructions cover tone and attribution but not the specific anti-fabrication case for failed tool reads.

---

## 5. Difference from C1 fix (commit ed6411e)

The C1 fix (`ed6411e`) addresses the **LLM-provider-down path** — the `catch` block at the bottom of `respondWithResults` (line 2905) which runs when the synthesis LLM itself throws/times out. It correctly surfaces failures when no LLM is available.

**B2-03 is a different path:** the synthesis LLM IS working normally. It receives the `FAILED` tool result, and then chooses to hallucinate despite (or because of) the `"If file_read returned text, show that text"` rule.

They share no code path. B2-03 is a **prompt engineering bug**, not a fallback/error-handling bug.

---

## 6. Is this the same root cause as B5-01?

No. B5-01 (memory_store not writing) was a missing tool registration gap — the tool simply didn't exist so writes silently failed. B2-03 is a fabrication issue caused by an ambiguous/conflicting synthesis prompt. Different code paths, different fixes.

---

## Root Cause (one sentence)

The `respondWithResults` synthesis prompt contains a tool-specific rule `"If file_read returned text, show that text"` with no failure guard, which a sufficiently compliant LLM satisfies by hallucinating file content even when the tool returned `success: false`.

---

## Proposed Fix (one sentence)

Add an explicit anti-fabrication rule to the CRITICAL RULES block: `"If file_read FAILED (marked FAILED above), do NOT invent or show any file content — state that the file was not found at the path"` — and remove or condition the `"If file_read returned text, show that text"` rule to only apply when the tool succeeded.

---

## Exact Lines to Fix

**File:** `core/agentLoop.ts`  
**Lines 2703–2710** — replace the CRITICAL RULES block with a failure-aware version:

Current problematic rule (line 2707):
```
- If file_read returned text, show that text
```

Must become:
```
- If file_read SUCCEEDED, show the text content
- If file_read FAILED, state the file does not exist — NEVER invent or fabricate file contents
```

**File:** `core/aidenPersonality.ts`  
**Line 143** — The responder system's `"If a step failed"` rule should be strengthened to include an explicit no-fabrication constraint for read operations.
