9.6 Long-Horizon Agent in Practice: Codebase Maintenance Assistant
Now, let's integrate ContextBuilder, NoteTool, and TerminalTool to build a complete long-horizon agent—Codebase Maintenance Assistant. This assistant can:
- Explore and understand codebase structure
- Record discovered issues and improvement points
- Track long-term refactoring tasks
- Maintain coherence under context window limitations
9.6.1 Scenario Setup and Requirements Analysis
Business Scenario
Suppose we are maintaining a medium-sized Python web application. This codebase contains about 50 Python files, built with the Flask framework, covering data models, business logic, API interfaces, and other modules, while also having some technical debt that needs to be gradually cleaned up. In this scenario, we need an intelligent assistant to help us explore the codebase, understand project structure, dependencies, and code style; identify issues in the code, such as code duplication, excessive complexity, lack of tests, etc.; track task progress, record to-do items, completed work, and encountered blockers; and provide coherent refactoring recommendations based on historical context.
Challenges and Solutions
This scenario faces several typical long-horizon task challenges. First is the problem of information exceeding the context window—the entire codebase may contain tens of thousands of lines of code, which cannot be placed in the context window all at once. We solve this by using TerminalTool for instant, on-demand code exploration, viewing specific files only when needed. Second is the cross-session state management challenge—refactoring tasks may last for days and need to maintain progress across multiple sessions. We address this by using NoteTool to record phased progress, to-do items, and key decisions. Finally, there's the issue of context quality and relevance—each conversation needs to review relevant historical information but cannot be overwhelmed by irrelevant information. We use ContextBuilder to intelligently filter and organize context, ensuring high signal density.
9.6.2 System Architecture Design
Our codebase maintenance assistant adopts a three-layer architecture, as shown in Figure 9.3:

Figure 9.3 Three-layer architecture of codebase maintenance assistant
9.6.3 Core Implementation
Now let's implement the core class of this system:
from typing import Dict, Any, List, Optional
from datetime import datetime
import json
from hello_agents import SimpleAgent, HelloAgentsLLM
from hello_agents.context import ContextBuilder, ContextConfig, ContextPacket
from hello_agents.tools import MemoryTool, NoteTool, TerminalTool
from hello_agents.core.message import Message
class CodebaseMaintainer:
"""Codebase Maintenance Assistant - Long-horizon agent example
Integrates ContextBuilder + NoteTool + TerminalTool + MemoryTool
Implements cross-session codebase maintenance task management
"""
def __init__(
self,
project_name: str,
codebase_path: str,
llm: Optional[HelloAgentsLLM] = None
):
self.project_name = project_name
self.codebase_path = codebase_path
self.session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
# Initialize LLM
self.llm = llm or HelloAgentsLLM()
# Initialize tools
self.memory_tool = MemoryTool(user_id=project_name)
self.note_tool = NoteTool(workspace=f"./{project_name}_notes")
self.terminal_tool = TerminalTool(workspace=codebase_path, timeout=60)
# Initialize context builder
self.context_builder = ContextBuilder(
memory_tool=self.memory_tool,
rag_tool=None, # This case does not use RAG
config=ContextConfig(
max_tokens=4000,
reserve_ratio=0.15,
min_relevance=0.2,
enable_compression=True
)
)
# Conversation history
self.conversation_history: List[Message] = []
# Statistics
self.stats = {
"session_start": datetime.now(),
"commands_executed": 0,
"notes_created": 0,
"issues_found": 0
}
print(f"✅ Codebase maintenance assistant initialized: {project_name}")
print(f"📁 Working directory: {codebase_path}")
print(f"🆔 Session ID: {self.session_id}")
def run(self, user_input: str, mode: str = "auto") -> str:
"""Run assistant
Args:
user_input: User input
mode: Running mode
- "auto": Automatically decide whether to use tools
- "explore": Focus on code exploration
- "analyze": Focus on problem analysis
- "plan": Focus on task planning
Returns:
str: Assistant's answer
"""
print(f"\n{'='*80}")
print(f"👤 User: {user_input}")
print(f"{'='*80}\n")
# Step 1: Execute preprocessing based on mode
pre_context = self._preprocess_by_mode(user_input, mode)
# Step 2: Retrieve relevant notes
relevant_notes = self._retrieve_relevant_notes(user_input)
note_packets = self._notes_to_packets(relevant_notes)
# Step 3: Build optimized context
context = self.context_builder.build(
user_query=user_input,
conversation_history=self.conversation_history,
system_instructions=self._build_system_instructions(mode),
custom_packets=note_packets + pre_context
)
# Step 4: Call LLM
print("🤖 Thinking...")
response = self.llm.invoke(context)
# Step 5: Post-processing
self._postprocess_response(user_input, response)
# Step 6: Update conversation history
self._update_history(user_input, response)
print(f"\n🤖 Assistant: {response}\n")
print(f"{'='*80}\n")
return response
def _preprocess_by_mode(
self,
user_input: str,
mode: str
) -> List[ContextPacket]:
"""Execute preprocessing based on mode, collect relevant information"""
packets = []
if mode == "explore" or mode == "auto":
# Explore mode: Automatically view project structure
print("🔍 Exploring codebase structure...")
structure = self.terminal_tool.run({"command": "find . -type f -name '*.py' | head -n 20"})
self.stats["commands_executed"] += 1
packets.append(ContextPacket(
content=f"[Codebase Structure]\n{structure}",
timestamp=datetime.now(),
token_count=len(structure) // 4,
relevance_score=0.6,
metadata={"type": "code_structure", "source": "terminal"}
))
if mode == "analyze":
# Analyze mode: Check code complexity and issues
print("📊 Analyzing code quality...")
# Count lines of code
loc = self.terminal_tool.run({"command": "find . -name '*.py' -exec wc -l {} + | tail -n 1"})
# Find TODO and FIXME
todos = self.terminal_tool.run({"command": "grep -rn 'TODO\\|FIXME' --include='*.py' | head -n 10"})
self.stats["commands_executed"] += 2
packets.append(ContextPacket(
content=f"[Code Statistics]\n{loc}\n\n[To-Do Items]\n{todos}",
timestamp=datetime.now(),
token_count=(len(loc) + len(todos)) // 4,
relevance_score=0.7,
metadata={"type": "code_analysis", "source": "terminal"}
))
if mode == "plan":
# Planning mode: Load recent notes
print("📋 Loading task planning...")
task_notes = self.note_tool.run({
"action": "list",
"note_type": "task_state",
"limit": 3
})
if task_notes:
content = "\n".join([f"- {note['title']}" for note in task_notes])
packets.append(ContextPacket(
content=f"[Current Tasks]\n{content}",
timestamp=datetime.now(),
token_count=len(content) // 4,
relevance_score=0.8,
metadata={"type": "task_plan", "source": "notes"}
))
return packets
def _retrieve_relevant_notes(self, query: str, limit: int = 3) -> List[Dict]:
"""Retrieve relevant notes"""
try:
# Prioritize retrieving blockers
blockers = self.note_tool.run({
"action": "list",
"note_type": "blocker",
"limit": 2
})
# Search relevant notes
search_results = self.note_tool.run({
"action": "search",
"query": query,
"limit": limit
})
# Merge and deduplicate
all_notes = {note.get('note_id') or note.get('id'): note for note in (blockers or []) + (search_results or [])}
return list(all_notes.values())[:limit]
except Exception as e:
print(f"[WARNING] Note retrieval failed: {e}")
return []
def _notes_to_packets(self, notes: List[Dict]) -> List[ContextPacket]:
"""Convert notes to context packets"""
packets = []
for note in notes:
# Set different relevance scores based on note type
relevance_map = {
"blocker": 0.9,
"action": 0.8,
"task_state": 0.75,
"conclusion": 0.7
}
note_type = note.get('type', 'general')
relevance = relevance_map.get(note_type, 0.6)
content = f"[Note: {note.get('title', 'Untitled')}]\nType: {note_type}\n\n{note.get('content', '')}"
packets.append(ContextPacket(
content=content,
timestamp=datetime.fromisoformat(note.get('updated_at', datetime.now().isoformat())),
token_count=len(content) // 4,
relevance_score=relevance,
metadata={
"type": "note",
"note_type": note_type,
"note_id": note.get('note_id') or note.get('id')
}
))
return packets
def _build_system_instructions(self, mode: str) -> str:
"""Build system instructions"""
base_instructions = f"""You are the codebase maintenance assistant for the {self.project_name} project.
Your core capabilities:
1. Use TerminalTool to explore codebase (ls, cat, grep, find, etc.)
2. Use NoteTool to record discoveries and tasks
3. Provide coherent recommendations based on historical notes
Current session ID: {self.session_id}
"""
mode_specific = {
"explore": """
Current mode: Explore codebase
You should:
- Actively use terminal commands to understand code structure
- Identify key modules and files
- Record project architecture in notes
""",
"analyze": """
Current mode: Analyze code quality
You should:
- Find code issues (duplication, complexity, TODOs, etc.)
- Evaluate code quality
- Record discovered issues as blocker or action notes
""",
"plan": """
Current mode: Task planning
You should:
- Review historical notes and tasks
- Formulate next action plan
- Update task status notes
""",
"auto": """
Current mode: Auto decision
You should:
- Flexibly choose strategies based on user needs
- Use tools when needed
- Maintain professionalism and practicality in responses
"""
}
return base_instructions + mode_specific.get(mode, mode_specific["auto"])
def _postprocess_response(self, user_input: str, response: str):
"""Post-processing: Analyze response, automatically record important information"""
# If issues found, automatically create blocker note
if any(keyword in response.lower() for keyword in ["issue", "bug", "error", "blocker", "problem"]):
try:
self.note_tool.run({
"action": "create",
"title": f"Issue found: {user_input[:30]}...",
"content": f"## User Input\n{user_input}\n\n## Issue Analysis\n{response[:500]}...",
"note_type": "blocker",
"tags": [self.project_name, "auto_detected", self.session_id]
})
self.stats["notes_created"] += 1
self.stats["issues_found"] += 1
print("📝 Automatically created issue note")
except Exception as e:
print(f"[WARNING] Failed to create note: {e}")
# If task planning, automatically create action note
elif any(keyword in user_input.lower() for keyword in ["plan", "next", "task", "todo"]):
try:
self.note_tool.run({
"action": "create",
"title": f"Task planning: {user_input[:30]}...",
"content": f"## Discussion\n{user_input}\n\n## Action Plan\n{response[:500]}...",
"note_type": "action",
"tags": [self.project_name, "planning", self.session_id]
})
self.stats["notes_created"] += 1
print("📝 Automatically created action plan note")
except Exception as e:
print(f"[WARNING] Failed to create note: {e}")
def _update_history(self, user_input: str, response: str):
"""Update conversation history"""
self.conversation_history.append(
Message(content=user_input, role="user", timestamp=datetime.now())
)
self.conversation_history.append(
Message(content=response, role="assistant", timestamp=datetime.now())
)
# Limit history length (keep recent 10 rounds of conversation)
if len(self.conversation_history) > 20:
self.conversation_history = self.conversation_history[-20:]
# === Convenience methods ===
def explore(self, target: str = ".") -> str:
"""Explore codebase"""
return self.run(f"Please explore the code structure of {target}", mode="explore")
def analyze(self, focus: str = "") -> str:
"""Analyze code quality"""
query = f"Please analyze code quality" + (f", focusing on {focus}" if focus else "")
return self.run(query, mode="analyze")
def plan_next_steps(self) -> str:
"""Plan next steps"""
return self.run("Based on current progress, plan next steps", mode="plan")
def execute_command(self, command: str) -> str:
"""Execute terminal command"""
result = self.terminal_tool.run({"command": command})
self.stats["commands_executed"] += 1
return result
def create_note(
self,
title: str,
content: str,
note_type: str = "general",
tags: List[str] = None
) -> str:
"""Create note"""
result = self.note_tool.run({
"action": "create",
"title": title,
"content": content,
"note_type": note_type,
"tags": tags or [self.project_name]
})
self.stats["notes_created"] += 1
return result
def get_stats(self) -> Dict[str, Any]:
"""Get statistics"""
duration = (datetime.now() - self.stats["session_start"]).total_seconds()
# Get note summary
try:
note_summary = self.note_tool.run({"action": "summary"})
except:
note_summary = {}
return {
"session_info": {
"session_id": self.session_id,
"project": self.project_name,
"duration_seconds": duration
},
"activity": {
"commands_executed": self.stats["commands_executed"],
"notes_created": self.stats["notes_created"],
"issues_found": self.stats["issues_found"]
},
"notes": note_summary
}
def generate_report(self, save_to_file: bool = True) -> Dict[str, Any]:
"""Generate session report"""
report = self.get_stats()
if save_to_file:
report_file = f"maintainer_report_{self.session_id}.json"
with open(report_file, 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2, default=str)
report["report_file"] = report_file
print(f"📄 Report saved: {report_file}")
return report9.6.4 Complete Usage Example
Now let's demonstrate the workflow of this long-horizon agent through a complete usage scenario:
# ========== Initialize Assistant ==========
from hello_agents import HelloAgentsLLM
maintainer = CodebaseMaintainer(
project_name="my_flask_app",
codebase_path="./my_flask_app",
llm=HelloAgentsLLM()
)
# ========== Day 1: Explore Codebase ==========
# 1. Initial exploration
response = maintainer.explore()
"""
🔍 Exploring codebase structure...
🤖 Thinking...
🤖 Assistant: I have scanned the codebase, this is a Flask web application, mainly containing the following modules:
📁 Project structure:
- app/
├── models/ # Data models (User, Product, Order, etc.)
├── routes/ # API routes
├── services/ # Business logic
└── utils/ # Utility functions
- tests/ # Test files
- migrations/ # Database migrations
- config.py # Configuration file
Total approximately 3,500 lines of Python code.
Recommended next steps:
1. Review core model definitions
2. Check routes and API design
3. Evaluate test coverage
"""
# 2. Deep dive into a specific module
response = maintainer.run("Please analyze the data model design in the app/models/ directory")
"""
🤖 Assistant: I reviewed the app/models/ directory and found the following data models:
1. User (user.py)
- Fields: id, username, email, password_hash, created_at
- Relationships: One-to-many Order
- Issue: ⚠️ Missing index, email field not set with unique constraint
2. Product (product.py)
- Fields: id, name, description, price, stock
- Issue: ✅ Well designed
3. Order (order.py)
- Fields: id, user_id, product_id, quantity, status
- Issue: ⚠️ Missing creation time field, not conducive to data analysis
Recommendations:
1. Add unique constraint to User.email
2. Add created_at and updated_at fields to Order
3. Consider adding soft delete support
I have recorded these issues as blocker notes.
"""
# [Automatically created blocker note]
# ========== Day 2: Analyze Code Quality ==========
# 1. Overall quality analysis
response = maintainer.analyze()
"""
📊 Analyzing code quality...
🤖 Thinking...
🤖 Assistant: Code quality analysis report:
📊 Code statistics:
- Total lines: 3,542 lines
- Average file length: approximately 150 lines
- Largest file: services/order_service.py (456 lines)
⚠️ Issues found:
1. Code duplication (HIGH):
- user_service.py and product_service.py have similar CRUD logic
- Recommendation: Extract base class BaseService
2. Excessive complexity (MEDIUM):
- order_service.py::process_order method has 8 levels of nesting
- Recommendation: Split into multiple small methods
3. Lack of tests (HIGH):
- Test coverage only 45%
- services/ directory has no tests at all
4. TODO/FIXME (12 items):
- Most are "optimize later" type comments
- Recommendation: Clean up or convert to formal tasks
I have recorded these issues as notes and marked priorities.
"""
# [Automatically created multiple blocker notes]
# 2. View specific issue
response = maintainer.run("Please review the process_order method in order_service.py and provide refactoring recommendations")
"""
🤖 Assistant: I reviewed the code of the process_order method:
【Original Code】
def process_order(self, order_id):
order = Order.query.get(order_id)
if order:
if order.status == 'pending':
product = Product.query.get(order.product_id)
if product:
if product.stock >= order.quantity:
# ... 8 levels of nesting continue ...
【Problem Analysis】
1. Deep nesting leads to poor readability
2. Lack of error handling
3. Business logic mixed with data access
【Refactoring Recommendations】
def process_order(self, order_id):
# 1. Early return pattern, reduce nesting
order = self._get_order(order_id)
if not order or order.status != 'pending':
return False
# 2. Extract methods, single responsibility
if not self._check_stock(order):
return False
# 3. Use transactions to ensure consistency
with db.transaction():
self._update_stock(order)
self._update_order_status(order, 'processing')
return True
【Next Steps】
1. I have created a refactoring plan note
2. Recommend writing test cases first
3. Refactor gradually, keeping functionality unchanged
"""
# [Automatically created action note]
# ========== Day 3: Plan Refactoring Tasks ==========
# 1. Review progress
response = maintainer.plan_next_steps()
"""
📋 Loading task planning...
🤖 Thinking...
🤖 Assistant: Based on the exploration and analysis from the past two days, I have organized refactoring task priorities:
🔴 High priority (complete this week):
1. [blocker] Add unique constraint to User.email
- Risk: Low, just database migration
- Effort: 1 hour
2. [blocker] Extract BaseService to reduce code duplication
- Risk: Medium, affects multiple service classes
- Effort: Half a day
3. [blocker] Refactor process_order method
- Risk: High, core business logic
- Effort: 1 day (including writing tests)
🟡 Medium priority (next week):
4. Add unit tests for services/
5. Clean up TODO comments
6. Add time fields to Order
🟢 Low priority (TBD):
7. Performance optimization
8. Documentation updates
Recommendations:
- This week, first resolve data model issues (tasks 1, 6)
- Next week, conduct code refactoring (tasks 2, 3)
- Continuously increase test coverage (task 4)
I have updated task status notes, you can check progress anytime.
"""
# 2. Manually create detailed refactoring plan
maintainer.create_note(
title="Weekly Refactoring Plan - Week 1",
content="""## Objectives
Complete optimization of data model layer
## Task Checklist
- [ ] Add unique constraint to User.email
- [ ] Add created_at, updated_at fields to Order
- [ ] Write database migration scripts
- [ ] Update related test cases
## Schedule
- Monday: Design migration scripts
- Tuesday-Wednesday: Execute migration and test
- Thursday: Update test cases
- Friday: Code Review
## Risks
- Database migration may affect production environment, needs to be executed during off-peak hours
- Existing data may have duplicate emails, need to clean up first
""",
note_type="task_state",
tags=["refactoring", "week1", "high_priority"]
)
print("✅ Created detailed refactoring plan")
# ========== One Week Later: Check Progress ==========
# View note summary
summary = maintainer.note_tool.run({"action": "summary"})
print("📊 Note summary:")
print(json.dumps(summary, indent=2, ensure_ascii=False))
"""
{
"total_notes": 8,
"type_distribution": {
"blocker": 3,
"action": 2,
"task_state": 2,
"conclusion": 1
},
"recent_notes": [
{
"id": "note_20250119_160000_7",
"title": "Weekly Refactoring Plan - Week 1",
"type": "task_state",
"updated_at": "2025-01-19T16:00:00"
},
...
]
}
"""
# Generate complete report
report = maintainer.generate_report()
print("\n📄 Session report:")
print(json.dumps(report, indent=2, ensure_ascii=False))
"""
{
"session_info": {
"session_id": "session_20250119_150000",
"project": "my_flask_app",
"duration_seconds": 172800 # 2 days
},
"activity": {
"commands_executed": 24,
"notes_created": 8,
"issues_found": 3
},
"notes": { ... }
}
"""9.6.5 Running Effect Analysis
Through this complete case study, we can see several key characteristics of long-horizon agents. First is cross-session coherence—the agent maintains task coherence across multiple days and sessions through NoteTool. Issues explored on day one are automatically considered during day two analysis, day three planning can synthesize all discoveries from the previous two days, and the complete history is preserved when checking a week later. Second is intelligent context management—ContextBuilder ensures high-quality context for each conversation, automatically gathering relevant notes (especially blocker types), dynamically adjusting preprocessing strategies based on conversation mode, and selecting the most relevant information within the token budget.
The third characteristic is instant file system access—TerminalTool supports flexible code exploration without needing to pre-index the entire codebase, can view specific file content instantly, and supports complex text processing (grep, awk, etc.). Fourth is automated knowledge management—the system automatically manages discovered knowledge, automatically creating blocker notes when issues are found, automatically creating action notes when discussing plans, and automatically storing key information in the memory system. Finally is human-machine collaboration—this system supports flexible human-machine collaboration modes, where agents can automatically complete exploration and analysis, humans can intervene and guide through the note system, and supports manually creating detailed planning notes.
This basic framework can be further extended, such as integrating RAGTool to build vector indexes for codebases combined with semantic retrieval, splitting into specialized explorers, analyzers, and planners to implement multi-agent collaboration, integrating testing tools to automatically verify refactoring results, executing git commands through TerminalTool to track code changes, or building visual interfaces using Gradio/Streamlit.