Skip to content

feat(rendering): ownership-ledger rendering pipeline + session recording + soak fixes — integration branch#442

Merged
Juliusolsson05 merged 106 commits into
mainfrom
integration/rendering-pipeline
Jul 7, 2026
Merged

feat(rendering): ownership-ledger rendering pipeline + session recording + soak fixes — integration branch#442
Juliusolsson05 merged 106 commits into
mainfrom
integration/rendering-pipeline

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

DRAFT — review surface for the full integration/rendering-pipeline branch (~100 commits ahead of main). Merge is the "stop at main" gate.

What this branch is

The ground-up rendering engine rewrite, plus the tooling and fixes that grew out of soak-testing it. Architecture: raw input → observation collectors → ownership decisions (selected AND rejected, each with a recorded reason + evidence) → chronologically ordered row objects → dumb React rows. One invariant: every visible feed artifact has exactly one owner; debug output is a serialization of the same decisions the paint used. Plan: docs/rendering/rendering-rewrite-plan-2026-07.md.

Landed on this branch

Rendering pipeline (Stages 1–3 built, cutover flag).

  • Ledger core: D4 ordering law, D11 identity stability, ownership pass, ghost five-rule predicate, fold-policy.
  • Collectors (committed / semantic / local / ghost) + adapter (the runtime→pipeline seam) + view bridge (ledger rows → existing Feed components).
  • Behind AGENT_CODE_RENDER_PIPELINE=1 the feed paints from the new engine; AGENT_CODE_RENDER_SHADOW=1 runs the divergence recorder alongside. Legacy renderer still present as the default + safety net (deleted only in the final cutover PR per docs/rendering/legacy-deletion-manifest.md).

Validation — three layers, ~430 tests.

Session Recording (#467). Continuous, debug-gated capture of a session's rendering-input stream to per-recording folders, replayable through the real pipeline; command-driven Start/Stop + Attach Recording Note; two-tier redaction; golden + invariant replay corpus. On its first real use it caught the subagent-feed leak below.

Soak & correctness fixes (found by running with the flag on): collapsed-running tail gate (#465), Claude priority-queue reconstruction (#469), subagent lifecycle #341 (wait-agent inference, stale status, pruning), committed tool-ownership + duplicate cards (#449), dangling tool chips (#451), JSON/MCP tool rows, task-notification rows, MCP-spawn cards, opencode dispatch, and the subagent-into-main-feed leak both halves — committed plane isSidechain filter (#479) and live proxy-plane cc_is_subagent attribution (#480, via claude-code-headless#35). Plus the headless tailer scoped-unwatch fix (the dead-channel/queue root cause).

Remaining before this merges to main

  1. Live soak sign-off (owner) — running with the pipeline flag across providers; new bugs become recordings → fixtures.
  2. Final cutover slice: delete SemanticStreamingTurn + ghost rule 3 atomically, remove legacy suppression guards, deletion-manifest grep check.
  3. This PR un-drafts and merges — the gate that stays with the owner.

🤖 Generated with Claude Code

Juliusolsson05 and others added 30 commits July 6, 2026 22:17
…aring tests

First implementation slice of docs/rendering/rendering-rewrite-plan-
2026-07.md (Stage 1: spec-first build, zero app wiring — nothing
imports this yet).

model/types.ts — the ledger contract from plan §3: block-level
RenderCandidate (D2) with plural identity fields (no single id spans
providers; toolUseId reserved first-class for the subagent surface,
rule 14), the fixture-gated RenderReason enum (rule 15), the
ownership_decision debug schema (D5), and the UnknownBehavior
structured-finding contract with #115 redaction rules.

model/order.ts — the D4 ordering law: true chronological merge with
phase ranks, the trust-hierarchy timestamp comparison (null sorts
after timestamped, never as 'now'), and the five-way tiebreak. This is
the #239 buried-prompt fix at the model layer.

model/ownership.ts — committed-ownership suppression with the
suppress-vs-reorder discriminator (D3): whole-turn suppression is a
provider POLICY bit (claude true, codex/opencode false — an id-
presence heuristic would wrongly whole-turn-suppress opencode, whose
committed rows carry message ids; drift report item 12), exact/
normalized text keys (never prefix/fuzzy), itemId keys (#194), and
tool-use vs tool-result ownership kept separate (dump invariant 10).

model/ledger.ts — the composed pass with the D11 identity-stability
contract: unchanged input references return the previous ledger BY
REFERENCE (load-bearing for every Feed memo; the render-churn class).

__tests__/ledger.test.ts — ten tests, each naming the incident it
guards: #239 ordering, #170 resp_*/rollout text split, claude-vs-codex
whole-turn asymmetry (#165/#191), tool-use/result separation, #344
decisions-for-every-candidate, D11 reference equality. All green
(vitest unit project); tsc node+web 0 errors on main checkout.

Test files here are per the plan's explicit exemption from the no-new-
tests convention: src/renderer/src/rendering/ IS its tests (plan §4).

Next slices: committed projection + candidate collectors (from
runtime state), ghost predicate port, fixture corpus directories from
the real bundles (buried-prompt-239, opencode-interleave-87f0eeef,
ghost-matrix).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tage1

Rendering pipeline Stage 1: ownership ledger core + first ten tests
…manifest

observations/committed.ts — the anti-corruption boundary from raw
transcript entries to ledger candidates. Visibility decided here with
decision records for HIDDEN entries too (meta-entry, not-conversation,
synthetic-user-filtered) so bundles can answer 'hidden or never
ingested'. Includes the #338 fix as the centralized predicate: Claude
non-meta user rows carrying <command-name>/<local-command-stdout>/
<environment_context> scaffolding (no permissionMode, text starts '<')
are filtered with reason — claude-scoped so a codex/opencode user
message legitimately starting with '<' is untouched. Ingest-time
stable ids (never visible-index); producer-clock timestamps only;
exact + NFKC-normalized text keys on assistant rows.

docs/rendering/legacy-deletion-manifest.md — the strip-out contract:
every legacy rendering file enumerated with fate (DELETE/GUT/ABSORB/
KEEP), LOC, deletion point, and a grep-based completeness check the
Stage 3 cutover PR must pass. The old system is not patched during
the rewrite (the new pipeline imports nothing from it) and does not
survive it.

17/17 tests green (vitest unit); tsc node+web 0 errors gated on main
checkout with the full integration diff applied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lectors

Rendering slice 2: committed collector (#338 fix) + legacy deletion manifest
model/ghostPredicate.ts — the full predicate ported with every scar
intact: superseded / orphaned-TTL (the only guard in the
turn_completed→JSONL race window) / semantic-owned (rule 3 carries its
death sentence: deleted at cutover together with SemanticStreamingTurn,
plan D6) / strictly-newer-than-tail on producer clocks with null-not-
zero (rule 4) / sidecar shape backstop with the 200-char empirical
anchor and tool_use exemption (rule 5). The rules-4-AND-5 pairing is
documented in-code with the tail-sidecar counterexample that overturned
the single-rule design.

__tests__/ghostPredicate.test.ts — the canonical 10-case matrix plus
the failed-fix regressions (686b94e stale-orphan class), the boundary
cases, the documented accepted trade-off as an explicit test, and
evidence-on-every-rejection (#344).

29/29 pipeline tests green; tsc node+web 0 errors on full integration
diff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rendering slice 3: ghost five-rule predicate as ledger decisions
…odel-level guards

observations/semantic.ts — current + history turns into block candidates
(plan D2). Guards that lived as React null-returns become decisions:
compaction synthesis yields zero candidates regardless of how the flag
was detected (#345's model-level kill), empty thinking / empty
write_stdin rejected with reasons, history turn matching the current
turn id dropped (dump invariant 14). Text ownership keys only on
FINALIZED text so streaming blocks can't be suppressed mid-growth.
History orders by endedAt, current by startedAt (the D4 choice that
makes the ordering law work).

36/36 pipeline tests green; tsc node+web 0 errors on full integration
diff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…antic

Rendering slice 4: semantic collector with model-level guards
…st end-to-end fixture

observations/local.ts — optimistic-submit candidates (echo providers
only; submit wall-clock so the stand-in sorts exactly where its
committed twin will) and lifecycle candidates (work is a phase fact —
always present when streamPhase non-idle; empty only when no content
survived).

ownership.ts — optimistic reconciliation: committed USER rows own
their optimistic stand-ins by normalized text (marker+text, never tail
position). Bias toward the optimistic row SURVIVING when ownership is
unprovable: too-long is visible and diagnosable, too-early is the
silent #339 class. committed.ts now stamps normalizedTextKey on user
rows for exactly this (assistant-suppression sets remain assistant-
only).

fixtures.buriedPrompt239.test.ts — THE first end-to-end run:
raw entries + history turn + optimistic prompt + work → collectors →
ledger → asserted FINAL ORDER (the #172 comment-5 bar). Covers the
core #239 shape, the D3 reorder-not-suppress discriminator, and the
optimistic→committed handoff without re-burying (#172 comment 6).

39/39 green; tsc node+web 0 errors on full integration diff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…al-fixtures

Rendering slice 5: local collectors + optimistic handoff + buried-prompt-239 fixture
The ledger's honesty when committed truth never arrives (#159 rollout
fork / #290 Claude analog): uncommitted history stays visible in
chronological order indefinitely (reordered, never suppressed — it is
the only copy that will ever exist), the feed structurally cannot
shrink, conservative text keys hold end-to-end (no fuzzy ownership),
and the crash-recovery ghost variant renders. The legacy correctness
model assumed JSONL eventually arrives; the ledger does not inherit
that assumption.

42/42 green; tsc clean on full integration diff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d-channel

Rendering slice 6: dead-committed-channel-159 fixture
model/unknowns.ts — the plan §3 contract as code: unknowns are
structured findings (shape paths to depth 3, FNV-1a payload hash,
first-seen + count), deduped by (plane, type, hash) so floods read as
one counted finding. Redaction is structural (#115): payloads are
never stored, previews are caller-redacted AND hard-capped, and
auth-looking key names are stripped from shape paths as a second belt.
queued_for_implementation is how future provider behavior is
discovered from bundles instead of raw-event archaeology.

46/46 green; tsc node+web 0 errors via the clean two-step gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nowns

Rendering slice 7: unknown-behavior registry
Concurrent assistant messages on one SSE stream (the render-spasm
bundle): interleaved content renders exactly once in chronological
order — the committed assembled message wins its slot with the bridge
copy suppressed by exact text, the never-committed concurrent message
survives in full, and the drift-report hazard is pinned: opencode
committed rows carry message ids but the unit-level policy bit forbids
claude-style whole-turn suppression from eating the other message.

50/50 green; tsc node+web 0 errors via clean two-step gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ncode-fixture

Rendering slice 8: opencode-interleave-87f0eeef fixture
Records what landed (PRs #424-#433), the four end-to-end fixtures, and
the exact remaining items before Stage 2 shadow wiring — so any fresh
session resumes from the plan doc alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage 1 progress marker (slices 1-8, full suite green)
…idecar-tail e2e fixture

The five-rule ghost predicate existed standalone; the ledger never
consulted it. LedgerInput now carries an optional ghost plane (candidate +
predicate-input pairs, plus GhostPredicateContext for rules 3/4), decided
LAST so ghosts never influence committed/live ownership, with survivors
competing in ordering via the existing ghost-fallback SOURCE_RANK.

Identity-stability cache compares ghostContext FIELD-WISE (scalar tail +
set reference) so adapters can mint the context object fresh each tick
without busting the D11 contract.

e2e fixture pins the sidecar-tail trap end-to-end: rules 1-4 pass for both
a sidecar blurb and a genuinely stuck tool_use ghost; only the shape
backstop separates them. 53/53 pipeline tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…st-ledger

feat(rendering): slice 9 — ghost plane in the ledger pass + sidecar-tail e2e
…m) + ghost collector

createLedgerInputAdapter() is the single module that vouches real
SessionRuntime shapes satisfy the collectors' structural types: record-
keyed semantic blocks → blockOrder-ordered arrays, optimistic rows
partitioned back out of entries (committed-grade trust would let them
SUPPRESS the live stream), ghost _atp metadata → predicate facts.

Reference stability is enforced per PLANE on the runtime slice references
each plane reads, composing with the ledger's last-call cache into
'no real change ⇒ same RenderLedger object' end-to-end (D11). Merged
live+optimistic array is memoized on both source refs.

collectGhostCandidates gates opencode entirely (supersede-less ghosts
would double every turn) and keeps the five rules provider-neutral.

Type-only seam assertions make the tsc gate fail on runtime-type drift
instead of it surfacing as a shadow-diff mystery. 56/56 pipeline tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…apter

feat(rendering): slice 10 — runtime→collector adapter (the shadow seam)
…e-handoff e2e; Stage 1 complete

policy/foldPolicy.ts encodes the two proxy-literal yield hatches from
legacy foldEvent (isCompletedProxyTurnReadyToYield /
isEmptyNonProxyShellTurn) as per-provider policy data
(terminalYieldSources / shellClaimants) with policy-parameterized
predicates — no provider-source literal in any logic path. Parity tests
pin both 2026-05-16 bundle classes, the flicker defense, and the shared
ended-turn rule. Legacy fold untouched until Stage 3 (plan drift item).

fixtures.queueHandoff.test.ts pins the queue→optimistic→committed race:
queue is a composer lane (D1, absence asserted), the committed twin takes
exactly the optimistic row's position, and the overlap tick (both rows
present) suppresses the optimistic copy by normalized text.

Plan progress marker updated: Stage 1 COMPLETE, 67 pipeline tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ldpolicy-queue

feat(rendering): slice 11 — fold-policy yield-hatch absorption + queue-handoff e2e (Stage 1 complete)
…DER_SHADOW

shadowDiff.ts normalizes both renderers to the coarsest grain they share
(committed/optimistic/ghost rows by stable id, semantic turns by turnId,
work/empty flags) so representation differences — block-level vs
turn-level, plane prefixes vs folded uuids — never read as divergence,
while real ones (missing/extra/mis-ordered rows) always do. Set
difference before order comparison so one missing row is one finding,
not a cascade of order mismatches.

useRenderShadow mounts in TileLeaf (the one component holding the full
runtime): probes the flag once app-wide via the existing dev-debug
config channel, recomputes the legacy model from the same pure functions
Feed uses, runs the adapter+ledger per-session (refs, so the D11 caches
are actually exercised on real ticks), and records divergences to a ring
buffer on globalThis.__agentCodeRenderShadow + console.warn once per
signature. Zero cost when the flag is off; DELETE-fated at Stage 3.

72 pipeline tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…adow

feat(rendering): slice 12 — Stage 2 shadow mode behind AGENT_CODE_RENDER_SHADOW
…ne in CI)

Runs the exact comparison useRenderShadow performs live — legacy
selectMergedEntries + deriveFeedRenderModel vs adapter + ledger, diffed
by shadowDiff — over constructed runtimes, so divergence triage starts
in CI with a minimal repro, not in a soak console. Seven scenarios from
the incident trail: idle conversation, live turn + work, claude
whole-turn suppression, empty session, stuck-tool ghost renders,
sidecar ghost hidden, and the #239 buried-prompt class (both sides now
agree at the unit grain — parity pinned, not just divergence tolerated).

All seven pass with ZERO divergences on first run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rity

test(rendering): slice 13 — shadow parity suite (legacy vs new pipeline in CI)
…derItems

ledgerToFeedItems maps RenderLedger rows to the FeedRenderItem shape Feed
already renders, so the Stage 3 flag swap replaces ONE call site (the
decision core) while LazyEntry, memo structure, autoscroll tail-signature
and TextProse/StreamingProse survive untouched. SemanticStreamingTurn
keeps rendering semantic items until the final cutover slice deletes it
with ghost rule 3 (D6) — decoupling the decision-core swap from a row-
component rewrite.

Order contract: items emit in LEDGER order with order fields engineered
so legacy sortFeedRenderItems is a no-op (matching phase partition, null
timeMs, constant source, index sequence). Payload references are the
runtime's own objects (Entry / SemanticLiveTurn) so downstream memos hold.
Unresolvable candidates surface in `dropped`, never silently omitted.

83 pipeline tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ewbridge

feat(rendering): slice 14 — view bridge: ledger rows → legacy FeedRenderItems
Juliusolsson05 and others added 9 commits July 7, 2026 16:29
New rendering/__tests__/recordingCorpus.test.ts (mirrors bundleCorpus.test.ts,
Mode 1 golden replay per plan §6): for each redacted recording fixture, replay
the SessionFeed channel stream through the REAL pipeline and assert the rows are
STABLE against the checked-in golden. BLESS-gated by AGENT_CODE_RECORDING_BLESS;
any un-tolerated change fails loudly.

Unlike the bundle corpus there is no external legacy ground truth — the golden
IS the pipeline's own last-blessed output, so bless rewrites `expected` and
triage is normally empty (kept for symmetry / knowingly-tolerated divergence).

The suite self-skips (describe.skipIf) when the fixtures dir is empty/absent so
CI stays green until real recordings land, and is SEEDED with two hand-built
fixtures (committed plane + semantic plane) so it actually runs today.

Replay is INLINE for now: reconstructSlices folds recorded events straight into
RuntimeLedgerSlices — a small local stand-in for slice 4's shared
RecordedSessionFeed harness (built in parallel on another branch). A later
integration commit swaps the reconstruction for the shared harness with no
fixture changes, since fixtures speak the channel stream, not the reconstruction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replay a captured Session Recording (meta.json + events.jsonl, the 9
SessionFeed channels) tick-by-tick through the REAL rendering pipeline and
yield per-tick { ledger, rows, feedItems }.

- reconstructSlices.ts folds the recorded events back into RuntimeLedgerSlices
  by REUSING the production leaf reducers (foldSemanticEvent, reduceStreamPhase,
  ghostsFromSemanticTurn, reconcileUpstream, the provider transcript mappers) —
  not a second fold. Only the thin orchestration glue is mirrored, cited to the
  useIpcSubscriptions sites it mirrors.
- recordedSession.ts constructs the adapter + ledger ONCE and drives them each
  tick, so the D11 identity caches are genuinely exercised. Because the running
  state is held across ticks and the reducers are reference-stable on no-op,
  unchanged planes hand back the SAME object reference and no-op ticks return
  the cached ledger — the reference pattern is faithful by construction (no
  content-pool needed).
- onBeforeFold clock seam lets a test pin Date.now() to the recorded wall clock
  (via vi.setSystemTime) without adding a clock seam to runtime code.

Fidelity: reducer-faithful + orchestration-mirrored, driven from the node
`unit` project (the React fold hook is unreachable there). Documented in the
module header, including which fold-glue paths are out of scope.

Round-trip test: hand-built prompt→tool→committed-handoff→completion→exit
recording; asserts rows per tick, the D11 same-object contract on no-op ticks,
torn-tail tolerance, and __note/__truncated skipping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
assertInvariants(replayResult) checks all four ledger invariants at EVERY tick,
with no hand-authored expected output — the permanent successor to the
dev-only render shadow (plan §6 Mode 2):

1. single-owner / dual-render: no two selected rows own the same artifact
   (row id, toolUseId, turn+block, or exact textKey).
2. no-vanish-without-replacement: a row gone at N+1 must be explained — a
   rejection decision names it, a replacement covers its artifact, OR its live
   semantic turn was torn down (exit) and thus left the runtime entirely.
3. no-unexplained-shrink: aggregate signal when the selected row count drops
   with unexplained drops.
4. D11 reference stability: a tick whose adapter inputs are reference-identical
   MUST return the same RenderLedger object (adapter.test.ts:119-149 over a
   real stream).

Lifecycle work/empty rows are excluded from single-owner and vanish checks,
mirroring bundleCorpus scopeUnits (they toggle on every phase edge).

Test: green over the realistic prompt→tool→completion→exit recording (with an
assertion that the tool→committed handoff actually happened, so "no violations"
isn't vacuous), plus one negative control per invariant proving each fires.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Slices 4 and 7 were built in parallel; the golden corpus (slice 7) shipped
a local reconstructSlices stand-in while slice 4 built the canonical
reducer-faithful replayRecording. Integration swaps the corpus onto the
shared harness (higher fidelity, one reconstruction path, no duplicate fold).

The swap surfaced a real fixture bug: hand-semantic-turn.json jumped to
block_completed with no block_started, which C's stand-in tolerated but the
REAL foldEvent reducer correctly ignores (block_completed no-ops when the
block was never opened - foldEvent.ts:668). Repaired the fixture into a
valid semantic stream and re-blessed against the real reducers' output;
both hand fixtures now paint their intended units (user row + semantic
turn) with empty triage. Verified green without bless.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(recording): Session Recording — full feature (slices 2–7, #467)
… honesty

A plan-vs-code audit found the shipped feature drifted from
docs/rendering/session-recording-plan-2026-07.md §7: recording was
auto-only (every session written from launch — a disk-burial trap), and
the record-session:start/stop handlers + Start/Stop command the plan
specified as the PRIMARY control were never built. Fixed here:

- Recording is now COMMAND-DRIVEN and opt-in. The manager records a
  session only after startRecording() is called; observe() ignores
  events for sessions the user never started. AGENT_CODE_SESSION_RECORD
  is downgraded to an OPTIONAL auto-start power flag (off by default),
  so a normal day never silently fills GBs.
- record-session:start / :stop / :is-recording ipcMain handlers (gated
  on the capability = dev-debug), preload start/stop/isRecording, and a
  "Toggle Session Recording" command that starts/stops the focused pane
  and labels itself from live recorder state. Gate split: capability
  (command available) = dev-debug; auto-start = the env flag.
- §3 header fields the audit flagged as missing: meta.json now carries
  kind:'session-recording' + redaction:'none', and provider/cwd are
  filled from session:started (providerSessionId stays best-effort).
- §4: the queue drop-oldest path now writes a __truncated{reason:
  'queue-drop'} tombstone at the gap position (was counter-only; the
  comment claiming an inline marker was inaccurate).
- Plan §0b "As-built reconciliation": records the intentional deviations
  (folder-per-recording, command-driven) and the one real limitation
  (replay is reducer-faithful, NOT the React fold — so the fold-glue bug
  class #469 is capturable in the recording but not yet in replay; needs
  the fold extracted from the React hook, converging with #394). No
  silent drift between plan and code.

9 recorder tests (incl. on-demand + auto-record power-flag cases);
full unit suite 427; both gates clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…demand

fix(recording): command-driven Start/Stop (§7) + header fields + plan honesty
Juliusolsson05 and others added 4 commits July 7, 2026 19:08
, Track A)

Subagent (Task/Agent) output was leaking into the parent's MAIN feed:
Claude interleaves a subagent's OWN turns into the parent transcript
tagged isSidechain:true, and the committed collector was structurally
blind to the flag — RawCommittedEntry didn't even carry it, so a
sidechain assistant turn (type:'assistant', not meta, text not '<')
sailed through to `selected` and painted as a plain main-feed row, with
minedToolIds even stamping its inner Read/Bash as committed-owned.
Confirmed live and captured in a session recording (#467); diagnosis and
plan in #477.

TDD: the two committed tests + the e2e adapter fixture were written
FIRST and FAILED (the sidechain turn was selected, its Read mined) —
that failure IS the leak. Then the fix:
- RawCommittedEntry gains isSidechain (the field already flows through
  the claude codec + history.ts:71; the feed just never honored it —
  only the session picker did, sessionList.ts:160).
- collectCommittedCandidates rejects sidechain entries BEFORE mining
  tools, reason 'sidechain-filtered' (new RenderReason). Safe by
  construction: the same activity already renders inside the Task card
  via the watcher channel (SubAgentState), so nothing is lost.
- e2e fixture proves the field flows adapter -> collector -> ledger and
  only the parent rows paint.

This is the COMMITTED (persistent) half. The LIVE proxy-plane half
(subagent turns multiplexed on the shared proxy stream, no isSidechain
on the wire) is Track B in #477 — needs flow attribution, separate PR.

Corpus unchanged (no fixture had sidechain entries); 170 rendering
tests; full unit suite 431; both gates clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(rendering): filter subagent sidechain turns from the main feed (#477, Track A)
…bution (#477 Track B)

Picks up Juliusolsson05/claude-code-headless#35: subagent API flows
(cc_is_subagent=true billing-header flag) are routed OUT of the parent's
visible turn stream instead of leaking into the main feed. The committed
half of the leak landed in #479 (isSidechain filter); this is the live
proxy-plane half. Subagent activity still renders in its own Task card
via the watcher channel (Record<toolUseId, SubAgentState>).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(subagents): subagent proxy-flow attribution — Track B (#477)
@Juliusolsson05 Juliusolsson05 changed the title feat(rendering): ownership-ledger rendering pipeline — integration branch (Stages 1–2) feat(rendering): ownership-ledger rendering pipeline + session recording + soak fixes — integration branch Jul 7, 2026
Juliusolsson05 and others added 6 commits July 7, 2026 19:43
Repo-cleanliness pass (findings from a 3-lens audit):

Deletions:
- issues.md (stale, long gone)
- CODEX.md (redundant — the codex CLI reads AGENTS.md)
- testing/rendering/ (the whole render harness — a 2-2.5mo stale vendored
  fork of the real components, superseded by the ownership-ledger
  corpus + recording-replay test net; no longer used)
- testing/regression-claude-tool-result.ts (dead orphan, zero references)
- the stale testing/rendering includes + orphaned comment in tsconfig.node.json

Fixes:
- AGENTS.md synced with the `Directory conventions` section CLAUDE.md had
  drifted ahead on (AGENTS/CODEX were byte-identical and missing it)
- MANIFESTO.md: removed a duplicated `## Structure` block (copy-paste bug)

Docs restructure:
- docs/archive/ created for superseded/closed material, each with a
  Status/ARCHIVED header: codex-rewrite-render (superseded notebook),
  issue-investigations (#147/#152/#154 closed), audit-plans (completed logs)
- rec-research-* grouped under research-2026-07/session-recording/ (was
  mixed with the rewrite research); the 2 path refs in the recording plan
  updated
- docs/README.md index added documenting the live-vs-archive convention

Both tsc gates clean; unit suite 434 (unchanged — the deleted harness had
no vitest tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stream 3)

src/renderer/src/rendering/__tests__/ was the ONLY __tests__ directory in
the repo — everywhere else colocates test next to source. Standardized on
colocation:

- 1:1 tests moved next to their source: adapter → adapter/
  collectLedgerInput.test.ts; committed/semantic → observations/;
  ledger/ghostPredicate/unknowns → model/; foldPolicy → policy/;
  ledgerFeedItems → view/; shadowDiff → shadow/; replay* → replay/
  (invariants.test.ts, recordedSession.test.ts).
- Cross-cutting tests with no single source (the corpus + e2e fixtures)
  moved to the rendering/ root: bundleCorpus, recordingCorpus,
  shadowParity, fixtures.*.
- bundleCorpus/recordingCorpus fixture paths dropped one ../ (moved up a
  level).

Pure move — vitest's src/**/*.test.ts glob already matches colocated
files. 170 rendering tests unchanged; full unit suite 434; both gates
clean. (Historical research docs keep their point-in-time __tests__
references by design.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tests

chore(rendering): colocate tests, remove the lone __tests__ dir
…l legacy renderer + flags (Stage 3)

The ownership-ledger pipeline now decides Feed's entire item list for BOTH
Feed consumers — the desktop (TileLeaf) and the phone (remote SessionView).
The legacy in-Feed decision core is deleted, unconditionally, with no flag.

WHY this landed as one PR after a scope discovery: the cutover was going to
be a desktop-only flag flip, but the remote phone client turned out to be a
SECOND <Feed> consumer still mounting the legacy deriveFeedRenderModel path
(it passed no renderItemsOverride). Deleting the legacy renderer would have
blanked the phone's feed. So the phone was migrated first, then legacy died.

- useLedgerFeedItems is now UNCONDITIONAL — the AGENT_CODE_RENDER_PIPELINE
  flag, its async probe, and the whole dev-debug plumbing for it (+ the
  AGENT_CODE_RENDER_SHADOW flag) are gone. Safe because shadow parity was
  green over the incident corpus + recorded sessions: identical item lists
  ⇒ identical paint through the unchanged row components.
- Phone migrated: remote SessionView builds a minimal SessionRuntime view
  (frozen empty ghost plane — the phone has no optimistic echo) and drives
  the same useLedgerFeedItems, passing renderItemsOverride like the desktop.
- deriveFeedRenderModel + its sort/visibility helpers + FeedRenderModelInput
  deleted from renderModel.ts; Feed's legacy branch deleted (maps the
  ledger's items unconditionally). deriveFeedCommittedProjection +
  feedRenderModelFromItems stay — the former still feeds SemanticStreamingTurn.
- Shadow subsystem deleted (useRenderShadow.ts + shadowParity.test.ts); the
  pure shadowDiff util stays (bundleCorpus/recordingCorpus assert through it).
  ledgerFeedItems.test's legacy oracle replaced with explicit expected shapes.

NOT in scope — the block-level un-collapse: the view bridge still collapses
the ledger's block-level semantic rows into turn-level items for
SemanticStreamingTurn. Retiring that component (and selectMergedEntries /
committedClaudeMessageTurnIds with it) needs the ledger to own live turns
block-by-block through the shared registry dispatch + live visual
verification the turn-grain corpus can't give. Tracked in the deletion
manifest's status section.

Gates: tsc node + web clean; unit 427, renderer 16, integration 30,
rendering 163 — all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(rendering): ledger is sole decision core; migrate phone, kill legacy renderer + flags (Stage 3)
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