The agent loop
Every GantryEngine runs four steps, in order, until the task is done.
observe → think → act → review
↑__________________________|
The four steps
Observe — if you gave the agent a DesktopScreen or WebPage, it takes a fresh
screenshot now and appends it to the message history. Without perception, this step
is a no-op.
Think — the LLM receives the full message history (task + observations + tool results) and decides what to do: call one or more tools, or output a final answer.
Act — tool calls run through the optional guardrail gate. Errors come back as
ToolMessage(status="error") so the LLM can self-correct instead of crashing.
Review — did the last LLM response contain tool calls? Yes → loop. No → done.
Creating an engine
from gantrygraph import GantryEngine
from langchain_anthropic import ChatAnthropic
agent = GantryEngine(
llm=ChatAnthropic(model="claude-sonnet-4-6"),
tools=[...],
perception=...,
max_steps=50,
max_consecutive_errors=5, # stop if the same error repeats N times
system_prompt="...",
on_event=lambda e: print(e),
approval_callback=...,
guardrail=...,
budget=...,
)
All parameters except llm are optional.
Running
=== "Sync"
result = agent.run("List the 5 largest files in /tmp")
=== "Async"
result = await agent.arun("List the 5 largest files in /tmp")
=== "Stream"
async for event in agent.astream_events("List the 5 largest files"):
print(event.event_type, event.step, event.data)
Use arun() directly in async code to avoid a nested event loop.
Limits
| Parameter | Default | What it controls |
|---|---|---|
max_steps |
50 |
Hard cap on act-node executions |
max_consecutive_errors |
5 |
Stop early when the same error repeats — prevents infinite loops caused by CAPTCHA walls, broken selectors, or unreachable pages |
BudgetPolicy.max_wall_seconds |
None |
Wall-clock timeout for the whole run |
from gantrygraph.security import BudgetPolicy
agent = GantryEngine(
llm=...,
max_steps=20,
max_consecutive_errors=4, # stop after 4 back-to-back failures
budget=BudgetPolicy(max_wall_seconds=60.0),
)
See Guardrails for the full set of safety controls.
Configuration from environment
from gantrygraph import GantryConfig
from langchain_anthropic import ChatAnthropic
cfg = GantryConfig.from_env()
agent = cfg.build(llm=ChatAnthropic(model="claude-sonnet-4-6"))
export GANTRY_WORKSPACE=/app
export GANTRY_MAX_STEPS=30
export GANTRY_MAX_WALL_SECONDS=120
Advanced — raw graph access
engine.get_graph() returns the compiled LangGraph StateGraph for custom nodes,
checkpointers, or streaming:
graph = agent.get_graph()
# CompiledStateGraph — use like any LangGraph graph
See also: Quickstart · Guardrails · API reference