Skip to content

Stop conditions

A loop stops in one of three ways. Pick the one that matches who decides.

Who decides How state.stopped_by
A check on the State, before every turn An until function The function's name
Your code or a tool, at a specific moment state.finish(answer) "finish"
A fixed maximum number of turns limit "limit"

Stop on a check

from alpineagents import Agent, State, loop


def spent_too_much(state: State) -> bool:
    return state.usage.output_tokens > 20_000


@loop(until=[State.is_answered, spent_too_much], limit=30)
def careful(agent: Agent, state: State):
    agent.think(state)
    if state.wants_tools():
        agent.use_tools(state)
  1. spent_too_much takes the State and returns True to stop.
  2. until takes a list. The loop stops when any function in it returns True.
  3. After the run, state.stopped_by is "is_answered", "spent_too_much" or "limit". The terminal output shows the same name.

Give stop conditions clear names. The name is the only record of why the run stopped.

Stop from a tool

Let the model decide when the work is done, and return a structured answer:

from alpineagents import Agent, State, tool


@tool
def submit(summary: str, files_changed: list[str], state: State) -> None:
    """Submit the finished work. Call it once, at the end."""
    state.finish({"summary": summary, "files_changed": files_changed})


agent = Agent(
    model="claude-sonnet-5",
    system="When the work is done, call submit.",
    tools=[submit],
)
print(agent.run("Rename the helper functions in utils.py"))
  1. The model calls submit when it thinks the work is done.
  2. state.finish(...) sets state.answer to the dict. The loop stops before the next turn.
  3. agent.run returns the dict.
  4. The State is now finished. Running it again or calling ask on it raises ValueError.

The answer can be any value. The model fills the tool's typed parameters, so the answer has a known shape.

Check for the limit

Reaching limit stops the loop without an exception. Check it when an unfinished run matters. agent is the Agent from the example above:

from alpineagents import State

state = State("Rename the helper functions in utils.py")
agent.run(state)
if state.stopped_by == "limit":
    print(f"Stopped after {state.stopped_limit} turns. The task may be unfinished.")