Skip to content

feat(protocol,gui-app): durable background cards for backgrounded commands - #918

Merged
AmiteshwarRandhawa merged 5 commits into
mainfrom
codex-background-commands
Aug 3, 2026
Merged

AmiteshwarRandhawa merged 5 commits into
mainfrom
codex-background-commands

Conversation

@AmiteshwarRandhawa

Copy link
Copy Markdown
Contributor

What / Why

Codex decides on its own to yield a long-running exec to a background terminal, and keeps it alive past the turn that started it — through the next turn, and through everything short of the app-server dying. The real terminal can land minutes later. Until now a command block had no way to say any of that: it was folded into the activity group like any other command, force-finalized when its turn ended, and its terminal arrived to a card that had already claimed an ending.

This adds the wire markers and the GUI rendering for a backgrounded command's own durable card, and fixes the renderer routing that dropped its late terminal.

Protocol

commandBlockSchema gains the same two markers toolCallBlockSchema already carries, with the same semantics:

  • backgroundTask (nullable, defaults false) — sticky: survives every terminal path and reload, so the card stays standalone for the whole lifecycle instead of collapsing back into the activity group.
  • stopped — the terminal outcome was an explicit stop, not a failure. The provider reports its own kill as a synthetic exit code, and rendering that as an error blames the command for something the host did.

command.started gains an optional backgroundTask, and the accumulator now upserts on it rather than blindly appending — a harness that learns about backgrounding only after the card is open (Codex decides at the parent turn's end) re-emits the event to stamp the marker, and appending would duplicate the card. The open block's timestamp (its elapsed anchor) and status are left alone.

command.completed gains terminationReason and backgroundTask, mirroring tool_call.errored / tool_call.completed.

streamingDetachedBlockIds now treats a background-marked command like a background-marked tool_call, so a clean turn.completed leaves it streaming instead of force-finalizing it — the block waits for its own terminal, which is the only frame that knows how it ended.

GUI

  • shouldPromoteCommandSegment mirrors the tool-call promotion gate (durable marker, plus the transient host backgroundItems set for a card whose marker hasn't arrived yet).
  • CommandSegment grows a real card variant — elapsed heartbeat as the collapsed preview, a "Stopped" badge in place of the exit code when the host stopped it.
  • Detached-owner routing fix: the renderer's detached router only recognized subagent.* and terminal tool_call.* events (Claude's background shapes). A parentless command.completed returned null, fell through toward the active turn, and was dropped — the card's timer ticked forever and only corrected on the next send, when the store re-derived from the host snapshot (live-repro on a 20s sleep that ticked past 2 minutes). command.completed is now routed exactly like a terminal tool_call.*: to its parentBlockId when it is a subagent child, otherwise to its own block's settled row, and mandatory when the terminal carries the background marker so it can never mint a duplicate card on an unrelated live turn.

Deliberately not included

A per-item stoppable flag on the wire. chat.subscribe is frozen through 1.5, so adding a field to backgroundItemBaseFields is a breaking host→client change at every released version; it needs a new minor with frozen copies, not an additive default. Consequence today: on a Codex build below the stop-API floor the host lists the command but declines the stop, and the GUI cannot yet hide the button for it.

Pairs with

This is the client half of the Codex background-work feature; the host half lives in an internal-repo PR (linked in a follow-up comment). Both are needed for the cards to appear.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Background commands now appear as standalone, reopenable timeline items.
    • Running background commands remain visibly in progress.
    • Explicitly stopped commands display appropriate status without being treated as errors.
    • Background command completion events are associated with their owning conversation.
  • Bug Fixes

    • Prevented detached background results from appearing in unrelated active turns.
    • Preserved command status and timing when transitioning to background execution.
    • Improved handling of promoted commands and background descendants.

Walkthrough

Background command state now flows through runtime events, persistence, chat state, activity grouping, and command rendering. Detached command completions update their owning settled assistant message.

Changes

Background command lifecycle

Layer / File(s) Summary
Command state contracts
protocol/src/persistence/epic/content-blocks.ts, protocol/src/host/agent/gui/agent-runtime.ts, clients/gui-app/src/stores/composer/chat-store.ts
Command blocks and runtime events now carry backgroundTask and stopped metadata.
Runtime accumulation and turn finalization
protocol/src/host/agent/gui/agent-runtime-accumulator.ts, protocol/src/host/agent/gui/__tests__/agent-runtime-accumulator.test.ts
The accumulator updates commands in place, records stopped completions, retains detached background commands, and finalizes foreground commands at turn end.
Detached completion routing
clients/gui-app/src/stores/chats/chat-session-store.ts, clients/gui-app/src/stores/chats/rendered-messages.ts, clients/gui-app/src/stores/chats/__tests__/*
Detached command.completed events resolve settled assistant owners. Ownerless events are ignored.
Timeline promotion and command rendering
clients/gui-app/src/components/chat/chat-activity-groups.ts, clients/gui-app/src/components/chat/chat-message-assistant-body.tsx, clients/gui-app/src/components/chat/segments/*, clients/gui-app/src/components/chat/__tests__/*
Background commands become standalone timeline items. Stopped commands avoid error and synthetic exit treatment.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AgentRuntime
  participant RuntimeAccumulator
  participant ChatSessionStore
  participant ActivityGroups
  participant CommandSegment
  AgentRuntime->>RuntimeAccumulator: emit command.started or command.completed
  RuntimeAccumulator->>ChatSessionStore: persist command metadata and ownership
  ChatSessionStore->>ActivityGroups: provide rendered command segment
  ActivityGroups->>CommandSegment: promote and render background or stopped state
Loading

Possibly related PRs

Suggested labels: protocol-compat-override

Suggested reviewers: hdkshingala, anur4ag

Poem

A rabbit tracks each command’s state,
Background work stays active.
Stopped commands show their mark,
Settled rows receive results.
Timeline cards stand apart.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the protocol and GUI changes that add durable background cards for backgrounded commands.
Description check ✅ Passed The description directly explains the protocol and GUI changes, including durable background cards, terminal routing, and command lifecycle handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex-background-commands

Comment @coderabbitai help to get the list of available commands.

@AmiteshwarRandhawa

Copy link
Copy Markdown
Contributor Author

Pairs with the internal-repo host PR: traycerai/traycer-internal#4788 (Codex background work — helper cards, background panel, and backgrounded commands). That PR's gitlink currently pins this branch head and will be re-bumped to the merge commit once this lands, so this one should merge first.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38289bb062

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread protocol/src/host/agent/gui/agent-runtime.ts Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe067025c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread protocol/src/persistence/epic/content-blocks.ts
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 3, 2026
…mands

Codex decides on its own to yield a long-running exec to a background
terminal, and keeps it alive past the turn that started it - through the
next turn, and through everything short of the app-server dying. Until
now a `command` block had no way to say so: it was folded into the
activity group like any other command, force-finalized when its turn
ended, and its real terminal - which can land minutes later - arrived to
a card that had already claimed an ending.

`commandBlockSchema` gains the same two markers `toolCallBlockSchema`
already carries, with the same semantics:

  - `backgroundTask` (nullable, defaulted false): sticky, survives every
    terminal path and reload, so the GUI keeps the card standalone for
    the whole lifecycle instead of collapsing it back into the group.
  - `stopped`: the terminal outcome was an explicit stop, not a failure.
    The provider reports its own kill as a synthetic exit code, and
    rendering that as an error blames the command for something the host
    did.

`command.started` gains an optional `backgroundTask`, and the
accumulator now UPSERTS on it rather than blindly appending - a harness
that learns about backgrounding only after the card is open (Codex
decides at the parent turn's end) re-emits the event to stamp the
marker, and appending would duplicate the card. The open block's
`timestamp` (its elapsed anchor) and `status` are left alone.

`command.completed` gains `terminationReason` and `backgroundTask`,
mirroring `tool_call.errored`/`tool_call.completed`.

`streamingDetachedBlockIds` now treats a background-marked `command`
like a background-marked `tool_call`, so a clean `turn.completed` leaves
it streaming instead of force-finalizing it - the block waits for its
own terminal, which is the only frame that knows how it ended.

GUI: `shouldPromoteCommandSegment` mirrors the tool-call promotion gate
(durable marker, plus the transient host `backgroundItems` set for a
card whose marker hasn't arrived yet), and `CommandSegment` grows a real
card variant - elapsed heartbeat as the collapsed preview, a "Stopped"
badge in place of the exit code when the host stopped it.

Deliberately NOT included: a per-item `stoppable` flag on the wire.
`chat.subscribe` is frozen through 1.5, so adding a field to
`backgroundItemBaseFields` is a breaking host->client change at every
released version; it needs a new minor with frozen copies, not an
additive default.

Signed-off-by: Amiteshwar Randhawa <amiteshwar04@gmail.com>
…rminal arrives

A codex backgrounded command reports its real ending minutes after its row
settled - the host routes that `command.completed` to the old row and
persists it, but the renderer's detached-owner router only recognized
`subagent.*` and terminal `tool_call.*` events (Claude's background shapes).
A parentless `command.completed` returned null, fell through toward the
active turn, and was dropped: the card's timer ticked forever and only
corrected on the next send, when the store re-derived from the host
snapshot (live-repro on a 20s sleep that ticked past 2 minutes).

Route `command.completed` exactly like a terminal `tool_call.*`: to its
`parentBlockId` when it is a subagent child, otherwise to its own block's
settled row, mandatory when the terminal carries the background marker so
it can never mint a duplicate card on an unrelated live turn.

Signed-off-by: Amiteshwar Randhawa <amiteshwar04@gmail.com>
…mand fields, and name only abnormal endings

The frozen `chat.subscribe@1.0-1.3` runtime-event unions bind the live
`command.*` schemas directly, so `backgroundTask` and `terminationReason`
leaked onto lines that shipped without them. Bind hand-frozen copies there
instead, and note on `runtimeEventSchemaV12` that it is the live union's base -
not the union `subscribe.ts` actually pins to 1.2 - so the next per-event freeze
lands in the right copy.

`terminationReason` also drops its `"error"` default and becomes optional: it
exists only when the ending was abnormal. A clean exit carries no reason at all,
which is also what an emitter that predates the field sends - so absence reads
the same either way, instead of every clean command claiming an error the
renderer has to ignore.

Signed-off-by: Amiteshwar Randhawa <amiteshwar04@gmail.com>
…nt fields

Rebase fallout: the fixture predates `backgroundTask`/`stopped` becoming part
of `CommandSegment`, so it stopped type-checking against the merged type.

Signed-off-by: Amiteshwar Randhawa <amiteshwar04@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f1e8766d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread protocol/src/host/agent/gui/agent-runtime-accumulator.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
protocol/src/host/agent/gui/agent-runtime-accumulator.ts (1)

1508-1541: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Propagate terminationReason: "error" to the command UI.

When exitCode is absent or zero, this event produces status: "completed" and stopped: false. The GUI derives command failure only from a non-zero exitCode, so it renders this genuine failure as a normal completion. Propagate an explicit failure signal and make CommandSegment consume it; setting only the block status is insufficient because the projection does not pass that status through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@protocol/src/host/agent/gui/agent-runtime-accumulator.ts` around lines 1508 -
1541, Update the command completion handling in the command.completed case to
propagate terminationReason: "error" as an explicit failure signal on both
updated and newly created command blocks, including when exitCode is absent or
zero. Then update CommandSegment to consume this signal when deriving the
rendered command result, rather than relying only on a non-zero exitCode;
preserve normal completion and stopped behavior for other termination reasons.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@protocol/src/host/agent/gui/agent-runtime-accumulator.ts`:
- Around line 1508-1541: Update the command completion handling in the
command.completed case to propagate terminationReason: "error" as an explicit
failure signal on both updated and newly created command blocks, including when
exitCode is absent or zero. Then update CommandSegment to consume this signal
when deriving the rendered command result, rather than relying only on a
non-zero exitCode; preserve normal completion and stopped behavior for other
termination reasons.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 68792861-9adf-4247-a4f4-563219ecb230

📥 Commits

Reviewing files that changed from the base of the PR and between cf2b139 and 3f1e876.

📒 Files selected for processing (19)
  • clients/gui-app/src/components/chat/__tests__/chat-activity-groups.test.ts
  • clients/gui-app/src/components/chat/__tests__/chat-find-projection.test.ts
  • clients/gui-app/src/components/chat/__tests__/chat-message-fixtures.ts
  • clients/gui-app/src/components/chat/__tests__/chat-messages.test.tsx
  • clients/gui-app/src/components/chat/chat-activity-groups.ts
  • clients/gui-app/src/components/chat/chat-message-assistant-body.tsx
  • clients/gui-app/src/components/chat/segments/__tests__/activity-group-segment.test.tsx
  • clients/gui-app/src/components/chat/segments/activity-group-segment.tsx
  • clients/gui-app/src/components/chat/segments/command-segment.tsx
  • clients/gui-app/src/stores/chats/__tests__/chat-session-store.test.ts
  • clients/gui-app/src/stores/chats/__tests__/rendered-messages.test.tsx
  • clients/gui-app/src/stores/chats/chat-session-store.ts
  • clients/gui-app/src/stores/chats/rendered-messages.ts
  • clients/gui-app/src/stores/composer/chat-store.ts
  • protocol/src/host/agent/gui/__tests__/agent-runtime-accumulator.test.ts
  • protocol/src/host/agent/gui/agent-runtime-accumulator.ts
  • protocol/src/host/agent/gui/agent-runtime.ts
  • protocol/src/persistence/epic/__tests__/__fixtures__/epic-schema-surface.ts
  • protocol/src/persistence/epic/content-blocks.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b0a787d4c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread clients/gui-app/src/stores/chats/chat-session-store.ts
@AmiteshwarRandhawa
AmiteshwarRandhawa merged commit a8c7aa5 into main Aug 3, 2026
18 checks passed
@AmiteshwarRandhawa
AmiteshwarRandhawa deleted the codex-background-commands branch August 3, 2026 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant