The scary failure mode of an agent isn't getting one step wrong — it's looping forever, or inventing an answer when a tool is down. Project 3 of my Agentic AI from Zero build is a ReAct planning agent designed so neither can happen: it reasons and acts in a loop, but it can never loop forever, it critiques its own progress, and it degrades cleanly when stuck instead of faking success. Hand-rolled, no framework. I ran it for real against NVIDIA NIM (meta/llama-3.1-8b-instruct, temperature 0), and the recorded run shows both a solve and an honest degradation on real model output.
The loop: observe → think → act → reflect
Each iteration the model emits a strict JSON {thought, action, action_input}, a tool runs and returns an observation, then a separate self-critic call reflects — on_track?, a critique, a next-hint — and that reflection is written into the transcript the next thought reads. The model picks one of five actions each step: three tools (calculator, knowledge_lookup, web_search), final, or give_up. A max_steps cap (default 6) means the for loop can never run away.
Demo A — solved, with a wrong turn caught mid-solve
The task: what did the fictional Zephyr Labs spend launching satellites? That needs two knowledge lookups then a multiply — a genuine multi-step plan the model has to sequence itself:
step 1 knowledge_lookup satellites_launched -> 47
step 2 knowledge_lookup cost_per_satellite_musd -> 12
step 3 calculator 47 * 12 -> 564
step 4 knowledge_lookup cost_per_satellite_musd_unit -> [ERROR] no such key
reflect: on_track=False — spurious unit lookup, recover
step 5 final -> 564 million USD SOLVED ✓ (4/6 steps, 11.84s)
Step 4 is the interesting one. The small model second-guessed a correct result and wandered off to look up a "unit" key that doesn't exist — the tool returned an error, the self-critic flagged it on_track=False, and the next step recovered and finalised the right answer. That's the self-critic earning its place: a wrong turn gets caught and corrected instead of derailing the run.
Demo B — a 503, and an honest partial answer
The second task needs a live figure only web_search could supply — the current market share — and I force the search backend into a simulated outage, so every call raises a SearchBackendError (a simulated HTTP 503). This is the path that separates a demo from a real agent:
step 1 web_search ... -> [ERROR] 503 (simulated outage)
step 2 knowledge_lookup ... -> [ERROR] no such key (critic: switch tools)
step 3 web_search ... -> [ERROR] 503
step 4 web_search ... -> [ERROR] 503
step 5 give_up -> DEGRADED ⚠ "Unable to find … due to repeated search outages"
Every tool failure came back as an ERROR observation — the loop never crashed. The self-critic kept flagging on_track=False and nudged the agent to change approach (it switched from web_search to knowledge_lookup and back). And because there was genuinely no way to get the figure, the agent returned an honest partial answer with the reason rather than inventing a market-share number — with the max_steps cap standing as the hard backstop right behind the give_up.
The four guardrails, together
- The loop — observe → think → act → reflect, a reflection after every observation.
- Max iterations — a hard step cap; it can never run forever, and returns a best-so-far answer if it hits.
- Self-critic — a distinct LLM call grading each step; it caught the Demo A wrong turn and redirected the Demo B tool choice.
-
Graceful degradation — tools turn exceptions into ERROR observations (no crash);
give_upreturns a partial answer + reason. Never a hallucinated success.
The point of all four is that the reasoner underneath is shaky — an 8B model that second-guesses correct tool results — and the guardrails keep it bounded and honest anyway. Solve when solvable, degrade honestly when not, and never fake it. Read the full recorded transcript and run it yourself:
Top comments (0)