Repository navigation
feat(chat): deep research in the web app, a plan card to approve and a live progress card - #517
Merged
Merged
Conversation
Importing app.domain.ai_tools on its own failed: it imported automations, which loads the actions, and the agent's action subclasses Toolbox before ai_tools had defined it. Running one test file (tests/api/test_chats.py) hit it. ai_tools now imports automations last. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Every answer keeps `meta`: the model and the host name of its server,
the tools offered and asked for, the scope it answered in (after
auto-scope), its limits and what it used, how it ended (answered,
direct, limit, fallback, stopped, error), tool results that read like
instructions to an AI, how long it took, Lens's version, and tokens and
cost from the ledger rows that name it. Questions keep how they were
sent (web, api, voice, room, automation) and their options.
The answer's id is taken once the question is saved and the answer
runs in activity.scope(chat:<cid>, chat_message:<mid>), in the web
chat, chat rooms and the automations' agent; the streamed answer holds
the scope across its steps (activity.scoped). App completions keep no
messages, so their record comes back in lens.record. The stream sends
`meta` before `done`; GET /chats/{cid}/messages/{mid} returns one
message with its record and ledger rows. Each tool step keeps its time
and whether it failed. The web app shows it all from an i in the
answer's header (a sheet on a phone); old answers say they weren't
recorded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…-r960xc-c1-record
The last check asked for 2026-10-17's note after the call without a date had made today's note, so on that day it found the note the deleted root had left behind. It now asks for a day no earlier call makes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
The answer's record uses C2's reserved answer_id for its ledger scope and is saved under the question (parent); a message of any version can be read with its record and ledger rows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
# Conflicts: # threat-model.yaml
Group the assistant's tools (GET /chats/tools lists the groups and tools a person has, what each does and why a group is missing), offer an answer only the tools picked (an admin's switch still wins), stop at a tool-call limit up to the new ai.max_tool_calls setting, and narrow one answer to its own namespaces. A conversation keeps its choice and new ones start with the last. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…record' into claude/project-thread-r960xc-c4-tools
…ient Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…t choice Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…-r960xc-c4-tools Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Web search (#472) joins the tool groups as `web`: its tools send data out, and the group says when no web search is set up (web.search). Main's SENDS_OUT is kept; web citations stay readable in the shared citation filter. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
The find box empties while the list for what was typed loads, so on a busy runner the first list could pass the wait and then vanish. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
The code sandbox (#473) joins the tool groups as `code`: there for those who may use it, its runs labelled as sending data out while its network is on, and the group says when the sandbox is off (sandbox.enabled) or for admins. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
The answer record (#469) is in main now; the client is regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
classify (#494) joins the archive's tools. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Ported from #512 so this PR's upgrade check passes while that lands: the entity-types step re-types weekday Terms as Dates, which the default entity list leaves out, so the check read them as gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
A goal (/research, research: true, POST /chats/{cid}/research, the assistant's and MCP's start_research) gets one planning call: follow-up questions and a plan with a labelled estimate, waiting as an approval of the assistant's (tool research). Approved in the web app, the inbox or with a yes in a chat room (an API key or an app only declines, unless research.agents_skip_review), it runs in the background: claimed by one process with a heartbeat, resumed from the last finished stage, one run per person, within its tool calls, research.max_minutes and budgets, stoppable. Gather uses only the plan's sources' read tools; write and check renumber citations and rewrite or mark unsupported claims. The report saves as a note (chat links) or makes a podcast.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…a live progress card
The composer's Deep research switch (and /research) sends a goal; its answer is a plan card: the follow-up questions with answers to tap, the sub-questions to reword, add or remove, where it looks, a tool-call stepper with the estimate, and Enter approves it (or revises it once a question is answered), with Decline. Approved, it becomes a progress card read from GET /research/{id}, so it survives a reload: stage, sub-questions ticked off, sources, tool calls, cost and minutes, Go on anyway when a budget holds it, and Stop. The report saves as a note (picking the namespace when there are several), reads aloud, makes a podcast or exports as Markdown. Notes open chat: links, and Settings > AI assistant > Deep research holds the research limits.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Base automatically changed from
claude/project-thread-r960xc-c8-research
to
main
October 18, 2026 01:59
main now carries the research backend as one squashed commit (#516), so this keeps only the web app's plan, progress and report cards on top of it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…-r960xc-c8-ui Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
adeelahmad
pushed a commit
that referenced
this pull request
Oct 18, 2026
…ay and clipper sign-in as built Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517 and #491 merged. The README's built list now says a conversation exports as Markdown, PDF or JSON and its answers, questions and code copy; a Tools chip picks an answer's tools, limit and namespaces; sandbox runs open under the answer with downloads, and Settings starts the gateway; deep research plans, runs and returns a cited report; ready-made MCP servers (the browser, GitHub) and the optional containers start from the web app; and the web clipper signs in or takes an API token. Each has its checks in docs/requirements.md. In progress drops the merged PRs and lists the open feature PRs; the Planned proof item keeps only the Raspberry Pi timing (#481 and #482 are closed); the docs index links requirements and checks, and published feeds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS
17 of 23 tasks
adeelahmad
pushed a commit
that referenced
this pull request
Oct 18, 2026
…ture 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
adeelahmad
pushed a commit
that referenced
this pull request
Oct 18, 2026
…ay and clipper sign-in as built Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517, #491, #400 and #506 merged. The README's built list now says the assistant answers questions about Lens itself; a conversation exports as Markdown, PDF or JSON and its answers, questions and code copy; a Tools chip picks an answer's tools, limit and namespaces; sandbox runs open under the answer with downloads, and Settings starts the gateway; deep research plans, runs and returns a cited report; ready-made MCP servers, the MCP servers page and the optional containers start from the web app; and the web clipper signs in or takes an API token. Each has its checks in docs/requirements.md. In progress lists only open PRs; the Nebula mesh (#546, merged, partly built) moves to Planned with what's next; the Planned proof item keeps only the Raspberry Pi timing; the docs index links requirements and checks, and published feeds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS
adeelahmad
added a commit
that referenced
this pull request
Oct 18, 2026
…ay and clipper sign-in as built (#536) Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517, #491, #400 and #506 merged. The README's built list now says the assistant answers questions about Lens itself; a conversation exports as Markdown, PDF or JSON and its answers, questions and code copy; a Tools chip picks an answer's tools, limit and namespaces; sandbox runs open under the answer with downloads, and Settings starts the gateway; deep research plans, runs and returns a cited report; ready-made MCP servers, the MCP servers page and the optional containers start from the web app; and the web clipper signs in or takes an API token. Each has its checks in docs/requirements.md. In progress lists only open PRs; the Nebula mesh (#546, merged, partly built) moves to Planned with what's next; the Planned proof item keeps only the Raspberry Pi timing; the docs index links requirements and checks, and published feeds. Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS 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 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.
Description
Before: deep research (#516) worked through the API, MCP, chat rooms and the assistant's tool. In the web app, though, a plan was only text in the conversation, and the run showed nothing until its report arrived.
After:
Asking. The composer has a Deep research switch (a telescope). With it on, the next message is a research goal, and a chip says you approve a plan first.
/research <goal>does the same, and neither offers Regenerate.Plan card. The answer is a plan card with:
Approving. Enter approves the plan with your edits. Once a question is answered, Enter revises the plan with your answers instead, and "Approve as it is" is still one click. Decline drops it. A research approval no longer also shows as a generic approval card.
Progress card. Once approved, it becomes a progress card read from GET /research/{id} every few seconds, so it carries on after a reload and on a phone. It shows:
When the report is saved, the conversation is read again so the report shows.
Report. The report's actions are:
Notes. A
chat:link opens the conversation.Settings. Settings → AI assistant → Deep research holds
research.max_tool_calls,max_minutes,modelandagents_skip_review.This is stacked on #516 (the backend). Merge that first, and this PR then retargets to main.
Motivation and Context
Module C8 of the chat-gaps plan, the web app half: plan card, progress card and report actions.
Screenshots (if appropriate):
None attached. The jest tests cover each card's behaviour.
Steps to reproduce (if appropriate):
Types of changes
!in the title)Checklist:
make hookschecks it on commit).docs/accordingly.## UnreleasedinCHANGELOG.md.Done questions (AGENTS.md)
/chats/{cid}/research,/research/{id}with/revise,/stop,/resume,/noteand/podcast, plus/approvals/{id}and the MCP tools.chat:link in a note opens a conversation only its owner can read.__tests__/chat-research.test.tsxhas 14 tests: stream event, helpers, plan approve/revise/decline/edit, progress, stop, resume, report refresh, note namespace pick, read aloud, podcast, export, and the switch;settings-web-search.test.tsxgains the server's research defaults;docs/assistant.md› Deep research and the CHANGELOG are updated.useVoice, the podcast and note APIs, settings fields.Checks on 95e897d:
tsc --noEmitis clean;mkdocs build --strictis clean.Generated by Claude Code