Agent loop
Since version 0.6.3 the backend AI Chat does not run its own tool loop.
ChatService delegates the whole chat turn to nr-llm's AgentRuntime
(nr-llm ADR-101), which drives the model over nr-llm's builtin tool
registry and returns the settled result synchronously.
Processing a turn
ChatService::processConversation() performs the following steps:
- If no nr-llm Task is configured (
llmTaskUidis0), the conversation is set tofailedwith a descriptive message. - Resolve the
LlmConfigurationthe chat should use from the configured Task (llmTaskUid->Task->getConfiguration()). A missing Task or Configuration fails loudly rather than silently degrading to a no-tools chat. - Set the conversation status to
processing. - Build the message transcript: a
systemmessage carrying the identity/behaviour contract and the resolved Task/Configuration prompts (see Architecture > System prompt priority), followed by the stored conversation messages. File attachments are expanded to the multimodal wire shape and forwarded as array messages. - Call
AgentRuntimeInterface::run()with anAgentRunRequestbuilt from the configuration, the messages and the initiating backend user uid.allowedToolNamesis left atnullso the run is offered the whole globally-enabled tool set; nr-llm's own tool gate (RBAC, global enable cascade, per-configuration groups) stays authoritative. - Map the returned
AgentRunResultonto the conversation.
Outcome mapping
AgentRuntime::run() never throws for a run outcome; it returns a
settled AgentRunResult. ChatService maps it as follows:
COMPLETED-- append the final assistant answer (ToolLoopResult::$finalContent) and set statusidle.- any other outcome (
FAILED,GUARDRAIL_BLOCKED,AWAITING_APPROVAL, …) -- set statusfailedwith a sanitized reason taken fromAgentRunResult::$erroror derived from the outcome. The mapping keeps a default arm becauseAgentRunOutcomegains cases in nr-llm minor releases.
The tools the model can call, their execution, retry/back-off on transient provider errors, budget enforcement and the iteration cap all live inside nr-llm now.
Synchronous execution and resume
AgentRuntime::run() is synchronous and drives the entire tool loop in
one call, so a turn never leaves persisted "pending tool calls" in the
conversation. The CLI worker (ai-chat:process / ai-chat:worker)
therefore always calls processConversation().
resumeConversation() re-runs the turn over the existing transcript
for a resumable conversation (processing, tool_loop or
failed), which is used to recover a conversation left processing
by a crashed worker.
MCP tool provider
The McpToolProvider / McpConnection classes remain in the
codebase but are no longer used by the chat turn. Direct MCP-server
tooling for the backend is superseded by nr-llm's builtin tool registry;
the MCP integration is retained for the planned move into nr-llm.