Skip to content

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 load calls 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.