Skip to content

feat(chat): regenerate every session's title from the sidebar menu - #22665

Open
neilieva wants to merge 1 commit into
kirodotdev:mainfrom
neilieva:feat/21976-bulk-regenerate-titles
Open

neilieva wants to merge 1 commit into
kirodotdev:mainfrom
neilieva:feat/21976-bulk-regenerate-titles

Conversation

@neilieva

@neilieva neilieva commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: Let a user give every open session a fresh title from its recent conversation in one step, from the sidebar.

Today a title can only be regenerated one session at a time, from each row's own menu. Someone with a dozen sessions whose early titles no longer describe them has to open a dozen menus.

Why it matters

Titles are how people find a session in the sidebar. Stale titles from the first message ("hi", "quick question") make a long sidebar hard to scan, and fixing them one by one is tedious enough that most people don't.

Not a goal

  • Changing how or when sessions are titled automatically.
  • Changing the per-row Regenerate title action.
  • A new title model, prompt or rate limit. The bulk action reuses the existing ones.

What changed (motivation → approach → change)

I added Regenerate all titles… to the sidebar header ⋮ menu, next to Clean up and Switch all sessions. It opens a panel that previews what will happen, then runs in the background.

Backend (src/kiro_crew/dashboard/chat_title_bulk.py):

  • POST /api/chat/slots/generate-titles with {include_renamed?, dry_run?}. A dry run returns the plan and spends nothing. A real run spends each queued session's title budget and starts a background task.
  • GET /api/chat/slots/generate-titles returns the latest run's counts and the key and title of each session that failed.
  • Both routes answer dashboard users only. An app token gets 403, logged to SEL as an app-isolation denial. A started run writes one SEL success row.
  • A second run while one is unfinished gets 409 bulk_titles_in_progress, so a double click can't spend budget twice.
  • Each session goes through _model_manual_title (the model call behind the per-row action) and apply_regenerated_title, with the same epoch and replaced-slot checks. The bulk path has no fallback to the opening message, so a model error counts as failed and keeps the title.
  • A failed save puts the previous title back and re-pushes it, unless a rename landed during the save. The shared apply_regenerated_title is unchanged.

Frontend (website/src/pages/chat-sidebar/bulkTitles.tsx, client in website/src/api/client/chatSlotSettings.ts): preview counts, a checkbox for renamed sessions, a live "Done: 4 of 12" counter and a finished summary. When sessions fail, the notice names each one as a link that opens it. Reopening the panel during a run shows that run.

The strings are in all 12 locales under website/src/i18n/locales/, with en-XA regenerated and the started plural in website/src/i18n/pluralKeys.json. docs/feature-map/README.md gets a row, and docs/system-specs/modules/learn-cron-dashboard.md describes both routes.

Decisions I'd like a maintainer to confirm

  1. Budget. Each session spends the existing per-session GENERATE_TITLE budget (one call per 5 minutes). A session with no budget left is skipped and counted. At most 2 titles generate at once. That budget is shared with the agent session_generate_title verb, so for 5 minutes after a bulk run that verb answers 429 on the same sessions. The per-row Regenerate never spends it. I recommend keeping the shared budget, because it is the cost bound the verb already has.
  2. Hand-renamed titles. Skipped by default, including legacy titles with no recorded origin. A checkbox in the panel includes them. A rename is the one title the user chose on purpose.
  3. Progress. The panel polls the status route once a second while a run is unfinished. A run over a dozen sessions takes minutes, so the counter is worth it. A push event would need a new socket message type, and the poll only runs while the panel is open. I recommend the poll.
  4. Running sessions are skipped, and re-checked when each session's turn comes, so one that starts a reply while queued is left alone.
  5. Empty sessions (no user or assistant message in the restored tail) are skipped, so planning never loads a pending transcript.
  6. Placement beside the other all-sessions actions in the header ⋮ menu.

Backwards compatibility

Compatible: two new routes and a new menu item. No existing route, field or default changes.

One effect on existing behavior: for 5 minutes after a bulk run, the agent's session_generate_title verb answers 429 on the sessions that run retitled, because both spend the same per-session GENERATE_TITLE budget (decision 1).

Removes nothing: the change only adds a route pair, a panel and catalog keys.

Tests

test/test_chat_title_bulk.py:

  • The plan puts each session in exactly one bucket, and includes renamed sessions only when asked.
  • It leaves out closing sessions, sessions still under construction and crewmate DM threads.
  • The run generates at most 2 titles at once.
  • A rename, a replacement or a running turn that lands before or during a session's turn keeps that session's title, with renamed sessions included or not.
  • A rename that lands after the route schedules the run but before it starts keeps that session's title.
  • One failure doesn't stop the others.
  • The route enforces the dry run, the budget spend, a budget spent between plan and apply, the audited 403 for non-dashboard callers and the 400 for non-boolean flags.
  • The status route counts each outcome once, reports a run in flight, returns null before any run, refuses a second run with 409 and counts an unsaved title as failed. It lists each failed session by key and title.
  • A model failure keeps the session's title, counts it as failed and lists it. A crewmate thread is never retitled.
  • The plan reads the restore tail without loading a pending window, and the run loads it off the event loop.
  • A failed save restores and re-pushes the previous title, unless a rename overtook it. A started run writes one SEL success row.

website/src/test/ChatSidebar.bulkTitles.test.tsx:

  • The preview counts appear, and the renamed checkbox re-previews.
  • Confirm is disabled when nothing would be retitled. Preview, start and progress errors show a notice. An apply that queued nothing returns to the preview.
  • The singular reads "1 session".
  • The counter counts down and shows the summary.
  • A run reopened mid-flight stays visible until its summary.
  • A refused confirm switches to the run in progress.
  • A finished run doesn't block a new preview.
  • Each failed session is named, an untitled one reads "Untitled session", and clicking a name opens that session.

test/test_chat_title.py checks that the per-row action still falls back to the opening message when the model fails.

Determinism: every wait the backend tests release themselves is bounded by await_until, so a regression fails by name. The backend file passed 5 runs in shuffled orders, and the frontend file passed 5 repeats. One frontend test waits on the real 1-second poll, with a 3-second timeout.

Manual verification

I ran an isolated gateway from this branch at b6033113cf on loopback, with a fresh data home, and drove it with playwright-cli. I sent one message each to three sessions and left a fourth empty. The preview counted 3 to retitle and 1 with no conversation yet. Applying it showed "Done: 0 of 3" while the titles ran, then "Finished. Retitled: 3". The new titles matched the old ones, because the model gave the same answer for the same conversation. The agent turns in that instance stopped with an MCP setup error from the fresh data home. That error is unrelated to this change, and the title calls still ran.

Screenshots / video

The new item in the sessions ⋮ menu:

1-menu

The preview, with three sessions to retitle and one empty session left alone:

2-preview

The live counter right after applying, then the finished summary:

3-counter 4-finished

A second round at fdb13dc, on the same kind of isolated gateway, covers the states the first round's screenshots left out. I gave four sessions one message each and renamed one of them by hand. The preview counted 3 to retitle and offered the renamed one behind the checkbox. Checking it re-counted to 4:

The preview with the renamed session left out The preview with the renamed session included

I ran it on the 3 and reopened the panel. All 3 now hit the 5-minute limit and are left alone:

The preview right after a run, with 3 sessions at the 5-minute limit

A session that is mid-reply, and a run where some titles fail, are hard to produce on purpose. For these shots I stubbed /api/chat/slots/generate-titles with fixed JSON and ran the real bundle from this head with scripted Playwright. These three frames are committed under temp-screenshots/21976/. The preview, with the rate-limit row's new wording:

The preview with a running session and a rate-limited one left alone (stubbed response)

A finished run where two sessions failed. Each name opens that session:

A finished run naming the two sessions that failed (stubbed response)

Clicking "Deploy triage" in the notice opened that session:

The Deploy triage session open after clicking its name in the notice (stubbed response)

Related Issues

Closes #21976

Review notes

Earlier review findings, fixed:

  • A session that starts running while queued is skipped, and a rename made while queued stands.
  • Sessions under construction and crewmate threads are left out.
  • The started message has real plural forms, and the preview shows the empty count.
  • A run reopened mid-flight shows its summary.
  • A model failure in the bulk run no longer replaces a title with the opening message.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (feature map row and the dashboard spec)
  • No secrets, credentials, or internal references in the diff

Contribution License Agreement

I contribute this under the project's Apache-2.0 license.

@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention labels Oct 21, 2026
@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from 4f2089a to 6a99569 Compare October 21, 2026 04:48
@neilieva
neilieva marked this pull request as ready for review October 21, 2026 05:50
@neilieva
neilieva requested review from a team and KiroCrewCaptain as code owners October 21, 2026 05:50
@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 21, 2026
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Design Review (Opus 5.5, fork) — ✅ PASS

Design-level review of 9ed521e9dbb23ca29dc00761116ef36b609569f2 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

Design-Verdict: PASS

Nothing blocks: the change only adds things, reuses the per-row title path, its guards and its budget, and the PR includes screenshots of this revision.

Notes

  • The description says the routes are at POST /api/chat/slots/generate-titles, but the code registers /api/chat/generate-titles. Correct the description, since the docs and client use the real path.
  • The budget for each queued session is spent when the run starts, not when that session's title is generated. A session that is later skipped (renamed, running or replaced while queued) still loses its 5-minute session_generate_title allowance. The description only covers this for sessions that actually get retitled.

[DESIGN-REVIEWED] 9ed521e

Description read

[DESCRIPTION-READ] 7fa848958ccf60c6008acb40e9185c475b242aa14f550eee0265562edaffa802

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 21, 2026 •

Copy link
Copy Markdown
Contributor

First Principles Review (Opus 5.5, fork) — ✅ PASS

Premise-level review of 9ed521e9dbb23ca29dc00761116ef36b609569f2 via the fork AI-review pipeline — 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

The feature has a clear, named harm and nothing duplicates it. The only extra is the plan response carrying lists of session keys that the panel only ever counts.

Notes

  • Item 9 — zero consumers: to_json returns "queued": [slot.key …], skipped_running, skipped_renamed, skipped_recent, skipped_empty as lists of keys. bulkTitles.tsx reads only .length on four of them, never reads a key, and never reads skipped_renamed at all (it uses plan.renamed). Subtraction: return integer counts in place of the five key lists, and drop skipped_renamed because renamed already carries the number. Shrink the TS return type in chatSlotSettings.ts to match.

What this change ships

Inventory (10 items) — 9 justified

Intent: let a user retitle every open session from its recent conversation in one step, from the sidebar. ADDITION.

  1. New "Regenerate all titles…" item in the sidebar header ⋮ menu — justified
  2. A preview panel counting sessions to retitle and skipped, with a checkbox to include renamed ones — justified
  3. Titles generate in the background, two at a time, with a live "Done: N of M" counter that polls every second — justified
  4. A finished summary that names each failed session as a link that opens it — justified
  5. After a bulk run, the agent's session_generate_title answers 429 for 5 minutes on those sessions — justified
  6. Hand-renamed and legacy no-origin titles are skipped by default — justified
  7. Two dashboard-only routes; an app token gets 403, logged to SEL — justified
  8. A second run while one is unfinished gets 409 — justified
  9. The plan response returns per-bucket key lists the panel only counts — zero consumers
  10. Three evidence screenshots committed under temp-screenshots/ — justified

[FIRST-PRINCIPLES-REVIEWED] 9ed521e

Description read

[DESCRIPTION-READ] 47ac4b20bdb113c56cd69169f57311b02394d14d5b11eca7847920da04a05569

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 21, 2026 •

Copy link
Copy Markdown
Contributor

UX Review (Opus 5.5, fork) — ✅ PASS (a BLOCK with no Trigger and no Outcome does not block)

UX-level review of 9ed521e9dbb23ca29dc00761116ef36b609569f2 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

UX-Verdict: PASS

cannot evaluate: missing the finished summary's "Left as they were: {{n}}" line and the three new error notices. No attached or committed screenshot shows any of them.

Evidence gaps

  • "Left as they were: {{n}}" (bulk_titles_unchanged, shown when run.unchanged > 0) sits in the finished summary between "Retitled" and "Failed". Nobody has seen how a user tells it apart from those two. To close: a screenshot of a finished run with at least one unchanged session.
  • "Couldn't count the sessions to retitle." (bulk_titles_preview_failed) replaces the preview counts. Nobody has seen whether the panel is still usable then (Confirm stays disabled with no plan). To close: a screenshot of the preview-error state.
  • "Couldn't start regenerating titles." (bulk_titles_failed) appears when a confirm is refused, alongside the preview. To close: a screenshot of the apply-error state.
  • "Couldn't read the title progress." (bulk_titles_status_failed) appears during a run while the poll keeps retrying. Nobody has seen how it looks next to the "Done: N of M" counter. To close: a screenshot of the status-error state mid-run.

Notes

  • attachment-07 shows outdated copy ("Hit the 5-minute title limit, left alone: 3"). Replace it with a screenshot of the current bulk_titles_skipped_recent wording on a disabled Confirm, so the evidence matches this revision.
  • In the finished state, "Started on 3 sessions. Each title updates in the sidebar when it is ready." stays above "Finished. Retitled: 1" (attachments 04, 09). Once run.finished, hide the started line so the summary reads as the result.

[UX-REVIEWED] 9ed521e

Description read

[DESCRIPTION-READ] a2019cf16234c98107f9ebdcd88048bdc619429de66498665b3f91d02c02cf5b

That sha256 names everything this verdict read: the PR description, plus the 13 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 21, 2026 •

Copy link
Copy Markdown
Contributor

Opus 5.5 Review (fork) — ✅ no blocking findings

Reviewed 9ed521e9dbb23ca29dc00761116ef36b609569f2 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 9ed521e

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

GPT 6.1 Review (fork) — ✅ no blocking findings

Reviewed 9ed521e9dbb23ca29dc00761116ef36b609569f2 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 9ed521e

@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 21, 2026
@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from 6a99569 to 38d9c7c Compare October 21, 2026 14:59
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 21, 2026
@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from 38d9c7c to ea61613 Compare October 21, 2026 17:25
@github-actions github-actions Bot removed the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Oct 21, 2026
@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from ea61613 to cbaf179 Compare October 21, 2026 18:01
@github-actions github-actions Bot added readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 21, 2026
@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from 37c87f4 to b4ef971 Compare October 22, 2026 18:28
@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 22, 2026
@neilieva

Copy link
Copy Markdown
Contributor Author

The Fork UX Review run for b4ef971 timed out at 30 minutes (run 39834121828). Could a maintainer rerun it? UX passed on the previous head, and the later push only changes backend code and docs.

@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from b4ef971 to fdb13dc Compare October 24, 2026 18:25
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Oct 24, 2026
@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from fdb13dc to cb263d6 Compare October 24, 2026 19:02
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 24, 2026
@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from cb263d6 to 16e5595 Compare October 25, 2026 18:46
@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 merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running labels Oct 25, 2026
@neilieva
neilieva force-pushed the feat/21976-bulk-regenerate-titles branch from 16e5595 to cb4db9d Compare October 25, 2026 19:23
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Oct 25, 2026
@neilieva

Copy link
Copy Markdown
Contributor Author

target=ux fixed in 20165d0

The rate-limit row now reads "Retitled in the last 5 minutes, try again shortly: {{n}}", the wording you suggested, in all 12 locales. The first screenshot under "Screenshots / video" shows it in the preview.

@neilieva

Copy link
Copy Markdown
Contributor Author

target=ux fixed in 20165d0

When a run finishes with failures, the failure notice now lists each failed session by title. Each title is a link that opens that session, and a session with no title shows as "Untitled session". The status route returns the key and title of each failed session for this. I left out a retry for just the failed ones. A retry within 5 minutes would hit the same per-session limit, so it would skip most of them. The last two screenshots show the list and the session it opens.

@neilieva

Copy link
Copy Markdown
Contributor Author

needs-a-decision for a maintainer

A bulk run spends the same per-session GENERATE_TITLE budget as the agent's session_generate_title verb. So for 5 minutes after a bulk run, that verb answers 429 title_rate_limited on every session the run queued. The per-row Regenerate doesn't spend this budget, so a person can still retitle by hand.

The question: keep one shared budget, or give bulk runs their own key? I recommend keeping it shared. It's the cost bound the verb already relies on, and a separate key would let a bulk run and the agent each spend a call on the same session in the same window. If the 429 turns out to bite in practice, a separate key is a small follow-up.

@neilieva

Copy link
Copy Markdown
Contributor Author
  • Bound the failure list so panel controls remain reachable
    outcome: fixed in 1db62e5

fixed: the failure list now has max-h-32 overflow-y-auto, the same cap sessionCleanup.tsx uses, and Close stays outside the scrolling list. The new test scrolls a long failure list and keeps Close outside it renders 30 failed sessions and checks both.

The same push also fixes three issues the previous GPT round raised on cb4db9d:

  • The bulk routes moved to /api/chat/generate-titles, so a session named generate-titles reads its own transcript again.
  • A tab now follows a run that replaced its own and shows that run's summary, instead of staying on "Started".
  • A session replying on the Slack or Discord path now counts as running, both in the plan and when its turn in the queue comes.

Each has a regression test that fails without the fix.

The sidebar header menu gains a Regenerate all titles panel. A dry-run preview counts the sessions it would retitle and the ones it leaves alone, and the run titles up to two sessions at once, counts down as each lands and names every session it could not retitle, each as a link that opens it. A model failure in the bulk path leaves the existing title in place instead of falling back to the opening message.

Closes kirodotdev#21976

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

fork Pull request from a fork (external contributor) merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: action required A blocking check or review needs attention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regenerate titles for all sessions at once

1 participant