n4nAI

How AI agents plan, act, and use tools

A technical breakdown of AI agent architecture — planning loops, tool calling patterns, and execution models engineers can actually use.

n4n Team6 min read1,306 words

Audio narration

Coming soon — every post will get a voice note here.

An AI agent is a system that uses a large language model to reason about a goal, select actions from a defined toolset, execute those actions, and incorporate the results into subsequent reasoning steps — repeating this loop until the goal is satisfied or a stopping condition is met. Unlike a single-turn prompt, an agent maintains state across multiple iterations, dynamically choosing which tools to invoke and in what order based on the evolving context. The defining characteristic is not the model itself but the orchestration layer that closes the loop between reasoning and external effects.

The planning loop

Every agent implements some variation of a reasoning–action–observation cycle. The most common pattern, popularized by ReAct (Reasoning and Acting), interleaves natural language reasoning traces with structured tool calls. At each step, the model receives the full conversation history — including prior tool results — and decides whether to emit a final answer or invoke a tool.

# Simplified ReAct loop
def run_agent(goal: str, tools: dict[str, Tool], max_steps: int = 10) -> str:
    messages = [{"role": "user", "content": goal}]
    
    for step in range(max_steps):
        response = llm_complete(messages, tools=tool_schemas(tools))
        
        if response.tool_calls:
            for call in response.tool_calls:
                result = tools[call.name].execute(call.arguments)
                messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
        else:
            return response.content  # final answer
    
    return "Max steps reached without resolution"

The loop terminates when the model produces a response without tool calls, when a hard step limit is hit, or when an explicit “finish” action is invoked. More sophisticated agents add reflection steps, critic models, or separate planning and execution phases.

Planning strategies

Single-step planning lets the model decide the next action greedily based on current context. This works for shallow tasks but fails when the optimal path requires non-obvious intermediate steps.

Multi-step planning asks the model to produce a full plan upfront — a sequence of tool calls with expected outcomes — then executes them sequentially or with a planner–executor split. The plan can be represented as a DAG, enabling parallel execution of independent branches.

{
  "plan": [
    {"id": "1", "tool": "search", "args": {"query": "n4n.ai pricing"}, "depends_on": []},
    {"id": "2", "tool": "search", "args": {"query": "OpenRouter pricing"}, "depends_on": []},
    {"id": "3", "tool": "compare", "args": {"sources": ["1", "2"]}, "depends_on": ["1", "2"]}
  ]
}

Hierarchical planning decomposes high-level goals into subgoals, each handled by a specialized sub-agent or prompt template. This mirrors how human operators break down complex operations.

Adaptive planning revises the plan mid-execution based on tool results. If a search returns no results, the agent might reformulate the query or switch to a different source. This requires the planning loop to re-invoke the planner with updated context.

Tool use mechanics

Tools are the agent’s interface to the outside world. Each tool exposes a name, a JSON schema describing its parameters, and an execution function. The model sees only the schema; the runtime handles invocation, validation, and result injection.

Tool definition patterns

from pydantic import BaseModel, Field
from typing import Callable, Any

class Tool:
    def __init__(
        self,
        name: str,
        parameters: type[BaseModel],
        execute: Callable[[BaseModel], Any]
    ):
        self.name = name
        self.description = description
        self.parameters = parameters
        self.execute = execute

    def to_openai_schema(self) -> dict:
        return {
            "type": "function",
            "function": {
                "name": self.name,
                "description": self.description,
                "parameters": self.parameters.model_json_schema()
            }
        }

A well-designed tool schema is as important as the implementation. The description field serves as documentation for the model — vague descriptions cause hallucinated parameters or wrong tool selection. Include concrete examples in the description when the parameter format is non-obvious.

class SearchArgs(BaseModel):
    query: str = Field(description="Search query. Use specific terms, e.g. 'n4n.ai latency p99' not 'pricing'")
    max_results: int = Field(default=5, ge=1, le=20)
    recency_days: int | None = Field(default=None, description="Limit results to last N days")

Tool categories

Read-only tools (search, database query, API GET) are safe to retry and can often run in parallel. They should be idempotent.

Write tools (API POST, database insert, file write) require confirmation patterns or compensation logic. Agents should surface these for human approval in production systems.

Long-running tools (code execution, video generation, batch jobs) need async handling — the agent submits a job, polls for completion, then incorporates the result. This breaks the synchronous request–response assumption of simple loops.

Composite tools wrap multiple primitive operations behind a single interface. A “research” tool might internally chain search, fetch, and summarize. This reduces the agent’s decision surface but hides failure modes.

Why this matters for engineers

Agents shift complexity from prompt engineering to system design. A single prompt that tries to do everything becomes brittle as requirements grow. An agent architecture lets you:

  • Isolate failure domains: A broken search tool doesn’t corrupt the summarization tool.
  • Swap components: Replace the planner model without rewriting tool implementations.
  • Observe and debug: Each loop iteration produces a trace you can log, replay, and evaluate.
  • Enforce policies: Rate limits, authentication, and data governance live in the tool layer, not the prompt.

The trade-off is latency and cost. A five-step agent loop with a 70B model can take 15–30 seconds and consume 10× the tokens of a single prompt. You pay for reasoning you could have hard-coded. Use agents when the task genuinely requires dynamic decision-making — unknown number of steps, conditional branching, or tool selection that depends on intermediate results.

Concrete example: automated code review

Consider an agent that reviews a pull request and posts inline comments. The goal: “Review PR #247 for security issues, performance regressions, and style violations.”

Tool set

tools = {
    "get_pr_diff": Tool(
        name="get_pr_diff",
        description="Fetch the unified diff for a pull request",
        parameters=GetPRDiffArgs,
        execute=github_get_pr_diff
    ),
    "get_file_content": Tool(
        name="get_file_content",
        description="Fetch full file content at base or head revision",
        parameters=GetFileContentArgs,
        execute=github_get_file_content
    ),
    "run_linter": Tool(
        name="run_linter",
        description="Run ruff/mypy on a file and return violations",
        parameters=RunLinterArgs,
        execute=run_linter
    ),
    "post_review_comment": Tool(
        name="post_review_comment",
        description="Post an inline comment on a specific line in the PR",
        parameters=PostCommentArgs,
        execute=github_post_review_comment
    ),
    "submit_review": Tool(
        name="submit_review",
        description="Submit the review with overall approval/request-changes",
        parameters=SubmitReviewArgs,
        execute=github_submit_review
    )
}

Execution trace (abbreviated)

Step 1: Agent calls get_pr_diff(pr_number=247)
        → Returns 347 lines changed across 12 files

Step 2: Agent calls get_file_content for auth.py (head revision)
        → Returns full file content

Step 3: Agent calls run_linter on auth.py
        → Returns: "line 42: SQL injection risk via f-string in execute()"

Step 4: Agent calls post_review_comment(file="auth.py", line=42, 
        body="Potential SQL injection: use parameterized queries...")
        → Comment posted

Step 5: Agent calls get_file_content for analytics.py...
        ... (repeats for each changed file)

Step N: Agent calls submit_review(decision="REQUEST_CHANGES", 
        body="Found 3 security issues and 12 style violations")

The agent dynamically decides which files to inspect based on the diff, which tools to run per file type (linter for Python, different checker for SQL migrations), and when it has enough evidence to submit. A fixed pipeline would either over-check unchanged files or miss context-dependent issues.

Common misconceptions

“Agents are just function calling”

Function calling is a model capability — the ability to emit structured tool invocations. An agent is the orchestration loop that decides which functions to call, when, and how to handle the results. You can have function calling without an agent (single-turn extraction), and you can build agents with models that don’t support native function calling (by parsing JSON from text). The loop is the agent; the function calling is a transport mechanism.

“More tools make the agent smarter”

Adding tools expands the action space, which increases the planning burden. A model with 50 tools spends more tokens disambiguating and more steps correcting wrong choices. Curate the tool set for the task. If an agent only needs search and fetch, don’t give it a code interpreter, database writer, and image generator “just in case.” Each tool should have a clear, non-overlapping purpose.

“The model plans optimally”

LLMs are poor planners for multi-step tasks with delayed rewards. They tend toward greedy, locally optimal choices — searching for the first keyword that comes to mind rather than decomposing the problem. Explicit planning prompts, separate planner models, or search-based planning (MCTS over tool sequences) outperform naive ReAct on complex benchmarks. Treat the model’s plan as a hypothesis, not a guarantee.

“Agents replace workflows”

Workflows (directed acyclic graphs of predefined steps) are more reliable, faster, and cheaper for known processes. Use a workflow when you can enumerate the steps at design time. Use an agent when the steps depend on runtime discoveries — “search until you find X, then decide whether to summarize or fetch more.” Most production systems are hybrids: a workflow that invokes an agent for the ambiguous substep.

“Memory solves context limits”

Stuffing the entire history into the context window doesn’t scale. A 20-step agent loop with tool results can exceed 100k tokens. Practical agents implement working memory (recent steps in context), episodic memory (vector store of past trajectories for few-shot retrieval), and semantic memory (extracted facts, user preferences, domain knowledge). The memory system is a separate architectural concern, not a context window hack.

Evaluation and observability

You cannot ship agents without evals. Static benchmarks (HotpotQA, ToolBench) measure component capabilities but not your specific task. Build a task-specific eval set with:

  • Golden trajectories: Expected tool sequences for known inputs
  • Outcome criteria: Did the agent achieve the goal? (Not: did it follow the exact path?)
  • Failure taxonomies: Wrong tool, hallucinated parameter, loop stall, premature termination

Log every iteration as a structured event:

{
  "trace_id": "abc-123",
  "step": 3,
  "model": "gpt-4o",
  "reasoning": "The diff shows changes to auth.py. Need to check for SQL injection...",
  "tool_calls": [{"name": "run_linter", "args": {"file": "auth.py"}}],
  "tool_results": [{"success": true, "output": "line 42: SQL injection risk..."}],
  "latency_ms": 2340,
  "tokens": {"input": 4120, "output": 380}
}

This lets you replay failures, measure step efficiency, and detect regressions when you swap models or prompts.

When to use an agent

Scenario Recommendation
Fixed sequence of known steps Workflow / DAG
Conditional branching on data values Workflow with decision nodes
Unknown number of search/fetch cycles Agent
Tool selection depends on intermediate results Agent
Need to recover from tool failures dynamically Agent
Human-in-the-loop at decision points Workflow with approval gates
Exploratory research / open-ended goals Agent with step budget

Start with the simplest thing that works. A prompt chain with explicit conditional logic beats an agent for 80% of “agent” use cases. Graduate to an agent when the control flow genuinely cannot be predetermined.

Tagsai-agentsplanningtool-use

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All ai agents fundamentals posts →