Skip to content

feat(pi,opencode): preserve dropped session data via events + Turn.model; remove dead Turn.extra plumbing#125

Open
akesling wants to merge 5 commits into
mainfrom
chore/purge-stale-turn-extra
Open

feat(pi,opencode): preserve dropped session data via events + Turn.model; remove dead Turn.extra plumbing#125
akesling wants to merge 5 commits into
mainfrom
chore/purge-stale-turn-extra

Conversation

@akesling

@akesling akesling commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Started as a cleanup of dead Turn.extra plumbing; grew to actually recover the provider data that removal exposed as dropped, using only current format affordances (ConversationView.events, Turn.model) — no new IR fields, no reintroduced provider blob.

Part 1 — data now preserved (was silently dropped)

  • pi metadata entries → events. ModelChange / ThinkingLevelChange / Label were discarded at read time and view.events was always empty. They now ride ConversationView.events (typed event_type + payload in data) and the projector re-materializes them as real Pi entries with their original ids/parentIds. Round-trip test: a session with a ModelChange and a Label survives pi → view → Path → view → pi intact.
  • opencode user-turn model → Turn.model. User messages carry model.modelID on the wire but user turns hardcoded model: None; now mapped like assistant turns (bare modelID, empty→None) and written back on projection. Survives the view-level round-trip; documented that Path-level restoration isn't attempted (the kind's conversation.append has no model field and user steps are human:user, not agent:{model}).
  • opencode snapshot SHAs → events. step-start/step-finish snapshot SHAs were dropped on projection, so a re-imported session couldn't regenerate diffs. They now ride view.events (part.snapshot) and the projector restores them onto the right step parts. Round-trip test proves the SHAs survive. In the course of this I found the compaction_roundtrip.rs "event does not survive derive→extract" comment was stale — the shared events pathway does round-trip; corrected and pinned with a new test.

Part 2 — dead plumbing removed + docs corrected (original scope)

  • Removed the *_extras() helpers (codex/opencode/pi) that unconditionally returned None and everything reachable only through them (~450 lines in pi alone), and gemini's always-empty extra["gemini"] lookups. Every removal verified closed over its uses; apply_turn_metadata in claude was kept (it does real cwd/git-branch work — the audit mislabeled it) with only its doc corrected.
  • Doc comments and docs/agents/formats/codex.md corrected to state what's actually dropped. Still-dropped items keep honest "dropped" wording: codex encrypted_content, claude per-line version/userType, cursor checkpointId/requestId/richText, gemini per-thought timestamps and tool/total counters.

Known fidelity edges (documented in-code)

  • opencode snapshot events chain per-message to avoid intra-message fan-out; this does not make the whole Path linear (opencode user turns are parentless roots, so a multi-exchange session is already a forest).
  • A pi message whose source parentId pointed at a now-eventified metadata entry loses that one parent edge on the Path round-trip (derive resolves turn parents only against turns). Entries' own ids/parentIds are preserved; pi's reader tolerates it. Candidate future toolpath-convo fix.

Versions & verification

toolpath-pi 0.6.0 → 0.6.2, toolpath-opencode 0.5.0 → 0.5.2 (the .1 patches are claimed by sibling PRs #121/#122 in flight — noted in each CHANGELOG). toolpath-convo untouched. No path-cli/toolpath-cli bumps. Full cargo build/test/clippy -D warnings/doc --workspace green; TDD (red-first) on every added behavior, both view-level and full-chain (Graph JSON) integration tests.

Part of a six-PR consistency series from a cross-harness audit; CHANGELOG/version lines may conflict with sibling PRs — resolve by keeping both entries.

…rvation claims

Turn.extra was removed from the toolpath-convo IR in an earlier change, but
codex/opencode/pi kept dead *_extras() functions that unconditionally
returned None (plus the now-unreachable branches they fed), gemini's
projector looked up extra["gemini"] in a map that was always empty, and
doc comments across providers still claimed provider-namespaced extras
were preserved on round-trip. This is behavior-preserving cleanup: every
dead-code claim was verified before removal, the plumbing and the
simplifications it enables were removed, and the stale comments (plus the
codex.md/gemini.md/pi.md format docs and affected crate READMEs) now
describe what's actually dropped.
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🔍 Preview deployed: https://aade66db.toolpath.pages.dev

akesling added 4 commits July 8, 2026 16:15
…re-materialize them on projection

ModelChange / ThinkingLevelChange / Label entries were discarded by
session_to_view (and therefore lost on any Path round-trip). They now
land in ConversationView.events (model_change / thinking_level_change /
label, payload fields in event data), which the shared derive already
emits as conversation.event steps and extract_conversation restores.
PiProjector re-materializes those events as real Pi entries with their
original ids and parentIds, inserting each directly after its parent for
sane file order; foreign event types (e.g. Claude attachments) are still
skipped rather than emitted as garbage entries.

New pi -> view -> Path -> view -> pi integration tests prove the entries
survive with ids, parentIds, and payloads intact, and that the projected
JSONL re-parses through Pi's own reader.
…art.snapshot events

Two previously-dropped pieces of opencode data now ride existing IR
affordances:

- User messages' wire model.modelID lands on Turn.model (same
  bare-modelID convention as assistant turns; providerID has no IR home
  and still falls back to the projector default). The projector writes
  it back into the projected user message. Known limitation, asserted in
  the round-trip test: the user model survives the view-level chain but
  not the Path layer - the shared derive attributes model via the step
  actor (agent:{model}) and user steps carry human:user, so closing that
  would need a toolpath-convo change (out of scope here).
- Snapshot SHAs from step-start / step-finish / snapshot parts now also
  ride ConversationView.events as part.snapshot events (data: snapshot
  SHA + part kind). A message's snapshot events chain linearly (first
  parents to the message, subsequent to the previous snapshot event) so
  the derived Path keeps its all-steps-on-head-ancestry shape. The
  projector groups events back to their turn by walking parent links and
  restores the step-start SHA onto StepStart and the step-finish SHA
  onto StepFinish - snapshot SHAs survive a full Path round-trip, so a
  re-imported projection can regenerate diffs.

Also tightens compaction_roundtrip.rs: its module doc claimed derive
does not emit conversation.event steps (stale - it does, and a new test
asserts the part.compaction event survives derive -> extract); the
remaining documented gap is that the projector only consumes
part.snapshot events, not compaction parts.
Behavior-adding changes on this branch (pi metadata entries via events;
opencode user model + snapshot-SHA events). The .1 patch numbers are
claimed by sibling PRs #121/#122 currently in flight, so both crates
skip to .2. All five sites updated: crate Cargo.toml, root workspace
dependency, site/_data/crates.json, CHANGELOG top section, Cargo.lock.
No path-cli / toolpath-cli / toolpath-convo bumps.
@akesling akesling changed the title docs+cleanup: remove dead Turn.extra plumbing and correct stale preservation claims feat(pi,opencode): preserve dropped session data via events + Turn.model; remove dead Turn.extra plumbing Jul 8, 2026
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