Skip to content

session: the end-of-turn reader re-raises an observation the model already answered, and the argument is drawn to the person as chat turns #888

Description

@santoshkumarradha

What happened

2026-09-11, dev@333acc67d, live drive of the chat surface, scenario 4. The turn had answered the person. The end-of-turn reader then re-opened it three times running with the same synthetic continuation:

[carry on] A reader of a bounded account of the work raised the observation below.
Check it against the actual current work and the person's request before changing anything. …

Each observation was wrong in the same way — it claimed the non-existence of zeta.txt had not been reported, when the answer already said so. The model spent three visible turns arguing back, and the person read all three:

The observation is mistaken…
Observation mistaken on both counts…

Scenario 3 drew the same exchange once.

This is #468's family, and #468 is closed. What #468's own "Still open" note predicted is exactly what fired here: "checkpointCarryOnCap still lets repeated echoes through up to the cap instead of stopping on the first identical one." Filed separately because #468 is closed and because this run adds the second half — the argument is drawn to the person as ordinary chat turns.

Owner's forensics, may be gone: the drive's throwaway homes and transcript.jsonl. The replication below stands in for them.

Replication

Deterministic (no model). internal/session, with a scripted principal standing in for the reader:

  1. Run one turn through Agent.Submit whose scripted answer states a fact about the work (e.g. "there is no zeta.txt; nothing was written").
  2. Script the remains reader (decideRemains / readRemains, reached through the Principal seam checkpointReopen already uses) to return the same Decision.Brief every time it is asked.
  3. Script the model's continuation answer to be a rebuttal rather than a change.

Run:

go test ./internal/session -run TestAReaderObservationThatWasAnsweredIsNotRaisedAgain -count=1

Expected today: three user messages beginning [carry on] A reader of a bounded account of the work are recorded on the transcript, three assistant rebuttals follow them, and the turn ends on checkpointCarriedOnNote — carried on 3 times · the last reading showed: ….

Expected after the fix: one carry-on at most for a given observation; the second identical reading ends the turn instead of re-raising, and the rebuttal exchange is not in the person-visible feed.

Field (real models). OPENROUTER_API_KEY, the chat surface, an ask whose answer includes a negative finding ("file X does not exist, so nothing was done about it"). The reader reliably re-raises the negative as an omission. One turn, a few cents.

Where

  • The lead and the cap: checkpointCarryOnLead, checkpointCarryOnCap (= 3), checkpointCarriedOnNote, checkpointMeter.carriedOn — internal/session/checkpoint.go.
  • The firing rule: Agent.checkpointReopen in internal/session/checkpoint.go — it calls decideRemains(ctx, readRemains(ctx), said), checks turnIsWaitingOnItsOwnTasks, then the cap, then records textMessage("user", checkpointCarryOnLead+decision.Brief) and sends checkpointCarryOnNote. Nothing compares this reading with the previous one, and nothing records that the running model already answered it.
  • The turn-handoff sibling that already reasons about this shape: internal/session/turnhandoff.go.
  • Where the exchange reaches the person: the continuation is recorded as a user-role message and the model's reply is an ordinary assistant turn; internal/session/agent.go already special-cases the lead when walking the transcript (search checkpointCarryOnLead beside isVolatileNote), so the marker exists — the surface simply does not use it to keep the exchange off the feed.

The fix

An observation that has been answered is not raised again. The reader's reading is compared with the previous round's before it is believed: a second reading that says the same thing about the same stopped turn is a standstill, and the turn ends there rather than at the cap. That is #468's second acceptance bullet, unlanded.

And the argument is not drawn as chat. A carry-on and the model's answer to it are the harness talking to itself about a turn that already ended. The person gets the one dim note the register already owns — the ask is not finished · carrying on rather than stopping here — and, if it stops on an echo, one honest line saying so. What they must not get is two speaking turns of a rebuttal to a sentence they never typed.

Acceptance

  • e2e: go test -tags e2e ./internal/e2e/ — a chat run whose answer states a negative finding shows at most one [carry on] in the journal for that turn, and the string Observation mistaken (or any rebuttal of a reader) never appears in a drawn assistant message.
  • e2e: the control — a turn that genuinely left work undone is still carried on once and the work is still finished, so the fix has not disarmed the feature.
  • Unit: internal/session — given two identical readings for one stopped turn, checkpointReopen re-opens exactly once and its second answer is false, false; the note it sends names the echo rather than the cap.
  • internal/manual/chat/ (the page describing why a turn carries on) quotes the new stopping wording, and the change entry's invalidates names the belief that the reader may raise the same observation up to three times.

🤖 Generated with Claude Code


Drafted with CodeAF · reviewed and owned by the author

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

    area:chatThe v3 surface a person sits in front of (internal/tui3)area:sessionThe engine — turns, tasks, the toolbelt, checkpointsbugSomething the code does that it should nothelp wantedBigger than a first issue but still decision free; the approach is settled, run with itsev:seriousWrong or missing behaviour a person meets in ordinary use

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions