Developer Guide
The AI Tool Loop (caller-driven)
The AI microservice is inference-only — it never calls application
tools. The caller drives the loop: the in-app chat
(src/lib/shell/ai-chat/use-ai-chat.svelte.ts) is the reference
implementation of the contract described server-side in the BE
MCP gateway page.
The turn
openChatStream→POST /api/v1/ai/chat(SSE) with the message, the assembledhistory(toLlmHistory) and anytool_resultspending from the previous step.- The stream emits events;
client-tool-callis the interesting one — the model asked for a tool and stopped. Each call lands inpendingToolCalls. - On stream end,
finishStreamStepcommits the streamed text (commitStreamingText, preservingtool_callsfor the transcript) and decides: execute pending calls or finish the turn. - Each call executes:
- server tool +
read_only: true→POST /system/mcp/callimmediately, no user prompt. - server write tool → gated behind a user confirmation (the
action card shows what will run); on approve, same
/system/mcp/call— the BE enforces RBAC on the call itself. - local client tool →
invokeClientToolagainst the client-tool registry (below).
- server tool +
resubmitLoopre-opens/ai/chatwithhistory+tool_results— repeat until the model emits a plainfinish. The loop is bounded (max steps) so a misbehaving model can't spin forever.
Results persist per-message: AiMessage.content.tool_results /
tool_calls are the durable transcript — the same shape resubmitted on
continuation.
Local client tools — client-tool-registry.ts
Not every tool is a server operation. registerClientTool lets the FE
offer UI-native tools executed locally:
Code
The built-in example is navigate — the model can send the user to a
route it discovered via the routes census. Local tools still produce a
tool_results entry and participate in the same resubmit contract.
Where this runs
shell/ai-chat/— the platform assistant (sidebar panel).components/ui/smart-ai/—ai-chat-panel+chat-actionsfor embedded assistant surfaces.components/ui/smart-guide/—guide-ai-chat-panel+guide-loop.ts(runGuideRetrievalLoop,selectActions): a separate retrieval loop — staged LLM calls for guide generation with its ownexec_config-drivenGuideLoopConfig, census-route candidates and action validation. Not the MCP tool loop; same caller-drives philosophy.components/ui/smart-regex-input/+smart-json-config/— scoped chat panels reusing the same stream plumbing.
Rules of thumb
- Never auto-run a write —
read_onlyfrom the tool declaration is the gate; the FE must not guess. - The model proposes, the caller disposes — anything emitted is
untrusted input: validate args before
/system/mcp/calljust as the guide loop validatesAiActions against census candidates. - Bound every loop — resubmit always carries an explicit step cap.
Last modified on