Repository navigation
feat(chat): title conversations after their first answer - #531
Merged
Merged
Conversation
A conversation nobody named gets its first question's words at once, then
a short title from the model after the first answer, written by the model
for summaries (ai.compact_model) when set, in one call counted for the
conversation in the activity ledger. A title someone gives is kept
(title_by), POST /chats/{cid}/title and the MCP tool rename_chat have Lens
title it again, and Settings › AI assistant › Title conversations turns it
off.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdR7CS12bmc7dcQFbdV4vn
22 of 23 tasks
adeelahmad
pushed a commit
that referenced
this pull request
Oct 18, 2026
…and-in model titles conversations Since #531 Lens asks the model for a conversation's title after its first answer, and the stand-in model's one reply became every conversation's title in the list beside Chat. chat-ask waits for the reply's words, found them at once in that list, and ended before its own answer was saved, so chat-regenerate and the journeys after it found a conversation with a question and no answer. The stand-in now answers the title prompt with the first question's words, as a model would, and first-question and chat-ask wait for the reply inside the newest answer, not anywhere on the page. The page survey counts a page's controls again for a few seconds when it finds none, so a settings page still loading isn't recorded with none. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Merged
19 of 23 tasks
adeelahmad
added a commit
that referenced
this pull request
Oct 18, 2026
…ing tools in a chat (#541) * fix(chat): copy an answer in one click An answer's Copy button opened a menu, so copying took two clicks. A click on Copy now copies it as Markdown (its citations as footnotes, as before); the arrow beside it, as Regenerate has, holds Copy as Markdown, Copy text and Export as Markdown, PDF or JSON. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B * fix(chat): keep each tool change made while the one before is being saved In a conversation, the Tools chip saved each change and then read the conversation back, and the next change was made from what the page still held. Switching a group off and then turning on the limit before the first save came back sent the old tools (Auto) with the limit, and the group came back on. Now each change shows at once, the changes are saved in the order they were made, and the conversation is read back after the last one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B * test(clicks): journeys for regenerating, copying, exporting and choosing tools in a chat chat-regenerate (from Home: the conversation, Regenerate, back to the first version), chat-copy, chat-export-pdf (the server's PDF downloaded, or the browser's print dialog counted outside the browser) and chat-tools (a group off and a limit), on a computer and a phone. They run before cloud-model, which stays last since it points Lens away from the stand-in model; the stand-in model now answers a regenerated question with "..., version 2." so the versions differ. Both baselines are from full runs, so they also carry entity-merge and graph-merge, which they lacked. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B * fix(clicks): the chat journeys wait for the answer itself, and the stand-in model titles conversations Since #531 Lens asks the model for a conversation's title after its first answer, and the stand-in model's one reply became every conversation's title in the list beside Chat. chat-ask waits for the reply's words, found them at once in that list, and ended before its own answer was saved, so chat-regenerate and the journeys after it found a conversation with a question and no answer. The stand-in now answers the title prompt with the first question's words, as a model would, and first-question and chat-ask wait for the reply inside the newest answer, not anywhere on the page. The page survey counts a page's controls again for a few seconds when it finds none, so a settings page still loading isn't recorded with none. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B * test(clicks): baselines from full runs with main merged in Both devices, 37 of 37 journeys ok, every journey's count as before. The per-page control counts move with main: Settings › AI assistant has the auto title switch, Settings › Sensors two more controls. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B --------- Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
adeelahmad
added a commit
that referenced
this pull request
Oct 18, 2026
…ine (#538) * feat(voice): read answers and podcasts aloud with Kokoro on this machine Kokoro v1.0 (hexgrad's Kokoro-82M, Apache-2.0) as a text-to-speech provider, voice.tts_provider "kokoro": spoken answers and podcast hosts are read on the server's own CPU through kokoro-onnx on ONNX Runtime, in any of its 54 voices across nine languages, with nothing sent anywhere and nothing billed. It's opt-in and fetched once it's picked: the `kokoro` extra (espeak-ng and phonemizer are GPL-3.0, so they stay out of the default install), and the model (voice.kokoro_model: fp32, fp16 or int8) and voices files from kokoro-onnx's release, checked against their SHA-256. Settings → AI assistant → Voice offers it with the model size and the voice picker; a podcast's second host gets a voice in the first host's language. kokoro.yml fetches and reads with each model on x64 and arm64. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * test(settings): carry ai.auto_title in the Kokoro settings fixture main's conversation titles (#531) added ai.auto_title to the section the Kokoro settings test renders, so its fixture names it too; without it Save sent an ai update beside the voice one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * docs(threat-model): count the Kokoro row in the JSON companion The threat model workflow's validate job wants a change to threat-model.md to change a companion too (#507): the Kokoro row is documented, so the JSON's documented count goes up by one, as #518 did for Moondream. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * fix(voice): offer Kokoro in full precision and 8-bit only On GitHub's arm64 runner, ONNX Runtime's CPU provider returned an empty waveform for kokoro-onnx's fp16 export, and kokoro-onnx's pause insertion then failed on a zero-size array. On x64 it was no faster than fp32, so voice.kokoro_model is now fp32 (the default) or int8, and the fp16 file is no longer pinned. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * test(settings): carry the research section in the Kokoro settings fixture Main's deep research (#517) added a research section to Settings › AI assistant, so the fixture carries its defaults and Save sends only the voice change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * test(settings): carry ai.compact_at in the Kokoro settings fixture Main added ai.compact_at (summarise long conversations early), so the fixture carries its default and Save sends only the voice change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * docs(readme): list answers about Lens itself as built, now that #400 is released Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * docs(changelog): name the Kokoro fragment after its pull request Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * docs(threat-model): count the Kokoro entries against main's newest model Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH * docs(readme): take Kokoro out of In progress, now that it's built Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH --------- Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
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 co
3064
mmit.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.
Requested by Adeel · project thread
Description
Before: a new conversation was called "New conversation" until the first question, then its first 80 characters, for good. Long questions made long, lookalike titles in the list.
After: a conversation nobody named still gets its first question's words the moment it's asked, then, once the first answer is in, the model gives it a short title of a few words from the question and the answer. The stream ends with a
titleevent, so the list updates without a reload. A conversation you name when you start it or rename later is yours, and Lens leaves it alone;title_byon a conversation says which it is. Editing the first question of a conversation Lens titled titles it again.POST /api/v1/chats/{cid}/titleand the MCP toolrename_chat(with atitle, or none for Lens's) rename or retitle it. Settings → AI assistant → Title conversations (on) turns the model's titles off, and the first question's words stay.How:
chat.auto_titlemakes one small call (max_tokens200, the reply cleaned of labels, quotes and Markdown) with the model for summaries (ai.compact_model) when set, else the chat's model, insideactivity.scope("chat:<id>")so the ledger row names the conversation and never the answer (whose record keeps one total per answer). It runs after the answer's stream, on a thread the stream waits on for at most 10 seconds; a slower title is saved when it comes. The save is conditional (WHERE title_by = 'assistant'), so a rename during the call wins.title_byis a new nullable field onchat: null means from before this change and reads as before (untitled is Lens's, anything else is kept), so no upgrade step.Motivation and Context
Adeel asked for chats to be titled automatically (project thread). Picks made on the way: the title waits for the first answer rather than the first question, since the answer says what the question was really about and the question's words already fill the gap; chat rooms, setup and automation conversations keep the names Lens gives them.
Types of changes
!in the title)Checklist:
make hookschecks it on commit).docs/accordingly.## UnreleasedinCHANGELOG.md.Done questions (AGENTS.md)
title_byonGET /chatsandGET /chats/{cid},POST /chats/{cid}/title,PATCH /chats/{cid}withtitle; MCPrename_chat, andlist_chats/read_chatshowtitle_by.ai.auto_title, on); not in the wizard.untrusted.neutralmarking summaries use, and the prompt says not to follow instructions in quoted text.model.chatrow forchat:<id>in the ledger;activity.Callrefuses it at the overall budget's hard limit, and the conversation keeps the first question's words.titleevent isn't yet shown live in the web app (the list refetches ondone, which is after the title is saved for a fast model; a slow one shows on the next refetch).test_chat_titles.py(7 tests),settings-web-search.test.tsx,docs/assistant.md#titles,docs/api.md,docs/mcp.md,docs/requirements.md;make openapiregenerated the schema and client.llm.chat,activity.scope, the compaction model setting and_saidformatting,notebook.refine's cleaning idea.Generated by Claude Code