Skip to content

Conversation

@rdheekonda
Copy link
Contributor

@rdheekonda rdheekonda commented Jan 14, 2026

Fix Agent Thread.fork() RecursionError from circular references

Key Changes:

  • Fixed infinite recursion in Thread.fork() caused by circular
    references in rigging Message objects
  • Replaced deepcopy with serialize/deserialize pattern to break
    circular references
  • Forked threads now start with empty events

Added:

N/A

Changed:

  • Thread.fork() now uses msg.model_dump() + Message.model_validate()
    instead of deepcopy() for each message
  • Forked threads no longer copy parent events (events are execution
    history, each fork should track its own)

When messages contained tool calls or parsed models with back-references,
Pydantic's deepcopy() would traverse circular object graphs in
pydantic_private attributes, causing infinite recursion. The
serialize/deserialize approach breaks these circular references and creates
truly independent Message instances.

Testing:

The forking example in agents/how-to/build-multi-agent-workflows.mdx now
works without RecursionError when exploring multiple branches in parallel.

Test code:

import asyncio
import dreadnode as dn

agent = dn.Agent(
    name="explorer",
    model="gpt-4o",
    tools=[dn.agent.tools.fs.Filesystem(path=".", variant="read")],
)

# Initial context
thread = dn.agent.Thread()
await agent.run("List the main modules in this project.", thread=thread)

# Fork and explore in parallel
async def explore_branch(focus: str) -> dn.agent.AgentResult:
    branch = thread.fork()  # Independent copy
    return await agent.run(f"Deep dive into {focus}. What issues do you find?", thread=branch)

# Explore multiple areas concurrently
results = await asyncio.gather(
    explore_branch("the authentication module"),
    explore_branch("the database layer"),
    explore_branch("the API endpoints"),
)

# Combine findings
for focus, result in zip(["auth", "database", "API"], results):
    print(f"\n{focus}:")
    print(result.messages[-1].content[:300])

Generated Summary:

  • Removed unnecessary deep copy imports to streamline code.
  • Updated the fork method to:
    • Create a new thread with the same messages but cleared events.
    • Avoid shared references by reconstructing message objects from their dict representations rather than copying.
  • Potentially improves performance by reducing overhead from deep copying large data structures.

This summary was generated with ❤️ by rigging

@rdheekonda rdheekonda requested a review from monoxgas January 14, 2026 05:30
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.

2 participants