Siedem wzorców pętli agentów i sytuacje, w których każdy z nich zawodzi: ReAct, Maker-Checker i inne
Praktyczny katalog projektów pętli agentów, od schematu plan-działanie-weryfikacja po eskalację z udziałem człowieka, zawierający wskazówki dotyczące sytuacji, w których dany schemat jest odpowiedni, kiedy należy go unikać oraz jak dokonać wyboru.
Źródła: Agentic Loops & Design Patterns
Nikt nie powinien oceniać agenta po tym, jak sprytnie brzmi jego odpowiedź. Liczy się zachowanie w sytuacjach dużego ruchu, gdy coś się zepsuje lub gdy przychodzi audytor. To właśnie w takich warunkach wzorce projektowania pętli okazują się ważniejsze od jakiegokolwiek pojedynczego polecenia. Pętle działają najlepiej, gdy traktuje się je jako elementy architektoniczne – każda z nich rozwiązuje określoną klasę problemów, wiąże się z knownymi kompromisami i powinna być wybierana celowo. Poniższe wzorce zawierają przykłady w stylu Claude oraz opisy sytuacji, w których należy ich unikać.
1. Planuj, działaj, sprawdzaj
To podstawowy wzorzec. Agent planuje jeden mały krok, podejmuje jedną akcję, a następnie sprawdza, czy ta akcja przybliżyła go do jasno określonego celu. Umożliwia to osiągnięcie równowagi pomiędzy elastycznością a kontrolą.
// Pseudo-code: Node/TypeScript orchestrating Claude as an agent
async function planActVerifyLoop(ticket) {
let iteration = 0;
const maxIterations = 5;
while (iteration < maxIterations) {
iteration++;
// 1. PLAN: ask Claude for the next step
const plan = await claude.chat({
model: "claude-3-opus",
messages: [
{
role: "user",
content: `You are a coding agent working on ticket ${ticket.id}.
Goal: Make tests pass for this ticket without changing public APIs.
Current context:
${ticket.description}
${ticket.latestFailureLog}
What is the single most useful next action?`
}
]
});
// 2. ACT: execute the suggested action if it's in the allowed action space
const action = parseAction(plan);
const result = await executeAction(action); // run tests, edit file, etc.
// 3. VERIFY: use tests as verification
const verification = await runTests(ticket.testSuite);
if (verification.allPassing) {
return { status: "done", iterations: iteration };
}
// Attach the failure output back into ticket context
ticket.latestFailureLog = verification.failureOutput;
}
return { status: "budget_exhausted" };
}
W tym schemacie Claude zajmuje się mikroplanowaniem następnego kroku, podczas gdy sam pętla odpowiada za weryfikację poprzez testy oraz narzuca ograniczenie liczby iteracji. Taki podział zapobiega temu, by system ogłaszał sukces tylko dlatego, że model czuje się pewny siebie.
Dobre przypadki to wieloetapowe zadania inżynieryjne z mierzalnym postępem, skrypty wdrażania oraz zmiany konfiguracji, które muszą osiągnąć sprawdzony stan funkcjonalny, a także narzędzia wewnętrzne, w których można zaimplementować ścisłe kontrole takie jak testy czy walidacja schematu.
Złe przypadki obejmują wyłącznie prace twórcze, takie jak teksty marketingowe czy dłuższe artykuły, decyzje subiektywne, gdzie weryfikacja mogłaby jedynie odzwierciedlać osobisty gust, oraz procesy w czasie rzeczywistym, które nie mogą sobie pozwolić na kilka iteracji na kluczowej ścieżce.
2. ReAct (rozumowanie, a następnie działanie)
ReAct łączy krótkie serię myśli z pojedynczymi działaniami. Zamiast z góry planować całą trasę, agent analizuje najnowszy stan, wybiera jeden narzędzie, go używa, a następnie ponownie myśli. Dzięki temu stanowi naturalny wybór do pracy eksploracyjnej lub diagnostycznej, gdzie każda nowa obserwacja może uzasadnione zmienić plan.
# Pseudo-code: Python coordinator orchestrating Claude tool calls
def react_loop(incident):
iteration = 0
max_iterations = 6
while iteration < max_iterations:
iteration += 1
# REASON: Claude decides what to inspect next
reasoning = claude.chat(
model="claude-3-opus",
messages=[
{
"role": "user",
"content": f"""
You are an SRE assistant triaging a payment incident.
Incident summary:
{incident.summary}
Recent metrics:
{incident.latest_metrics}
Logs snippet:
{incident.logs_snippet}
Decide one next diagnostic action from:
- CHECK_METRICS
- CHECK_LOGS
- CHECK_DB_HEALTH
- SUMMARIZE_FINDINGS_AND_RECOMMEND_ACTION
Explain your reasoning briefly and output JSON with 'action' and 'target'.
"""
}
]
)
action = parse_json(reasoning)
if action["action"] == "SUMMARIZE_FINDINGS_AND_RECOMMEND_ACTION":
return claude.chat(... ) # final summary + recommended steps
# ACT: run the selected diagnostic
observation = run_diagnostic(action, incident)
# Update incident state for the next reasoning step
incident.update_with_observation(observation)
Każdy krok diagnostyczny jest celowy, a Claude otrzymuje nowy kontekst po każdej akcji.
Można go wykorzystać do diagnozowania incydentów i analizy przyczyn źródłowych, w złożonych procesach obsługi klienta, gdzie każda odpowiedź zmienia to, co należy sprawdzić dalej, oraz przy dochodzeniach bezpieczeństwa, w których dowody pojawiają się stopniowo i są niekompletne.
Należy go pominąć, gdy plan jest stabilny, a procedura jasna, jak w przetwarzaniu dokumentów w stylu ETL, gdy prędkość przetwarzania jest na tyle wysoka, że rozważanie każdego kroku staje się zbyt kosztowne, lub gdy zakres możliwych działań jest tak wąski, że dodatkowe rozważania nic nie dają.
3. Refleksja i korekta
Tutaj dwie fazy – generowanie i krytyka – są następnie poprzedzone etapem rewizji. Dzięki temu ustala się rozsądny standard dla wszystkiego, co wymaga sprawdzenia przed dotarciem do klienta, organu regulacyjnego lub systemu produkcyjnego. Kluczowym elementem jest separacja: osoba dokonująca krytyki może być innym promptem, tańszym modelem lub nawet silnikiem reguł.
async function reflectReviseLoop(draftInput: string) {
// 1. GENERATE
const draft = await claude.chat({
model: "claude-3-opus",
messages: [
{
role: "user",
content: `
Write a customer-facing email explaining a declined payment due to suspected fraud.
Constraints:
- empathetic but clear
- no admission of fault
- no promises about future approvals
Context:
${draftInput}
`
}
]
});
// 2. CRITIQUE (using a cheaper model as checker)
const critique = await claude.chat({
model: "claude-3-haiku",
messages: [
{
role: "user",
content: `
You are a compliance checker.
Review the following email for:
- policy violations
- misleading statements
- over-commitments
Output a JSON with:
- issues: list of strings
- safe: boolean
Email:
${draft.content}
`
}
]
});
const review = JSON.parse(critique.content);
if (review.safe) {
return draft.content;
}
// 3. REVISE
const revised = await claude.chat({
model: "claude-3-opus",
messages: [
{
role: "user",
content: `
You wrote this email:
${draft.content}
Compliance issues:
${review.issues.join("\n")}
Rewrite the email to resolve all issues while preserving intent.
`
}
]
});
return revised.content;
}
Ten wzorzec nadaje się do wiadomości od klientów na delikatne tematy, takie jak oszustwa, zwroty płatności czy zmiany regulacyjne, do wewnętrznych analiz postmortem, które mogą być audytowane, oraz do wszelkich treści, w których zgodność z polityką ma większe znaczenie niż styl.
Nie nadaje się natomiast do mało ryzykownych wewnętrznych sesji brainstorming, gdzie krytyka powoduje opór i nie zapewnia rzeczywistej bezpieczeństwa, do bardzo krótkich odpowiedzi w czatach, gdzie druga próba jest przesadą, ani do procesów wymagających szybkiej reakcji, takich jak komunikacja w trakcie transakcji, gdzie wystarczy jedna próba.
4. Szkicowanie, testowanie, korygowanie
Zespoły inżynieryjne często preferują ten cykl, ponieważ wynik jest obiektywny i niezależny od modelu: zestaw testów albo daje pozytywny wynik, albo pozostaje negatywny. W każdym etapie agent proponuje zmianę, uruchamia testy i sprawdza, co nie zadziałało. Następnie naprawia problemy i powtarza proces, przerywając go, gdy system osiągnie znany dobry stan lub zostanie osiągnięty limit iteracji.
async function draftTestFixLoop(issue: Issue) {
const maxIterations = 4;
let iteration = 0;
while (iteration < maxIterations) {
iteration++;
// DRAFT: Claude proposes code changes
const patch = await claude.chat({
model: "claude-3-opus",
messages: [
{
role: "user",
content: `
You are an autonomous coding agent.
Ticket:
${issue.title}
${issue.body}
Current failing tests:
${issue.failingTests}
Propose a minimal patch as a diff that makes tests pass
without changing public APIs.`
}
]
});
applyPatchToGitRepo(patch.content);
// TEST: run CI locally or via API
const testResult = await runCi(issue.branchName);
if (testResult.success) {
// FIX_DONE: open a PR with the diff
await openPullRequest(issue, patch.content);
return { status: "done" };
}
// FEEDBACK: update failingTests for next iteration
issue.failingTests = testResult.failureSummary;
}
return { status: "needs_human_review" };
}
Funkcjonuje w przypadku zleceń o niskim ryzyku, dobrze zdefiniowanych i oznaczonych jako gotowe do przekazania agentowi, w narzędziach wewnętrznych, gdzie odwrócenie działań jest proste, oraz na niekrytycznych ścieżkach, które mogą znieść opóźnienia i dodatkowe koszty.
Należy unikać jego stosowania przy jakichkolwiek zmianach, które bezpośrednio wpływają na przepływy finansowe w kluczowych ścieżkach transakcyjnych, przy zmianach schematu, migracjach oraz refaktoryzacji o charakterze ogólnym, a także przy logice regulowanej, gdzie automatyczna edycja mogłaby naruszyć obowiązujące zasady.
5. Krytyk i twórca
Jeden agent tworzy, a drugi sprawdza. W obszarze płatności przypomina to mechanizm twórca–sprawdzający, już stosowany przy operacjach o wysokim ryzyku, co ułatwia przedstawienie go zespołom ds. ryzyka i zgodności.
Podoba się to tam, gdzie oczekuje się już drugiej pary oczu, gdy automatyzacja dotyka systemów bliskich produkcji, takich jak konfiguracja, routowanie czy powiadomienia, oraz przy raportach lub panelach analitycznych o dużym znaczeniu, które są czytane przez kierownictwo.
Nie nadaje się do prostych zadań, w których dodatkowa weryfikacja kosztuje więcej, niż przynosi korzyści, do procesów wrażliwych na opóźnienia, które muszą udzielić odpowiedzi w jednym kroku, ani do łańcuchów przetwarzania, w których słabe zasady zmusiłyby sprawdzacz do przyjęcia wszystkiego, co wydaje się wystarczająco dobre.
6. Próba ponowna z pamięcią
Większość prostych pętli po prostu próbuje ponownie. Pętla z możliwością ponownej próby z pamięcią rejestruje stany niepowodzeń i wykorzystuje je, aby uniknąć powtarzania tych samych błędów.
def retry_with_memory_loop(task, max_attempts=3):
failures = []
for attempt in range(1, max_attempts + 1):
# Ask Claude to consider past failures before deciding the next move
decision = claude.chat(
model="claude-3-opus",
messages=[
{
"role": "user",
"content": f"""
You are handling a task with a flaky external API.
Task:
{task.description}
Past failures:
{failures}
Decide whether to:
- RETRY_API
- FALLBACK_TO_CACHE
- ESCALATE_TO_HUMAN
Explain briefly and output JSON: {{ "choice": "...", "reason": "..." }}
"""
}
]
)
choice = parse_json(decision)
if choice["choice"] == "RETRY_API":
result = call_api(task)
elif choice["choice"] == "FALLBACK_TO_CACHE":
result = use_cache(task)
else:
return {"status": "escalated", "failures": failures}
if result.success:
return {"status": "success", "attempts": attempt}
failures.append(result.error_summary)
return {"status": "max_attempts_exhausted", "failures": failures}
Odpowiednimi celami są niestabilne integracje, API z ograniczeniami szybkości oraz systemy zewnętrzne poza twoją kontrolą, parsowanie dokumentów o niestandardowym formacie oraz procesy niekrytyczne, w których możliwe są ponowne próby.
Nieodpowiednimi celami są kluczowe procesy transakcyjne, w których ponowna próba może powtórzyć efekty, operacje, które nie są idempotentne i których ponowna próba nie jest bezpieczna, oraz sytuacje, w których awaria musi być natychmiastowa i widoczna, a nie może być ponawiana w tajemnicy.
7. Eskalacja przez człowieka
To wzorzec zabezpieczeniowy dla wszystkiego, co dotyczy rzeczywistych pieniędzy, klientów lub ryzyka regulacyjnego. Pętla działa samodzielnie do pewnego momentu, a następnie przejmuje kontrolę w momencie spadku pewności lub wzrostu ryzyka.
async function triageLoop(ticket: Ticket) {
const autoActions = ["LABEL", "ROUTE_TO_QUEUE", "REQUEST_MORE_INFO"];
const decision = await claude.chat({
model: "claude-3-opus",
messages: [
{
role: "user",
content: `
You are a support triage agent in a payments company.
Ticket:
${ticket.body}
Decide one of:
- LABEL (low risk)
- ROUTE_TO_QUEUE (medium risk)
- ESCALATE_TO_HUMAN (high risk / unclear)
Return JSON with:
- choice
- risk_level
- rationale
`
}
]
});
const choice = JSON.parse(decision.content);
if (choice.choice === "ESCALATE_TO_HUMAN") {
await createHumanTask(ticket, choice.rationale);
return { status: "escalated" };
}
// LABEL or ROUTE_TO_QUEUE are automated but bounded
await applyAutomatedTriage(ticket, choice);
return { status: "auto_treated" };
}
Wykorzystaj go do wszelkiej automatyzacji, która może poprawić doświadczenie klienta lub wrażenie sprawiedliwości, w procesach, gdzie częściowa automatyzacja jest pomocna, ale pełna autonomia niesie ryzyko, oraz przy wczesnych wdrożeniach, które muszą się nauczyć, zanim zostaną złagodzone ograniczenia.
Unikaj jego stosowania w całkowicie wewnętrznych, mało ryzykownych zadaniach, gdzie ludzie jedynie dodawaliby zamieszania, w pracach zbiorczych w tle, gdzie ludzka weryfikacja nie może być skalowana, oraz w sytuacjach, gdzie eskalacja obciążyłaby pracowników bez poprawy wyników.
Wybór w praktyce
Gdy pojawia się nowa możliwość wykorzystania agenta, błędne jest pierwsze pytanie „jak mam to skierować?”. Cztery lepsze to:
- Jaki sygnał potwierdza wynik: testy, schematy, reguły, metryki czy osąd człowieka?
- Ile kroków faktycznie zajmuje to zadanie?
- Co się dzieje, gdy system zawodzi?
- Kto ucierpi, jeśli coś pójdzie nie tak?
Odpowiedzi wskazują na określony wzorzec:
- Mocna, binarna weryfikacja sugeruje procedurę: projektuj, testuj, poprawiaj.
- Eksploracyjna ścieżka wskazuje na podejście ReAct.
- Zagrożenia związane z polityką i tonem sugerują refleksję i rewizję, albo rolę krytyka i twórcy.
- Niestabilne środowisko wymaga ponownej próby z uwzględnieniem informacji z pamięci.
- Wysokie ryzyko oznacza konieczność uwzględnienia możliwości eskalacji przez ludzi od samego początku.
Inżynieria pętli polega w rzeczywistości mniej na sprytnym koordynowaniu, a bardziej na zdyscyplinowanym myśleniu o systemach.