14.3 Agent System Design
14.3.1 Agent Responsibility Division
In the deep research assistant, we designed three specialized Agents, each responsible for a specific task. This makes each Agent simple, easy to understand and maintain.
In Chapter 7, we learned how to use SimpleAgent to build agents. The design philosophy of SimpleAgent is simple and direct: each time the run() method is called, the Agent analyzes the user's question, decides whether to call tools, and then returns the result. This design is very effective when handling simple tasks, but when facing complex tasks like deep research, we need to continue using a multi-agent collaboration approach.
As shown in Table 14.1, the three Agents are respectively responsible for planning, summarization, and report generation.
Table 14.1 Responsibility Division of Three Agents

Let's introduce the design of each Agent in detail.
Agent 1: Research Planning Expert (TODO Planner)
Responsibility: Decompose research topics into 3-5 subtasks
Design Philosophy: The core task of the research planning expert is to understand the user's research topic, analyze the key aspects of the topic, and then generate a series of subtasks. This process is similar to the "brainstorming" stage of human researchers before starting research.
Prompt Design:
todo_planner_instructions = """
You are a research planning expert. Your task is to decompose the user's research topic into 3-5 subtasks.
Current date: {current_date}
Research topic: {research_topic}
Please analyze this research topic and decompose it into 3-5 subtasks. Each subtask should:
1. Cover an important aspect of the topic
2. Have a clear research objective
3. Be able to find relevant materials through search engines
Please return the subtask list in JSON format, each subtask containing:
- title: Task title (concise and clear)
- intent: Task intent (why research this)
- query: Search query (query string for search engines, can use English for better search results)
Example output:
[
{{
"title": "What is a multimodal model",
"intent": "Understand the basic concepts of multimodal models to lay the foundation for subsequent research",
"query": "multimodal model definition concept 2024"
}},
...
]
Please ensure:
1. Number of subtasks is between 3-5
2. Subtasks have logical relationships (e.g., from basics to applications, from current status to trends)
3. Search queries can accurately find relevant materials
4. Only return JSON, do not include other text
"""Key Design Points: The prompt includes the current date to get the latest information, explicitly requires JSON format output for easy parsing, helps the Agent understand expected output through examples, and emphasizes constraints such as number of subtasks and logical relationships.
Implementation Code:
The ToolAwareSimpleAgent here is an extension of SimpleAgent. You can learn about it in Section 14.3.2, no need to delve into it here.
class PlanningService:
def __init__(self, llm: HelloAgentsLLM):
self._agent = ToolAwareSimpleAgent(
name="TODO Planner",
system_prompt="You are a research planning expert",
llm=llm,
tool_call_listener=self._on_tool_call
)
def plan_todo_list(self, state: SummaryState) -> List[TodoItem]:
prompt = todo_planner_instructions.format(
current_date=get_current_date(),
research_topic=state.research_topic,
)
response = self._agent.run(prompt)
tasks_payload = self._extract_tasks(response)
todo_items = []
for idx, item in enumerate(tasks_payload, start=1):
task = TodoItem(
id=idx,
title=item["title"],
intent=item["intent"],
query=item["query"],
)
todo_items.append(task)
return todo_items
def _extract_tasks(self, response: str) -> List[dict]:
"""Extract JSON from Agent response"""
# Use regex to extract JSON part
json_match = re.search(r'\[.*\]', response, re.DOTALL)
if json_match:
json_str = json_match.group(0)
return json.loads(json_str)
else:
raise ValueError("Unable to extract JSON from response")Agent 2: Task Summarization Expert (Task Summarizer)
Responsibility: Summarize search results, extract key information
Design Philosophy: The core task of the task summarization expert is to read search results, extract key information, and present it in a structured way. This process is similar to human researchers taking notes after reading literature.
Prompt Design:
task_summarizer_instructions = """
You are a task summarization expert. Your task is to summarize search results and extract key information.
Task title: {task_title}
Task intent: {task_intent}
Search query: {task_query}
Search results:
{search_results}
Please carefully read the above search results, extract key information, and return a summary in Markdown format.
The summary should include:
1. **Core Viewpoints**: Core viewpoints and conclusions from search results
2. **Key Data**: Important numbers, dates, names, etc.
3. **Source Citations**: Add source citations for each viewpoint (using [1], [2], etc.)
Please ensure:
1. Summary is concise and clear, avoiding redundancy
2. Retain important details and data
3. Add source citations for each viewpoint
4. Use Markdown format (headings, lists, bold, etc.)
Example output:
## Core Viewpoints
Multimodal models are AI models that can process multiple types of data[1]. Unlike traditional unimodal models, multimodal models can simultaneously understand text, images, audio, etc.[2].
**Key Features:**
- Cross-modal understanding[1]
- Unified representation[3]
- End-to-end training[2]
## Sources
[1] https://example.com/source1
[2] https://example.com/source2
[3] https://example.com/source3
"""Key Design Points: The prompt includes task title, intent, query and other context to help the Agent understand the task, explicitly requires output to include core viewpoints, key data, and source citations, emphasizes adding source citations for each viewpoint, and helps the Agent understand the expected output format through examples.
Implementation Code:
class SummarizationService:
def __init__(self, llm: HelloAgentsLLM):
self._agent = ToolAwareSimpleAgent(
name="Task Summarizer",
system_prompt="You are a task summarization expert",
llm=llm,
tool_call_listener=self._on_tool_call
)
def summarize_task(
self,
task: TodoItem,
search_results: List[dict]
) -> str:
# Format search results
formatted_sources = self._format_sources(search_results)
prompt = task_summarizer_instructions.format(
task_title=task.title,
task_intent=task.intent,
task_query=task.query,
search_results=formatted_sources,
)
summary = self._agent.run(prompt)
return summary
def _format_sources(self, search_results: List[dict]) -> str:
"""Format search results"""
formatted = []
for idx, result in enumerate(search_results, start=1):
formatted.append(
f"[{idx}] {result['title']}\n"
f"URL: {result['url']}\n"
f"Snippet: {result['snippet']}\n"
)
return "\n".join(formatted)Agent 3: Report Writing Expert (Report Writer)
Responsibility: Integrate summaries of all subtasks and generate final report
Design Philosophy: The core task of the report writing expert is to integrate the summaries of all subtasks into a structured report. This process is similar to human researchers writing research reports after completing all investigations.
Prompt Design:
report_writer_instructions = """
You are a report writing expert. Your task is to integrate the summaries of all subtasks and generate a structured research report.
Research topic: {research_topic}
Subtask summaries:
{task_summaries}
Please integrate all the above subtask summaries and generate a structured research report.
The report should include:
1. **Title**: Research topic
2. **Overview**: Briefly introduce the research topic and report structure (2-3 paragraphs)
3. **Detailed Analysis of Each Subtask**: Organize in logical order (using level-2 headings)
4. **Summary**: Summarize the main findings of the research (1-2 paragraphs)
5. **References**: All source citations (grouped by subtask)
Please ensure:
1. Report structure is clear and logically coherent
2. Eliminate duplicate information
3. Retain all source citations
4. Use Markdown format
Example output:
# Latest Advances in Multimodal Large Models
## Overview
This report systematically researched the latest advances in multimodal large models...
## 1. What is a Multimodal Model
(Insert summary of subtask 1 here)
## 2. What are the Latest Multimodal Models
(Insert summary of subtask 2 here)
...
## Summary
Through this research, we learned about...
## References
### Task 1: What is a Multimodal Model
[1] https://example.com/source1
...
"""Key Design Points: The prompt explicitly requires the report to include title, overview, detailed analysis, summary, references and other structures, emphasizes organizing content in logical order, requires merging duplicate information to eliminate redundancy, and retains all source citations.
Implementation Code:
class ReportingService:
def __init__(self, llm: HelloAgentsLLM):
self._agent = ToolAwareSimpleAgent(
name="Report Writer",
system_prompt="You are a report writing expert",
llm=llm,
tool_call_listener=self._on_tool_call
)
def generate_report(
self,
research_topic: str,
task_summaries: List[Tuple[TodoItem, str]]
) -> str:
# Format subtask summaries
formatted_summaries = self._format_summaries(task_summaries)
prompt = report_writer_instructions.format(
research_topic=research_topic,
task_summaries=formatted_summaries,
)
report = self._agent.run(prompt)
return report
def _format_summaries(
self,
task_summaries: List[Tuple[TodoItem, str]]
) -> str:
"""Format subtask summaries"""
formatted = []
for idx, (task, summary) in enumerate(task_summaries, start=1):
formatted.append(
f"## Task {idx}: {task.title}\n"
f"Intent: {task.intent}\n\n"
f"{summary}\n"
)
return "\n".join(formatted)14.3.2 ToolAwareSimpleAgent Design
In Chapter 7, we implemented SimpleAgent, which is the basic Agent of the HelloAgents framework. But in the deep research assistant, we need an Agent that can record tool calls. This is where ToolAwareSimpleAgent comes from.
In the deep research assistant, we need to record the tool call status of each Agent for:
- Debugging: View which tools the Agent called and what parameters were passed
- Logging: Record all operations during the research process
- Analysis: Analyze the Agent's behavior patterns
- Progress Display: Show in real-time what the Agent is doing
SimpleAgent itself does not support tool call listening, so we need to extend it.
ToolAwareSimpleAgent adds a tool_call_listener parameter on top of SimpleAgent. This is a callback function that is called every time a tool is called.
Usage Example:
from hello_agents import ToolAwareSimpleAgent
def tool_listener(call_info):
print(f"Agent: {call_info['agent_name']}")
print(f"Tool: {call_info['tool_name']}")
print(f"Parameters: {call_info['parsed_parameters']}")
print(f"Result: {call_info['result']}")
agent = ToolAwareSimpleAgent(
name="Research Assistant",
system_prompt="You are a research assistant",
llm=llm,
tool_call_listener=tool_listener
)ToolAwareSimpleAgent inherits from SimpleAgent and overrides the _execute_tool_call method:
class ToolAwareSimpleAgent(SimpleAgent):
def __init__(
self,
name: str,
system_prompt: str,
llm: HelloAgentsLLM,
tool_registry: Optional[ToolRegistry] = None,
tool_call_listener: Optional[Callable] = None,
):
super().__init__(
name=name,
system_prompt=system_prompt,
llm=llm,
tool_registry=tool_registry,
)
self._tool_call_listener = tool_call_listener
def _execute_tool_call(self, tool_name: str, parameters: str) -> str:
"""Execute tool call and notify listener"""
# Parse parameters
parsed_parameters = self._parse_parameters(parameters)
# Call tool
result = super()._execute_tool_call(tool_name, parameters)
# Notify listener
if self._tool_call_listener:
self._tool_call_listener({
"agent_name": self.name,
"tool_name": tool_name,
"parsed_parameters": parsed_parameters,
"result": result,
})
return resultIn the deep research assistant, we use ToolAwareSimpleAgent to record all Agent tool calls:
class DeepResearchAgent:
def __init__(self, config: Configuration):
self.config = config
self.llm = HelloAgentsLLM(...)
# Create tool call listener
def tool_listener(call_info):
self._emit_event({
"type": "tool_call",
"agent": call_info["agent_name"],
"tool": call_info["tool_name"],
"parameters": call_info["parsed_parameters"],
})
# Create three Agents, all using the same listener
self.planner = PlanningService(self.llm, tool_listener)
self.summarizer = SummarizationService(self.llm, tool_listener)
self.reporter = ReportingService(self.llm, tool_listener)This way, all Agent tool calls are recorded and pushed to the front-end via SSE, displayed to the user in real-time.
14.3.3 Agent Collaboration Mode
The three Agents have a sequential collaboration relationship, as shown in Figure 14.6.

Figure 14.6 Agent Collaboration Process
The characteristics of the sequential collaboration mode are:
- Linear Process: Agents execute in a fixed order
- Clear Input and Output: Each Agent's input comes from the previous Agent's output
- No Concurrency: Only one Agent is working at the same time
DeepResearchAgent is the core coordinator of the entire system, responsible for scheduling the three Agents:
class DeepResearchAgent:
def run(self, research_topic: str) -> str:
# 1. Planning stage
self._emit_event({"type": "status", "message": "Planning research tasks..."})
todo_list = self.planner.plan_todo_list(research_topic)
self._emit_event({"type": "tasks", "tasks": todo_list})
# 2. Execution stage
task_summaries = []
for task in todo_list:
self._emit_event({
"type": "status",
"message": f"Researching: {task.title}"
})
# Search
search_results = self.search_service.search(task.query)
# Summarize
summary = self.summarizer.summarize_task(task, search_results)
task_summaries.append((task, summary))
self._emit_event({
"type": "task_completed",
"task_id": task.id
})
# 3. Reporting stage
self._emit_event({"type": "status", "message": "Generating report..."})
report = self.reporter.generate_report(research_topic, task_summaries)
self._emit_event({"type": "report", "content": report})
return report