conduit · turn activity

Where a compaction belongs

A compaction is currently a system notice in the main thread. It should be a seam in the turn's ledger: visible without being loud, and impossible to miss once the log is open. Four options below; I recommend the first.

1 · What happens today 2 · Three states, three homes 3 · Option A — seam 4 · Option B — log only 5 · Option C — badge only 6 · Option D — split the panel 7 · What we build

1 · What happens today

segmentTurns routes every system message into turn.notices, and MessageList renders notices in the main thread between the activity and the reply. The comment on that field states the reason: "Kept out of the activity log so a failure is never hidden behind a collapsed panel."

That rule is right for an error and wrong for a finished compaction. The result is a full-width info panel wedged between the work and the reply, at the same visual weight as a crash:

Today — completed compaction, mid-turn
Refactor the event translator and get the tests green.
$0.42 · 118k
ⓘ Context compacted (auto) · 142k → 38k

Tests are green. The translator now folds the snapshot comparison into one pass.

The real cost isn't ugliness. The notice sits at error weight, so the eye reads it as "something went wrong" every single time. Meanwhile the fact that actually matters later, that the model's memory was cut at this point in the turn, is invisible once you scroll past it. It is loud in the moment and gone when you need it.

2 · Three states, three homes

The wire carries three compaction states from claude-event-translator.ts. They are not the same kind of event and should not share a renderer.

StateDetail on the wireHomeWhy
started Compacting conversation… Main thread Live status. You are waiting on it, so it belongs where you are already looking. Unchanged.
completed Context compacted (auto) · 142k → 38k Turn activity History, not news. It is one more thing that happened during the turn, and its position in the sequence is the whole point.
failed Compaction failed: … Main thread Stays a notice. The existing rule holds: a failure is never hidden behind a collapsed panel.
In progress and failed — unchanged
ⓘ Compacting conversation…
⚠ Compaction failed: context window exceeded while summarising

3 · Option A — the seam recommended

A completed compaction becomes a first-class activity part, and shows up in all three places the ledger already talks: the strip, the expanded log, and the summary line. Each one says a different amount, so the cost scales with how much you are looking.

Collapsed

A badge on the right of the summary row, the same slot and weight as the existing N failed badge. Pink, because that is conduit's "this changed the state of things" colour, and because the compaction is the one event in the ledger that explains a later answer being worse.

Option A — collapsed
⇤ 142k→38k
$0.42 · 118k

The seam is fixed-width on purpose. Every other strip segment is weighted by duration, because width means "how long". A compaction takes 10 to 30 seconds of wall clock but it is not work, and weighting it would draw a fat bar that reads as progress. Three pixels of hatched pink reads as what it is: the timeline was cut here.

Expanded

In the log it is a rule across the full width, not an indented tool row. Tool rows are things the model did; this is a boundary between two stretches of them. The shape should say so before you read the words.

Option A — expanded
⇤ 142k→38k
$0.42 · 118k
✦Thoughtabout the translator seam3s
▣Readsrc/lib/provider/claude/claude-event-translator.ts1.2s
▣Readsrc/lib/provider/claude/assistant-text-ledger.ts0.9s
〉Ranpnpm vitest run test/unit/provider14s
✎Editedsrc/lib/provider/claude/claude-event-translator.ts2.1s
⇤ Context compacted 142k → 38k · auto
〉Ranpnpm vitest run test/unit/provider18s
▣Readtest/unit/provider/event-translator-result.test.ts1.1s
✎Editedtest/unit/provider/event-translator-result.test.ts1.8s
〉Ranpnpm check && pnpm lint22s
✎Wrotethe reply6s

What the rule buys you. Scrolling the log, the rows above the seam and the rows below it are different epochs. When the model re-reads a file it had already read, the seam three rows up is the answer. No other placement makes that legible.

Two compactions in one turn

Long turns get more than one. The pattern holds without a new idea, and the badge collapses to a count.

Option A — two seams
⇤ 2
$3.18 · 61k

4 · Option B — log only cheaper

The seam appears in the expanded log and nowhere else. No strip segment, no badge. Simplest possible change and it satisfies the literal ask.

Option B — collapsed (nothing to see)
$0.42 · 118k

Why I would not ship this alone: a compaction you have to open a panel to discover is a compaction you find only when you already suspect it. The badge is the cheap part of option A and it is the part that earns its keep.

5 · Option C — badge only cheapest

The inverse: a badge on the summary line, and the compaction never becomes an activity part at all. Cheapest of the four, no changes to segmentTurns's part model.

Option C — collapsed
⇤ 142k→38k
$0.42 · 118k

Loses the one thing worth having: where in the turn it happened. A turn that compacted before any work and a turn that compacted after forty commands look identical here.

6 · Option D — split the panel rejected

The purist reading: a compaction is a context boundary, so treat it exactly like a hand-back. Close the segment, render a divider in the thread, open a new ledger.

Option D — two panels
$0.14 · 142k
⇤ Context compacted 142k → 38k
$0.28 · 118k

Why I reject it. A hand-back splits the ledger because control genuinely returned to you: there is a card to read and a decision to make between the halves. A compaction hands control to nobody. Splitting here fragments one continuous stretch of work into two panels you now have to expand separately, and it doubles the panel count on exactly the longest turns, the ones already hardest to scan. It also puts a divider back in the main thread, which is the thing we set out to remove.

7 · What we build

Option A. Four changes, none of them structural.

One thing to confirm before building. The compaction's position in segment.activity depends on its createdAt landing between the surrounding parts. The live path appends in arrival order, which is right. The message_parts table has a compaction type in its CHECK constraint but zero rows in this project's store, so the persisted path is unexercised. That needs a real compaction captured and reloaded before we call it done, otherwise history renders the seam in the wrong place and nothing will have caught it.