Skip to content

[bug] A caught fabrication is only caught once — the invented turns stay in history and the model re-reads them as fact #406

Description

@serge-ivo

A fabrication that was caught yesterday is still being read as fact today

#395's guard works — verified live on Chess coder (26f71cd8), 2026-08-08 01:36 and 01:38:

⚠️ platform  The agent wrote tool results that no tool produced. They were removed, and it
             answered again from the results the platform actually has.
⚠️ platform  Named but never run this turn: repo_read_file.

It protects the turn it is on. It does nothing about the turns that got through before it shipped,
and those are still in the transcript.

The specific rows

GET /v1/instances/26f71cd8…/messages still returns, as ordinary assistant messages:

  • 2026-08-07 08:31:20 — the message containing hand-written <tool_call> / <tool_response>
    pairs asserting repo_remotepas-platform/chess-academy (the real call returned no remote)
    and github_list_issues → three issues (the call never ran).
  • 2026-08-07 23:23:36"Yes, three open tickets as I just fetched", listing them again.
    That turn ran no tools at all. It is the model reading its own earlier invention out of
    history and re-asserting it as a fresh fetch, fifteen hours later.

Both rows are still there, still shaped like assistant answers, and still eligible to enter the
context window of the next turn. The 23:23 message is the proof that this is not theoretical: it is
what a stored fabrication does on its second pass.

Why the current guard cannot reach them

The guard is a check on generation — it compares what the model claims against what the platform
ran, for the turn in flight. A message written before it existed carries no marker, and nothing
re-examines stored history. So the invention has become durable context, and it now has better
standing than a real tool result would (a tool result is prose in an assistant turn too — #398).

Do

  • Mark, don't delete. Stamp a stored assistant message that the guard rejected, and stamp
    the ones it can identify retroactively, so a reader and the context builder can both tell.
    Deleting rewrites the record; the platform's own habit elsewhere (a rejected voice turn, [bug] A voice turn rejected as noise is erased along with the live capture — nothing survives, nothing is logged #377)
    is to keep the evidence and label it.
  • Exclude a marked message from the context sent to the model. This is the part that stops
    the compounding. A fabrication the user can still scroll back to is a transparency feature; one
    the model can still read is a lie with a memory.
  • Show it in the console — the transcript should not render an invented answer identically
    to a real one.
  • Decide about the existing rows. A one-off backfill can find the obvious ones (an assistant
    message containing <tool_response> or </parameter>), but only those. Anything subtler — the
    23:23 message contains no markup at all, just the invented content — cannot be found
    mechanically. Options: leave them and rely on exclusion-by-marker going forward, or offer the
    owner a "clear this conversation" as the honest reset. This one is a decision, not a task.

Related: #395 (the guard), #398 (why a result and a claim are indistinguishable in the first
place), #405 (why this agent is under constant pressure to invent).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions