Skip to content

Read Agent SDK session records, and surface ones that cannot render - #133

Merged
hbrooks merged 1 commit into
mainfrom
sdk-0.15-agent-sdk-records
Aug 24, 2026
Merged

Read Agent SDK session records, and surface ones that cannot render#133
hbrooks merged 1 commit into
mainfrom
sdk-0.15-agent-sdk-records

Conversation

@hbrooks

@hbrooks hbrooks commented Aug 24, 2026

Copy link
Copy Markdown
Member

Summary

  • Adopt @ellipsis-dev/sdk@0.15.0, whose store reads the Agent SDK record shape (claude_sdk@1: discriminated by kind, flat envelope, cost_usd per turn). The pinned 0.13.0 read the old stream-json shape, so every user and assistant record rendered as zero rows.
  • Show a one-line count in the footer status bar when records arrive that render nothing, so the next wire change is not invisible. Clears on your next send.
  • A record whose payload the reader throws on now costs one row, not the whole render.
  • Follow 0.15.0's other renames: rate cards moved cents -> millicents, CCEvent -> SdkRecord.

Why

session_7U5s22wfghWiHfq7qxeDWzNLJmzXb700wadFz7MH showed no messages in the CLI while rendering correctly in the dashboard. The server sent all 44 records and the socket held; the CLI received them, advanced its resume cursor, and rendered nothing, because eventToItems returns [] for a shape it does not recognize. Three layers drop silently (stream JSON parse, unknown frame type, unrenderable payload) and none of them said a word.

The footer count addresses the second half of that: the reader is now correct, but the next harness change should not cost a debugging session to notice.

Test plan

  • bun run test (414 pass)
  • bunx tsc --noEmit
  • agent session records session_7U5s… prints the user and assistant text where it previously printed raw JSON
  • reshapeTranscript over that session's real records: 5 rows, undisplayed: 0
  • agent session connect against a live session -- transcript renders, footer shows no warning

Important

Adopts @ellipsis-dev/sdk@0.15.0, whose agent records use a new discriminated shape (kind field, flat content) and scale rates in millicents instead of cents. Surfaces unrenderable records in the footer so wire changes do not render invisibly.

  • Records now use kind instead of type, have content directly instead of nested in message, and use record_format: 'claude_sdk@1' to discriminate across harness versions.
  • Rate card fields renamed: input_cents_per_1m_tokensinput_millicents_per_1m_tokens (1000x multiplier). rateDollars updated to divide by 100,000 instead of 100.
  • reshapeTranscript now returns { items, undisplayed } counting agent records that arrive but render nothing — from unrecognized record_format, empty content, or reader errors.
  • Records whose payload throws are caught and rendered as zero rows (not the whole transcript), and included in the undisplayed count.
  • Footer shows a one-line warning ("N events could not be displayed") when undisplayed > seen, clearing on your next send so only new drops are visible.
  • Renamed CCEventSdkRecord, updated test fixtures to include record_format field.

This description was created by Ellipsis for 62e8756. It will automatically update as commits are pushed.

Sessions run on the Agent SDK now, whose records carry a different payload
shape (claude_sdk@1). The pinned SDK could not read it, so the chat and
`session records` dropped every user and assistant message with no error
anywhere: the record still arrived, still advanced the resume cursor, and
rendered zero rows.

Adopt SDK 0.15.0 for the reader, and make an unrenderable record visible
instead of silent -- the footer counts them, since a transcript that quietly
omits what was said is worse than one that admits it cannot read it.
@hbrooks
hbrooks merged commit 7a0a02c into main Aug 24, 2026
1 check passed

@ellipsis-dev ellipsis-dev Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Changes requested ❌ — 1 issue

Reviewed 62e8756 in 9 minutes, 20 seconds.
  • Reviewed 1 commit with 415 lines of code in 8 files
  • Ran 1 review agent producing 1 comment where 1 was posted
  • This pipeline runs no gatekeeper, so findings are posted as written.
  • View full details on ellipsis.dev

This review was created by Ellipsis. You can tag @ellipsis in this pull request.

Comment thread src/ui/ConnectApp.tsx
} catch {
rendered = []
}
if (rendered.length === 0 && r.record_type !== 'system') undisplayed++

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The undisplayed count treats records the SDK reader is silent on by design as drops, so healthy sessions get the "this CLI may be out of date" footer; only record_type === 'system' is excluded, but eventToItems also returns [] for kind: 'rate_limit' (part of the persisted claude_sdk@1 union) and codexEventToItems returns [] for every codex frame except item.completed/error/turn.failed.

Verified against the installed 0.15.0 by calling reshapeTranscript directly: a rate_limit record yields {items: [], undisplayed: 1}, and a normal six-record codex turn (thread.started, turn.started, item.started, item.updated, item.completed, turn.completed) yields one rendered row and undisplayed: 5. Since agent session connect does not filter by harness and the platform persists every Codex exec --json ThreadEvent verbatim (source=codex, codex_jsonl@1), connecting to a codex session shows e.g. "37 events could not be displayed, this CLI may be out of date" while rendering the transcript correctly — and because the warning takes the whole meta line and only clears on a send, a watch-only or closed session (canSend false, --no-input) loses status/spend/session id for the rest of the run.

Suggested change
if (rendered.length === 0 && r.record_type !== 'system') undisplayed++
// Silent BY DESIGN is not a drop: claude system/rate_limit rows, and the
// codex frames that carry no row of their own (thread/turn/item.started,
// item.updated, turn.completed).
const silentByDesign =
r.source === 'codex'
? r.record_type !== 'item.completed' && r.record_type !== 'error'
: r.record_type === 'system' || r.record_type === 'rate_limit'
if (rendered.length === 0 && !silentByDesign) undisplayed++

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