fix(spawn): resolve parent agent from context in managed mode (#4)

In managed mode, multiple agents share a single tool registry. The spawn
and subagent tools were constructed with a hardcoded parentID of "default",
which does not exist as an agent in managed mode deployments. This caused
subagent announces to fail with "agent default not found" when any
non-default agent (e.g. a secondary agent) tried to spawn a subagent.

Fix: add ctxAgentKey to tool context keys (same pattern as channel/chatID),
inject the calling agent's key from the loop, and read it in spawn/subagent
tools with a fallback to the construction-time default for backward
compatibility with standalone mode.
This commit is contained in:
Michael authored and GitHub committed 2026-02-27 16:06:18 +07:00
1 parent 406f83160e
commit 0105bab2d5
4 files changed
+30 -2

No files matched your search

+3
View File
@@ -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 {
+13
View File
@@ -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 (
+7 -1
View File
@@ -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())
+7 -1
View File
@@ -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))