Introduction

This book teaches the basics of agentic software development by reconstructing this repository stage by stage.

The project is intentionally small: a bounded command-line agent answers a filesystem question by proposing actions, waiting for the host to validate and execute them, recording observations, and finishing only when evidence supports the answer.

The core lesson is:

model proposal != environmental fact

The model may suggest tool calls. The host program owns validation, execution, budgets, observations, and completion checks. That boundary is what turns an LLM that emits tool-shaped JSON into a debuggable agentic system.

What this book covers

The current repository history contains:

  • v0: a documentation-only foundation.
  • stage/1-hello-agent-world: the complete Stage 1 implementation history.
  • v1: the squashed Stage 1 checkpoint on main.
  • refactor/v1.5-maintainability: the complete maintainability refactor history.
  • v1.5: the squashed refactor checkpoint on main.

Stage 2 through Stage 5 are described in the existing curriculum, but their implementation branches and tags are not present in the current local or remote Git refs. This book therefore documents their current status without inventing implementation history.

Local reading

Install and serve the book with:

cargo install mdbook
mdbook serve docs

If port 3000 is already occupied or the browser shows a blank page, run the server on an explicit IPv4 address and alternate port:

mdbook serve docs --hostname 127.0.0.1 --port 3001 --open

Repository History

This repository separates clean learning checkpoints from detailed implementation history.

main contains clean milestone commits. In the current checkout, main points at v1.5, which contains the Stage 1 implementation plus a maintainability refactor as squashed milestone commits.

Tags identify stable snapshots:

  • v0: foundation before Stage 1.
  • v1: bounded observable filesystem agent.
  • v1.5: Stage 1 maintainability refactor.

The stage/* and refactor/* branches retain the complete development history where that history exists:

  • stage/1-hello-agent-world: Stage 1 implementation commits.
  • refactor/v1.5-maintainability: Stage 1.5 refactor commits.

No stage/2-*, stage/3-*, stage/4-*, stage/5-*, v2, v3, v4, or v5 refs are present in the current local or upstream repository. The Stage 2-5 pages in this book are placeholders until those implementation histories exist.

How to inspect changes

Each discussed commit is linked to its GitHub commit page:

https://github.com/milaforge/hello_agentic_world/commit/<sha>

That page includes the commit diff. Use it to inspect the exact evolution of code, tests, and documentation.

Useful local commands:

git log --reverse --oneline v0..stage/1-hello-agent-world
git log --reverse --oneline v1..refactor/v1.5-maintainability
git show <sha>

The intended teaching workflow is:

  1. Read a stage goal.
  2. Inspect the branch history chronologically.
  3. Study the grouped implementation chapters.
  4. Open the linked commit diffs for exact code changes.
  5. Compare the branch to the stable tag for the checkpoint.

Run Replay

Step details

Execution detail

Raw JSON

{}

Stage 1: Hello Agent

Stage goal

Build the smallest useful agentic system:

goal -> propose action -> validate -> execute -> observe -> continue or finish

The agent answers:

How many Python files exist under workspace/, excluding .venv, and what is their total size?

The model can only propose narrow tools. The host validates tool names and arguments, enforces workspace boundaries, executes tools, records observations, applies budgets, and accepts completion only when evidence supports the answer.

Starting state

Stage 1 starts from v0, a documentation-only foundation. There is no package, CLI, tool dispatcher, observation log, model adapter, evaluator, or tests for executable behavior.

Concepts introduced

  • A model proposal is not an observed fact.
  • Filesystem access is confined to workspace/.
  • Tool execution belongs to the host, not the model.
  • Every accepted or rejected action becomes inspectable state.
  • Completion is verified against evidence, not trusted because the model says it is done.
  • Evaluations run outside the agent and compute ground truth independently.

Final architecture

Stage 1 ends with:

  • cli.py: command-line entry point.
  • tools.py: bounded filesystem tools.
  • dispatcher.py: tool name and payload validation plus execution.
  • observations.py: recorded action results.
  • agent.py: bounded action-observation loop.
  • model.py: Ollama response adapter.
  • task_state.py: observed workspace state used for grounding.
  • verification.py: finish validation.
  • traces.py: run trace writing.
  • evals/run.py: repeated scenario evaluation.

Stage checkpoint

v1 is the squashed Stage 1 checkpoint. The detailed development history is on stage/1-hello-agent-world.

The branch history includes a later local-only evaluation fix, fix(eval): save runs, which is documented because it is present in this repository branch.

Framing the Lesson

Problem

The project needed a concrete teaching target before code existed.

Why

Agentic systems become hard to reason about when the lesson starts with a full framework. Stage 1 narrows the problem to one loop, three tool shapes, one workspace boundary, and one verifiable answer.

Implementation

The first commits created the stage documentation and specified the task:

list_directory(path)
get_file_metadata(path)
finish(result, evidence)

The docs also defined the trust boundary: the model proposes actions, while the host owns authorization, execution, observations, budgets, and completion checks.

How it works

The stage specification makes unsupported claims visible. A tool call is only a request. A fact exists only after the host validates the call, executes it, and records an observation.

Test

No executable tests existed yet. The testable requirements were introduced as acceptance criteria in STAGE.md and docs/stages/01-hello-agent.md.

Observed failure or limitation

At this point, the repository taught the goal but had no runnable implementation.

Next step

Create the package and command-line entry point so the stage can be exercised.

CLI Foundation

Problem

The repository needed a runnable surface before agent behavior could be tested.

Why

A command-line entry point gives the stage a narrow interface: accept a user goal, run the host program, and print the result. Keeping this small made later changes testable without committing to a broader framework.

Implementation

The package skeleton, pyproject.toml, lockfile, cli.py, and CLI tests were added.

The important design choice was to start with a host-controlled executable path rather than giving the model direct access to the environment.

How it works

The CLI parses input and invokes repository code. It is the outer boundary for local usage, not the source of agent intelligence.

Test

tests/test_cli.py was introduced to lock the command behavior.

Observed failure or limitation

The CLI alone could not inspect workspace/, validate tools, or prove completion. It only established the runnable package.

Next step

Add bounded filesystem tools and tests for their safety properties.

Safe Filesystem Tools

Problem

The agent needed to inspect files without receiving broad filesystem authority.

Why

The stage is about safe tool use. Direct shell access or recursive host-side globbing would bypass the lesson. The model should be forced to gather evidence through narrow, observable operations.

Implementation

tools.py and contracts.py introduced bounded tool contracts and filesystem operations. Tests covered basic file inspection behavior.

A later fix changed boundary handling so authorization checks happen before other path behavior. Test fixtures were then shared through tests/conftest.py.

The observable correction in this stage is path handling: workspace inputs are relative to workspace_root, and the root is addressed as ".".

How it works

The tool layer exposes limited operations:

list_directory(path)
get_file_metadata(path)

The host resolves each requested path under the configured workspace root and rejects paths that escape the boundary or violate the stage rules.

Test

tests/test_tools.py checks successful reads and rejection behavior. Fixture reuse moved shared setup into tests/conftest.py, reducing duplicated test scaffolding.

Observed failure or limitation

The commit history shows a path-boundary correction. The important lesson is that path normalization and authorization order are security-sensitive; checking the boundary after other path handling can make tests pass for the wrong reason.

Next step

Add a dispatcher so model-proposed tool names and payloads can be validated before execution.

Dispatcher and Observations

Problem

Tool functions existed, but the host still needed a central place to accept or reject model proposals and record what happened.

Why

An agent loop is only debuggable if actions and outcomes are explicit. Unknown tools, malformed arguments, authorization failures, and successful results should all become inspectable records.

Implementation

dispatcher.py added tool dispatch. observations.py added structured observations and IDs. Tests expanded around accepted calls, rejected calls, and recorded outcomes.

How it works

The dispatcher receives an untrusted tool call, validates the tool name and arguments, runs the matching host function only if valid, and returns an observation.

That keeps this boundary clear:

model output -> host validation -> host execution -> observation

Test

tests/test_dispatcher.py was introduced and then expanded when observations became first-class records.

Observed failure or limitation

Initial dispatch handled execution, but the later observation layer made failures and rejections part of the trace instead of incidental control flow.

Next step

Use the dispatcher inside a bounded loop that repeatedly asks for an action until the task finishes or the budget is exhausted.

Agent Loop and Scripted Strategy

Problem

The system needed to run more than one tool call and decide when enough evidence had been collected.

Why

Agentic behavior is iterative. A single function call is not enough to teach budgets, state, retries, evidence accumulation, or stop conditions.

Implementation

agent.py introduced the bounded host loop. scripted.py provided a deterministic action source so the loop could be tested before relying on an LLM.

The path correction commit changed the system to use workspace-relative paths consistently. The counting strategy was then updated to perform the real investigation instead of a placeholder behavior.

How it works

The host loop repeatedly:

  1. asks the action source for the next proposed tool call;
  2. dispatches the call through host validation and execution;
  3. records the observation;
  4. checks whether the finish action is valid;
  5. stops when complete or when the action budget is exhausted.

The scripted strategy made this deterministic enough to test exact behavior.

Test

tests/test_agent.py covered loop execution. Dispatcher and tool tests changed with the workspace-relative path correction.

Observed failure or limitation

The history shows that path shape mattered. Using paths with a workspace/ prefix did not match the eventual contract. The corrected contract uses "." for the workspace root and relative paths for files beneath it.

Next step

Replace the deterministic action source with an LLM adapter while preserving the same host-owned tool boundary.

Model Integration

Problem

The deterministic loop proved the host behavior, but Stage 1 still needed the model in the loop.

Why

The useful teaching point is not that Python can count files. It is that an untrusted model can drive a bounded process while the host preserves evidence, safety, and correctness checks.

Implementation

model.py added an Ollama-backed adapter. The CLI gained model support, and debug.py helped inspect model interaction. Later commits added task state as grounding context and support for multiple tool calls in one model response.

How it works

The model adapter turns an LLM response into host-understood tool calls. The model still does not execute anything directly. Its output remains a proposal that must pass through the dispatcher.

Task state gives the model a compact view of what has already been observed, reducing reliance on unsupported memory.

Test

tests/test_model.py was added for model-response parsing and multi-tool-call behavior. CLI and dispatcher tests were updated around the Ollama integration.

Observed failure or limitation

The model can produce multiple proposed actions or malformed responses. The host must adapt valid proposals and reject invalid ones without treating either as environmental truth.

Next step

Add finish verification so the model cannot end the run with an answer that is unsupported by observations.

Finish Verification and Traces

Problem

The model needed a way to finish, but accepting a finish call blindly would reintroduce unsupported claims.

Why

The stage asks for a count and total size. Those numbers are only trustworthy if they are supported by observed file metadata. Completion has to be a checked operation, not a polite convention.

Implementation

verification.py added finish validation. The agent loop was updated to use it. The CLI gained model selection, and traces.py added preliminary run output for replay and inspection.

How it works

The finish action is accepted only when the recorded evidence supports the structured answer. If the evidence is incomplete or inconsistent, the host rejects completion and the loop continues until the budget is exhausted or a supported answer appears.

Traces make the run auditable after the fact. They are useful for seeing not only the final answer, but also which actions were tried and which observations were recorded.

Test

tests/test_verification.py introduced direct tests for completion checks. tests/trace_test.py covered trace output, and CLI tests were updated for model selection and trace behavior.

Observed failure or limitation

The verifier can only validate against observations the host recorded. If the agent failed to inspect a file, completion should fail instead of filling the gap with inference.

Next step

Evaluate the full behavior repeatedly across scenarios, using an evaluator that computes ground truth outside the agent.

Evaluation Loop

Problem

Passing unit tests does not prove that the model-driven loop succeeds reliably.

Why

Agentic systems fail through behavior over time: repeated unsupported finishes, path mistakes, budget exhaustion, or plausible but incomplete answers. Stage 1 needs repeated-run evaluation, not only deterministic unit tests.

Implementation

evals/run.py added automatic evaluation. It creates scenarios, runs the agent, computes ground truth independently, and reports pass or failure details. A later fix saved run outputs.

How it works

The evaluator is outside the agent. It can inspect the fixture directly because it is measuring the agent, not participating in the agent loop.

That separation keeps the lesson intact:

agent: must gather evidence through tools
evaluator: computes ground truth independently

Test

tests/test_evals_run.py was added. Agent tests were updated around evaluation behavior.

Observed failure or limitation

The fix(eval): save runs commit shows that evaluation is more useful when failures leave artifacts. Without saved runs, debugging repeated behavior is harder.

Next step

Squash the completed stage to a clean v1 checkpoint, then refactor maintainability without changing the teaching contract.

Stage 1.5 Refactor

Problem

After Stage 1 worked, several maintainability issues remained visible in the implementation.

Why

The teaching system should stay small, but not brittle. Refactoring after a working checkpoint makes the production lesson explicit: preserve behavior while improving boundaries, diagnostics, and testability.

Implementation

The refactor/v1.5-maintainability branch made six observable changes:

  • clearer verification diagnostics;
  • scripted actions derived from task state;
  • finish verification failures surfaced by the agent;
  • runtime dependencies injected into the agent;
  • tool specifications centralized in the dispatcher path;
  • tool payloads typed in contracts.

How it works

The refactor keeps the Stage 1 contract intact while making internal responsibilities sharper. The agent loop receives dependencies rather than constructing everything implicitly. Tool schemas move toward a single source of truth. Payload typing makes model and tool boundaries more explicit.

Test

The refactor updated tests in:

  • tests/test_verification.py
  • tests/test_agent.py
  • tests/test_dispatcher.py

Those tests anchor the behavior that should not change while internals become clearer.

Observed failure or limitation

The refactor history does not add a new stage capability. It improves the maintainability of Stage 1 and prepares the code for later stages.

Next step

Future stages can add investigation, repair, persistence, and policy on top of the cleaned Stage 1 shape. Their implementation history is not present in the current refs.

Stage 2: File Detective

Stage 2 is described in the curriculum as evidence-based investigation over a non-trivial repository question.

The current repository does not contain a stage/2-* branch or v2 tag in local refs or upstream refs. Because this book is generated from observable branch history, this page does not invent implementation chapters, motivations, failures, or architecture.

When the Stage 2 implementation history exists, this chapter should be rebuilt from:

  • the Stage 2 branch commit history;
  • commit diffs;
  • tests added or changed by each commit;
  • visible corrections and architectural decisions;
  • the stable v2 checkpoint.

Planned curriculum source: docs/stages/02-file-detective.md.

Stage 3: Self-Correcting Agent

Stage 3 is described in the curriculum as feedback-driven repair using environmental feedback.

The current repository does not contain a stage/3-* branch or v3 tag in local refs or upstream refs. Because this book is generated from observable branch history, this page does not invent implementation chapters, motivations, failures, or architecture.

When the Stage 3 implementation history exists, this chapter should be rebuilt from:

  • the Stage 3 branch commit history;
  • commit diffs;
  • tests added or changed by each commit;
  • visible corrections and architectural decisions;
  • the stable v3 checkpoint.

Planned curriculum source: docs/stages/03-self-correcting.md.

Stage 4: Persistent Agent

Stage 4 is described in the curriculum as preserving and recovering useful state across interruption.

The current repository does not contain a stage/4-* branch or v4 tag in local refs or upstream refs. Because this book is generated from observable branch history, this page does not invent implementation chapters, motivations, failures, or architecture.

When the Stage 4 implementation history exists, this chapter should be rebuilt from:

  • the Stage 4 branch commit history;
  • commit diffs;
  • tests added or changed by each commit;
  • visible corrections and architectural decisions;
  • the stable v4 checkpoint.

Planned curriculum source: docs/stages/04-persistent.md.

Stage 5: Governed Agent

Stage 5 is described in the curriculum as completing an issue-to-patch workflow under explicit policy.

The current repository does not contain a stage/5-* branch or v5 tag in local refs or upstream refs. Because this book is generated from observable branch history, this page does not invent implementation chapters, motivations, failures, or architecture.

When the Stage 5 implementation history exists, this chapter should be rebuilt from:

  • the Stage 5 branch commit history;
  • commit diffs;
  • tests added or changed by each commit;
  • visible corrections and architectural decisions;
  • the stable v5 checkpoint.

Planned curriculum source: docs/stages/05-governed.md.