Refactor RAG from agent-level config to standard toolset type#2210
Refactor RAG from agent-level config to standard toolset type#2210dgageot merged 4 commits intodocker:mainfrom
Conversation
Replace the special-cased RAG configuration (AgentConfig.RAG []string referencing top-level Config.RAG map) with a standard toolset type (type: rag) that follows the same patterns as MCP and other toolsets. Key changes: - Add 'type: rag' to toolset registry with ref support for shared definitions - Introduce RAGToolset wrapper (mirrors MCPToolset) for top-level rag section - Add RAGConfig field to Toolset for inline/resolved RAG configuration - Add resolveRAGDefinitions() mirroring resolveMCPDefinitions() - Extract rag.NewManager() for per-toolset manager creation - Implement tools.Startable on RAGTool for lazy init and file watching - Remove RAG special-casing from Team, LocalRuntime, and teamloader - Add v6→v7 migration for old rag agent field to toolset entries - Update schema, docs, and all example YAML files Assisted-By: docker-agent
The PR docker#2210 moved RAG from agent-level config to standard toolset type (tools.Startable) but removed the event forwarding that showed indexing progress in the TUI sidebar. This restores event forwarding by: - Adding an EventCallback to RAGTool that forwards rag.Manager events during Start() initialization - Having StartBackgroundRAGInit discover RAG tools from agent toolsets and wire up the event callback before initialization happens - Converting RAG manager events (indexing started/progress/completed, usage, errors) back to runtime events for the TUI Assisted-By: docker-agent
Signed-off-by: David Gageot <david.gageot@docker.com>
- Remove RAGInitializer interface and StartBackgroundRAGInit indirection. RAG event callbacks are now wired in configureToolsetHandlers alongside other handler setup, using the same pattern as Elicitable/OAuthCapable. - Remove deprecated NewManagers wrapper (no callers after toolset refactor). - Clean up RAGTool: unexport internal types (QueryRAGArgs, QueryResult), inline sortResults, remove verbose debug logging from Tools(), simplify handleQueryRAG. Assisted-By: docker-agent
| return nil, errors.New("query cannot be empty") | ||
| } | ||
|
|
||
| results, err := t.manager.Query(ctx, args.Query) |
There was a problem hiding this comment.
🔴 HIGH SEVERITY: Nil pointer dereference
The handleQueryRAG method calls t.manager.Query(ctx, args.Query) without checking if t.manager is nil. While the normal construction path via createRAGTool ensures the manager is non-nil, the Start() and Stop() methods both include defensive nil checks (if t.manager == nil { return nil }), suggesting that a nil manager is considered a valid state.
If a RAGTool is somehow constructed with a nil manager (e.g., through a test fixture or future code path), invoking the query_rag tool will panic.
Recommendation: Add a nil check at the start of handleQueryRAG:
func (t *RAGTool) handleQueryRAG(ctx context.Context, args queryRAGArgs) (*tools.ToolCallResult, error) {
if t.manager == nil {
return nil, fmt.Errorf("RAG manager not initialized")
}
// ... rest of implementation
}| // ragEventForwarder returns a callback that converts RAG manager events to runtime events. | ||
| func ragEventForwarder(ragName string, r *LocalRuntime, sendEvent func(Event)) builtin.RAGEventCallback { | ||
| return func(ragEvent ragtypes.Event) { | ||
| agentName := r.CurrentAgentName() |
There was a problem hiding this comment.
The ragEventForwarder closure captures the runtime and reads r.CurrentAgentName() at the time each event is processed (line 14), not when the RAG tool is initialized. If an agent starts a long-running RAG indexing operation and then hands off to another agent, subsequent indexing events will be attributed to the new agent rather than the agent that initiated the operation.
This could confuse users in the TUI, as RAG indexing progress would appear under the wrong agent's context.
Recommendation: Capture the agent name at the time the forwarder is created:
func (r *LocalRuntime) ragEventForwarder(sendEvent func(event.Event)) func(rag.Event) {
agentName := r.CurrentAgentName() // Capture once at creation time
return func(e rag.Event) {
sendEvent(event.Event{
Type: event.TypeRAG,
AgentName: agentName, // Use captured name
RAG: &e,
})
}
}| func() { events <- Authorization(tools.ElicitationActionAccept, a.Name()) }, | ||
| r.managedOAuth, | ||
| ) | ||
|
|
There was a problem hiding this comment.
The old code in pkg/app/app.go (removed lines 119-131) called StartBackgroundRAGInit() early in the application lifecycle to begin indexing before the agent loop started. The new design relies on the tools.Startable interface and RAGTool.Start() method, but the diff doesn't show where Start() is called.
If Start() is only invoked lazily (e.g., when the tool is first used), RAG indexing will be delayed compared to the previous behavior, potentially degrading user experience.
Recommendation: Verify that Start() is called early in the toolset initialization path (e.g., in configureToolsetHandlers or during agent setup) to maintain the previous early-indexing behavior. If not already implemented, add an explicit call to start RAG tools during agent initialization.
rumpl
left a comment
There was a problem hiding this comment.
I <3 when we remove things from the runtime
Replace the special-cased RAG configuration (AgentConfig.RAG []string referencing top-level Config.RAG map) with a standard toolset type (type: rag) that follows the same patterns as MCP and other toolsets.
Key changes:
Assisted-By: docker-agent