Skip to content

fix: skip memory persistence for delegated sub-agent calls#1154

Open
NeuZhou wants to merge 1 commit intoVoltAgent:mainfrom
NeuZhou:fix/skip-memory-persist-delegated-subagent
Open

fix: skip memory persistence for delegated sub-agent calls#1154
NeuZhou wants to merge 1 commit intoVoltAgent:mainfrom
NeuZhou:fix/skip-memory-persist-delegated-subagent

Conversation

@NeuZhou
Copy link

@NeuZhou NeuZhou commented Mar 16, 2026

Problem

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 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 duplicate/phantom messages.

Root Cause

In \SubAgentManager.handoffTask(), the parent's \conversationId\ is passed directly to the sub-agent via \�aseOptions. When the sub-agent has \memory, its persistence pipeline treats the delegated task as a new user message in the parent's conversation.

Fix

\shouldPersistMemoryForContext()\ now checks for \parentAgentId\ on the \OperationContext. This field is already set during delegation (line 3692/3742 in agent.ts), so no additional plumbing is needed. When a sub-agent is executing a delegated task, it skips memory persistence entirely.

This is the minimal, non-breaking fix — sub-agents acting as stateless task executors don't need to persist to the parent's conversation history. The existing \metadata.subAgentId\ / \metadata.subAgentName\ fields (from PR #786) remain available for any future read-side filtering needs.

Workaround (before this fix)

Remove \memory\ from sub-agents entirely.

Fixes #1026


Summary by cubic

Skip memory persistence for delegated sub‑agent calls to stop polluting the parent conversation and avoid duplicate/phantom messages. Agent.shouldPersistMemoryForContext now returns false when OperationContext.parentAgentId is set (fixes #1026).

Written for commit 01ba714. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed duplicate message entries appearing in conversation memory when sub-agents are delegated tasks.

…#1026)

When a parent agent delegates a task to a sub-agent via delegate_task,
the sub-agent was persisting its messages under the parent's
conversationId, causing duplicate messages in the parent's history.

This fix checks for parentAgentId on the OperationContext — which is
already set during delegation — and skips memory persistence for
sub-agent calls. Sub-agents acting as stateless task executors should
not pollute the parent conversation's memory.

Fixes VoltAgent#1026
@changeset-bot
Copy link

changeset-bot bot commented Mar 16, 2026

⚠️ No Changeset found

Latest commit: 01ba714

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

Adds a guard condition in shouldPersistMemoryForContext to skip memory persistence when an OperationContext contains a parentAgentId, preventing duplicate messages from being written to parent conversation memory during sub-agent delegation.

Changes

Cohort / File(s) Summary
Agent Memory Persistence
packages/core/src/agent/agent.ts
Adds conditional check to prevent memory persistence for sub-agent operations with a parentAgentId, avoiding duplication in parent conversation history.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related issues

Poem

🐰 A tiny guard stands at memory's gate,
Whisp'ring to sub-agents: "Please don't duplicate!"
When parentAgentId is found,
No echoes in parent's ground—
Just one voice, true and straight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: skipping memory persistence for delegated sub-agent calls, which is the core fix in this PR.
Description check ✅ Passed The description provides comprehensive context including problem, root cause, fix details, and workaround, though it lacks explicit coverage of all template sections like related issue link, tests, docs, and changesets.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/agent/agent.ts`:
- Around line 3797-3802: The current guard checking oc.parentAgentId only
affects shouldPersistMemoryForContext paths, but delegated contexts still
persist input because prepareMessages calls prepareConversationContext(..., {
persistInput: shouldPersistMemory }) and queueSaveInput(...) uses
shouldPersistMemory which ignores parentAgentId; update those call sites
(prepareMessages, prepareConversationContext invocation, and queueSaveInput
usage) to also consult this.shouldPersistMemoryForContext(oc) — e.g., compute a
boolean like shouldPersist = this.shouldPersistMemoryForContext(oc) &&
shouldPersistMemory and pass that into prepareConversationContext and gate calls
to queueSaveInput to prevent persisting input when
shouldPersistMemoryForContext(oc) is false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b4a4334-7b75-46df-a49d-32f3cf15d074

📥 Commits

Reviewing files that changed from the base of the PR and between a3aa5f6 and 01ba714.

📒 Files selected for processing (1)
  • packages/core/src/agent/agent.ts

Comment on lines +3797 to +3802
// Skip memory persistence for delegated sub-agent calls to prevent
// polluting the parent conversation's memory with duplicate messages.
// See: https://github.com/VoltAgent/voltagent/issues/1026
if (oc.parentAgentId) {
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Delegated calls can still persist input despite this guard

Good direction, but this guard only covers paths that call shouldPersistMemoryForContext(). In delegated contexts (parentAgentId set), prepareMessages() still persists input via prepareConversationContext(..., { persistInput: shouldPersistMemory }) (Line 4490) and queueSaveInput(...) (Line 4533), where shouldPersistMemory only checks readOnly. That can still write delegated input into the parent conversation.

Consider gating those input-persistence paths with this.shouldPersistMemoryForContext(oc) as well.

Proposed fix
-    const shouldPersistMemory = resolvedMemory.readOnly !== true;
+    const shouldPersistMemory =
+      resolvedMemory.readOnly !== true && this.shouldPersistMemoryForContext(oc);

...
-              { persistInput: shouldPersistMemory },
+              { persistInput: shouldPersistMemory },

...
-          if (isSemanticSearch && shouldPersistMemory && oc.userId && oc.conversationId) {
+          if (isSemanticSearch && shouldPersistMemory && oc.userId && oc.conversationId) {
             try {
               ...
               this.memoryManager.queueSaveInput(oc, inputForMemory, oc.userId, oc.conversationId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/agent/agent.ts` around lines 3797 - 3802, The current guard
checking oc.parentAgentId only affects shouldPersistMemoryForContext paths, but
delegated contexts still persist input because prepareMessages calls
prepareConversationContext(..., { persistInput: shouldPersistMemory }) and
queueSaveInput(...) uses shouldPersistMemory which ignores parentAgentId; update
those call sites (prepareMessages, prepareConversationContext invocation, and
queueSaveInput usage) to also consult this.shouldPersistMemoryForContext(oc) —
e.g., compute a boolean like shouldPersist =
this.shouldPersistMemoryForContext(oc) && shouldPersistMemory and pass that into
prepareConversationContext and gate calls to queueSaveInput to prevent
persisting input when shouldPersistMemoryForContext(oc) is false.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/src/agent/agent.ts">

<violation number="1" location="packages/core/src/agent/agent.ts:3800">
P2: Delegated sub-agent input persistence is still enabled in `prepareMessages()` because it bypasses `shouldPersistMemoryForContext(oc)` and only checks read-only mode.</violation>

<violation number="2" location="packages/core/src/agent/agent.ts:3800">
P2: New persistence guard is incomplete: `toTool()` nested agent calls share parent conversation context without `parentAgentId`, so memory can still be persisted in delegated flows.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -3794,6 +3794,12 @@ export class Agent {
}
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Delegated sub-agent input persistence is still enabled in prepareMessages() because it bypasses shouldPersistMemoryForContext(oc) and only checks read-only mode.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/agent/agent.ts, line 3800:

<comment>Delegated sub-agent input persistence is still enabled in `prepareMessages()` because it bypasses `shouldPersistMemoryForContext(oc)` and only checks read-only mode.</comment>

<file context>
@@ -3794,6 +3794,12 @@ export class Agent {
+    // Skip memory persistence for delegated sub-agent calls to prevent
+    // polluting the parent conversation's memory with duplicate messages.
+    // See: https://github.com/VoltAgent/voltagent/issues/1026
+    if (oc.parentAgentId) {
+      return false;
+    }
</file context>
Fix with Cubic

// Skip memory persistence for delegated sub-agent calls to prevent
// polluting the parent conversation's memory with duplicate messages.
// See: https://github.com/VoltAgent/voltagent/issues/1026
if (oc.parentAgentId) {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: New persistence guard is incomplete: toTool() nested agent calls share parent conversation context without parentAgentId, so memory can still be persisted in delegated flows.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/agent/agent.ts, line 3800:

<comment>New persistence guard is incomplete: `toTool()` nested agent calls share parent conversation context without `parentAgentId`, so memory can still be persisted in delegated flows.</comment>

<file context>
@@ -3794,6 +3794,12 @@ export class Agent {
+    // Skip memory persistence for delegated sub-agent calls to prevent
+    // polluting the parent conversation's memory with duplicate messages.
+    // See: https://github.com/VoltAgent/voltagent/issues/1026
+    if (oc.parentAgentId) {
+      return false;
+    }
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant