Skip to content

feat(context): one window-style breakdown with Kiro Crew first - #24038

Open
chenmingwei23 wants to merge 2 commits into
mainfrom
feat/context-breakdown-panel-24030
Open

chenmingwei23 wants to merge 2 commits into
mainfrom
feat/context-breakdown-panel-24030

Conversation

@chenmingwei23

@chenmingwei23 chenmingwei23 commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: Show what filled a session's context at its newest turn as one window-style breakdown, with Kiro Crew's own share first and in one place, and keep the fold behind it a fixed size.

#23648 recorded the engine's per-turn context breakdown, but the Context panel showed it as a separate block of numbers under Kiro Crew's own rows. Readers could not see at a glance what filled the context, and Kiro Crew's share was split across two places. Turns from harnesses that do not report a breakdown (kiro-cli) showed only Kiro Crew's characters under a token headline, so the rest read as missing. The usage fold also kept up to 200 per-turn rows with per-block labels, so its state and every pushed update grew with the session.

Why it matters

The Context panel is where a user checks why a session is slow, expensive or near its window. If Kiro Crew's own blocks are scattered and the rest of a kiro-cli turn reads as missing, the user cannot tell whether Kiro Crew or the engine is filling the context. The growing fold cost every push on long sessions about 196 KB and 0.8 ms of on-loop json.dumps at 1,000 turns.

Not a goal

  • Known gap, tracked in Context breakdown: record context for cron, sub-agent and Task Runner turns #24523: cron, sub-agent and Task Runner turns are not measured, because they open no crew-log turn, so their measurements have nothing to attach to (the fold refuses them, and a test pins that).
  • Turns KAS starts on its own (KAS workflows) are not measured: Kiro Crew does not enable KAS workflows today, and the session-level declaration will be added when it does.
  • A breakdown for harnesses whose engine does not report one. Those turns show Kiro Crew's estimate and one "Rest of the context" row.

What changed (motivation -> approach -> change)

Goal: one picture of the newest turn, Kiro Crew first, rows that add up to the total. Approach: a window-style tree (headline, bar, flat list) drawn from one fixed-size fold state, so the panel reads the newest state and nothing grows.

Panel

  • Row 1 is always Kiro Crew (rules, memory, skills, session details), a group that can be folded and opens by default. "Kiro Crew session details" (was "Kiro Crew other") holds every Kiro Crew block outside the other three: the date, runtime, workspace, user, folder, replayed history, hook context and reply format rules. The subtitle reads "What the AI's context held, and how it grew.", true for the newest turn and a picked one. Your messages is row 2 on every turn.
  • Then the engine's categories, largest first, zero rows hidden: Messages (history less Kiro Crew's blocks: replies, reasoning, summary, other text sent as you), Built-in tool definitions, Tool calls and results (Calls / Results), MCP tools, Steering and agent resources, Engine system prompt, Agents / Skill list (engine) / Powers with counts, Workspace, Memory, Attached files, Other.
  • Engine rows are named apart from Kiro Crew's own: "Kiro instructions" -> "Engine system prompt" (beside "Kiro Crew rules"), "Skills list" -> "Skill list (engine)" (beside "Kiro Crew skills"), "Other user text" -> "Other text sent as you" (beside "Your messages"), in every locale. Rows add up to the request's characters. If Kiro Crew's sums exceed the history, Messages is held at 0 and a line says so.
  • One stacked bar (Kiro Crew first, in its own colour family) plus a thin window bar ("3% of the 1M-token window"). The headline reads "N characters - about M tokens, from the window reading". Images and files get their own line outside the total.
  • The engine's own "Other" has its own pink (--ctx-eng-other) in the list and the bar. Grey means only "Rest of the context", in the list and in the chart.
  • Turns the engine does not split (kiro-cli and other harnesses): every row is in tokens so the rows add up to the harness total. Kiro Crew and Your messages are estimated at the composer's characters-per-token ratio and marked "~", with characters beside them. The remainder is Rest of the context, with an info tip that names the session's harness, the place to switch, and what switching does ("New sessions then use Kiro Agent; this session keeps its current agent."). If the estimate exceeds the total, the remainder is held at 0 and a line says so.
  • In a narrow panel (the 320px side panel) the grey character count goes under the tokens and labels wrap instead of being cut; from 26rem of panel width they sit side by side. It is a container query on the row list, so it follows the panel, not the window.
  • On Kiro Agent, a turn not split yet (its measurement has not arrived) gets the tip "Estimated. The engine hasn't measured this turn yet." (a past turn: "Estimated. The engine didn't measure this turn.") in every locale. The switch-harness tip shows only on other harnesses. A text tag beside the (i) says which tip it is before any hover: "Not measured yet" (newest Kiro Agent turn), "Not measured" (a past one), "How to split it" (other harnesses).
  • On a harness that never splits, the trend chart is in estimated tokens ("Unit: tokens (estimated)", characters / characters-per-token), so it matches the token headline and the start row's tokens; elsewhere it stays in characters. Each token column carries a "Rest of the context" band up to that turn's own harness total (each ring row now carries its occupancy reading, used), so the line reaches the 84,000-token headline, and the band is in the legend. A turn with no total gets no such band, and the caption adds "Turns with no reported total have no grey band."
  • If the selection-capabilities read fails (for example a 503), its error is shown beside the trace's, with Ask the agent, and "Rest of the context" gets no tip, since the harness is not known.
  • Numbers with units go through src/i18n/format.ts (fmtCompact for the window size, fmtBytes for media), and the label tables store full catalog keys.
  • Any kept turn can be picked. Each of the 31 rows the chart keeps holds its turn whole in the fold state (not in the pushed value; see Backend): its Kiro Crew blocks by name (up to 64, the composer's own most), a copy of its KAS measurement (14 categories, history split, list counts, toolIO, workspace and memory splits, window, media, time to first output) and its own occupancy reading. Clicking a column, the arrow keys, the nearest-turn surface on a narrow plot, or the session-start row reads that turn from GET /api/telemetry/context-trace?slot=<key>&turn=<ordinal> (cached by slot, ordinal and that row's own numbers in the trace, so a new turn elsewhere re-reads nothing; a loading line, or the error, shows in place on the first read, and while a changed row is re-read the previous read stays on screen with its disclosures open) and shows its panel exactly like the newest one, with a disclosure listing its blocks; a turn the engine did not measure shows the estimated view for that turn. The pick is the turn's ordinal, so a new turn shifting the full ring keeps the panel on the picked turn; once it leaves the ring the newest is shown with a note. While a past turn is picked, a "Show latest turn (N)" button returns to the newest turn, and Escape or Home on a chart column does the same; this matters when the newest turn is still open and not yet a column. The blocks disclosure counts every block the turn sent, before the every-turn members are grouped into one list row and including the ones past the list ("13 Kiro Crew blocks sent this turn": 7 sent, 5 rows listed, "6 more blocks not shown"). The start row names itself as a picker ("Show turn N (session start)", with a visible Show / Shown above), marks itself while its turn is shown, and states the same size as that turn's headline; a session-start turn is called that everywhere, plus "latest" when it is also the newest.
  • The trend chart keeps the newest 30 turns, each labelled with its own ordinal. The legend lists only non-zero bands.

Backend

  • The context part of the usage fold is a fixed-size state: five running sums (restarted by a new unit or a compaction), the newest engine measurement, the newest occupancy reading (reset by a new unit), a 30-row trend ring, one session-start row, and the newest 200 dated occupancy readings plus the configured window for the peak (below); each row holds its turn whole (see above). Every apply touches at most one row. A test folds 210 and 1,000 turns and asserts the same state shape.
  • The pushed value is slim. The fold keeps every row whole, but it renders (and so pushes) only the newest turn whole: each ring row is its ordinal, the five sums, loop and its own reading used, and the start row adds figure, the size its panel heads with. A real session pushes about 6 KB.
  • A past turn is a separate read. GET /api/telemetry/context-trace?slot=<key>&turn=<ordinal> serves one kept row as a whole-turn view in latest's shape, from the in-memory fold state (a new read_slot_state reader, the same warm cell read_slot_projection renders), never from a crew-log read-back. Same dashboard-only gate and app-caller 404 as the trace; 400 turn_invalid unless turn is a positive ASCII integer, 404 turn_not_kept once the turn left the ring. The trace page carries revision, and its turns[] (the Session Breakdown tree) come from the state's per-turn blocks.
  • The occupancy peak is unchanged. peak_context_used / context_window is still one pair from the FULLEST turn closed inside days: the highest used / window ratio (integer cross-multiplication, >= keeps the newer of equally full turns, a reading with no window is not a candidate), else 0 and the configured window. The state keeps the newest 200 dated readings with a window (peaks, the reach the old 200 rows had) and the unit's newest non-zero request/configured window (conf_window), and context_peak takes the pair from them. A session at 90k of a 100k window that switches to a 1M window at 10k reports 90%, not 1%.
  • Frame-level delta push on the crew-log bus is tracked separately; this PR does not change bus.py or FoldAdvanced.
  • Each ring row stores the ordinal it closed at, so a slot with several session starts labels its rows correctly (starts at ordinals 1, 3 and 5 give trend [2, 4, 6] and one earlier turn).
  • A turn sent again (a retry, a regenerate, an edit-resend of an earlier message, which truncates the conversation and recurs at a lower turn number) keeps its ordinal and replaces its own ring row, so no two bars carry one label. Ring rows also store the unit and turn number they were composed under, for that lookup (nine integers each). The new attempt starts unmeasured: the previous attempt's breakdown, time to first output and engine turn id are dropped until it reports its own.
  • A rewind, regenerate or edit-resend reads the turn's ordinal from a bounded memo of the unit's turn numbers (turn_memo, at most CONTEXT_ORDINAL_MEMO_LIMIT = 10,000, pinned equal to the slot's live message cap), so a rewind far past the ring keeps its ordinal and cuts the ring rows after it. A refused turn and a turn that closes without composing (a slash command) take an ordinal too, once per turn number. A new, non-resumed session also clears the stored engine measurement, so the panel shows no breakdown left from the previous session.
  • An in-place compaction the gateway runs (kiro-cli; compaction/applied, which the engine does not measure) restarts Kiro Crew's resident sums: they keep only what was composed after the newest turn that closed with an occupancy reading. The verdict lands either before the next turn composes or, when deferred, at the next turn that carries a reading while that turn is still open, so a deferred verdict keeps the prompt composed before it arrived. Without this, a session with 40,000 injected characters, then a 1,000-character prompt and a 1,500-token reading, showed the old 41,000 characters as resident and the rest of the context as 0.
  • parse_breakdown keeps the summary-mode fields: per-category percent, list counts, toolIO input/output, workspace and memory splits, window size and usage, and media counts. context/measured declares them.
  • GET /api/telemetry/context-trace carries the session's harness id so the tip can name it.
  • Specs: metrics.md (context_trace and the panel), the usage row in crew-log-projection.md, fold-paths.md (derived by calling the folds) describe the fixed-size state. The dashboard catalog declares the new nullable leaves.

i18n: every locale updated for the new and changed strings, three plural keys registered, en-XA regenerated.

Size (old fold vs this one), measured with the fold's own step over 1,000 real-shaped turns (16 Kiro Crew blocks each, the captured KAS frame on every turn for "KAS", no frame for "Kiro CLI"), and the worst case built by worst_case_context():

Old fold, 1,000 turns KAS, 1,000 turns Kiro CLI, 1,000 turns Worst case
Context state in memory 111,123 B 105,784 B 90,028 B 928,582 B
Whole usage value pushed per update (JSON) 196,181 B 6,849 B 5,913 B
Its context part (JSON) 6,236 B 5,300 B 21,074 B
On-loop json.dumps per push (mean) 798 us 49 us 40 us
One ?turn= read (JSON) 1,976 B 651 B 15,625 B

The state is bounded whatever the session's length: 31 rows (the 30-turn ring and the session-start row), each at most 64 blocks with names under TEXT_LIMIT plus one engine measurement, an ordinal memo of at most 10,000 turn numbers (the slot's live message cap, the range a rewind can reach), and at most 200 peak readings of three integers each. It stays under the slot-fold cache's 995,342-byte member ceiling, and test_the_worst_case_context_state_is_bounded_under_the_cache_ceiling pins it under 950,000 B. The worst pushed context part is the newest turn's 64 blocks at the longest name plus a full engine view; test_the_worst_case_pushed_value_is_a_few_kb pins it under 22,000 B (20,908 B compact).

Decision changed. An earlier pin said the trace carries no chars-per-token estimate of the remainder of a turn the engine does not split. It is reversed because the owner found the panel misleading without it (the rows did not add up to the headline: 85,339 tokens on top, 49,338 characters of rows below), so the remainder is now shown as "Rest of the context", marked "~" and captioned "characters / 4".

Backwards compatibility

Breaking: the usage fold's rendered context drops by_source, window, sources_omitted and measured_unknown, and context.turns changes from the 200-row list to the turn counter. GET /api/telemetry/context-trace keeps every top-level key except engine_unknown, which nothing reads; its turns, totals and injected_chars are built from the trend, each turn row is {phase, blocks, total_chars, ordinal, first_start} (the always-blank ts, model, context_used and context_window are gone, since the state keeps none of them per turn), peak_context_used / context_window keep their meaning (the fullest turn inside days), and days selects nothing else. Writer sweep on origin/main @ 5631aab: the fold is the only writer, and the readers are below. The fold's state version moves, so old savepoints retire to a cold fold.

Reader: src/kiro_crew/dashboard/handlers/usage_context.py:context_trace -- crew page -- reads latest/trend/start from the new state; test/metrics/test_context_trace.py, test/metrics/test_context_measured.py
Reader: website/src/pages/SessionBreakdownTree.tsx:SessionBreakdownTree -- chat -- reads turns/totals/injected_chars/peak_context_used, which the trace still returns; website/src/test/SessionBreakdownTree.test.tsx
Reader: website/src/pages/ContextBreakdownPanel.tsx:ContextBreakdownTab -- chat -- reads latest/trend/start/earlier, and a past turn from ?turn=, since rows no longer carry detail; website/src/test/ContextBreakdownPanel.test.tsx, website/src/test/ContextBreakdownTab.push.test.tsx
Reader: src/kiro_crew/dashboard_types.py:_OPAQUE -- app -- the two removed keyed maps are dropped and the new nullable leaves declared; test/test_dashboard_types.py
Reader: scripts/fold_catalogue.py:main -- release -- the committed catalogue matches the code; test/test_fold_catalogue.py
Reader: src/kiro_crew/crew_log/projection_usage.py:_usage_step -- crew page -- the only caller of measured_start/measured_step/note_composed/note_closed/note_unit_start/decode_measured, now calls context_start/context_apply/context_render; test/test_crew_log_projection.py
Reader: src/kiro_crew/crew_log/projection.py:_EXPORTS -- crew page -- the facade served those six names and now serves the new ones; test/test_projection_facade.py, test/test_entry_types_facade.py

Tests

  • Slim view and the turn read: test_the_pushed_value_carries_no_per_row_detail (ring rows are {turn, ours, loop, used}, the start row adds figure, the newest turn is whole), test_the_worst_case_pushed_value_is_a_few_kb, test_a_kept_turn_reads_whole_and_one_that_left_the_ring_reads_as_none (test/metrics/test_context_measured.py); test_a_kept_turn_reads_whole_and_one_not_kept_is_404, test_a_turn_that_is_not_a_positive_integer_is_400 (8 cases), test_an_app_caller_asking_for_a_turn_is_refused_the_same_way (test/metrics/test_context_trace.py); the picker: asks for a picked past turn the trace does not carry, and shows that turn's read, heads the start row with the size the trace sends for it (ContextBreakdownPanel.test.tsx) and reads a picked past turn from the turn route, once per version of its row and keeps a picked past turn on screen, disclosures open, across new revisions (a new turn elsewhere re-reads nothing; turn 2 sent again is re-read while the old read stays shown, blocks list open; ContextBreakdownTab.push.test.tsx, the second fails on the previous head). All but the app-caller test fail on the previous head.

  • The peak, restored from the base unchanged: test_peak_is_the_fullest_ratio_not_the_largest_used_across_a_switch, test_the_occupancy_pair_comes_from_one_turn, test_a_reading_whose_window_is_unknown_is_not_a_peak_candidate, test_the_window_falls_back_to_the_configured_size_with_no_reading_yet, test_a_peak_outside_the_day_window_is_not_reported (its old turns == [] line dropped, since days no longer filters turns) and test_an_open_turn_does_not_drag_the_peak_down (test/metrics/test_context_trace.py); test_the_configured_window_is_the_newest_stated_in_this_unit (test/metrics/test_context_measured.py). The first, second, fourth and fifth fail on the previous head.

  • The catalog: usage.context.start is declared as {turn, ours, loop, used, figure} with figure nullable {n, unit}, and usage.context.latest gains blocks (opaque, keyed by block name) and blocks_omitted. test_every_declared_nullable_type_matches_what_the_fold_writes now fails on a declared leaf the driven render does not contain (checked by declaring start.crew_raw again: it fails).

  • The token chart and the blocks count: fills each token column up to its own harness total with the rest band and counts every block sent in the heading, before grouping and past the list (ContextBreakdownPanel.test.tsx); both fail on the previous head.

  • The way back and the readability fixes: always offers the way back to a running newest turn that is not a column yet (pick a column, then the button, Escape and Home each return to the newest turn), speaks tokens in the chart when the panel does, on a harness that never splits, names which tip it is before any hover and names the engine rows apart from the Kiro Crew rows (ContextEngineBreakdown.test.tsx). Each fails on the previous head.

  • website/src/test/ContextBreakdownPanel.test.tsx: the three restored picking tests (moves the detail to a clicked turn and back with the arrow keys, selecting the start row drives the detail like any other turn, keeps every turn reachable: one pointer surface maps a click to the nearest turn), a past turn shows its own block names and its own engine rows, keeps the picked turn when a new turn shifts the full ring, and says so once it leaves (pick turn 12 of ring 2..31, complete turn 32: still turn 12), names the start row as a picker and marks it while its turn is shown, and calls a session-start turn that is also the newest one thing in both places; the last three fail on the previous head.

  • test/test_crew_log_projection.py: test_usage_a_rewind_past_400_turns_keeps_the_turns_ordinal and test_usage_the_ordinal_memo_covers_the_rewindable_range, restored; the first fails on the previous head (ordinal 1001, not 10). test/metrics/test_context_measured.py: test_a_new_session_shows_no_breakdown_from_the_previous_session (fails on the previous head) and test_the_session_tree_draws_what_each_turn_sent_not_the_running_sum (three turns of 1,000 draw 1,000 each).

  • test/metrics/test_context_measured.py: test_a_past_turn_keeps_its_own_blocks_and_its_own_engine_breakdown, test_a_row_keeps_at_most_the_composers_blocks_and_counts_the_rest, and test_the_worst_case_context_state_is_bounded_under_the_cache_ceiling (31 full rows, 894,000 B).

  • test/metrics/test_context_measured.py: test_a_rewound_turn_reuses_its_ordinal_not_a_new_one (turns 1, 2, 3 then turn 2 again: ring 1, 2, 3 once each, turn 2's row is the resend's; the old fold gave [1, 2, 3, 3]) and test_a_new_attempt_drops_the_previous_attempts_measurement; both fail on the previous head.

  • test/metrics/test_context_trace.py::TestEveryTurnTakesAnOrdinal, restored from the base: a composition-less turn (composed 1, slash command 2, composed 3 gives rows 1 and 3), retried once, two in a row, as the first turn, a turn composed twice, a refused turn, refused twice, and a session start after a refused turn 1. The four composition-less tests fail on the previous head ([1, 2] for [1, 3]).

  • test/metrics/test_context_trace.py: test_a_turn_carries_only_fields_with_a_value_and_a_reader (a turn row is exactly {phase, blocks, total_chars, ordinal, first_start} and the page has no engine_unknown).

  • website/src/test/ContextBreakdownTab.push.test.tsx: shows a failed harness read and gives no harness advice (fails on the previous head).

  • website/src/test/ContextEngineBreakdown.test.tsx: the Kiro Agent tip says "Estimated. The engine hasn't measured this turn yet." with no switch path; the engine's Other is --ctx-eng-other in the list and the bar, Rest is --ctx-eng-loop; in a narrow panel the characters stack under the tokens and the label wraps.

  • test/metrics/test_context_measured.py: test_an_in_place_compaction_keeps_only_what_was_composed_after_it (40,000 injected, compaction applied, then a 1,000-character prompt and a 1,500-token reading: resident 1,000, rest of the context > 0) and test_a_deferred_compaction_verdict_keeps_the_prompt_composed_before_it_arrived; both fail on the previous head (41,000 resident).

  • test/metrics/test_context_measured.py: test_every_row_keeps_its_own_ordinal_across_several_session_starts (starts at 1, 3, 5 give trend [2, 4, 6], earlier 1) and test_a_new_session_does_not_show_the_previous_sessions_occupancy; both fail on the previous head. Also the fixed-size state, the O(1) apply, compaction and unit restarts, the ring bound and the refused orphan measurement.

  • test/test_dashboard_types.py: the new nullable leaves are declared and driven to real values.

  • Python, the test files that reference the touched modules: 8,096 passed (163 files). One failure, test_app_agent_launch_copy.py::...[opted-out], fails the same way on the base commit and is unrelated.

  • vitest: ContextEngineBreakdown, ContextBreakdownPanel, ContextBreakdownTab.push, catalogParity, registerCatalogs (1,375 passed across the panel, Session Breakdown and src/i18n tests). npm run i18n:check against the merge base: every check passes (pages/chat/ErrorCard.tsx appears only in the report-only dynamic-keys list, which this branch does not touch). tsc, eslint --max-warnings 0 on the touched files, mypy, flake8, isort, black, docs lint, the comment-history gate and the fold catalogue check.

Manual verification

  • A pod with real turns on KAS (chat, shell tool, attached image) and on kiro-cli, at an earlier head: the breakdown, the window bar, the tip and the trend rendered from real traces. Those shots showed the old start row and the old names, so they are no longer attached.
  • Every shot below is rendered from this head in Storybook, from fixture traces shaped like the captured KAS summary frame (test/fixtures/acp_frames/kas/profile/).

Screenshots / video

All from this head (Storybook, light unless noted). KAS turn, Kiro Crew first, the engine rows named apart from Kiro Crew's (Engine system prompt, Skill list (engine), Other text sent as you), the engine's Other in pink:

KAS turn, light

KAS turn, dark:

KAS turn, dark

KAS turn at 560px:

KAS turn, wide

KAS turn in the 320px side panel, labels wrap:

KAS turn, narrow

kiro-cli turn, Kiro Crew folded: rows in tokens marked ~, the chart in estimated tokens with a "Rest of the context" band up to each turn's harness total (turn 6 reaches the 84,000-token headline), the start row in tokens, and the "How to split it" tag beside the (i):

kiro-cli, chart in tokens with the rest band

The kiro-cli tip, which says what switching does:

Rest of the context tip, kiro-cli

Kiro Agent turn not measured yet: the "Not measured yet" tag and its tip:

Kiro Agent turn not measured yet

Turn 9 is still running and not a column yet; turn 5 was picked from the chart. The heading has no "latest", the column is marked, and "Show latest turn (9)" goes back (so do Escape and Home on a column):

Past turn 5 picked, Show latest turn

The session-start row picked: "Shown above", the same 168,000 characters as the headline:

Start row picked, Shown above

The blocks disclosure open on a picked turn: 13 blocks sent (7 by name, two of them grouped into "Every-turn instructions", so 5 rows) and "6 more blocks not shown":

Blocks open, more not shown

Turn 11 was picked, then turn 41 closed and pushed it out of the last 30: the note says so and the newest turn is shown:

Picked turn has left the chart

The selection-capabilities read failed (503): its error with Ask the agent, and "Rest of the context" has no tip or tag:

Harness read failed

kiro-cli session whose newest turn is the session start: the start row shows the headline's own tokens and "Shown above":

kiro-cli session start is the newest turn

320px side panel, kiro-cli turn with about 400,000 Kiro Crew characters: characters under the tokens, labels wrap. Turn 3 reported no total, so its column has no grey band and the caption says so:

Narrow panel, kiro-cli

320px side panel, Kiro Agent turn not measured yet, with its tag and tip:

Narrow panel, Kiro Agent tip

The same kiro-cli turn at 560px, values side by side, and turn 3's column without a total:

Wide panel, kiro-cli

Related Issues

Closes #24030. Follow-up to #23648.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

Contribution License Agreement

Contributed under the project's Apache-2.0 license.

@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Oct 25, 2026
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Design Review (Opus 5.5) — ✅ PASS

Design-level review of 496f15dd35c9f9b5ce7ef42b28ecdbb01f9641e6 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

Design-Verdict: PASS

No design-level blocker: the fold state version is bumped so saved checkpoints get re-folded, the trace keeps the field names its untouched reader uses, and 16 rendered screenshots cover the surface.

Notes

  • SessionBreakdownTree now gets blocks merged into five category labels by _as_blocks ("rules": "lessons", "skills": "loaded_skill", …), not the per-label detail it had before. That coarser tree is a visible change to a surface this PR doesn't otherwise touch, and the description should say so.
  • The Backwards compatibility section is past the description's truncation point, so I didn't judge it. The fields removed from the usage fold (context_by_source, context_turns, context_measured_unknown) have no readers left in the tree.

[DESIGN-REVIEWED] 496f15d

Description read

[DESCRIPTION-READ] 1961ceeab2057d8d5b775c1e725061ecbce2c222fda4f5c319fcb132af28e296

That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

First Principles Review (Opus 5.5) — ✅ PASS

Premise-level review of 496f15dd35c9f9b5ce7ef42b28ecdbb01f9641e6 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

First-Principles-Verdict: PASS

Nothing blocks. Two riders that the visible description does not mention leave surface behind that can be deleted: a dead unknown-category branch and a re-derived set of Session Breakdown totals.

Notes

  • Item 8 — one consumer, generalized: breakdownOf(latest, unknownNames = [], …) has 1 production caller (ContextBreakdownPanel.tsx:726), and it passes []. The engine_unknown map that fed it is deleted (- "engine_unknown": (), so the engine_unknown_named branch can never fire. Subtraction: delete the unknownNames parameter, the engine_unknown_named branch at ContextEngineBreakdown.tsx:385 and its locale keys.
  • Item 9 — undeclared: the tree's totals are now _as_blocks(latest.get("ours")), five relabelled category sums. Before, they were per-label sums over the turns, and the same function still builds those turns from context_turn_blocks. Subtraction: delete _CATEGORY_LABEL/_as_blocks and sum the turns[].blocks already built a few lines above.

What this change ships

Inventory (10 items) — 8 justified

Intent: let a user see at a glance what filled a session's context at its newest turn, with Kiro Crew's share in one place, while keeping the fold that holds it a fixed size. This is an ADDITION (a panel redesign plus a fold restructure).

  1. The Context panel is one window-style breakdown (headline, bar, flat list), with the Kiro Crew group first — justified
  2. Engine rows are renamed: "Engine system prompt", "Skill list (engine)", "Other text sent as you", "Kiro Crew session details" — justified
  3. A turn the engine does not split shows estimated tokens, a "Rest of the context" row and a harness tip, and selection-capabilities now returns backend — justified
  4. KAS percent, list counts, splits, window and media are recorded on context/measured — justified
  5. On a harness that never splits, the trend chart is in estimated tokens with a grey band — justified
  6. Picking a past turn reads GET context-trace?turn= from the fold state, which keeps the existing per-turn selection — justified
  7. The usage fold's growing 200-row list is replaced by a fixed ring of 30 rows plus the start row, and by_source is removed — justified
  8. Unknown engine categories are no longer named; a dead unknownNames branch remains — undeclared, one consumer, generalized
  9. Session Breakdown tree bars come from Crew's five sums, and its turns lose time, model and occupancy — undeclared
  10. Peak occupancy comes from 200 kept dated readings — justified

[FIRST-PRINCIPLES-REVIEWED] 496f15d

Description read

[DESCRIPTION-READ] 8863f2df6937826b8f1713677f56c4c6c8086588f5d6ca4e7bf988acee9c81b4

That sha256 names the PR description this verdict read. Recompute it with .github/scripts/pr-description-capture.sh; if it differs, the description changed after this verdict was formed and any finding drawn from the description is unproven.

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

UX Review (Opus 5.5) — ✅ PASS

UX-level review of 496f15dd35c9f9b5ce7ef42b28ecdbb01f9641e6 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

UX-Verdict: PASS

No primary control was misread, and the screenshots cover every new state: past pick, start-row pick, the evicted-pick note, the tips, the error and narrow widths.

Notes

  • When a Kiro Agent turn hasn't been measured yet, the panel mixes units (shot 7). The headline reads "38,500 tokens" while "Unit: characters" sits over a 200,000 axis and "168,000 characters" in the start row. The blind reader said "the chart's numbers can't be compared with the big number". Draw the chart in estimated tokens for this case, as the never-split path already does.
  • The disclosure "N Kiro Crew blocks sent this turn" shows no chevron. The reader said "nothing on the line looks clickable". Give it the same chevron the Kiro Crew group row uses.
  • The newest turn, when it isn't a chart column yet, has no marker. Shots 7, 14 and 16 show "Turn 9 · latest" next to a chart ending at Turn 8, and "All 7 turns" next to "Turn 8 · latest" reads as a wrong count. Add a "Turn N (open)" end tick, or say in the caption that the newest turn isn't drawn yet.

[UX-REVIEWED] 496f15d

Description read

[DESCRIPTION-READ] b8c6c8ced4c69ab2b2b38248f51875186a280e4ce7c3b85c9f2d56b77d9936f7

That sha256 names everything this verdict read: the PR description, plus the 19 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Oct 25, 2026
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

GPT 6.1 Review — ✅ no blocking findings

GPT 6.1 completed its review of 496f15dd35c9f9b5ce7ef42b28ecdbb01f9641e6 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 496f15d

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 496f15dd35c9f9b5ce7ef42b28ecdbb01f9641e6 false-positive: <reason, 20 to 500 characters>

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Opus 5.5 Review — ✅ no blocking findings

Reviewed 496f15dd35c9f9b5ce7ef42b28ecdbb01f9641e6 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 496f15d

Verdict parsed from the review's SHA-scoped output markers for commit 496f15dd35c9f9b5ce7ef42b28ecdbb01f9641e6.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 496f15dd35c9f9b5ce7ef42b28ecdbb01f9641e6 false-positive: <reason, 20 to 500 characters>

@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 25, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from 4dc6d9b to 0f4272a Compare October 25, 2026 17:16
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 25, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from 0f4272a to 61baa40 Compare October 25, 2026 18:19
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 25, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from 61baa40 to 979b978 Compare October 25, 2026 19:50
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 25, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from 979b978 to 89131f9 Compare October 25, 2026 21:04
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Oct 25, 2026
@github-actions github-actions Bot added the readiness: action required A blocking check or review needs attention label Oct 26, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from 02cd927 to 553e3e6 Compare October 26, 2026 01:27
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Oct 26, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from 553e3e6 to c4c73c3 Compare October 26, 2026 01:54
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Oct 26, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from c4c73c3 to 7f4ecbf Compare October 26, 2026 02:52
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 26, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from 7f4ecbf to 02e1b45 Compare October 26, 2026 04:26
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 26, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from 02e1b45 to db9c0c4 Compare October 26, 2026 05:37
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 26, 2026
@chenmingwei23
chenmingwei23 force-pushed the feat/context-breakdown-panel-24030 branch from db9c0c4 to aa3cc85 Compare October 26, 2026 06:22
@chenmingwei23

Copy link
Copy Markdown
Contributor Author

Author confirmation for the First Principles "unverified premise" item (_meta.kiro.automaticTurnProfiling on session/new, session/load and the model probe):

  • Local KAS: verified. With kiro-cli 2.34.0 (kiro-cli acp --agent-engine v3), session/new and session/load both carrying the field succeeded with no error, and the prompts that followed ended normally. The redacted capture is in the PR body.
  • Cloud KAS: not exercised from this branch. The KAS performance metrics guide (section "Turns the agent starts") states that the cloud create and load paths drop this field, so it is ignored there rather than rejected.
  • Keeping it is intended: agent-started turns (send_message wakes, workflow notifications and steps) must be measured, otherwise per-session and per-project totals undercount.

Follow-up to #23648. The Context panel now shows the newest turn as one
list and one bar instead of a separate "measured" section.

Panel
- Row 1 is always Kiro Crew (rules, memory, skills, other), open by
  default; Your messages is row 2 on every turn.
- Then the engine's categories by size: Messages (history less Kiro
  Crew's blocks), built-in tool definitions, tool calls and results
  (Calls / Results), MCP tools, steering and agent resources, Kiro
  instructions, agents / skills / powers lists with counts, workspace,
  memory, attached files, other. Zero rows are hidden; rows add up to the
  request's characters.
- One stacked bar (Kiro Crew first in its own color family) and a thin
  window bar ("4% of the 1M-token window"). Images and files get their
  own line outside the total.
- Turns the engine does not split (kiro-cli and other harnesses) are
  shown in tokens so the rows add up to the harness total: Kiro Crew and
  Your messages are estimated at the composer's characters-per-token
  ratio and marked "~", the remainder is "Rest of the context", with an
  info tip naming the session's harness and the Settings path to Kiro
  Agent.
- Trend chart keeps the newest 30 turns; the legend lists only
  non-zero parts.

Backend
- The context part of the usage fold is a fixed-size state: five
  running sums, the newest measurement, the newest window reading, a
  30-row trend ring and one session-start row. Every apply is O(1) and
  the state is the same size after 10 and 1,000 turns.
- parse_breakdown keeps per-category percent, list counts, the toolIO /
  workspace / memory splits, window size and usage, and media counts.
- /api/telemetry/context-trace carries the session's harness id.

i18n: every locale updated for the new and changed strings; plural keys
registered; en-XA regenerated.

Not yet measured: cron, sub-agent and task turns open no crew-log turn,
so a measurement for them has nothing to attach to (follow-up).
Turns KAS starts on its own (KAS workflows) are not measured either:
Kiro Crew does not enable KAS workflows today (follow-up).

Refs #24030

Review fixes folded in:

Review fixes for the fixed-size context fold and the panel.

- Each trend ring row now stores the turn ordinal it closed at (seven
  integers per row), and the render reads it back instead of counting
  down from the newest turn. A slot with session starts at ordinals 1, 3
  and 5 now labels its rows [2, 4, 6] with one earlier turn.
- A new, non-resumed session resets the occupancy reading (used = -1,
  window = 0), so a new session never shows the previous one's tokens.
- A turn sent again (retry, regenerate, edit-resend of an earlier
  message) keeps its ordinal and replaces its own ring row, so no two
  rows share a number; ring rows also carry their unit and turn number
  for that lookup. Its previous attempt's measurement, ttft and engine
  turn id are dropped until the new attempt reports its own.
- An in-place compaction (compaction/applied) restarts Kiro Crew's
  resident sums: they keep only what was composed after the newest turn
  that closed with an occupancy reading, so a deferred verdict keeps the
  prompt composed before it arrived. Without it a kiro-cli session showed
  summarized-away prompts as resident and the rest of the context as 0.
- The owning specs describe the fixed-size state: metrics.md
  (context_trace and the panel), the usage row in crew-log-projection.md,
  fold-paths.md (derived from the folds).
- The dashboard catalog declares the new nullable context leaves
  (usage.context.latest, usage.context.start) and drops the two keyed
  maps the fold does not render; the usage fold's savepoint pin and
  the facade's type-checking imports are re-recorded.
- Panel: number + unit strings go through the i18n format helpers,
  table labels store full catalog keys, and the message colour is a
  design token. In a narrow panel the grey character count sits under
  the tokens and labels wrap instead of being cut. On Kiro Agent an
  unsplit turn's tip says it is not measured yet, instead of asking to
  switch harness (new key in every locale).
- Catalog hygiene: literal i18n keys at every call site, the product
  name through {{productName}}, the informal register and spacing each
  locale's style guide asks for, and the old panel's unused keys and
  their descriptions removed. Facade manifests, the retired facade
  names' inventory and the facade import paths are updated to match.
- The engine's own "Other" gets a hue of its own, so grey is only the
  rest of the context. The headline's token figure says it comes from
  the window reading, and the over-estimate warning names "Rest of the
  context".

@iamwhatever iamwhatever 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.

Request changes: a promised follow-up has no issue. Reviewed 01264b6fd063f902a8779c7eb932c543bb080d13.

The description's Not a goal says: "Known gap, left for a follow-up: cron, sub-agent and Task Runner turns are not measured." No issue is linked for it, and #24030 covers only the redesign. Please open one issue for that gap and link it in the description, or close the gap here.

Please also reply to the advisory notes on this head:

  • Design: trace turns rows now always carry empty ts/model, zero context_used/context_window, and engine_unknown: {} (usage_context.py#L296-L332). Drop them, or document them as legacy-only.
  • UX: the turn count in the chart heading, the missing column for an open newest turn, and mixed units on an unmeasured turn.
  • First Principles: the dropped 14-day row filter. The description does declare it.

The new ?turn= read sits behind the existing dashboard-only gate with strict input validation. All lanes are PASS or no findings, checks are green, and there are no change requests.

— crew-pr-reviewer (r127)

Each of the 31 rows the chart keeps (the 30-turn ring and the session
start row) now holds its turn whole, so picking a past turn shows it
exactly as the newest one is shown.

- A row stores the turn's Kiro Crew blocks by name (at most 64, the
  composer's own most; a name under TEXT_LIMIT; the rest counted), a
  copy of its engine measurement (14 categories, history split, list
  counts, toolIO, workspace and memory splits, window, media, ttft) and
  its own occupancy reading. The fold stays apply-only and bounded:
  31 rows times that per-row cap. A rerun still replaces its own row,
  found by ordinal.
- The trace handler turns each row into `detail`, a whole-turn view in
  `latest`'s shape, from the same engine-view builder.
- The panel: chart columns are buttons again (click, arrow keys, the
  nearest-turn surface on narrow plots) and the start row is one too.
  The picked turn's panel has the engine's rows when it was measured,
  the estimated view otherwise, and a disclosure listing its blocks.
  The pick is the turn's ordinal, so a new turn shifting the full ring
  keeps the panel on the picked turn; once that turn leaves the ring
  the newest is shown with a note. The start row names itself as a
  picker ("Show turn N (session start)"), marks itself while its turn
  is shown, and states the same size as that turn's headline; a turn
  is called "session start" wherever it is shown, plus "latest" when
  it is also the newest.
- A turn that closes without composing (a slash command) takes an
  ordinal, once per turn number, as a refused turn does, so the next
  composed turn is not numbered one short.
- A failed selection-capabilities read shows its error beside the
  trace's, and Rest of the context then gives no harness advice.
- Names: "Kiro Crew other" is "Kiro Crew session details" (every Crew
  block outside rules, memory and skills); the subtitle no longer says
  "at the newest turn"; a Kiro Agent turn not measured yet says
  "Estimated. The engine hasn't measured this turn yet."
- The pushed value is slim: the fold keeps every row whole but renders
  only the newest turn whole; a ring row is its ordinal, five sums and
  loop, and the start row adds the size its panel heads with. A real
  session pushes about 6 KB (was about 29 KB).
- A past turn is read on demand: GET /api/telemetry/context-trace
  ?slot=..&turn=<ordinal>, served from the in-memory fold state through
  read_slot_state (never a log read-back), with the trace's own gate;
  400 for a turn that is not a positive integer, 404 once it left the
  ring. The panel caches it by slot, ordinal and revision.
- A picked past turn always has a way back: "Show latest turn (N)", and
  Escape or Home on a chart column, return to the newest turn, also
  while it is still open and not yet a column.
- Engine rows are named apart from Kiro Crew's: "Engine system prompt",
  "Skill list (engine)", "Other text sent as you".
- The Rest of the context (i) carries a visible tag saying which tip it
  is ("Not measured yet", "Not measured", "How to split it"), and the
  switch tip says new sessions use Kiro Agent while this one keeps its
  agent.
- On a harness that never splits, the chart is in estimated tokens, to
  match the token headline and start row.
- The blocks heading counts every block sent: before the every-turn
  members are grouped, and the ones past the list.
- On a token chart each column carries a Rest of the context band up to
  its turn's own reading (ring rows carry `used`); a turn with no total
  gets none, and the caption says so.
- The occupancy peak keeps its meaning: one pair from the fullest turn
  closed inside the requested days, from the newest 200 dated readings
  the state keeps, else 0 and the configured window.
- The dashboard catalog declares what context_render writes for latest
  and start, and its driven test fails on a declared leaf it does not.
- A picked past turn's read is keyed by its own row in the trace, not
  the fold revision: a new turn elsewhere re-reads nothing, and while a
  changed row is re-read the previous read stays shown, disclosures open.
- Trace turn rows drop the always-blank ts, model, context_used and
  context_window, and the page drops engine_unknown: nothing reads them
  and the state keeps none of them per turn.
- The usage fold's state version moves, so older savepoints refold.

Refs #24030
@chenmingwei23

chenmingwei23 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

The follow-up for the cron, sub-agent and Task Runner gap is #24523; the "Not a goal" line now links it.

Replies to the r127 review (head 496f15d).

Design: blank fields on trace turns rows. Fixed in 496f15d. Nothing reads them: the Session Breakdown tree reads only blocks and total_chars, and the panel reads trend, not turns. Each row is now {phase, blocks, total_chars, ordinal, first_start}; ts, model, context_used and context_window are gone, and so is the always-empty engine_unknown map. The ContextTurn and ContextTrace interfaces, the fixtures and metrics.md say the same, and test_a_turn_carries_only_fields_with_a_value_and_a_reader pins the row shape.

UX: turn count in the chart heading. "All N turns" / "Last N turns" counts the chart's columns, the kept ring. The session-start row sits above the chart, and the turns before the ring are counted on their own line ("Turns 1-N earlier"), so the three numbers add up to the session without overlap. No change.

UX: no column for an open newest turn. By design: a column is a closed turn with its own numbers, and the running turn has none yet. The panel above the chart always shows the newest turn, running or not, and while a past turn is picked, "Show latest turn (N)" (and Escape or Home on a column) goes back to it. No change.

UX: mixed units on an unmeasured turn. By design, and every figure names its unit. An unmeasured turn only has the harness token total, so its headline and rows are in tokens, and the Kiro Crew rows are marked "~" with their characters beside them. The chart is in characters while any kept turn was measured by the engine, since those columns are character counts. It says "Unit: characters" or "Unit: tokens (estimated)" above it. No change.

First Principles: the dropped 14-day row filter. Declared in the description under Backwards compatibility: days is accepted and echoed as window_days, and selects nothing else. The fold keeps a fixed 30-turn ring with no per-turn history to filter. The one reader that needs a day bound, the peak_context_used / context_window pair, still takes the fullest turn inside days.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

readiness: action required A blocking check or review needs attention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Context breakdown panel: redesign the Developer Mode layout and bound the usage fold state

2 participants