feat(session): persist AI-generated session titles#407
Draft
feat(session): persist AI-generated session titles#407
Conversation
Generate a short title (3-7 words) via the auxiliary model after the first user→assistant exchange and persist it on the session row. The chat sidebar and `/api/chat/recent` prefer the stored title, falling back to the existing first/last message preview when no title exists. Schema migration adds nullable `title` + `title_source` columns; the hook is fire-and-forget so it never blocks the user-visible reply. Pattern mirrors Hermes Agent's `title_generator.py` and DeerFlow's `TitleMiddleware`.
…mer) - Replace `countUserMessagesForSession` with the already-loaded `turnIndex === 1` check at the gateway hook sites; drop the helper and its dedicated COUNT query. - Replace the local `truncate` helper with the existing `trimSessionPreviewText` from `session-preview.ts`. - Rename `userMessageCount` → `isFirstTurn` in `MaybeAutoTitleSessionParams` to match the new gate semantics. - Drop one redundant `flushMicrotasks` call in the unit tests.
02744de to
c876b57
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Generate a short title (3–7 words) via the auxiliary model after the first user→assistant exchange and persist it on the session row. The chat sidebar (
/chat) and/api/chat/recentprefer the stored title, falling back to the existing first/last-message preview when none exists. Pattern mirrors Hermes Agent'sagent/title_generator.pyand DeerFlow'sTitleMiddleware.Why
Today the sidebar always derives a title on read from the first/last message via
buildSessionBoundaryPreview(src/session/session-preview.ts) — clunky labels like"hey can you" ... "Sure — here's the…". A real, persisted title makes sessions scannable and searchable by topic.What's in this PR
src/memory/db.ts): nullabletitleandtitle_sourcecolumns onsessionsviaaddColumnIfMissing.setSessionTitle(id, title, source)(auto source usesWHERE title IS NULLfor race-safety),getSessionTitle(id),countUserMessagesForSession(id).getRecentSessionsForUserprefers the stored title and falls back to the existing boundary preview.src/session/session-title.ts:generateSessionTitle(...)— Hermes-style "3–7 words, return only title text" prompt; normalizer strips quotes,Title:prefix,<think>tags, trailing punctuation; caps at 80 chars; rejects empty/single-char/untitledoutputs.maybeAutoTitleSession(...)— sync gate (userMessageCount > 1or existing title → bail), thenvoid (async () => …)()fire-and-forget body wrapped inwithSpan('hybridclaw.session.title', …). Never blocks the user-visible reply.'session_title'task added toTASK_MODEL_KEYSandauxiliaryModelsinruntime-config.ts+config.example.json(defaultprovider: "auto", override withAUXILIARY_SESSION_TITLE_MODEL/_PROVIDER).src/gateway/gateway-chat-service.ts): hooked into all threerecordSuccessfulTurnsites — concierge respond, version-only, main success — using the just-recorded exchange (no re-fetch, race-safe vs compaction).chat-sidebar.tsx:111already renderss.title, which the gateway now resolves to stored-or-derived.Out of scope (next iteration)
/titleslash command for manual override (thetitle_sourcecolumn is reserved for'user').Test plan
npm run typecheck(root + console)npm run check(biome)tests/session-title.test.ts— 12 new unit tests (normalizer, generator happy/error paths, gate behavior)tests/memory-service.test.ts— 5 new cases (set/get round-trip, race-safety withautosource, user override,countUserMessagesForSession, stored title surfaces throughgetRecentSessionsForUser)npm run test:unit(root): 2874 passing — the 9 remaining failures (admin-terminal,eval-command,host-runner.*) are pre-existing flakes confirmed onmainbaselinenpm --workspace console run test: 216 / 216 passing🤖 Generated with Claude Code