Skip to content

⚙️ SYSTEM shows 0 closed after an ISA closes every claim, and DriftReminder says "no closer" on every long reply #2199

Description

@corobis

Both bugs make a status line wrong while the underlying state is right. I searched open and closed issues for renderSystemLine, system-surfaces, LastResponseCache and DriftReminder and found no report of either.

Part A: renderSystemLine() keeps the first detail per label, so the ISA delta is stale

hooks/lib/system-surfaces.ts renderSystemLine() deduplicates entries by label with:

if (!prior || (!prior.detail && e.detail)) byLabel.set(e.label, e);

The comment says it keeps "the richest detail", but the test keeps the first entry that has any detail. Entries arrive in write order, and SystemChangeSurface.hook.ts gives every ISA write a detail, either N closed or first→closed. So when a turn writes the same ISA more than once, the first write wins. An ISA scaffolded with 0 of 6 closed and finished at 6 of 6 in the same turn renders as 0 closed.

Repro: in one turn, write an ISA with no claims closed, then write it again with all claims closed. The ⚙️ SYSTEM line reports 0 closed.

Fix that works here: let the latest detail win.

if (!prior || e.detail) byLabel.set(e.label, e);

An entry without a detail still cannot displace one that has it.

Caveat: covered by a unit test on renderSystemLine() here. I have not yet seen the corrected line render live on a multi-write ISA turn.

Part B: LastResponseCache keeps only the first 2000 characters, so the closer is cut off

hooks/LastResponseCache.hook.ts writes lastResponse.slice(0, 2000) to MEMORY/STATE/last-response.txt. hooks/DriftReminder.hook.ts reads that file and sets closer: /🗣️/.test(text). The closer is always the last line of a reply, so any reply over 2000 characters loses it in the cache, and the next turn is told the last response had "no closer" when it had one. Long replies are the ones most likely to carry real work, so the false flag lands where it costs the most trust.

Repro: send a reply longer than 2000 characters that ends with the 🗣️ closer. The next turn's format reminder reports "no closer".

Fix that works here: raise the cap, and past it keep the head and the tail so both the banner and the closer survive.

const CAP = 20000;
const cached = lastResponse.length <= CAP
  ? lastResponse
  : `${lastResponse.slice(0, CAP / 2)}\n[…]\n${lastResponse.slice(-CAP / 2)}`;
writeFileSync(cachePath, cached, 'utf-8');

Caveat: covered by two unit tests here (a reply over 2000 characters keeps its closer; a reply over the cap keeps banner and closer). I have not yet confirmed it live on a reply over 2000 characters. The only other reader is hooks/SatisfactionCapture.hook.ts. It slices its own 500-character preview, and it passes the full cached text as context when it writes a low-rating learning, so those files can grow with the larger cap. Doctor.ts only names the file as a cache.

Environment: Linux (WSL2), Bun, Claude Code 2.1.278, LifeOS 7.40.4.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions