Skip to content

Unify turns and events into ConversationView.items - #144

Draft
benbaarber wants to merge 10 commits into
mainfrom
ben/items-ir
Draft

Unify turns and events into ConversationView.items#144
benbaarber wants to merge 10 commits into
mainfrom
ben/items-ir

Conversation

@benbaarber

@benbaarber benbaarber commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Extracted from #108 as its structural prerequisite — the items IR without any compaction content. #108 will restack on this branch.

  • ConversationView.turns/.events (two parallel vecs) become one ordered items: Vec<Item> (Turn | Event), preserving the source stream's exact interleaving; turns()/events() remain as iterators.
  • derive_path emits conversation.event steps for events (previously dropped — a Claude session lost its attachments and system entries on import/export), resolves duplicate step ids by content-aware renaming instead of erroring, and splices events onto the head's ancestry so they don't dangle as false dead ends. extract_conversation restores items, spliced parents, session files_changed, and synthetic-model actors.
  • Every provider's to_view builds the ordered item stream. Native compaction markers stay opaque here (a generic event, or pi's System turn) — typed boundaries are the follow-up's concern.
  • The claude projector emits events inline at their item position (real Claude interleaves attachments/system entries with turns; the trailing-pass writer regrouped them at end-of-file). wire_order_roundtrip pins the projected entry-type sequence to the captured fixture's, and the caveat entry keeps isMeta.
  • Property tests: unique derived step ids, derive → extract → derive stability, byte-identical replay dropping — over randomized interleavings with id collisions, dangling parents, and file mutations.

All 7 quality gates pass. Verified across every provider crate's full test suite.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

ben-emp pushed a commit that referenced this pull request Jul 23, 2026
…1.2.0)

Restacked onto ben/items-ir (#144) with the standalone fixes split to
#143; the pre-restack 43-commit history is preserved at
ben/compaction-pre-split.

The Compaction item + the conversation.compact step type (kind
v1.2.0): a context-compaction boundary recorded as its own step
between the turns it separates, with trigger, summary, pre_tokens, and
the anchor-based kept contract (kept_from + expand_kept; the wire
carries the expanded contiguous run). Compaction provenance is a
closed typed set — no catch-all extra; native detail richer than the
typed fields is deliberate loss.

Per-provider support, each verified against the real harness TUI by
resuming projected sessions (versions in docs/agents/tui-drift-check.md):

- claude: compact_boundary + synthetic summary → typed boundary and
  back, preservedMessages as the kept run, transcript-only + isMeta
  render flags, empty-turn thinking-block projection.
- codex: compacted marker → wholesale boundary (2026-07 payload:
  encrypted summary + prefix-keep documented as deliberate loss);
  projector emits the event_msg/context_compacted marker the TUI
  renders, with native turn_context placement.
- opencode: per-boundary summary pairing, compaction-host parent
  redirects, tailStartID → kept anchor.
- pi: compaction entries with parent-chain kept expansion;
  model_change/thinking_level_change/label as typed events with the
  chain's model context threaded into projected assistant messages.
- copilot: the observed 1.0.68 session.compaction_start/complete pair
  mapped both directions (wholesale + summary).
- gemini/cursor: documented no-boundary.

Verification: executable round-trip oracle
(testing::{check_view_invariants, assert_fixpoint}), compaction
proptests, real captured compacted sessions for claude/codex/pi/
copilot/opencode as fixtures with looped stability tests, the
cross-harness compaction matrix (kept-anchor capability flags), the
env-gated real-session matrix mode, and import-time event-type
surfacing.
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

🔍 Preview deployed: https://5482a221.toolpath.pages.dev

Extracted from #108 as its structural prerequisite — the items IR
without any compaction content.

- ConversationView.turns/.events (two parallel vecs) become one ordered
  items: Vec<Item> (Turn | Event), preserving the source stream's exact
  interleaving. turns()/events() stay as iterators.
- derive_path emits conversation.event steps for events (previously
  dropped — a Claude session lost its attachments and system entries on
  import/export), resolves duplicate step ids by content-aware renaming
  instead of erroring, and splices events onto the head's ancestry so
  they don't dangle as false dead ends. extract_conversation restores
  items, spliced parents, session files_changed, and synthetic-model
  actors.
- Every provider's to_view now builds the ordered item stream. Native
  compaction markers stay opaque (a generic event, or pi's System
  turn) — typed boundaries are the follow-up's concern.
- The claude projector emits events inline at their item position;
  real Claude interleaves attachments and system entries with turns,
  and the trailing-pass writer regrouped them at the end of the file.
  wire_order_roundtrip pins the projected entry-type sequence to the
  captured fixture's, and the caveat entry keeps isMeta.
- Property tests: unique derived step ids, derive → extract → derive
  stability, and byte-identical replay dropping, over randomized
  turn/event interleavings with id collisions, dangling parents, and
  file mutations.
path-cli and toolpath-cli 0.16.0 collided with the 0.16.0 already
released from main on 2026-07-16 — the identical version edit merged
cleanly, so the collision was invisible; both move to 0.17.0.

toolpath, toolpath-git, toolpath-github, toolpath-dot, and toolpath-md
had no source changes on this branch; their bumps are reverted so the
release does not republish five unchanged crates.

site/_data/crates.json brought in line with the surviving bumps.
to_view assembled items as all surviving turns followed by all events,
so the derived path misordered every conversation.event step — a
compacted marker landed after the post-compaction turns, losing the
stream position that gives the boundary its meaning.

Events now carry a turn watermark recorded at push time and assembly
merges the two streams on it, reproducing the rollout's exact
interleaving. The compaction_roundtrip suite pins the full item shape
through derive -> extract, replacing the now-false 'the IR drops it on
the floor' known-limitation note.

Also drops the dead CompactedItem type (defined and re-exported, never
used - it belongs to the compaction-provenance follow-up) and the stale
projector comment describing compaction handling that does not exist on
this branch.
A byte-identical re-emission of an already-derived turn (the Claude
chain-merge replay shape) was compared against the stored step's
resolved bytes. When the original had been spliced onto an intervening
event, or renamed after an id collision, the resolved forms no longer
matched, so the replay was renamed-kept, became the head, and orphaned
the original and its event as false dead ends.

Resolved forms are not comparable across the stream - splicing rewires
parents and parent mappings mutate as colliding steps rename - so
replays are now recognized before resolution, by comparing source bytes
per source id (seen_turn_sources / seen_event_sources). push_step keeps
its rename machinery for genuine same-id-different-content collisions.

Event steps now also stamp source_parent (null = positional chaining,
otherwise the resolved parent), and extract restores it. Without the
stamp, extract handed back the resolved chain, erasing the source
distinction between an event that named a parent and one chained by
position - two such events with equal data collapsed into one on the
next derive, breaking gen-2 stability. This also fixes the review
finding that an event natively parented across an adjacent event lost
that linkage on project-out.

Restores the two duplicate-id tests deleted from the merge base (the
renamed-parent-reference pin and the Claude reused-attachment-uuid
case), adds a regression test for the replay-after-splice shape, widens
the replay property to spliced originals, makes duplicate event ids and
turn/event id collisions reachable in the proptest generator, and drops
the stale proptest-regressions seeds carried over from the compaction
branch (they no longer match this generator's shape). Also removes the
always-equal prev_anchor_step threading (compaction scaffolding that
made the second splice disjunct unreachable).
… ref

The regenerated pi fixture was a degenerate capture - one assistant
turn, zero tool calls - so the pi real-fixture roundtrip and the
cross-harness matrix tool/usage invariants passed vacuously. The
previous capture (11 assistant turns, 10 tool calls, 10 tool results)
is restored until a fresh elicitation produces a session with real
tool activity.

ConversationView.items doc no longer mentions compaction boundaries or
a compactions() accessor - both belong to the follow-up PR.
The seen_uuids replay strip was untested drop logic whose comment cited
a function that does not exist on this branch. The comment now describes
the observed re-emission pattern honestly (the format docs do not cover
it), and two unit tests pin the behavior: byte-identical replays are
stripped with the tool result assembled once, and compact boundaries
are exempt so repeated markers survive to the item stream.

compaction_roundtrip's header claimed the boundary is dropped on the
floor - it survives as an inline event now - and a new test pins its
stream position through derive -> extract. wire_order_roundtrip's
module comment now states exactly what is pinned (the entry-type
sequence of the direct pipeline) and what is not (parentUuid values,
11/45 of which legitimately diverge for re-synthesized tool-result
carriers).
…attachment-only user turns

opencode: empty-text user messages are now suppressed only when they
hosted a compaction part - an attachment-only user message emits a turn
again, as before the items migration. The msg_redirects/resolve_parent
docs describe the actual implementation (the old comments described a
removed boundary-index design), the always-equal last_anchor_id
duplicate of last_turn_id is gone, and the projector's never-read
id_map scaffolding is removed. Restored compaction_becomes_event and
fixture_loads_with_compaction_part, extended them to pin the boundary
event's in-stream position and parent, added a derive -> extract
survival test, and covered monotonize_times and the redirect path with
unit tests.

pi: restored test_compaction_produces_system_turn (deleted with no
replacement) and added resolve_item_parent tests for the
nearest-ancestor walk, the virtual root, and the cycle guard.
… event positions

Cursor was the only provider discarding its native compaction marker:
capabilityType-22 bubbles were skipped outright, leaving the typed-
compaction follow-up nothing to anchor on and dropping the marker from
projected sessions. The bubble now becomes a summarization event at its
stream position, and the projector writes it back as a well-formed
marker bubble (shape verified against real state.vscdb rows), so the
marker survives a full cursor -> toolpath -> cursor round-trip. The
real-DB sanity test now checks event invariants, and
docs/agents/formats/cursor.md documents the marker so the code's doc
references resolve.

copilot: the watermark merge had no positional coverage; four unit
tests pin where events land relative to in-progress, dropped, leading,
and trailing turns.
…s, cargo fmt

The version bumps shipped without their required CHANGELOG.md and
site/_data/crates.json updates; both are now in place (crates.json in
the earlier renumber commit). CLAUDE.md per-crate test counts match the
suites as of this branch. Remaining files are cargo fmt output - the
branch previously failed the format gate with diffs in five crates.
…curacy

Round-2 adversarial review of the fix stack surfaced two defects in the
replay skip and a handful of accuracy residues.

derive_path: turn replays are now identified in a prepass and excluded
from every per-turn structure. The in-loop skip still consumed a
turn_groups slot and a synthesized-id slot, so a byte-identical
same-group replay at a group tail silently dropped the group's
token_usage stamp, and an id-less turn after a skipped replay shifted
its step-NNNN id - both violating the replay-is-a-no-op contract.
Unreachable through shipped providers (only claude sets group_id, and
it strips replayed uuids before derive), but real contract defects;
both now have regression tests.

opencode: the user-turn parent synthesis (the one round-1 test gap the
fix round missed) is pinned; the format doc's event types match the
code (part.compaction / part.retry); the projector comment no longer
claims compaction boundaries land at position (events are dropped on
projection); a dangling see-module-note reference is gone.

claude: the dedup comment is reframed as defensive - no 2.1.x session
on this machine shows the re-emitted block, so the code guards the
shape rather than documenting an observed one; the wire_order comment
attributes the 11 parentUuid divergences by projected outcome instead
of a source-parent breakdown that miscounted.

codex/convo: two comments naming nonexistent compaction machinery
reworded.
ben-emp pushed a commit that referenced this pull request Jul 27, 2026
…1.2.0)

Restacked onto ben/items-ir (#144) with the standalone fixes split to
ben/compaction-pre-split.

The Compaction item + the conversation.compact step type (kind
v1.2.0): a context-compaction boundary recorded as its own step
between the turns it separates, with trigger, summary, pre_tokens, and
the anchor-based kept contract (kept_from + expand_kept; the wire
carries the expanded contiguous run). Compaction provenance is a
closed typed set — no catch-all extra; native detail richer than the
typed fields is deliberate loss.

Per-provider support, each verified against the real harness TUI by
resuming projected sessions (versions in docs/agents/tui-drift-check.md):

- claude: compact_boundary + synthetic summary → typed boundary and
  back, preservedMessages as the kept run, transcript-only + isMeta
  render flags, empty-turn thinking-block projection.
- codex: compacted marker → wholesale boundary (2026-07 payload:
  encrypted summary + prefix-keep documented as deliberate loss);
  projector emits the event_msg/context_compacted marker the TUI
  renders, with native turn_context placement.
- opencode: per-boundary summary pairing, compaction-host parent
  redirects, tailStartID → kept anchor.
- pi: compaction entries with parent-chain kept expansion;
  model_change/thinking_level_change/label as typed events with the
  chain's model context threaded into projected assistant messages.
- copilot: the observed 1.0.68 session.compaction_start/complete pair
  mapped both directions (wholesale + summary).
- gemini/cursor: documented no-boundary.

Verification: executable round-trip oracle
(testing::{check_view_invariants, assert_fixpoint}), compaction
proptests, real captured compacted sessions for claude/codex/pi/
copilot/opencode as fixtures with looped stability tests, the
cross-harness compaction matrix (kept-anchor capability flags), the
env-gated real-session matrix mode, and import-time event-type
surfacing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant