08 · AI Agents 101

An agent is a model that can act: it reads, runs, observes and repeats until a goal is reached. Agents are the biggest shift in how software gets built since version control. This page explains how they work and how to supervise them well.

Level 140 minTrack 7 · Agents

By the end of this page you can

From chatbot to agent

A chatbot answers. An agent pursues a goal. The difference is three things wrapped around the model:

  1. Tools: functions it can call (read file, edit file, run command, search, call an API, query a database).
  2. A loop: after each tool call, the result goes back into context and the model decides what to do next.
  3. A goal and a stopping condition: "tests pass", "the user's question is answered", "the PR is open".

The agent loop

Goalfrom you Think / planwhat next? Actcall a tool Observetool result → context Done ✓ goal met tools:read / edit filerun commandsearch code / webMCP tools
Think → act → observe, repeated. Each loop adds tool results to the context, which is why context management (page 04) matters so much for agents.

A real trace (simplified)

THINK  I should see the failure first.
ACT    run_command("sbt \"core/testOnly *OrderServiceSuite\"")
OBSERVE  1 failed: "total should include tax" expected 107.00 but got 100.00

THINK  Look at how total is computed.
ACT    search_code("def total")
OBSERVE  modules/core/src/main/scala/orders/OrderService.scala:42

ACT    read_file("modules/core/.../OrderService.scala", lines 30-60)
OBSERVE  total = items.map(_.price).sum   // tax not applied

THINK  Tax is defined in TaxPolicy. Apply it.
ACT    edit_file(... total = TaxPolicy.applyTo(items.map(_.price).sum) ...)
ACT    run_command("sbt \"core/testOnly *OrderServiceSuite\"")
OBSERVE  all 12 tests passed

ACT    run_command("sbt test")
OBSERVE  all 148 tests passed
DONE   "Fixed: OrderService.total now applies TaxPolicy. All tests pass."

How tool calling works

The model doesn't execute anything itself. The application gives it a list of tool definitions (name, description, JSON schema of parameters). When the model wants a tool, it outputs a structured tool call. The application executes it and sends back the result.

Model App / agent host Tool / system prompt + tool definitions tool_call: get_weather(city="Oslo") execute (after permission check) {"temp_c": 4, "sky": "rain"} tool result added to context "It's 4°C and raining in Oslo."
{
  "name": "run_tests",
  "description": "Run the test suite for a module. Use after changing code to verify it.",
  "input_schema": {
    "type": "object",
    "properties": {
      "module":  { "type": "string", "description": "sbt project name, e.g. core" },
      "pattern": { "type": "string", "description": "optional test class glob" }
    },
    "required": ["module"]
  }
}

Three practical consequences:

Levels of autonomy

LevelWhat the AI doesYour roleExample
0 · SuggestCompletes code as you typeAuthorInline completion
1 · AnswerExplains and drafts in chatAuthor, copy-pasteChat / Ask mode
2 · EditProposes diffs you acceptEditor-in-chiefInline edit
3 · Supervised agentMulti-step work, asks before risky actionsSupervisorIDE agent mode, terminal agent
4 · Background agentWorks alone in a sandbox and returns a PRReviewer / managerCloud coding agents
5 · Multi-agentSeveral agents plan, implement, review in parallelArchitect / orchestratorParallel agents, orchestrators (Level 3)
Autonomy follows clarity The clearer the goal and the better the automated checks (types, tests, linters), the more autonomy you can safely give. Vague goals with no tests need tight supervision. This is why investing in tests pays off twice in the AI era.

Key agent techniques

Plan first

For non-trivial tasks, ask for a plan (or use a plan mode) and review it before any code is written. A plan typically lists files to change, steps, risks and how to verify. Fixing a plan takes seconds. Fixing 500 lines of wrong code takes an hour.

Give it a way to check itself

Agents improve dramatically when they can run a compiler, tests, linters or a type checker. State the verification commands in AGENTS.md and in your prompt ("Done = sbt test passes").

Sub-agents

A main agent can delegate a self-contained task (for example "find every place we parse dates") to a sub-agent with its own fresh context. The sub-agent explores and returns only a short summary, which keeps the main context clean. Sub-agents can also run in parallel.

Checkpoints and git

Commit (or use the tool's checkpoint feature) before letting an agent loose. If it goes wrong, revert and retry with a better prompt. That is often faster than trying to steer a confused agent back.

Human-in-the-loop

Good agents ask questions when requirements are ambiguous. Encourage it: "If something is unclear, stop and ask rather than guess."

How agents fail (and what to do)

FailureLooks likePrevention
Goal driftSolves a different problem than you meantPrecise goal + plan review
Test gamingWeakens or deletes tests, hard-codes expected values"Never modify existing tests without asking"; review test diffs carefully
LoopingRepeats the same failing fixStop it, give a hint or more context, or restart fresh
Scope creepRefactors half the repo"Change only what is needed; list other suggestions separately"
Hallucinated APIsCalls methods that don't existCompiler and tests in the loop; ground with docs
Unsafe actionsDeletes files, force-pushes, touches prodPermissions, sandboxes, hooks; never give agents production credentials
Context overflowForgets early instructions on long tasksBreak into sessions; progress notes; sub-agents
Prompt injectionFollows instructions hidden in a web page, issue or fileTreat external content as untrusted; require approval for sensitive tools (page 14)

What makes a great agent prompt

Goal: <what and why, 1-3 sentences>
Context: <relevant files / modules / similar existing code to follow>
Requirements:
  1. ...
  2. ...
Constraints: <what must not change; no new deps; style rules>
Verification: <exact commands that must pass>
Process: First propose a short plan and wait for my OK. Ask if anything is ambiguous.
Goal: Add pagination to GET /orders so the mobile app can load orders in pages of 50.
Context: Routes in modules/http/.../OrderRoutes.scala; repository in modules/db/.../OrderRepo.scala.
  Follow the pattern used for GET /customers (CustomerRoutes.scala), which already paginates.
Requirements:
  1. Query params `limit` (default 50, max 200) and `cursor` (opaque string).
  2. Response includes `nextCursor` (null when no more data).
  3. Stable ordering by (createdAt, id).
Constraints: No breaking change for clients that send no params. No new dependencies.
Verification: sbt scalafmtCheckAll "http/test" "db/test" must pass; add tests for limit bounds and cursor.
Process: Propose the plan first and wait for my OK.

Agents beyond code

The same loop powers research agents (search → read → synthesise), data agents (query → check → chart), operations agents (read logs → diagnose → propose a fix) and "computer use" agents that operate a browser or desktop through screenshots and clicks. Page 12 explores these across IT.

Try it (20 min) Give your IDE or terminal agent a small, well-defined task with tests, using the template above. Watch the trace of tool calls. Count how many loop iterations it took, and note one moment where you would have done something differently.

Key takeaways