22
33GuardLoop is a production runtime guardrail for AI agents. It wraps model
44clients and tools with hard budget caps, timeout control, tool-call limits, and
5- per-tool circuit breakers, with OpenTelemetry traces for every protected call.
6- Runaway agent loops can be stopped before they burn through money, and flaky
7- tools can be cut off before an agent retries them into a bigger incident.
5+ per-tool circuit breakers, re-runs an agent against verifiers until the output
6+ passes, and emits OpenTelemetry traces for every protected call. Runaway agent
7+ loops can be stopped before they burn through money, flaky tools can be cut off
8+ before an agent retries them into a bigger incident, and confidently-wrong
9+ answers get a second pass.
810
9- The v0.2 focus is intentionally sharp: ** runtime guardrails for async Python
10- agents** using direct OpenAI and Anthropic wrappers plus protected tool calls.
11+ The v0.3 focus is intentionally sharp: ** runtime guardrails for async Python
12+ agents** — direct OpenAI and Anthropic wrappers, protected tool calls, per-tool
13+ circuit breakers, and a verify-fix-retry loop.
1114
1215``` python
1316from guardloop import (
@@ -16,6 +19,8 @@ from guardloop import (
1619 CircuitBreakerConfig,
1720 CircuitBreakerPolicy,
1821 RunContext,
22+ VerifierConfig,
23+ is_json_object,
1924)
2025
2126runtime = GuardLoop(
@@ -31,13 +36,18 @@ runtime = GuardLoop(
3136 recovery_timeout_seconds = 30 ,
3237 )
3338 ),
39+ verifiers = [is_json_object(required_keys = [" answer" ])],
40+ verifier_config = VerifierConfig(max_retries = 2 ),
3441)
3542
3643
3744async def agent (ctx : RunContext, prompt : str ) -> str :
45+ instructions = prompt
46+ if ctx.retry_feedback:
47+ instructions += " \n\n Fix the previous attempt: " + " ; " .join(ctx.retry_feedback)
3848 response = await ctx.openai.responses.create(
3949 model = " gpt-5.2" ,
40- input = prompt ,
50+ input = instructions ,
4151 max_output_tokens = 300 ,
4252 )
4353 return str (response.output_text)
@@ -58,13 +68,51 @@ flowchart LR
5868 U["User code"] --> R["GuardLoop"]
5969 R --> B["BudgetController"]
6070 R --> CB["CircuitBreakerRegistry"]
71+ R --> V["VerifierChain"]
6172 R --> T["OpenTelemetry spans"]
6273 R --> C["RunContext"]
6374 C --> O["Wrapped OpenAI client"]
6475 C --> A["Wrapped Anthropic client"]
6576 C --> W["Wrapped tools"]
77+ V -. "feedback on retry" .-> C
6678```
6779
80+ ## Verifier Retry Loop
81+
82+ Agents can return confidently wrong answers. Attach verifiers — plain callables,
83+ sync or async — and GuardLoop runs them after the agent finishes. On rejection
84+ it feeds the verifier's feedback into ` ctx.retry_feedback ` and re-invokes the
85+ agent, up to ` VerifierConfig.max_retries ` times. Every attempt shares the same
86+ budget and the run's timeout, so the retry loop can never spend past a cap.
87+
88+ ``` python
89+ from guardloop import GuardLoop, RunContext, VerifierConfig, VerifierContext, VerifierResult
90+
91+
92+ def no_todo (output : object , ctx : VerifierContext) -> VerifierResult:
93+ if " TODO" in str (output):
94+ return VerifierResult(passed = False , feedback = " Replace the TODO placeholder." )
95+ return VerifierResult(passed = True )
96+
97+
98+ runtime = GuardLoop(verifiers = [no_todo], verifier_config = VerifierConfig(max_retries = 2 ))
99+
100+
101+ async def agent (ctx : RunContext, task : str ) -> str :
102+ # On a retry, ctx.retry_feedback holds the verifier's complaints — read it.
103+ ...
104+
105+
106+ result = await runtime.run(agent, " draft the release notes" )
107+ print (result.verification_passed, result.verification_attempts, result.verification_feedback)
108+ ```
109+
110+ Built-in rule-based verifiers ship in ` guardloop ` : ` non_empty() ` ,
111+ ` matches_regex(...) ` , ` is_json_object(required_keys=...) ` . By default an output
112+ that fails every retry comes back as ` success=False ` with
113+ ` terminated_reason="verification_failed" ` but with ` output ` still populated;
114+ set ` VerifierConfig(raise_on_failure=True) ` for a hard stop.
115+
68116## Project Guide
69117
70118For a deeper walkthrough of what has been implemented, how the code is
@@ -113,6 +161,15 @@ uv run python examples/tool_circuit_breaker.py
113161This demo uses a failing fake tool. GuardLoop allows the first failures,
114162opens the circuit breaker, then rejects the next call without invoking the tool.
115163
164+ ``` bash
165+ uv run python examples/verifier_retry_loop.py
166+ ```
167+
168+ This demo's agent first returns a bad answer (a ` TODO ` placeholder, then
169+ malformed JSON). A verifier chain rejects it with feedback, the agent reads
170+ ` ctx.retry_feedback ` and self-corrects, and the run ends with
171+ ` verification_passed: true ` after three attempts.
172+
116173## Live Provider Smoke Tests
117174
118175``` bash
@@ -135,20 +192,27 @@ uv run ruff format --check .
135192uv run pyright
136193```
137194
138- ## v0.2 Scope
195+ ## v0.3 Scope
139196
140197- Async Python runtime with ` src/ ` package layout.
141198- Hard caps for cost, tokens, time, and tool calls.
142- - Per-tool circuit breakers with closed, open, and half-open states.
143- - Global default breaker policy plus per-tool overrides.
144- - Direct wrappers for ` AsyncOpenAI.responses.create ` .
145- - Direct wrappers for ` AsyncAnthropic.messages.create ` .
146- - OpenTelemetry spans for agent runs, LLM calls, and tools.
199+ - Per-tool circuit breakers with closed, open, and half-open states; global
200+ default breaker policy plus per-tool overrides.
201+ - Verify-fix-retry loop: sync or async output verifiers, fail-fast chains,
202+ built-in rule-based verifiers, feedback into ` ctx.retry_feedback ` , and an
203+ opt-in strict mode — all attempts share one budget and the run timeout.
204+ - Direct wrappers for ` AsyncOpenAI.responses.create ` and
205+ ` AsyncAnthropic.messages.create ` .
206+ - OpenTelemetry spans for agent runs, LLM calls, tools, and verifiers.
147207- Fake-client tests and demos that do not require API keys.
148208
149209## Roadmap
150210
151- - v0.2: per-tool circuit breakers.
152- - v0.3: verifier/self-healing retry loop.
211+ - v0.2: per-tool circuit breakers. ✅
212+ - v0.3: verify-fix- retry loop. ✅
153213- v0.4: LangGraph and OpenAI Agents SDK adapters.
154- - v0.5: Jaeger/Phoenix trace screenshots, blog post, and GitHub release.
214+ - v0.5: Jaeger/Phoenix trace screenshots, demo video, and blog post.
215+ - v0.6: persistent breaker state, YAML/TOML policy, multi-model pricing, loop detection.
216+ - v1.0: stable API, changelog, docs site, release checklist.
217+
218+ See [ docs/roadmap.md] ( docs/roadmap.md ) for details.
0 commit comments