diff --git a/internal/agent/loop.go b/internal/agent/loop.go index 1f276ff0..5133f87c 100644 --- a/internal/agent/loop.go +++ b/internal/agent/loop.go @@ -434,6 +434,9 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error) } } + // Inject agent key into context for tool-level resolution (managed mode: multiple agents share tool registry) + ctx = tools.WithToolAgentKey(ctx, l.id) + // Security: truncate oversized user messages gracefully (feed truncation notice into LLM) maxChars := l.maxMessageChars if maxChars <= 0 { diff --git a/internal/tools/context_keys.go b/internal/tools/context_keys.go index bceff734..8a9b8f88 100644 --- a/internal/tools/context_keys.go +++ b/internal/tools/context_keys.go @@ -20,6 +20,7 @@ const ( ctxSandboxKey toolContextKey = "tool_sandbox_key" ctxAsyncCB toolContextKey = "tool_async_cb" ctxWorkspace toolContextKey = "tool_workspace" + ctxAgentKey toolContextKey = "tool_agent_key" ) func WithToolChannel(ctx context.Context, channel string) context.Context { @@ -76,6 +77,18 @@ func ToolWorkspaceFromCtx(ctx context.Context) string { return v } +// WithToolAgentKey injects the calling agent's key into context. +// In managed mode multiple agents share a single tool registry; the agent key +// lets tools like spawn/subagent identify which agent is the parent. +func WithToolAgentKey(ctx context.Context, key string) context.Context { + return context.WithValue(ctx, ctxAgentKey, key) +} + +func ToolAgentKeyFromCtx(ctx context.Context) string { + v, _ := ctx.Value(ctxAgentKey).(string) + return v +} + // --- Vision / ImageGen config (per-agent overrides) --- const ( diff --git a/internal/tools/subagent_spawn_tool.go b/internal/tools/subagent_spawn_tool.go index 3ae1b7f5..0e114f84 100644 --- a/internal/tools/subagent_spawn_tool.go +++ b/internal/tools/subagent_spawn_tool.go @@ -61,7 +61,13 @@ func (t *SpawnTool) Execute(ctx context.Context, args map[string]interface{}) *R peerKind := ToolPeerKindFromCtx(ctx) callback := ToolAsyncCBFromCtx(ctx) - msg, err := t.manager.Spawn(ctx, t.parentID, t.depth, task, label, modelOverride, + // Resolve parent agent from ctx (managed mode) with fallback to construction-time default + parentID := ToolAgentKeyFromCtx(ctx) + if parentID == "" { + parentID = t.parentID + } + + msg, err := t.manager.Spawn(ctx, parentID, t.depth, task, label, modelOverride, channel, chatID, peerKind, callback) if err != nil { return ErrorResult(err.Error()) diff --git a/internal/tools/subagent_tool.go b/internal/tools/subagent_tool.go index b6884257..a1593bc8 100644 --- a/internal/tools/subagent_tool.go +++ b/internal/tools/subagent_tool.go @@ -148,7 +148,13 @@ func (t *SubagentTool) executeRun(ctx context.Context, args map[string]interface channel := ToolChannelFromCtx(ctx) chatID := ToolChatIDFromCtx(ctx) - result, iterations, err := t.manager.RunSync(ctx, t.parentID, t.depth, task, label, + // Resolve parent agent from ctx (managed mode) with fallback to construction-time default + parentID := ToolAgentKeyFromCtx(ctx) + if parentID == "" { + parentID = t.parentID + } + + result, iterations, err := t.manager.RunSync(ctx, parentID, t.depth, task, label, channel, chatID) if err != nil { return ErrorResult(fmt.Sprintf("Subagent '%s' failed: %v", label, err))