Action is proposed as structured data (tool + args).
Problem (aus der Praxis)
Agenten sind Loops. Loops wollen weiterlaufen.
Wenn du keinen Step Cap hast, dann ist „fertig“ einfach: wenn der Agent zufällig aufgibt. In Prod heißt das: er gibt nicht auf.
Warum das in Production bricht
1) „It’ll stop when it’s done“ ist keine Strategie
In echten Systemen gibt es:
- flaky tools
- rate limits
- partial outages
- ambiguous tasks
Und die Kombination aus „ambiguous“ + „tools“ + „no cap“ ist, wie du 700 Calls in ein Logfile bekommst.
2) Ohne Stop-Reason ist es unsichtbar
Wenn du nur „timeout“ siehst, weiß niemand, dass Steps eskaliert sind. Stop-Reasons sind Debugging.
3) Steps müssen in der Run Loop enforced werden
Nicht nur im UI. Nicht nur im Agent Code „meistens“. In der Loop, jedes Mal.
Implementierungsbeispiel (echter Code)
Ein Step Guard, der:
- step counter incremented
- stop reason wirft
- im Ergebnis surfaced
from dataclasses import dataclass
@dataclass(frozen=True)
class StepPolicy:
max_steps: int = 25
class StepExceeded(RuntimeError):
def __init__(self, stop_reason: str):
super().__init__(stop_reason)
self.stop_reason = stop_reason
def run(task: str, *, policy: StepPolicy) -> dict:
steps = 0
try:
while True:
steps += 1
if steps > policy.max_steps:
raise StepExceeded("max_steps")
action = llm_decide(task) # (pseudo)
if action.kind != "tool":
return {"status": "ok", "answer": action.final_answer, "steps": steps}
obs = call_tool(action.name, action.args) # (pseudo)
task = update(task, action, obs) # (pseudo)
except StepExceeded as e:
return {"status": "stopped", "stop_reason": e.stop_reason, "steps": steps}export class StepExceeded extends Error {
constructor(stopReason) {
super(stopReason);
this.stopReason = stopReason;
}
}
export function run(task, { maxSteps = 25 } = {}) {
let steps = 0;
try {
while (true) {
steps += 1;
if (steps > maxSteps) throw new StepExceeded("max_steps");
const action = llmDecide(task); // (pseudo)
if (action.kind !== "tool") return { status: "ok", answer: action.finalAnswer, steps };
const obs = callTool(action.name, action.args); // (pseudo)
task = update(task, action, obs); // (pseudo)
}
} catch (e) {
if (e instanceof StepExceeded) return { status: "stopped", stopReason: e.stopReason, steps };
throw e;
}
}Echter Incident (mit Zahlen)
Wir hatten einen Agenten, der „einfach nur“ eine Liste von Ergebnissen sammeln sollte.
Er hatte:
- keine Step Caps
- keine Loop Detection
- ein Tool, das gelegentlich leicht andere Ergebnisse liefert
Ergebnis:
- ~700 Tool Calls in einem Run
- ~18 Minuten Laufzeit
- bill war nicht gigantisch (glücklicherweise), aber Rate Limits + Queue Delay waren real
Fix:
- Step cap + stop reason
- loop detection (same args hash)
- dedupe/caching bei gleichen queries
Abwägungen
- Step Limits stoppen manchmal früh. Besser als unbounded.
- Wenn du zu niedrig setzt, bekommst du mehr „stopped“ Responses → du brauchst UX.
- Step Limits ohne Time/Cost Budgets sind unvollständig (aber trotzdem wertvoll).
Wann du es NICHT nutzen solltest
- Eigentlich immer nutzen. Wenn du wirklich keine Steps limitieren willst, brauchst du andere harte Budgets.
Checkliste (Copy/Paste)
- [ ]
max_stepspro run - [ ] Stop reason
max_stepsgeloggt + surfaced - [ ] Step count in traces (debug)
- [ ] loop detection / no-progress stop
- [ ] kombiniert mit time + cost budgets
Sicheres Default-Config-Snippet (JSON/YAML)
step_limits:
max_steps: 25
stop_reasons:
surface_to_user: true
log: true
FAQ (3–5)
Von Patterns genutzt
Verwandte Failures
Q: Wie setze ich max_steps?
A: Starte mit 25. Miss stops. Wenn’s oft stoppt, sind Tooling/Prompt/Task-Scoping das Problem – nicht die Zahl.
Q: Reicht max_steps alleine?
A: Nein. Du willst auch max_seconds, max_tool_calls und oft max_usd.
Q: Was ist der beste Stop-Reason Name?
A: Kurz und maschinenlesbar: max_steps. Du willst Alerts und Dashboards.
Verwandte Seiten (3–6 Links)
- Foundations: The ReAct loop explained · Planning vs reactive agents
- Failure: Infinite loop failure · Tool spam loops
- Governance: Budget controls · Kill switch design
- Production stack: Production agent stack