CODING CHALLENGE · N°66

Build Your Own Agent Loop

Medium AI EngineeringAgentsLoop Engineering

Strip an "AI agent" of its mystique and this is what is left: a loop. Read the model’s next move — a tool call or a final answer — run the tool, feed the result back as an observation, repeat until it answers or the step cap trips. This is the ReAct inner loop, and it is a pure, testable function.

The problem

Implement agent_loop(model_outputs, tools, max_steps). Each step consumes the next entry of model_outputs: either {"type": "final", "text": …} (the agent is done) or {"type": "tool", "name": …, "arg": …} (call a tool). tools is a lookup of {name: {arg: result}}. Execute a tool by looking up its result and appending it to observations; if the tool name is unknown append "unknown_tool", if the name is known but the arg is not append "unknown_arg". Stop on a final (status "answered") or when steps reaches max_steps or model_outputs runs out (status "max_steps", answer stays None). Return {"answer", "steps", "observations", "status"}.

EXAMPLE 1
Input model_outputs=[{"type":"tool","name":"search","arg":"cats"}, {"type":"final","text":"felis catus"}], tools={"search":{"cats":"felis catus"}}, max_steps=5
Output {'answer': 'felis catus', 'steps': 2, 'observations': ['felis catus'], 'status': 'answered'}
call the tool, observe the result, then answer — two steps
EXAMPLE 2
Input model_outputs=[{"type":"tool","name":"calc","arg":"2+2"}], tools={"calc":{"2+2":"4"}}, max_steps=1
Output {'answer': None, 'steps': 1, 'observations': ['4'], 'status': 'max_steps'}
the cap trips before a final answer ever arrives — no answer, loudly
EXAMPLE 3
Input model_outputs=[{"type":"tool","name":"web","arg":"x"}, {"type":"final","text":"done"}], tools={"search":{"a":"b"}}, max_steps=5
Output {'answer': 'done', 'steps': 2, 'observations': ['unknown_tool'], 'status': 'answered'}
a hallucinated tool name is observed as "unknown_tool" — the loop keeps going
CONSTRAINTS
  • One model_outputs entry is consumed per step; steps counts entries consumed (including the final).
  • Tool result: tools[name][arg] when both exist; "unknown_tool" if name is absent; "unknown_arg" if the name exists but the arg does not. Append it to observations in order.
  • A final ends the loop with status "answered" and answer set to its text.
  • Ending any other way — steps == max_steps OR model_outputs exhausted — is status "max_steps" with answer left as None.
SOLVE IT YOURSELF

Your turn — write it

Edit the stub, hit Run (or ⌘/Ctrl + Enter), and watch the hidden tests. Stuck? the hints are right above and Reveal solution is one click away.

YOUR TASK

Implement agent_loop(model_outputs, tools, max_steps) — step through the model’s moves, execute tools into observations, and stop on a final answer or the step cap. Return the full result dict.

HINTS — 4 IDEAS
  1. Loop while steps < max_steps AND steps < len(model_outputs). Read model_outputs[steps], then increment steps.
  2. On {"type": "final"}, set answer to its text, status "answered", and break.
  3. On a tool: check name in tools first ("unknown_tool"), then arg in tools[name] ("unknown_arg"), else tools[name][arg]. Append whichever to observations.
  4. If the loop ends without a final, answer stays None and status is "max_steps" — this covers both hitting the cap and running out of moves.
CPython · WebAssembly
Approach, complexity & discussion — open after you solve

The approach

Drive one index through model_outputs, incrementing steps as you consume each move. On a final, set the answer, mark "answered", and break. On a tool, resolve it in order — name missing → "unknown_tool", arg missing → "unknown_arg", else the looked-up result — and append to observations. Loop while steps < max_steps AND moves remain; if you fall out without a final, the answer stays None and the status is "max_steps".

Complexity

Time O(s) where s is the number of steps actually taken (bounded by max_steps and the number of moves); space O(s) for the observations list.

Common mistakes

  • Reading the move after incrementing steps, so the step count is off by one.
  • Checking arg in tools[name] before confirming name in tools — a KeyError on the hallucinated tool.
  • Treating “ran out of moves” and “hit the cap” as different statuses — both are "max_steps".
  • Returning the answer from a final but forgetting to also stop the loop.

Where this shows up

This is the ReAct inner loop every coding agent, deep-research agent, and copilot runs: read the model’s move, execute a tool, feed the observation back, repeat until it answers or the budget trips. Distinguishing this inner loop (act/observe until done) from the outer verify/retry loop is the whole mental model of agent design — and the step cap is what stops a runaway from burning your budget.

Finished this one? 0 / 75 Challenges done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More Challenges