Skip to content

The tools stop being part of the conversation loop - #162

Open
WaylandYang wants to merge 1 commit into
devfrom
feat/extract-tools
Open

The tools stop being part of the conversation loop#162
WaylandYang wants to merge 1 commit into
devfrom
feat/extract-tools

Conversation

@WaylandYang

Copy link
Copy Markdown
Contributor

Pure refactor, no behaviour change. Groundwork for the MCP server: today the seven tools are seven match arms inside the chat SSE handler, 20–70 lines each, closing over the streaming loop's locals. That was fine while chat was the only caller. MCP is the second one, and two implementations of entity_facts that drift apart is exactly the failure worth spending a refactor to avoid.

chat.rs 1244 → 700 lines. New api/tools.rs holds the execution; the JSON schemas stay in chat.rs, because those are part of the prompt and belong with the conversation strategy.

Shape

ToolCtx   — what a call can see: state, kb, workspace, fallback query,
            mounted sources, can_write. Read-only.
ToolSink  — what a call accumulates: citation ids, citation JSON, resolved entities.
dispatch(ctx, sink, name, args) -> (text for the model, step for the UI)

The sink exists because citation numbering is stateful: the 3 in [3] depends on how many sources were already cited this turn, so tools cannot each number independently and merge afterwards — the same chunk would get two numbers. The de-dup-and-number logic, previously copy-pasted between search_chunks and search_docs, is now one cite() helper.

Three accumulators in the handler (source_ids, sources, resolved_acc) collapse into one ToolSink.

Verifying that nothing changed

There is no LLM configured on the dev deployment, so a live chat round-trip was not available. Two checks instead:

Every string literal, compared. These tools are almost entirely string assembly, so the literals and format templates are the behaviour. Extracted all double-quoted literals from the old match arms and from the new functions, sorted and diffed: 72 on each side, zero added, zero lost.

The one genuine structural change, read line by line. cite() is the only place where inline code became a helper. It pushes the key, then uses source_ids.len() — same order as the original, so the number assigned is identical.

Plus: the seven arms and both guard conditions (query_data needs mounted sources, remember needs write) survive in dispatch; the seven tests that covered changes_window and change_line moved with those functions and pass as api::tools::tests; fmt, clippy --all-targets -D warnings, and the workspace test suite are green.

One thing worth flagging: truncate was initially rewritten from memory rather than copied, dropping the leading trim(). Caught by comparing against the original before it reached a commit — but it is the reason the literal-by-literal check above exists rather than a glance at the diff.

Next

Personal tokens per ADR 0014, then Streamable HTTP as a route on this server.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5 <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.

1 participant