Agent Architecture¶
The run_agent Loop¶
The core agent logic lives in agents/agent.py:
def run_agent(
tools_dict, # Tool registry
cmd, # rendering cmd module
user_message, # User's natural language request
agent_type=None, # Specialist type (or None for router)
context=None, # Context from previous agent in pipeline
conversation_context=None, # Chat history
active_scope=None # Active structure for scoping
) -> str:
Flow¶
graph TD
A[User message] --> B[Build session context]
B --> C[Build system prompt]
C --> D[LLM call with tool schemas]
D --> E{Response type?}
E -->|Text| F[Return response]
E -->|Tool calls| G[Execute each tool]
G --> H[Feed results back to LLM]
H --> I{More rounds?}
I -->|Yes, max 5| D
I -->|No| F
Step 1: Session Context¶
The agent queries the viewer for the current state:
- Loaded objects and their types
- Chain information
- Atom counts
- Protein/ligand/water presence
This context is injected into the system prompt so the LLM knows what's available.
Step 2: System Prompt¶
The system prompt includes:
- Session state description
- Specialist guidance (if agent type is set)
- Conversation history summary
- Rules (act on current state, use aliases, minimize tool calls)
Step 3: Multi-Round Tool Calling¶
The LLM can call multiple tools per round, up to 5 rounds:
sequenceDiagram
participant LLM
participant Bridge as ToolBridge
participant Engine
LLM->>Bridge: tool_call("representations", "cartoon protein")
Bridge->>Engine: dispatch(...)
Bridge-->>LLM: "Applied cartoon to protein"
LLM->>Bridge: tool_call("color", "bychain")
Bridge->>Engine: dispatch(...)
Bridge-->>LLM: "Colored by chain"
LLM-->>LLM: "Done — here's my response"
Step 4: Scope Management¶
The agent tracks scope just like the manual command flow:
/io loadcalls are never scoped (they create new structures)- After a load, the agent detects new objects and sets scope to the newest
- All subsequent tool calls use the updated scope
# Agent detects new structure after load
_known_objects = set(cmd.get_object_list())
# ... execute load tool ...
new_objects = set(cmd.get_object_list()) - _known_objects
if new_objects:
_agent_scope = new_objects.pop()
LLM Provider¶
The agent uses litellm for provider-agnostic LLM calls:
response = litellm.completion(
model=get_model(agent_type),
messages=messages,
tools=tool_schemas,
)
Supported providers:
- OpenAI (
gpt-4o,gpt-4o-mini) - Anthropic (
claude-sonnet)
Configured via environment variables or runtime overrides (/agents config model ...).
Conversation Memory¶
agents/memory.py provides ConversationMemory with auto-summarization — long conversations are compressed to stay within context limits while preserving important details.