feat(native-chat): the chat strip and the sidebar read the host's child records - #22614
brennanb2025 wants to merge 11 commits into
Conversation
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Host publishes its child records. The status summary gains optional
children?: AgentChildWorkView[], withbackgroundTasksnow derived from the same views (live rows only); the chat strip channel readsreadChildWorkfrom the host's status sink instead of the adapter's task DTO, republishes only on a changed fingerprint, and sends an explicitnullonce a live provider has no rows. The parent row ingestscanonicalStatusStore.getChildren(subject)rather than the summary's task list. - Retention without a timer. The status feed tracks the newest root turn id in the journal (subagent turns excluded) and, when it changes, admits a new
turn-startedevidence edge; the reducer removes settled children that no longer own live work, so a finished child stays listed with its outcome until the session's next turn. - Both readers draw from the records. The renderer bridge decodes
summary.childrenpermissively, copies them onto the row verbatim, derives the legacysubagentsroster from them, and reuses the child array across repeated summaries; the strip consumes the same views plus achildRowContextbuilt exactly as the sidebar builds it, so a lost or stale parent reads the same on both surfaces. - The old writer is gone.
onBackgroundTasksChangedis deleted end to end (Claude/Codex adapters, close/teardown funnels, runtime wiring,host.publishBackgroundTaskState); a new adapter seambackgroundTaskStopssupplies the provider's stop capability for provider-side decisions only. - Wire compatibility. New capability
agent-session.background-task-child-views.v1is advertised by the shared remote list and the desktop renderer; the RPC boundary withholdschildrenand settled-only rosters from a reader that does not advertise it.decodeAgentChildWorkViewsignores unknown keys and degrades unknown enum arms rather than dropping rows, and Codex view ids map back tocodex-agent:<thread>for legacy shapes.
I read the complete diff (all 66 files) and ran the new and rewritten suites touching this surface — shared view-wire/reconciliation, the status-feed and feed-children suites, the end-to-end structured-child-records-switch, the RPC capability suite, and the renderer switch/background-tasks-view tests — all green. The abstraction is coherent: one store read feeds one projection, and the legacy shapes are derived from it rather than maintained in parallel, which is the failure mode this PR exists to remove. No blocking or non-blocking findings.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
3422cca to
78e73c6
Compare
9acc0c1 to
619639b
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This run reviewed the delta since the previous Pullfrog review. The branch was rebased from base 3422ccaf4c onto 78e73c646a, which now carries the mainAgent lane; git range-diff shows the patch content is otherwise unchanged, so the re-review is the rebase integration plus the header-comment fix.
- Rebased onto the
mainAgentlane.structuredAgentSessionAgentStatusnow takeschildWork(records or views) instead of the summary'sbackgroundTasks, while returning the base'smainAgent. The only PR edit to that function is the input swap, so the host's parent row, the renderer bridge, and the CLI still fold one liveness source. - Held-open verdict preserved. The ingest and bridge date a child-held row with
isAgentStatusHeldOpenByChildWork({ state, mainAgent }), which is exactly the removedfromChildWork(mainAgent.state === leadState && state !== 'done'); the parent row's freshness clock is unchanged. - Readers still draw from the views. The bridge copies
summary.childrenonto the row and derives the legacy roster from them; the live-entry builder reuses the child array across repeated summaries; the strip counts only live views toward the monitoring indicator. - Wire and retention unchanged. The
agent-session.background-task-child-views.v1capability, the RPC-boundary withholding ofchildrenand settled-only rosters, theturn-startedretention edge, and the header-comment reword all stand as previously reviewed.
I read the complete diff end to end, plus the base versions of the files the rebase touched, and traced the fromChildWork → isAgentStatusHeldOpenByChildWork equivalence. No new findings.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
78e73c6 to
04d1d40
Compare
619639b to
8dac1b1
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This run reviewed the delta since the previous pullfrog review (8dac1b1 → bb15e1c): two commits that move conversation-command admission off the provider adapter's own task roster and onto the host's child records. One non-code note: the PR description's Review section still lists conversation-command admission among the adapter-tracker readers, which this delta no longer holds.
- Admission reads the strip's records.
conversationCommandBlockednow takeschildWorkand folds it throughagentChildWorkLiveness, the same fold the strip's monitoring indicator uses, so a settled roster blocks nothing and only work the strip lists refuses a command. - One owner for the stop-offer rule. The new shared
agentChildWorkViewOffersStopis used by both the row model'scanStopand admission'sstripOffersStop, andstripOffersStopmirrors the strip's own controls: a per-row stop wheresupportsTaskStop, otherwise the single untargeted stop wheresupportsStopAll. readChildWorkthreaded to the decision.StructuredAgentSessionMutationContextgainsreadChildWork(wired fromclientDelivery.readChildWork); bothrunStructuredConversationCommandandrewindStructuredAgentSessionpass it.- Adapter interface trimmed.
backgroundTaskStateleavesStructuredAgentSessionAdapterand the router; the concrete Claude/Codex adapters keep it as a test-only accessor for holding the tracker roster beside the records. - Tests. A new end-to-end test proves a tracker roster that claims live work refuses nothing while the strip is empty, refuses
Stop…for a live record, and admits once the record settles; the unit suite covers the targeted / untargeted / no-stop matrix with the tracker present throughout. - Comments only. The legacy death condition, the summary clock-tolerance invariant, and the retention lifecycles.
I read the full diff and the delta end to end, traced stripOffersStop against the strip's render gating (supportsStop / supportsStopAll and the per-row stopId), confirmed agentChildWorkViewOffersStop is exactly the old canStop predicate, and ran the admission unit suite (7/7) and the new integration suite (1/1) green, plus pnpm tc:node and oxlint on the changed files.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
b4cf0d2 to
5406ab2
Compare
bb15e1c to
254db2d
Compare
5406ab2 to
359483b
Compare
…mary and the chat strip The status summary and the background-task channel now read a session's child records from the host's canonical store, through the status sink its row landed in, and derive the legacy task and subagent shapes from the same views. The parent row folds its child-work liveness from those records at ingest, not from the summary's task list. The adapters no longer push their task DTO to clients: the onBackgroundTasksChanged path is gone, and a child-work ingest is what republishes both the summary and the strip. Finished children stay listed until the session's own next turn starts. A reader that predates child views never receives a roster whose rows are all settled.
… its parent's verdict The strip's roster now renders from the child views its channel carries, and passes the verdict the session's own status row gives its children, built the way the sidebar builds it (the row's freshness and the status feed's observation). So one child reads the same in the strip and the sidebar, live, after the transport drops, and once the row goes stale. A roster of finished children stays shown until the next turn but no longer animates the monitoring indicator or blocks conversation commands. Tests: an end-to-end run on a host with no renderer (a real hook server as the status sink) shows the summary and the strip channel carrying the same records at every step, the parent row folded from them, retention, and an older client's task list holding live work only; a wired renderer test shows both surfaces agree when live, lost and stale.
…s its live roster, a finished roster holds nothing open - The view decoder ignores unknown keys, degrades unknown kinds, states, outcomes and memberships, and drops only rows it cannot identify. - At the RPC boundary a reader that predates child views gets no strip for a roster of finished children and never the views themselves; a stop-only reader keeps rows whose host offers no targeted stop. - The strip shows finished children without reading them as live work. - The row keeps its child list's identity when a summary repeats it.
…on's live rows, unfiltered
…lue import to the renderer boundary The renderer node-builtin boundary test scans raw text, so a header comment that said "imports" ahead of the import block turned the type-only import of agent-status-child-work into a value edge that reaches node:crypto.
…ts own module The status feed crossed the file-size limit once the summary gained the main agent's turn outcome beside the child views. Which summary changes reach every session list now lives in structured-agent-session-status-summary-equality.ts.
A conversation command was refused on the provider tracker's own roster while the strip read the host's child records, so a drift between the two rule sets could refuse /clear with a stop instruction the strip had no button for. Admission now reads the same records through the same read as the strip, uses the strip's liveness fold, and asks for a stop only when the strip renders one. The adapter contract no longer exposes the tracker roster, so no host decision can read it. Also records when the legacy child shapes die, every earlier death of a settled child, and the display-precision invariant behind the summary's clock tolerance.
254db2d to
20bff9f
Compare

ELI5
A chat session's subagents show up in two places: as indented rows under the session in the sidebar, and as rows in the chat's background-task strip. Until now both were drawn from a task list the provider adapter published, and the sidebar's copy was converted again in the renderer, losing facts on the way. The previous PRs in this stack gave the host one record per child (what it is doing, how it ended, when) and one row model for both surfaces. This PR is the switch: the host now sends those records to both surfaces, both surfaces draw from them, and the old adapter-to-client task feed is removed. A finished subagent now stays listed with how it ended until the parent's next turn, a subagent whose shell is still running reads "Monitoring background tasks" in both places, and when Orca loses contact with the host both surfaces stop vouching for the child in the same words.
Stacked on #22565 (the shared row, head
3422ccaf4c), which is stacked on #22553 → #22536 → #22521. The base branch isbrennanb2025/c7-shared-subagent-row.Merge order
onChildWorkEvidence→ store path this PR reads, so once both land, default-mode Codex helpers appear in the strip and the sidebar with no further change. Either merge order works.What Changed
Delivery: the host publishes its child records (new optional fields only)
agentSession.subscribeStatus, read by every session list): gainschildren?: AgentChildWorkView[], the host's records for the session, live and recently settled, read from the canonical store through the status sink the session's row landed in (StructuredAgentSessionStatusSink.readChildWork→AgentHookServer.getStructuredChildWorkViews, wired at both entry points, desktop main andorcad).backgroundTasksis still published, now derived from the same views (live rows only, as issubagents: an older client never folds a settled child into the parent's state, so a settled row showschildrenwith its records and an emptysubagents).agentSession.subscribe/historybackgroundTasks): gainschildren?, andtasks/settledTasksare derived from the same views. The channel no longer reads the adapter's task DTO: it reads the records, and it republishes when a child-work ingest changed them (an unchanged roster sends nothing).ingestStructuredStatusfolds child liveness fromcanonicalStatusStore.getChildren(subject), not from the summary's task list.structuredAgentSessionAgentStatustakeschildWork(records or views) instead ofbackgroundTasks; the fold itself is untouched. After every child-work ingest the feed re-reads the records and republishes, which is what re-folds the parent row.session-endedevidence removes every record, e.g. a provider exit or the post-exit release), when the host closes or releases the session (the store forgets the row and its records, and the retained summary drops them), or when it is the oldest past the 32-settled-record cap. The feed watches the newest root turn id in the journal (a subagent's turn never counts) and, when a new one appears, sends a newturn-startedevidence edge; the reducer removes settled records then, keeping any that still owns live work (the same exclusion as the 32-record cap). No timer.children/backgroundTasks: the store forgets them with the row.Readers
StructuredAgentSessionStatusBridge): copiessummary.childrenverbatim onto the row asentry.children(decoded permissively, below).subagentsis derived from those views through the shared projection; the renderer's own conversion of the task DTO remains only for an older host that publishes no views. Liveness for the row reads the views.children, reusing the previous array when a summary repeats it (liveEntryChildFields), and a done row's retention change now countschildren.NativeChatStructuredSessionStatuspasses the channel's views aschildViewsandchildRowContext, built from the session's own status row exactly as the sidebar builds it:agentChildRowContextForParent(entry, isExplicitAgentStatusFresh(entry, now, AGENT_STATUS_STALE_AFTER_MS)), where the row'ssubagentObservationis the status feed'sgetSessionObservation. So a lost or stale parent reads the same on both surfaces. A roster of finished children is shown but no longer counts as live work (the monitoring indicator and the conversation-command block read live views only).Writer removed
onBackgroundTasksChangedpublication path is deleted end to end (Claude and Codex adapters, their close/teardown funnels, the runtime wiring,host.publishBackgroundTaskState). The strip, the summary and conversation-command admission no longer read the provider tracker's roster, andbackgroundTaskStateis no longer on the adapter contract (see Architecture review).backgroundTaskStops(sessionId)supplies the provider's stop capability (Claude: targeted; Codex: none), which the DTO used to carry.Wire and permissive decoding
decodeAgentChildWorkViews(src/shared/agent-status-child-work-view-wire.ts) is the client-side reader: unknown keys are ignored; an unknownkinddegrades tounknown,statetounverifiable,outcometounknown,operation.basistoreported,membershipto a live row that asserts nothing;stoppablemust betrueto offer a stop; only a row with no id, clocks or fence is dropped. The strict record codec stays host-only.providerIdis its bare thread id (the journal linkage key). Every legacy shape derived from views maps it back tocodex-agent:<thread>(structuredChildWorkLegacyTasks/structuredChildWorkLegacySubagents), so old clients keep their row ids. On a new client the view path keys rows bychildWorkId, so row keys change once at the switch; Codex agents are not stoppable, so no stop path sees the difference.agent-session.background-task-child-views.v1, advertised by the desktop renderer, remote and web clients. At the RPC boundary (structured-agent-session-background-task-capability.ts), a reader that does not advertise it never receiveschildrenand never receives a roster whose rows are all settled (it would read that as live work: animate the indicator and refuse conversation commands); it gets its pre-feature view. The existing unstoppable-row withholding now applies only when the host offers a targeted stop (a reader draws per-row Stop only then), so a stop-only reader keeps Codex rows.Status-summary churn (measured)
The summary goes to every session list, remote ones included, so it compares child views with usage stripped and the child's evidence clocks (
observedAt,operation.observedAt) tolerated up to 60 s (SUMMARY_CHILD_CLOCK_TOLERANCE_MS, the precision of the "No update in Nm" reading) — the same shape as the existingtotalTokensexclusion. The strip channel still carries every tick. Measured with a throwaway harness (real Claude adapter overagentSession.*, real hook server as the sink), 5 background children, 20 rounds oftask_progresseach (100 frames):3422ccaf4c)A tick that changes only usage or a clock inside 60 s republishes nothing; the 5 heartbeat-only republishes are one per child when its clock crossed 60 s. What does republish is a child's current tool changing — the fact the sidebar row now shows. The base republished 0 because the task DTO carried no operation at all.
Why
A child had up to three materializations: the adapter's task DTO (strip), a renderer conversion of it (sidebar), and the hook-lane roster (CLI). Each hop dropped facts (monitoring collapsed to working, failed became blocked, the running tool was discarded). #22521–#22553 built one host record per child; #22565 built one row model. Neither changes anything until both surfaces read the record, and the old writer is gone — this PR does both at once so there is never a window where the two surfaces read different sources.
Alternatives considered:
docs/reference/agent-status-store.md).childreninside the roster. Rejected: more frame surface for the same effect; the existing RPC-boundary projection already hands older readers their pre-feature roster.startedAtis provider time. The journal's root turn identity is the parent's own fact.Linked Issue
None — part of the structured chat status/orchestration program.
Visual Proof
Captured with the
$electronskill: a dev instance of this branch launched withORCA_BACKGROUND_LAUNCH=1on a throwaway profile, Playwright over CDP, CDP screenshots of the hidden window; no window was shown and no OS input was used. This is a renderer-seeded fixture, not a live provider session: the session's status row was written through the realsetAgentStatuspath with the payload the bridge writes (views, the derivedsubagents, the observation), using this branch's shared modules, and the realNativeChatStructuredSessionStatus(this branch's version, reading the parent's verdict from the store by pane key) was mounted over a roster built by the realstructuredSessionBackgroundTasksView. Children: working withBash: npm test -- parser; finished while itsnpm run devshell runs; finished; failed.Fixture screenshots (sidebar card and strip, before and after losing contact with the host):
01-live-window.png,02-live-sidebar-card.png,03-live-strip.png: sidebar and strip agree row for row —Summarize the grammar - Grammar has 42 productions(finished, green),Monitoring background tasks - Start the dev server(yellow monitoring icon; strip nestsnpm run devunder it),Fuzz the tokenizer - Exit code 1: fuzz target crashed(failed),Run the parser tests - Bash: npm test -- parser(working); strip header "4 agents — 1 working, 1 monitoring, 1 blocked, 1 done".04–06-lost-transport-*.png: the same session after the status feed reports the host unverifiable. Both surfaces:Start the dev server - No update in 3m,Run the parser tests - No update in 0m; settled outcomes unchanged.Live run (real providers)
A design-check pass ran this head against real provider CLIs:
$electronwith Playwright over CDP, a hidden dev instance (ORCA_BACKGROUND_LAUNCH=1, throwaway profile, no OS input), three sessions.1 agent — workingwith the child's own current tool:Run sleep and echo · Bash: sleep 40; echo CHILD_DONE. The subagent moved itssleepto the background, and that shell appeared as a command owned by the agent. When the subagent returned, the agent settledsucceededand its killed background shell settledcancelled(shown with the interrupted dot, by design; see Review). The sidebar showed the session row with an indentedRun sleep and echo - general-purposechild row, the same name and state as the strip. Both stayed listed with their outcomes after the turn ended, and on the next user message both surfaces cleared within one 3 s poll.sleep_childreadworking · Agent, with its shell/bin/zsh -lc 'sleep 45; echo CHILD_DONE'nested under it on its own clock, then settled as1 agent — done / sleep_child · CHILD_REPLY … ended 0s ago. The sidebar showed the same child row.1 shell command — 18s, then— done, retained. The rawcodex item:collabAgentToolCallrows in the transcript are pre-existing and are feat(native-chat): register Codex default-mode helpers as subagents #22619's scope, not this PR's.Not covered live: Linux, Windows, SSH and mobile; Claude waiting/blocked children (not produced; see Review); a merged tree with #22619.
Testing
All run with
env -u ORCA_STRUCTURED_SESSION, macOS.pnpm tc:node,tc:web,tc:cli: exit 0.src/main/runtime/structured-child-records-switch.test.ts— end to end on a host with no renderer: the production runtime, a Codex session, and a realAgentHookServeras the status sink. At each step the status summary's views and the strip channel's views carry the same children (kind, label, state, membership, outcome, current tool, owner), with literal expectations (deliberately not compared against the tracker's DTO, which shares the executions with the producer — feat(native-chat): Codex sessions write their subagents into the host status store #22553's caveat). Also: the parent row readsworkingwhile the subagent runs after the parent's turn,working/monitoringwhen only the child's dev server remains,doneafter it exits; an older client's task list holds live work only; finished children stay until the next root turn, then leave both surfaces; provider exit removes the records.src/renderer/src/components/native-chat/structured-child-records-switch.test.tsx— wired renderer parity (real bridge, real store, realNativeChatStructuredSessionStatus, real sidebar child rows): views copied verbatim, legacysubagentsderived with the Codex id, parent fold from views; entry identity reused on a repeated summary; and one child reads the same on both surfaces live, after the status stream drops (lost transport), and when the parent row is stale.structured-agent-session-status-feed-children.test.ts(7),agent-status-child-work-view-wire.test.ts(5),structured-session-background-tasks-view.test.ts(3), retention in the reducer suite, child-view gating in the RPC capability suite (+4), records-not-summary in the ingest suite.HEAD):turn-startedreducer armchildRowContext)Regression:
src/main/{claude,codex,native-chat,agent-hooks,runtime,ipc,orcad,startup},src/shared, renderernative-chat,sidebar,dashboard,store,runtime,lib,web, and refactor(sidebar): one subagent row for CLI and structured children, shared with the chat strip #22565's parity test: 4,458 files, 44,356 tests: 44,141 passed, 1 expected-fail, 208 skipped, 6 failed. 5 are the real-Claude-binary files (claude-structured-real-cli,claude-tui-resume-real-binary), which fail identically on the base per feat(native-chat): Claude sessions write their subagents into the host status store #22536/feat(native-chat): Codex sessions write their subagents into the host status store #22553. The 6th (web-runtime-clientcapability list) was this PR's and is fixed, as is the CLI transport list (8/8). Timing-only failures seen once under load (refusal-retry,WorktreeCard.compact-hover,proven-dead-retry) pass alone.Renderer-root tests (
src/renderer/src/*.test.ts, missing from the sweep above, found in review): at the first pushed headrenderer-node-builtin-boundary.test.tswas red (1 of 2). Its raw-text scan read the word "imports" in the header comment of the newagent-status-child-work-view-wire.tsas the start of the import block, turning the type-onlyimport type {…} from './agent-status-child-work'into a value edge that reachesnode:crypto. Fixed by rewording that comment (commit9acc0c1d79): now 4 files, 36/36; with the old header restored the boundary test is red again (positive control).Cross-version against the newest release tag:
cross-version-agent-session-wire14/14 andcross-version-terminal-wire10/10, each run alone. Running the whole folder at once timed out extracting several baselines in parallel (tar -xf timed out after 45000ms), an environment limit; the two suites that cover this surface pass alone.Quality:
check:code-quality:changedagainst the base: 0 new findings in every category. Fulloxlinton every changed TS/TSX file: 0 (it did reportmax-lineson two files during development, both split).audit:anti-slopexit 0. No new docs;pnpm-lock.yamlabsent from the range; no new i18n strings.I manually tested these changes locally
Automated tests added/updated, or explained why not below
Review
Not verified:
collabAgentToolCalland nosubAgentActivity, and feat(native-chat): Codex sessions write their subagents into the host status store #22553 registers agent children only from the latter — so no agent child record exists: no agent row in the strip or the sidebar, and the strip header counts no agents. The helper's shell still reaches the records as an unowned command row (1 shell command — …), retained like any settled child. Verified live with GPT-5.5 at this head. Codex row parity here holds forsubAgentActivity(v2) sessions, verified live with GPT-5.6-Terra; default-mode agent rows arrive with feat(native-chat): register Codex default-mode helpers as subagents #22619, whose records flow through the same store read this PR's surfaces use.item:collabAgentToolCallas raw provider status rows in the transcript, in both multi-agent modes. The transcript is not this PR's surface; feat(native-chat): register Codex default-mode helpers as subagents #22619 renders those calls as named tool rows.task_updated.patch.erroris not mapped toblocked, so a Claude child waiting on approval readsworkingin both surfaces (the pane-level wait still shows on the parent). Codex produceswaitingfrom the child thread's status (feat(native-chat): Codex sessions write their subagents into the host status store #22553) and both surfaces render it. No state parity is claimed for Claude waiting/blocked. Producing them belongs to the Claude producer lane and is deferred to a follow-up PR (not open yet), gated on a captured transcript that proves the attribution.cancelledoutcome to the interrupted dot, as a CLI row does. In the live Claude run, the background shell killed when its subagent returned shows that dot; it is the recorded outcome, not a defect.unverifiable, never a record write.claude-structured-control-actions.tsreads the tracker's stoppable ids). Conversation-command admission no longer reads it (see Architecture review).mainAgentlane PR (feat(agent-status): publish the main agent's own state beside the combined row state #22452) is not on main; this is implemented against today'sstructuredAgentSessionAgentStatusinputs and changes only its liveness input. The fold,mainAgent, and their consumers are untouched; rebase if feat(agent-status): publish the main agent's own state beside the combined row state #22452 lands first.childrenRevisionfrom the plan is not added: equality is derived from the views, so there is no second counter to keep in step.Architecture review
An architecture pass on this PR asked for four changes. What changed and why:
/clear,/compactand rewind on the provider tracker's own roster, while the strip and the client-side block read the host's child records. Two rule sets fold the same frames, so if they ever drift, the host could refuse with "Stop background tasks" while the strip shows no row and no Stop button. Admission now reads the same records through the same read as the strip (readChildWork), uses the same liveness fold as the strip's monitoring indicator (a finished row blocks nothing), and asks for a stop only when the strip actually renders one: a per-row stop on a live, stoppable, addressable row when the provider targets stops, else its single untargeted stop. Otherwise it says "Wait for background tasks to finish". The per-row rule is now one shared function used by both the row model and admission.backgroundTaskStateis removed from the adapter contract and the router, so no host code can read the tracker's roster; the concrete Claude and Codex adapters keep it only for the tests that check the tracker against the records. Tests:structured-conversation-command-admission.test.ts(7) and a host-levelstructured-conversation-command-child-work.test.ts, where the adapter's roster claims live work throughout and the refusal still follows only what the strip lists. Ablations at the final head: removing the records check fails 5 of 8; restoring the tracker-roster read fails 3 of 8; asking for a stop based only on the provider's stop capability fails 1 of 8.structured-agent-session-child-work-legacy.tsnow gives the condition. OnceMIN_COMPATIBLE_RUNTIME_CLIENT_VERSIONgoes above the protocol version in force whenagent-session.background-task-child-views.v1shipped (3), every client the host accepts readschildren. At that point the summary'sbackgroundTasksderivation, the channel'stasks/settledTasks, the Codexcodex-agent:id remap and the capability gate'swithoutChildViewsare deleted together. The summary derivation and the gate both point to that header.session-endedremoves the records before close, so retaining them at close would not keep them in that case anyway.SUMMARY_CHILD_CLOCK_TOLERANCE_MSnow states its rule. Anything that reads the summary's child clocks must show staleness no more precisely than that (today it shows whole minutes). A reader that needs finer freshness uses the background-task channel. The equality compares children by position, so it relies on the store returning them in insertion order.Recorded deviations:
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
children; they keep readingbackgroundTasks/tasks/settledTasks/subagents, derived from the views (live rows in the summary; Codex agents keepcodex-agent:<thread>), never a settled-only roster, never the views. New client → old host: nochildrenanywhere, so the bridge converts the old task list as before and the strip readstasks; pixels are the refactor(sidebar): one subagent row for CLI and structured children, shared with the chat strip #22565 fallback. Nothing new is persisted (structured rows are never saved).tests/e2e/cross-version-wireexercises the agent-session surface in both skew directions.agent-status-store.md: the structured row folds the store's child records).Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)