8000 feat(chat): deep research, a plan to approve and a cited report by adeelahmad · Pull Request #516 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(chat): deep research, a plan to approve and a cited report - #516

Merged
adeelahmad merged 36 commits into
mainfrom
claude/project-thread-r960xc-c8-research
Oct 18, 2026
Merged

adeelahmad merged 36 commits into
mainfrom
claude/project-thread-r960xc-c8-research

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Description

Before: the assistant answered one question at a time. A goal that needs many look-ups across the archive, the graph, notes and the web had to be broken up by hand, and nothing produced a report you could keep.

After: you give a goal with /research <goal>, research: true on a question, POST /api/v1/chats/{cid}/research, the assistant's start_research tool, or MCP. One planning call comes back with:

  • up to three follow-up questions, each with answers to pick;
  • the sub-questions it will answer;
  • where it will look: the archive, the graph, notes, the web or the code sandbox;
  • how many tool calls it may make;
  • the report's outline;
  • a cost estimate, labelled as one.

The plan waits as an approval. You can approve it as it is, approve it edited (params), revise it (POST /research/{id}/revise), or decline it. You can do this in the web app, the Approvals inbox, or by saying "yes" in a chat room.

Once approved, it runs in the background:

  • gather: each sub-question is looked up with only the plan's sources' read tools;
  • write: a Markdown report, with citations renumbered;
  • check: an unsupported claim is rewritten once or marked.

The cited report comes back in the conversation as an answer of kind research. From there it can be saved as a note (archive citations become mention links, plus a chat: link back that only the conversation's owner sees) or made into a podcast episode from the assets it cites.

This PR adds the backend, REST, MCP and assistant tool. The web app's plan and progress cards follow in a stacked UI PR. The docs and CHANGELOG label those cards as in progress.

Motivation and Context

Module C8 of the chat-gaps plan ("Deep research").

Screenshots (if appropriate):

n/a (backend only; the cards come in the UI PR).

Steps to reproduce (if appropriate):

  1. Set up a language model that uses tools, then send /research what did customers say about pricing? in a conversation. The answer is the plan, and an approval waits.
  2. POST /api/v1/approvals/{id} with {"decision": "approve"}, then poll GET /api/v1/research/{id}. You see the stage, the sub-questions done, sources, tool calls and cost.
  3. When it is done, the report is the conversation's newest answer. POST /api/v1/research/{id}/note saves it as a note.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change; ! in the title)

Checklist:

  • The title is a Conventional Commit.
  • My code follows the code style of this project (make hooks checks it on commit).
  • I added or updated tests.
  • My change requires documentation updates, and I have updated docs/ accordingly.
  • Users need more than the title to understand it, so I wrote it under ## Unreleased in CHANGELOG.md.

Done questions (AGENTS.md)

  • Goal: the backend half of C8. The web app's cards are the stacked UI PR. This PR merges on green.
  • Agents: REST (/chats/{cid}/research, /research, /research/{id} with /revise, /stop, /resume, /note and /podcast), the MCP tools start_research and research_status, and the assistant tool start_research. Voice and chat rooms reach it through the assistant tool and "yes".
  • Zero setup: research needs only an LLM with tools, which is already configured. There is no new prerequisite.
  • Fewest clicks and keystrokes: one approval, with "yes" in rooms. Defaults come from the plan.
  • Configurable: research.max_tool_calls (40), research.max_minutes (20), research.model (empty: the conversation's) and research.agents_skip_review (off). All are validated in settings with defaults. Their web fields come in the UI PR.
  • Light: no new service or dependency. Runs are claimed with CREATE-as-claim, one per person at a time.
  • Private and secure: the threat model gains a deep-research entry point and a P-RESEARCH property, plus an addendum to P-INJECTION-EGRESS. The addendum covers that an approved plan's web tools don't ask again within the run, bounded by the plan, the tool-call budget and the ledger. An API key or an app only declines a plan unless research.agents_skip_review is on.
  • Cost: planning is one call, gathering is bounded by the plan's tool calls, and write/check are a few calls. Every call is in the activity ledger under research:<id> and the conversation. Budgets hold a run (resume with /resume). Estimates are labelled.
  • User decides: nothing runs before the person approves the plan. Existing defaults are unchanged.
  • Upgrades: new tables only (research, research_run). chat: links are accepted by notes with no rewrite, so no upgrade step is needed (docs/database.md, Upgrades).
  • Honest: the report cites its sources and is checked against them. The docs label the web cards as in progress.
  • Tests, docs and generated files updated: tests/api/test_research.py (14 tests, all passing); docs/assistant.md › Deep research; docs/database.md; CHANGELOG; OpenAPI and the client regenerated with the tooling.
  • Open standards; dependencies keep Lens MIT. No new dependency.
  • Result reported with evidence. Refine later: sub-questions in parallel, research on a schedule, sharing a report with namespace members, research as an automation action, an editable report template.
  • Reuse: chat.tool_answer, check_sources, approvals, the podcast pipeline, notebook pages, budgets and the activity ledger.

Checks run on 7f38bae:

  • ruff and ruff format are clean;
  • mypy is clean (157 files);
  • the targeted pytest run (research, MCP, notebook, settings, approvals, chat tools, bridge, chats, chat records, chat versions) has 120 passing;
  • ci/validate_threat_model.py agrees;
  • regenerating OpenAPI leaves it unchanged.

Generated by Claude Code

claude added 26 commits October 16, 2026 23:28
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
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
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
# Conflicts:
#	CHANGELOG.md
#	fastapi_backend/app/api/mcp_tools.py
#	threat-model.yaml
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
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
@adeelahmad
adeelahmad merged commit a1b8924 into main Oct 18, 2026
12 checks passed
@adeelahmad
adeelahmad deleted the claude/project-thread-r960xc-c8-research branch October 18, 2026 01:59
adeelahmad pushed a commit that referenced this pull request Oct 18, 2026
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
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
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>
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

0