Skip to content

[BUG] Sub-agent with memory writes duplicate messages to parent conversation history via delegate_task #1026

@wattbuild-josh-diehl

Description

@wattbuild-josh-diehl

Description

When a parent (supervisor) agent delegates a task to a sub-agent via delegate_task, and the sub-agent has a memory instance configured, the sub-agent persists its own user message (the delegated task text) and assistant response into the same conversationId as the parent agent. On subsequent turns, the parent's prepareMessages() loads these extra messages, causing the LLM to see "phantom" user messages it did not originate.

This results in the LLM re-answering prior prompts or producing confused responses in multi-turn conversations.

Steps to Reproduce

  1. Create a supervisor agent with memory configured
  2. Create a sub-agent also with memory configured (sharing the same Memory instance or separate — both reproduce)
  3. Register the sub-agent on the supervisor via subAgents: [mySubAgent]
  4. Call supervisorAgent.streamText(prompt, { conversationId: "conv-1", userId: "user-1" })
  5. The supervisor delegates to the sub-agent via delegate_task
  6. Inspect the memory store — two user messages now exist for the same turn:
    • The original user message (from the supervisor's streamText call)
    • The delegated task text (stored by the sub-agent when it processes the delegation)
  7. Send a second message to the same conversationId
  8. The supervisor now sees both user messages in history and the LLM attempts to answer both

Expected Behavior

Sub-agent delegation should not pollute the parent conversation's memory. Only the supervisor's user/assistant messages should be persisted under the parent conversationId. Sub-agent internal messages should either:

  • Not be persisted at all, or
  • Be persisted under a separate conversationId, or
  • Be automatically filtered out when loading parent conversation history

Actual Behavior

SubAgentManager.handoffTask() passes the parent's conversationId directly to the sub-agent:

const handoffConversationId = conversationId || crypto.randomUUID();
// ...
const baseOptions = {
  conversationId: handoffConversationId,
  userId,
  // ...
};

The sub-agent's streamText() then stores messages under this same conversationId, creating duplicates.

Root Cause

In packages/core/src/agent/subagent/index.ts, handoffTask() reuses the parent's conversationId for the sub-agent call. When the sub-agent has memory, its prepareMessages() → memory load → generate → memory persist pipeline treats the delegated task as a new user message in the parent's conversation.

The includeAgentsMemory supervisor config is read-side only (controls what appears in the supervisor's system prompt) and does not prevent sub-agents from writing to memory.

The metadata.subAgentId / metadata.subAgentName fields added in PR #786 enable post-hoc filtering but are not automatically used during message loading.

Workaround

Remove memory from sub-agents. Since sub-agents are typically stateless task executors, they don't need conversation persistence:

// Before (broken)
const subAgent = new Agent({ name: "subAgent", memory, tools: [...] });

// After (workaround)
const subAgent = new Agent({ name: "subAgent", tools: [...] });

Suggested Fix

One or more of:

  1. Skip memory persistence for delegated calls: When parentAgentId is set on the OperationContext (which already happens during delegation), skip memory persistence in the sub-agent
  2. Use a separate conversationId for sub-agents: Generate a child conversationId (e.g., sub-${parentConversationId}-${subAgentName}-${uuid}) instead of reusing the parent's
  3. Auto-filter on read: When loading messages for the parent agent, automatically filter out messages where metadata.subAgentName is set

Environment

  • @voltagent/core: latest (installed from npm)
  • Memory adapter: @voltagent/libsql (LibSQLMemoryAdapter)
  • Node.js 22

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions