The ConversationTreeComponent enables branching conversation history, allowing agents to explore multiple reasoning paths and compare outcomes.
Tree-structured conversations support:
- Branching: Create alternative conversation paths from any message
- Navigation: Switch between branches to explore different reasoning
- Linearization: Convert tree to flat message list for LLM compatibility
- Message Tracking: Parent-child relationships via
parent_message_id
Messages in a tree have parent-child relationships:
from ecs_agent.types import ConversationMessage
root_msg = ConversationMessage(
id="msg_root",
parent_message_id=None, # Root message has no parent
role="user",
content="What's the capital of France?",
)
child_msg = ConversationMessage(
id="msg_1",
parent_message_id="msg_root", # Links to parent
role="assistant",
content="Paris is the capital of France.",
)Branches track conversation paths from root to leaf:
from ecs_agent.types import ConversationBranch
branch = ConversationBranch(
branch_id="branch_main",
leaf_message_id="msg_1", # Current end of this branch
)The component stores all messages and branches:
from ecs_agent.components import ConversationTreeComponent
world.add_component(
entity,
ConversationTreeComponent(
messages={"msg_root": root_msg, "msg_1": child_msg},
current_branch_id="branch_main",
branches={"branch_main": branch},
),
)from ecs_agent import World
from ecs_agent.components import ConversationTreeComponent
from ecs_agent.types import ConversationMessage, ConversationBranch
world = World()
entity = world.create_entity()
# Create root message
root = ConversationMessage(
id="msg_0",
parent_message_id=None,
role="user",
content="Explain quantum entanglement.",
)
# Create initial branch
branch = ConversationBranch(
branch_id="main",
leaf_message_id="msg_0",
)
# Add tree component
world.add_component(
entity,
ConversationTreeComponent(
messages={"msg_0": root},
current_branch_id="main",
branches={"main": branch},
),
)Use the conversation_tree module to add messages:
from ecs_agent.conversation_tree import add_message_to_tree
tree = world.get_component(entity, ConversationTreeComponent)
if tree:
new_msg = ConversationMessage(
id="msg_1",
parent_message_id="msg_0",
role="assistant",
content="Quantum entanglement is...",
)
add_message_to_tree(tree, new_msg, branch_id="main")Branch from any message to explore alternatives:
from ecs_agent.conversation_tree import create_branch
# Create alternative response branch
alt_msg = ConversationMessage(
id="msg_alt",
parent_message_id="msg_0", # Same parent as msg_1
role="assistant",
content="Let me explain it differently...",
)
create_branch(tree, alt_msg, new_branch_id="alternative", parent_message_id="msg_0")Change the active branch to explore different paths:
from ecs_agent.conversation_tree import switch_branch
switch_branch(tree, "alternative")
# Now tree.current_branch_id == "alternative"Most systems expect flat message lists. Convert tree to linear:
from ecs_agent.conversation_tree import linearize_branch
messages = linearize_branch(tree, branch_id="main")
# Returns list[Message] following parent_message_id chainThe ReasoningSystem can work with both flat and tree conversations:
from ecs_agent.systems.reasoning import ReasoningSystem
from ecs_agent.components import LLMComponent, ConversationComponent
# Option 1: Use ConversationComponent (flat)
world.add_component(
entity,
ConversationComponent(messages=[...]),
)
# Option 2: Use ConversationTreeComponent (branching)
# System will automatically linearize current branch
world.add_component(
entity,
ConversationTreeComponent(...),
)
# Both work with ReasoningSystem
world.register_system(ReasoningSystem(priority=0), priority=0)Explore multiple solution approaches:
# Create main branch
add_message_to_tree(tree, user_msg, branch_id="main")
# Try approach A
create_branch(tree, approach_a_msg, new_branch_id="approach_a", parent_message_id=user_msg.id)
# Try approach B
create_branch(tree, approach_b_msg, new_branch_id="approach_b", parent_message_id=user_msg.id)
# Compare outcomes
results_a = linearize_branch(tree, "approach_a")
results_b = linearize_branch(tree, "approach_b")Backtrack to earlier messages and try alternatives:
# Find message to backtrack to
target_msg = tree.messages["msg_3"]
# Create new branch from that point
retry_msg = ConversationMessage(
id="msg_retry",
parent_message_id=target_msg.id,
role="user",
content="Let's try a different approach...",
)
create_branch(tree, retry_msg, new_branch_id="retry", parent_message_id=target_msg.id)
switch_branch(tree, "retry")Tree structure enables visual representation:
msg_0 (user)
├── msg_1 (assistant) [branch: main]
│ └── msg_2 (user)
│ └── msg_3 (assistant)
└── msg_alt (assistant) [branch: alternative]
└── msg_alt_2 (user)
add_message_to_tree(tree, message, branch_id)— Append message to branchcreate_branch(tree, message, new_branch_id, parent_message_id)— Create new branchswitch_branch(tree, branch_id)— Change active branchlinearize_branch(tree, branch_id)— Convert branch to flat message listget_branch_messages(tree, branch_id)— Get all messages in branchfind_common_ancestor(tree, msg_id_1, msg_id_2)— Find branch point
messages: dict[str, ConversationMessage]— All messages by IDcurrent_branch_id: str | None— Active branchbranches: dict[str, ConversationBranch]— All branches by ID
- Conversation Component — Flat conversation
- Memory System — Message management
- Serialization — Persisting trees