You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The main RockBot.Agent process exposes ~58 native tools to the LLM (6 MCP gateway + 26 direct + 26 memory/skills/rules/tasks). DI already shapes the surface across separate executables (e.g., RockBot.ResearchAgent, RockBot.SampleAgent register far fewer tools), but inside the main agent process several in-process roles share one DI container:
The first three (subagent + A2A result) currently use ad-hoc inline .Where/HashSet filters to scope the tool list. ScheduledTaskHandler and the three other A2A handlers leak the full set. There is no shared mechanism, no drift detection, and the existing BuildAgentToolFunctions(... filter) hook is barely used.
Related: #420 (per-server typed MCP tools), #338 (search_conversation_history), #418 (SavedResponseTools) — all propose new tools and benefit from a sane scoping mechanism landing first.
Deny source a2a. Applied to all 4 A2A handlers + the late-reply fold-back handler (#424) so the synthesis LLM cannot dispatch a fresh invoke_agent while handling the current result.
New overload: ToolRegistryExtensions.BuildAgentToolFunctions(..., ToolProfile profile, ...) delegates to existing filter path. Existing callers unchanged (default = Main semantics).
In-code, not hot-reloadable. Profiles are a safety boundary (subagents must not register MCP servers, must not recurse). A bad PVC edit silently opening that boundary is a worse failure mode than a redeploy. Revisit only after telemetry shows real usage patterns.
Implementation order
src/RockBot.Tools/ToolProfile.cs (new) — record + composition helpers
src/RockBot.Tools/ToolRegistryExtensions.cs — add BuildAgentToolFunctions(..., ToolProfile, ...) overload; log profile + allowed/denied counts at Information
tests/RockBot.Host.Tests/ToolProfileSnapshotTests.cs (new) — [DataRow] per profile; snapshots tool-name list; catches drift when new sources land
Wire ToolProfiles.Subagent into SubagentRunner.cs (keep inline .Select loop; only the predicate changes — SubagentRegistryToolFunction wrapper preserved)
Wire ToolProfiles.Scheduled into ScheduledTaskHandler.cs
Wire ToolProfiles.A2ASynthesis into the 4 A2A handlers (replacing inline HashSets at A2ATaskResultHandler, A2ATaskStatusHandler, A2ATaskErrorHandler, InputRequiredHandler) and the late-reply fold-back handler from A2A late-reply fold-back to primary session #424
Explicit ToolProfiles.Main in UserMessageHandler.cs and UserFeedbackHandler.cs — no behavior change, locks in intent so future tool additions do not silently leak into restricted profiles
ToolProfileSnapshotTests — per-profile name lists; drift detector — adding a new ToolRegistrar without updating profiles fails the snapshot, forcing a conscious decision
Handler tests asserting filtered tool membership via captured ChatOptions
MSTest + Rocks (per repo convention)
Risks
Profile drift toward closed, not open.Main matches everything; restricted profiles are explicit allowlists. New tool sources show up in Main automatically and only enter restricted profiles by conscious update (forced by the snapshot test). Fails safe.
SubagentRegistryToolFunction vs RegistryToolFunction divergence. Subagent uses a namespace-scoping wrapper. Keep the inline .Select loop; only the predicate changes — no refactor of the wrapper class.
Out of scope
Per-MCP-server pruning (sub-profile that includes some MCP servers but not others)
Dynamic per-request tool selection by an LLM router
MCP gateway rework
Renaming any existing tools or Source values
Externalizing profiles to a markdown/JSON file on the PVC (deferred until usage telemetry exists)
Changing AgentLoopRunner.RunAsync's signature
Wisp executor's tool resolution path (resolves by name from the full registry; does not advertise to an LLM, unaffected)
Related
A2A late-reply fold-back to primary session #424 A2A late-reply fold-back — lands first; its handler will use the unfiltered registry until this issue lands, then move to ToolProfiles.A2ASynthesis
Background
The main
RockBot.Agentprocess exposes ~58 native tools to the LLM (6 MCP gateway + 26 direct + 26 memory/skills/rules/tasks). DI already shapes the surface across separate executables (e.g.,RockBot.ResearchAgent,RockBot.SampleAgentregister far fewer tools), but inside the main agent process several in-process roles share one DI container:UserMessageHandler(main turn)SubagentRunner(spawned in-process; uses parent DI)ScheduledTaskHandler(in-process message handler)A2ATaskResultHandler,A2ATaskStatusHandler,A2ATaskErrorHandler,InputRequiredHandler)LateA2ANotificationHandler(added in A2A late-reply fold-back to primary session #424)The first three (subagent + A2A result) currently use ad-hoc inline
.Where/HashSet filters to scope the tool list.ScheduledTaskHandlerand the three other A2A handlers leak the full set. There is no shared mechanism, no drift detection, and the existingBuildAgentToolFunctions(... filter)hook is barely used.Related: #420 (per-server typed MCP tools), #338 (search_conversation_history), #418 (SavedResponseTools) — all propose new tools and benefit from a sane scoping mechanism landing first.
Design
New file
src/RockBot.Tools/ToolProfile.cs:Static
ToolProfilesexposing named profiles built viaWith/Withoutcomposition:MainSubagentsubagent; deny namescancel_scheduled_task,mcp_register_server,mcp_unregister_server. Allows A2A invoke, schedule create/list, MCP invoke.Scheduledsubagent; deny namesmcp_register_server,mcp_unregister_server.A2ASynthesisa2a. Applied to all 4 A2A handlers + the late-reply fold-back handler (#424) so the synthesis LLM cannot dispatch a freshinvoke_agentwhile handling the current result.New overload:
ToolRegistryExtensions.BuildAgentToolFunctions(..., ToolProfile profile, ...)delegates to existingfilterpath. Existing callers unchanged (default =Mainsemantics).In-code, not hot-reloadable. Profiles are a safety boundary (subagents must not register MCP servers, must not recurse). A bad PVC edit silently opening that boundary is a worse failure mode than a redeploy. Revisit only after telemetry shows real usage patterns.
Implementation order
src/RockBot.Tools/ToolProfile.cs(new) — record + composition helperssrc/RockBot.Tools/ToolRegistryExtensions.cs— addBuildAgentToolFunctions(..., ToolProfile, ...)overload; log profile + allowed/denied counts at Informationtests/RockBot.Host.Tests/ToolProfileSnapshotTests.cs(new) —[DataRow]per profile; snapshots tool-name list; catches drift when new sources landToolProfiles.SubagentintoSubagentRunner.cs(keep inline.Selectloop; only the predicate changes —SubagentRegistryToolFunctionwrapper preserved)ToolProfiles.ScheduledintoScheduledTaskHandler.csToolProfiles.A2ASynthesisinto the 4 A2A handlers (replacing inline HashSets atA2ATaskResultHandler,A2ATaskStatusHandler,A2ATaskErrorHandler,InputRequiredHandler) and the late-reply fold-back handler from A2A late-reply fold-back to primary session #424ToolProfiles.MaininUserMessageHandler.csandUserFeedbackHandler.cs— no behavior change, locks in intent so future tool additions do not silently leak into restricted profilesTests
ToolProfileTests— pure-functionMatches+ compositionToolProfileSnapshotTests— per-profile name lists; drift detector — adding a newToolRegistrarwithout updating profiles fails the snapshot, forcing a conscious decisionChatOptionsRisks
Mainmatches everything; restricted profiles are explicit allowlists. New tool sources show up inMainautomatically and only enter restricted profiles by conscious update (forced by the snapshot test). Fails safe.SubagentRegistryToolFunctionvsRegistryToolFunctiondivergence. Subagent uses a namespace-scoping wrapper. Keep the inline.Selectloop; only the predicate changes — no refactor of the wrapper class.Out of scope
SourcevaluesAgentLoopRunner.RunAsync's signatureRelated
ToolProfiles.A2ASynthesis