Skip to content

feat(chat): surface the agent TODO list as a pill above the composer - #569

Merged
CrysisDeu merged 1 commit into
mainfrom
feat/todo-pill
Jul 27, 2026
Merged

feat(chat): surface the agent TODO list as a pill above the composer#569
CrysisDeu merged 1 commit into
mainfrom
feat/todo-pill

Conversation

@kyleseaman

Copy link
Copy Markdown
Collaborator

Problem

kiro-cli tracks its own task list — it calls a todo_list tool to record multi-step work and tick items off as it goes. None of that reached the dashboard. To find out what the agent thought it was working on, or how far through a plan it was, you had to scroll the transcript and read tool calls.

Why it matters

The agent's plan is the single most useful piece of state during a long turn, and it was the one thing the UI didn't show. Without it there's no glanceable answer to "how far along is this?" — which is exactly the question you ask while waiting.

Fix (symptom → root cause → change)

Symptom: the TODO list exists in the agent but never renders.

Root cause — and the trap. The obvious hook is wrong. src/kiro_crew/acp/types.py already defined UPDATE_PLAN = "plan", parked in KNOWN_SESSION_UPDATES under the comment "Updates we recognise but don't yet surface (plumbing-only)". That reads like a ready-made seam for this feature. It never fires. I probed a live kiro-cli acp session (v2.14.0) three times and saw zero plan updates; kiro-cli's own tui.js receives a plan update but logs it as "not yet mapped", so neither side implements it. Building against UPDATE_PLAN would have shipped a pill that silently never populates, with nothing in the code to explain why.

What actually crosses the wire is an ordinary tool_call_update whose real tool name appears only in _meta.kiro.toolName == "todo_list" — the visible title is LLM-authored prose ("Creating task list: …"), so it cannot be used to identify the tool. The full list rides in rawOutput:

{ "tasks": [ { "id": "1", "task_description": "read config", "completed": false } ],
  "description": "Config workflow", "context": [], "modified_files": [] }

Change. Every todo command (create / complete / list) echoes the entire list, so this is a snapshot, never a delta — there is no incremental state to reconcile.

  • acp/_dispatch.pyparse_todo_snapshot() identifies the tool via _meta, normalises the payload, redacts task text, and caps size. Emitted as an additive EVENT_TODO_UPDATE; the tool call itself still flows through as tool_result + tool_call_update so it keeps rendering in the transcript.
  • dashboard/state.py — stored on _ChatSlot and serialised in to_dict(). That one dict feeds both /api/chat/slots (cold load) and the WS slots snapshot, so the pill rehydrates on reconnect from a single edit. Deliberately not added as a top-level key to _broadcast, which rebuilds the slots envelope key-by-key and would silently drop it (the bug that made feat(source-panel): GitLab parity for the PR panel and self-hosted GitLab hosts #466's gitlabHostsGeneration push inert).
  • dashboard/chat_runner.py — pushes a todo_update WS delta so the pill moves mid-turn, gated on set_todo() reporting an actual change so a repeated snapshot doesn't fan out to every socket.
  • Frontend — new TaskProgressBar, joining the existing bar stack beside SubagentProgressBar. The live delta patches the same dashboard.slots array the snapshot populates, so the two paths cannot disagree about a slot's list.

Two decisions worth calling out for review:

completed is a plain boolean — there is no in-progress state. So "current task" is derived as the first incomplete item. That's an inference, not something the agent reports.

completed / total / current are computed server-side, not in the browser, so the pill's "N of M" label cannot drift from the list it summarises.

Absent vs empty is load-bearing. null means the agent never used its todo tool (no pill); a present list with zero tasks means it cleared the list. Different states, preserved end to end.

Tests

test/test_todo_list_surface.py (25 tests) — payload shapes captured from a real kiro-cli acp session, not invented.

  • Identification via _meta not title, in both directions (a fs_read call with a task-list-shaped payload must not match; a real todo result with no _meta must not match).
  • The todo event is additive — locks in that tool_result / tool_call_update still flow, so "handling" the todo can never silently drop a tool call from the transcript.
  • set_todo change detection — gates the broadcast; an identical re-echo must report no change.
  • Cleared list stays distinct from absent.
  • Caps, credential redaction of task text, malformed/non-dict entries, missing ids, shape drift in the rawOutput wrapper.

website/src/test/TaskProgressBar.test.tsx (17 tests) — collapsed/expanded disclosure, server-derived counts, current-task derivation, all-complete state, per-slot isolation, progressbar a11y sync, partial-state tolerance, and both hydration paths (live todo_update delta and reconnect slots snapshot).

The load-bearing tests are revert-verified — I broke change detection, event-swallowing, title-based identification, default-expanded, and hide-on-complete in turn, and confirmed each failed the test that names it and passed again restored.

Manual verification

Screenshots below were captured from the real component via a temporary Vite harness with a mocked slot store. Component, CSS, theme tokens, icons, and the collapse interaction are genuine.

What is not proven: no live agent turn ran, so the wire path (todo_list tool call → ACP parse → slot store → websocket) is covered by unit tests only, not end to end. Two environment limits blocked it on my machine: kirocrew pod can't start (systemctl has no D-Bus connection), and I could not authenticate a browser into a dev-backend.sh instance. Flagging so a reviewer can weigh it rather than assuming end-to-end coverage.

Screenshots

Collapsed — a small pill that hugs its content, directly above the composer:

TODO pill collapsed

Expanded on click — the full list, completed items checked and struck through:

TODO pill expanded

Full context — collapsed above the composer, and light theme

Collapsed in context

Expanded, light theme

An earlier revision was full-width when collapsed; it read as another horizontal bar competing with the composer. The screenshots caught it — unit tests never would have.

Gates

Backend 18003 passed. Frontend 4695 passed across 406 files. isort, flake8, mypy (481 files), tsc, eslint all clean.

Three pre-existing failures in test/test_dashboard_origin.py reproduce identically on a clean origin/main worktree (a KIROCREW_PORT=6776 in my host env) and are unrelated. Two further failures in the first full run were flakes: test_apps_registry.py sandbox-backend contention under xdist (passes in isolation on main and this branch) and a ChatPage.responsivePanel ordering flake (full re-run 406/406 green).

kiro-cli tracks its own task list via a server-side `todo_list` tool, but
nothing in the dashboard showed it -- the user could not see what the agent
believed it was working on without reading the transcript.

The list arrives as an ordinary ACP `tool_call_update` identified only by
`_meta.kiro.toolName`, NOT as the ACP `plan` session update (kiro-cli 2.14.0
never emits `plan`, so the pre-existing UPDATE_PLAN constant stays inert).
Every todo command echoes the whole list in `rawOutput`, so the snapshot is
stored wholesale rather than merged from deltas.

Backend parses the snapshot in the shared ACP dispatch, emits it as an
additive EVENT_TODO_UPDATE (the tool call still renders in the transcript),
stores it per slot, and serializes it in `_ChatSlot.to_dict` -- the one dict
feeding both /api/chat/slots and the WS `slots` snapshot, so the pill
rehydrates on reconnect. A `todo_update` WS event pushes mid-turn changes.

Frontend adds a collapsed pill that hugs its content and expands to a
full-width list on click, reading the slot's todo off the same slots array
that the snapshot populates.
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — 🟡 CONCERNS

Advisory design-level review of 0c25c414f3495e8bfd49ad23bbc6284689488a77 — updated in place on each push; does not block merge.

Design-Verdict: CONCERNS

Sound feature built on the only viable hook, but it ships 4 temp screenshot binaries into the source tree and silently degrades whenever kiro-cli's undocumented wire shape shifts.

Watch

  • The whole feature keys off kiro-cli internals it doesn't own — _meta.kiro.toolName == "todo_list" and the rawOutput {items:[{Json:{...}}]} wrapper (_dispatch.py parse_todo_snapshot). The PR itself notes these are "an internal detail we do not control." When kiro-cli renames the tool or reshapes the wrapper, parse_todo_snapshot returns None → the pill silently stops populating, with no log/telemetry to signal it broke. Fail-closed is correct, but consider a one-line debug log on "matched todo tool name but payload unparseable" so a future shape drift is diagnosable instead of invisible.

Suggestions

  • temp-screenshots/todo-pill/*.png (4 files, ~600KB) are committed to the repo. They're PR-review artifacts from a throwaway Vite harness — they don't belong in the source tree (the directory name even says "temp"). Drop them from the branch; link them in the PR body instead.

[DESIGN-REVIEWED] 0c25c41

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Arbiter — ✅ no blocking findings

Arbiter found no unresolved long-term items that require action before merging 0c25c414f3495e8bfd49ad23bbc6284689488a77.

Second-order review for 0c25c414f3495e8bfd49ad23bbc6284689488a77; this comment is updated in place on each push.

Review details

I've read both files. The line-level reviewers (Claude Opus 5, GPT 5.6) reported no findings. Only the design reviewer raised CONCERNS, with two sub-threshold items. Neither clears the narrow one-way-door / concrete-harm bar.

Arbiter-Verdict: PASS

No sub-threshold finding meets the long-term-impact bar.

Considered but not escalated

  • kiro-cli internal coupling / silent degradation (Design Review) — the feature keys off undocumented kiro-cli internals (_meta.kiro.toolName == "todo_list", the {items:[{Json:{...}}]} wrapper) and parse_todo_snapshot returns None with no log when the shape drifts. Not escalated: the behavior is fail-closed (the pill silently stops populating; no crash, no data loss, no schema/wire contract this repo owns is locked in), and adding a debug log is a trivial, fully reversible later change. Reversible-in-a-later-change → follow-up, not a gate.
  • temp-screenshots/todo-pill/*.png committed (4 files, ~600KB) (Design Review) — PR-review artifacts from a throwaway harness landed in the source tree. Not escalated: these are non-secret PNGs, not a credential, license-incompatible dependency, API/schema/persisted-data decision, or any concrete production harm. Removing them is a trivial commit; the only permanence is minor history bloat, which does not meet "expensive-to-reverse contract." Cleanup item, not a blocker.

Suggested follow-ups (open as issues — non-blocking)

  • Drop the committed screenshots before/right after merge — remove temp-screenshots/todo-pill/*.png from the branch and link them in the PR body instead; they don't belong in the source tree (the dir name says "temp"). Best done in this branch if a follow-up push happens, otherwise a quick cleanup PR.
  • Add a one-line debug log on shape drift — in parse_todo_snapshot (src/kiro_crew/acp/_dispatch.py), emit a debug log when _kiro_tool_name(update) == KIRO_TOOL_TODO_LIST but _todo_payload returns None, so a future kiro-cli wire-shape change is diagnosable instead of an invisible silent-empty pill.

[ARBITER-REVIEWED] 0c25c41

False positive or not applicable? A repository writer can comment:
/ai-review override arbiter 0c25c414f3495e8bfd49ad23bbc6284689488a77: <one-sentence reason>

For a broader accepted-risk deferral, apply defer-longterm and explain why.

@github-actions

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 0c25c414f3495e8bfd49ad23bbc6284689488a77 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[CODEX-REVIEWED] 0c25c41

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 0c25c414f3495e8bfd49ad23bbc6284689488a77: <one-sentence reason>

@github-actions

Copy link
Copy Markdown
Contributor

Opus 5 Review — ✅ no blocking findings

Reviewed 0c25c414f3495e8bfd49ad23bbc6284689488a77 — this comment is updated in place on each push.

No findings.

Verdict recorded via the action's structured output for commit 0c25c414f3495e8bfd49ad23bbc6284689488a77.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 0c25c414f3495e8bfd49ad23bbc6284689488a77: <one-sentence reason>

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Jul 27, 2026
@CrysisDeu
CrysisDeu self-requested a review July 27, 2026 16:38
@CrysisDeu
CrysisDeu merged commit e8735af into main Jul 27, 2026
40 checks passed
@CrysisDeu
CrysisDeu deleted the feat/todo-pill branch July 27, 2026 16:39
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Jul 27, 2026
tlobinger pushed a commit that referenced this pull request Aug 6, 2026
push_guard.py redact_credentials now strips everything after the URL
authority (scheme://host[:port]) so path-embedded tokens, query strings,
and fragments are all suppressed.  Diagnostics retain the scheme, host,
and git error class.

The authority regex covers bracketed IPv6 addresses ([::1], zone-ids
like %25eth0) and begins the redacted remainder at /, ?, or # — so
query-only URLs (https://host?token=x) with no path component are also
redacted.  An exhaustive RFC 3986 authority-shape matrix pins the
coverage so no further round of redaction bypass exists.

Also: errors='replace' on all subprocess.run text calls, bounded
patch-id replay check (REPLAY_HISTORY_WINDOW), explicit-refspec fetches,
origin/<base> ancestry validation, single_commit gating, rebase-first
recovery guidance.

Closes the IPv6 + query-only credential-redaction bypass (GPT 5.6
blocking lane) and the CodeQL bare-substring sanitization alert (#569).
tlobinger pushed a commit that referenced this pull request Aug 6, 2026
Phase 1 step 2 sync fetch now uses an explicit
+refs/heads/<base>:refs/remotes/origin/<base> refspec so narrow-refspec
clones (single-branch CI checkouts) cannot rebase onto a stale base in
multi-commit workflows.

Prior rounds on this branch: authority-only URL redaction with RFC 3986
shape matrix, errors='replace' decoding, bounded patch-id replay check,
origin/<base>-ancestor-of-HEAD check, single_commit gating, rebase-first
recovery text, posture allowlist entries, brand spelling fix, IPv6 and
query-only redaction with CodeQL #569 assertion removed.
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
…irodotdev#569)

kiro-cli tracks its own task list via a server-side `todo_list` tool, but
nothing in the dashboard showed it -- the user could not see what the agent
believed it was working on without reading the transcript.

The list arrives as an ordinary ACP `tool_call_update` identified only by
`_meta.kiro.toolName`, NOT as the ACP `plan` session update (kiro-cli 2.14.0
never emits `plan`, so the pre-existing UPDATE_PLAN constant stays inert).
Every todo command echoes the whole list in `rawOutput`, so the snapshot is
stored wholesale rather than merged from deltas.

Backend parses the snapshot in the shared ACP dispatch, emits it as an
additive EVENT_TODO_UPDATE (the tool call still renders in the transcript),
stores it per slot, and serializes it in `_ChatSlot.to_dict` -- the one dict
feeding both /api/chat/slots and the WS `slots` snapshot, so the pill
rehydrates on reconnect. A `todo_update` WS event pushes mid-turn changes.

Frontend adds a collapsed pill that hugs its content and expands to a
full-width list on click, reading the slot's todo off the same slots array
that the snapshot populates.

Co-authored-by: Kyle Seaman <kseam@dev-dsk-kseam-1b-55230d27.us-east-1.amazon.com>
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.

2 participants