8000 feat(chat): choose tools, a tool-call limit and a scope per answer by adeelahmad · Pull Request #510 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(chat): choose tools, a tool-call limit and a scope per answer - #510

Merged
adeelahmad merged 23 commits into
mainfrom
claude/project-thread-r960xc-c4-tools
Oct 17, 2026
Merged

adeelahmad merged 23 commits into
mainfrom
claude/project-thread-r960xc-c4-tools

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Description

Before: every answer was offered every tool the person had, made as many calls as ai.max_steps rounds allowed, and read the conversation's scope alone. Nothing said which tools ask first or send data out.

After: a conversation, or one question, picks the tool groups or tools its answers may use (tools), caps their tool calls (max_tool_calls), and can narrow one answer to its own namespaces (scope). GET /api/v1/chats/tools lists the groups a person has, what each tool does (read, asks first, sends data out), and why a group is missing, with the setting that turns it back on. An admin's switches still win. A new conversation starts with the person's latest choice. Settings → AI assistant gains "Most tool calls a question may ask for" (ai.max_tool_calls, default 50, 1 to 500).

How:

  • ai_tools.GROUPS puts every built-in tool (find_resources and classify from main included) in exactly one group: archive, graph, web, notes, memory, activity, changes, files, code (the sandbox), extensions, server and browser.
  • extensions.Active.groups adds mcp:<name> and ext:<name> groups.
  • Toolbox(> filters specs() after ai.disabled_tools.
  • chat.tool_answer(max_tool_calls=) counts calls. Calls past the limit get a reply saying they weren't made, then the answer is written (outcome limit).
  • The answer record (feat(chat): each answer records what produced it #469) keeps what was picked and the limits.
  • A flaky entities merge test now waits for the typed search (it failed once in local CI under load). The web search settings test now includes ai.max_tool_calls in the AI section's server values.
  • A per-answer scope skips the namespace auto-pick.
  • The last choice is kept on the account as chat_tools. Absent means Auto, so no upgrade step is needed.
  • Setup conversations always keep every tool.

Motivation and Context

Chat gaps, module C4: tool selection, namespace scope per answer, and a tool-call limit. The web app's Tools chip is the stacked follow-up PR.

Local CI (ci/local-ci.sh --jobs backend,frontend,pre-commit,title) on e8073d6, the stacked UI head that contains this branch: FastAPI pass (1417 passed, 20 skipped), Next.js pass, pre-commit pass, PR title pass.

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 (tests/api/test_chat_tools.py).
  • My change requires documentation updates, and I have updated docs/ accordingly (docs/assistant.md "Choosing tools, namespaces and a limit", docs/api.md).
  • Users need more than the title to understand it, so I wrote it under ## Unreleased in CHANGELOG.md.

Done questions (AGENTS.md)

  • Goal: delivers the backend of tool, limit and scope choice. The web app chip is in the stacked PR. Merged on green CI: pending.
  • Agents: everything is in the API (GET /chats/tools, tools/max_tool_calls/scope on POST /chats, PATCH /chats/{cid} and POST /chats/{cid}/messages).
  • Zero setup: no new prerequisite. Auto (the old behaviour) is the default.
  • Fewest clicks: the choice is remembered per conversation and per account, so it is picked once.
  • Configurable: ai.max_tool_calls is in Settings → AI assistant, default 50. It is not in the setup wizard.
  • Light: no new dependency or service.
  • Private and secure: choices only narrow what an admin allows. The catalog shows only the person's own extensions, and a per-answer scope can't widen access: an unreadable namespace returns 404. The threat model is unchanged: no new data leaves Lens and nothing grants access.
  • Cost: a limit caps tool calls, and tools: [] answers from a search with no tool rounds. Model calls go through the existing ledger.
  • User decides: defaults are unchanged (Auto, no limit). Tools that ask first still ask.
  • Upgrades: the new fields are optional and absent means Auto, so no stored data changes shape.
  • Honest: docs label the web app chip as in progress (it ships in the stacked PR). Each answer's record says what it was offered and its limits.
  • Tests, docs and generated files updated (make openapi commands).
  • Open standards; no new dependencies.
  • Result reported with evidence. Refine later (in docs/assistant.md): tool choice in chat rooms and voice mode, a default per namespace, and model-suggested tools.

Generated by Claude Code

claude added 21 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
@adeelahmad
adeelahmad merged commit 8d2dbed into main Oct 17, 2026
11 checks passed
@adeelahmad
adeelahmad deleted the claude/project-thread-r960xc-c4-tools branch October 17, 2026 06:05
adeelahmad pushed a commit that referenced this pull request Oct 17, 2026
Brings in the chat tool picker (#510, #511); lens_help joins it as the
"help" group (Lens's docs).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHrWfYDCaCbKKDX4w9at9C
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
8BD0
…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

100
0