-
Notifications
You must be signed in to change notification settings - Fork 2
session_tool_registry_lifecycle_leak
Open — documented 2026-08-03. No runtime fix has been applied.
This is a cross-repository lifecycle defect. The registry implementation lives
in mcpagent; the production chat/session lifecycle that should terminate it
lives mainly in mcp-agent-builder-go.
Think of the tool registry as a per-chat phonebook.
When a chat starts, the runtime writes down:
- which custom tools that chat may call;
- the exact executor function for each tool;
- agent-specific virtual tools such as discovery helpers;
- the latest virtual-tool scope for the chat; and
- the chat's tool allow-list.
That separation is necessary. Two workflows can expose a tool with the same name but different folder guards, workspace paths, or permissions. Looking up a tool in the wrong chat's phonebook could execute it with the wrong authority.
The defect is not that these phonebooks exist. The defect is that completed chats never have their phonebooks thrown away.
The immediate symptom is gradual process growth rather than one deterministic request failure:
- session maps grow for the lifetime of the backend process;
- executor closures can retain agents, workflow paths, guards, and other state;
- old allow-lists and virtual scopes remain addressable;
- registry diagnostics and logs become increasingly noisy; and
- a long-running server carries stale authorization/execution state for chats that no longer exist.
Map lookup remains approximately constant time, so this finding alone does not prove that every slow API request is caused by the registry. It is a confirmed resource/lifecycle leak and a stale-state risk, not yet a complete explanation for general backend latency.
A scan of agent_go/logs on 2026-08-03 found:
4,047 "Session-scoped custom tools registered"
278 "Session-scoped virtual tools registered"
0 "Cleaned up session-scoped tools"
maximum logged total_sessions: 274
The 4,325 registration events do not mean 4,325 distinct chats. Dynamic
custom-tool registration republishes the session's cumulative tool map, so one
agent can produce many registration events while its tool count rises. The
total_sessions=274 observation is the clearer retained-phonebook count.
The code confirms the missing production lifecycle:
-
mcpagent/agent/codeexec/registry.godefinesCleanupSession(sessionID)and removes custom tools, virtual scopes, latest-scope pointers, and allow-lists. - Repository-wide search finds calls only in tests and one stress-test command. No production completion path calls it.
-
mcpagent/agent/turn_session.go:Session.Closeclears only in-memory turn history. -
mcpagent/agent/agent.go:Agent.Closedeliberately preserves session-level resources because agents are rebuilt between turns. -
mcpagent/agent/session.go:CloseSessioncloses MCP connections and removes the isolated workspace, but does not clean the code-execution tool registry. -
mcpagent/agent/session.go:CloseHTTPSessiondelegates to the MCP connection registry. That closes the mapped MCP sessions, but likewise does not retire their tool-registry entries. -
mcp-agent-builder-goalready callsCloseHTTPSessionfrom session stop and cleanup paths, so the application believes it has ended the session while one of its resource registries survives.
Do not simply call CleanupSession from every Agent.Close or after every
turn.
An Agent is often replaced between turns of the same live chat. Connections
and session-scoped tools are intentionally reusable across those replacements.
Background agents and virtual-tool scopes can also share the base chat session.
Cleaning the phonebook when one temporary agent finishes could remove tools
while another turn or child agent is still using them.
Cleanup belongs to the true session owner, after active work has been canceled and joined—not to a short-lived agent instance.
Create one idempotent session-termination operation in mcpagent that owns all
session resources. Conceptually:
EndSession(sessionID)
1. prevent new work/reconnection for the session
2. wait for or cancel active tool calls
3. close MCP connections
4. remove custom-tool executors
5. remove base and :vt: virtual-tool scopes
6. remove latest-scope pointers and allow-lists
7. remove the isolated session workspace
CloseSession should become this complete operation rather than a connection-
only operation. CloseHTTPSession must enumerate every MCP session associated
with the HTTP chat and invoke the same complete termination path for each one.
The implementation should not make mcpclient import codeexec; the higher
mcpagent layer should coordinate both registries.
mcp-agent-builder-go should then call this single operation at its existing
true terminal boundaries:
- explicit chat/session clear;
- explicit stop after active work is canceled;
- terminal workflow or scheduled-run cleanup when the session will not be reused; and
- server shutdown for every remaining session.
If stop-and-resume intentionally reuses the same session ID, the next turn must rebuild its registry before tool execution. That behavior needs an explicit test; retaining stale executors is not a valid resume mechanism.
A bounded idle-session sweeper can be added as crash/leak defense, but it should not replace deterministic lifecycle cleanup.
Add a read-only registry snapshot with counts only:
- custom-tool session count and executor count;
- virtual-tool base/scope count and executor count;
- allow-list session count;
- oldest/last-touched session age; and
- cleanup count by reason (
completed,stopped,cleared,shutdown,expired).
These metrics make the fix measurable without exposing tool arguments, credentials, or executor details.
- Creating and ending 100 unique chats returns every session-registry count to its original baseline.
- Ending chat A does not change the available tools or allow-list of concurrent chat B.
- Cleanup removes
sessionID, allsessionID:vt:*scopes, the latest-scope pointer, and the allow-list. - Cleanup is idempotent and race-tested against an in-flight tool call.
- A stopped then resumed chat rebuilds the correct guarded executors before its first tool call.
- Normal agent replacement between turns does not trigger session cleanup.
-
CloseHTTPSessionand server shutdown leave zero retained tool sessions. - Production logs or metrics show one terminal cleanup reason per ended session instead of zero cleanup activity.
- Do not remove session scoping; it prevents cross-workflow contamination.
- Do not merge all tools into a global executor map.
- Do not couple cleanup to chat-history deletion—the persisted conversation and the live executable phonebook have different lifetimes.
- Do not redesign tool categories or discovery as part of this fix.
Auto-synced from docs/ on main. Edit there, not here.