Understanding the GenericAgent Agent Loop Architecture and Implementation
The GenericAgent Agent Loop is a turn-based, generator-driven orchestration system that continuously builds LLM conversations, dispatches tool calls to handlers, and yields execution status until completion or maximum turn limits.
The GenericAgent repository by lsdefine implements a modular architecture for autonomous LLM agents. At its core lies the GenericAgent Agent Loop, a deterministic generator that manages the conversation flow between the language model and tool implementations. This architecture cleanly separates orchestration logic from tool execution and LLM client abstraction.
Core Components of the GenericAgent Agent Loop
The architecture consists of four primary components that work together to create a deterministic, extensible execution environment.
agent_runner_loop Generator
The agent_runner_loop generator function, defined in agent_loop.py, serves as the central orchestration engine. It implements a while loop that continues until the handler signals completion or the system reaches the maximum turn count. The generator yields human-readable status strings such as "LLM Running (Turn 3)…" and tool execution logs, enabling real-time streaming to user interfaces.
BaseHandler and Dispatch Mechanism
BaseHandler, located at lines 14-30 in agent_loop.py, provides the abstract foundation for all tool implementations. It defines the dispatch method that dynamically routes tool calls to concrete handler methods using the naming convention do_<tool_name>. For example, a tool named code_run dispatches to do_code_run. This reflection-based approach allows new tools to be added without modifying the loop logic.
StepOutcome Data Structure
The StepOutcome class, defined at lines 5-9 in agent_loop.py, encapsulates the result of each turn. It carries three critical pieces of information: the tool execution data, the next prompt content for the following turn, and a boolean flag indicating whether the loop should terminate. When the next prompt is None or the handler sets should_exit to True, the generator concludes its execution.
How the GenericAgent Agent Loop Executes
The loop follows a strict six-step execution pattern that maintains deterministic state management across turns.
Step 1: The loop constructs the LLM conversation by combining the system prompt with the current user input and accumulated conversation history.
Step 2: It calls the LLM client—accessed via client.chat from llmcore.py—passing the message history and the JSON-encoded tool schema loaded from assets/tools_schema.json.
Step 3: The system parses the LLM response. If the model returns a function call, the loop extracts the specific tool name and arguments using the schema definitions.
Step 4: The loop dispatches the call via BaseHandler.dispatch, which locates the corresponding do_<tool_name> method in the concrete handler—typically GenericAgentHandler implemented in ga.py. This handler executes the actual logic, whether running code, reading files, or driving a browser.
Step 5: The loop collects the tool result from the returned StepOutcome, checking for early exit signals such as should_exit or a None next prompt. It then constructs the next prompt containing updated working memory, recent turn history, and any system warnings.
Step 6: The loop feeds the next prompt back into the message list and repeats. Execution terminates when the handler signals should_exit, the tool returns a final next_prompt of None, or the turn counter exceeds the maximum—defaulting to 40 turns or extending to 80 in plan mode.
Implementation Details: File Structure and Key Functions
The GenericAgent Agent Loop implementation spans six critical files that enforce separation of concerns between orchestration, tool execution, and LLM abstraction.
| File | Role | Key Implementation Details |
|---|---|---|
agent_loop.py |
Core orchestration logic | Contains agent_runner_loop generator, BaseHandler class (lines 14-30), and StepOutcome dataclass (lines 5-9). |
agentmain.py |
High-level entry point | Implements GeneraticAgent.run() (lines 99-138), handles LLM client selection via next_llm(), loads tool schemas via load_tool_schema(), and manages the UI display queue. |
ga.py |
Concrete tool implementations | Defines GenericAgentHandler with all do_* methods (e.g., do_code_run, do_file_read) that execute actual operations and return StepOutcome objects. |
llmcore.py |
LLM client abstraction | Provides ToolClient, ClaudeSession, and other session classes that implement the chat(messages, tools) interface consumed by the loop. |
assets/tools_schema.json |
Tool definitions | JSON schema describing available tools, their parameters, and descriptions; loaded at runtime to construct the LLM function-calling context. |
frontends/*.py |
UI integrations | Platform-specific implementations (e.g., tgapp.py for Telegram) that queue tasks and stream the generator output to chat interfaces. |
Practical Code Examples
These examples demonstrate how to interact with the GenericAgent Agent Loop at different levels of abstraction.
Running the Loop Manually
To execute the loop outside the full agent context for testing or debugging:
from llmcore import ToolClient, ClaudeSession
from agent_loop import agent_runner_loop, StepOutcome
from ga import GenericAgentHandler
import json, os
# Load the tool schema from assets
with open(os.path.join('assets', 'tools_schema.json'), encoding='utf-8') as f:
TOOLS_SCHEMA = json.load(f)
# Initialize the LLM client
client = ToolClient(ClaudeSession(cfg={'api_key': 'YOUR_KEY'}))
# Configure prompts
system_prompt = "You are a helpful assistant."
user_input = "List all .py files in the repository."
# Create handler instance
handler = GenericAgentHandler(parent=None)
# Execute the generator and stream output
for chunk in agent_runner_loop(
client, system_prompt, user_input,
handler, TOOLS_SCHEMA, max_turns=5, verbose=True):
print(chunk, end='')
This approach mirrors the internal logic of GeneraticAgent.run() while providing isolation for unit testing.
Adding a Custom Tool
Extending the agent with new capabilities requires implementing a handler method and updating the schema:
# In ga.py, add to GenericAgentHandler
def do_git_branch(self, args, response):
import subprocess
branch = subprocess.check_output(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
text=True
).strip()
return StepOutcome(
{"branch": branch},
next_prompt=self._get_anchor_prompt()
)
After adding the corresponding entry to assets/tools_schema.json, the loop automatically routes git_branch calls to this method without modifying the core orchestration logic.
Integrating with a Web Front-end
The generator design enables seamless UI integration. This simplified example from the Telegram frontend demonstrates the pattern:
from agentmain import GeneraticAgent
# Initialize the agent
bot = GeneraticAgent()
bot.next_llm(0) # Select first LLM configuration
bot.verbose = False # Disable console output for chat UI
# The UI layer pushes user messages via bot.put_task()
# and streams bot.run() output back to the chat client
The front-end merely queues tasks and consumes the display queue; the GenericAgent Agent Loop handles the complex orchestration of LLM calls and tool execution.
Summary
The GenericAgent Agent Loop architecture provides a robust, generator-based framework for autonomous LLM agents with these key characteristics:
- Generator-driven execution enables real-time streaming of turn status and tool execution logs through the
agent_runner_loopgenerator inagent_loop.py. - Reflection-based dispatch allows dynamic tool routing via
BaseHandler.dispatch, which automatically maps LLM function calls todo_<tool_name>methods inGenericAgentHandler. - Deterministic termination occurs through explicit signals (
should_exit,Nonenext prompt) or configurable turn limits (default 40, 80 in plan mode). - Clean separation of concerns between orchestration (
agent_loop.py), LLM abstraction (llmcore.py), tool implementation (ga.py), and entry points (agentmain.py).
Frequently Asked Questions
How does the GenericAgent Agent Loop handle tool execution errors?
The loop captures exceptions within the do_<tool_name> methods implemented in ga.py. When an error occurs, the handler returns a StepOutcome containing the error message in the data field, which the loop incorporates into the next prompt as context for the LLM. This allows the agent to reason about failures and potentially retry or adjust its approach in subsequent turns.
What is the maximum number of turns the GenericAgent Agent Loop can execute?
By default, the loop enforces a maximum of 40 turns before automatically terminating to prevent infinite loops. When operating in plan mode, this limit extends to 80 turns to accommodate complex multi-step reasoning. These limits are configurable through the max_turns parameter passed to agent_runner_loop or set on the handler instance.
Can the GenericAgent Agent Loop work with different LLM providers?
Yes, the architecture abstracts LLM interactions through the ToolClient interface defined in llmcore.py. The loop accepts any client implementing the chat(messages, tools) method, including ClaudeSession for Anthropic models, OpenAI-compatible clients, and native implementations. Swapping providers requires only changing the client instantiation in agentmain.py without modifying the core loop logic.
How does the loop maintain state between consecutive turns?
The loop maintains state through the conversation message list and the StepOutcome objects returned by tool handlers. After each turn, the system appends the tool result and next prompt to the message history, which is passed back to the LLM client in the subsequent iteration. The GenericAgentHandler may also maintain internal state (such as working memory or file system references) that persists across turns until the loop terminates or the handler signals an exit.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →