Load a run to inspect its execution
Each tool call appears once, with its action and result combined.
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.
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.
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
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.
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:
Load a saved run to see its outcome, root cause, and tool calls without reading the raw trace.
Each tool call appears once, with its action and result combined.
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.
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.
workspace/.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.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.
The project needed a concrete teaching target before code existed.
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.
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.
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.
No executable tests existed yet. The testable requirements were introduced as acceptance criteria in STAGE.md and docs/stages/01-hello-agent.md.
At this point, the repository taught the goal but had no runnable implementation.
Create the package and command-line entry point so the stage can be exercised.
The repository needed a runnable surface before agent behavior could be tested.
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.
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.
The CLI parses input and invokes repository code. It is the outer boundary for local usage, not the source of agent intelligence.
tests/test_cli.py was introduced to lock the command behavior.
The CLI alone could not inspect workspace/, validate tools, or prove completion. It only established the runnable package.
Add bounded filesystem tools and tests for their safety properties.
The agent needed to inspect files without receiving broad filesystem authority.
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.
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 ".".
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.
tests/test_tools.py checks successful reads and rejection behavior. Fixture reuse moved shared setup into tests/conftest.py, reducing duplicated test scaffolding.
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.
Add a dispatcher so model-proposed tool names and payloads can be validated before execution.
Tool functions existed, but the host still needed a central place to accept or reject model proposals and record what happened.
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.
dispatcher.py added tool dispatch. observations.py added structured observations and IDs. Tests expanded around accepted calls, rejected calls, and recorded outcomes.
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
tests/test_dispatcher.py was introduced and then expanded when observations became first-class records.
Initial dispatch handled execution, but the later observation layer made failures and rejections part of the trace instead of incidental control flow.
Use the dispatcher inside a bounded loop that repeatedly asks for an action until the task finishes or the budget is exhausted.
The system needed to run more than one tool call and decide when enough evidence had been collected.
Agentic behavior is iterative. A single function call is not enough to teach budgets, state, retries, evidence accumulation, or stop conditions.
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.
The host loop repeatedly:
The scripted strategy made this deterministic enough to test exact behavior.
tests/test_agent.py covered loop execution. Dispatcher and tool tests changed with the workspace-relative path correction.
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.
Replace the deterministic action source with an LLM adapter while preserving the same host-owned tool boundary.
The deterministic loop proved the host behavior, but Stage 1 still needed the model in the loop.
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.
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.
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.
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.
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.
Add finish verification so the model cannot end the run with an answer that is unsupported by observations.
The model needed a way to finish, but accepting a finish call blindly would reintroduce unsupported claims.
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.
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.
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.
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.
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.
Evaluate the full behavior repeatedly across scenarios, using an evaluator that computes ground truth outside the agent.
Passing unit tests does not prove that the model-driven loop succeeds reliably.
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.
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.
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
tests/test_evals_run.py was added. Agent tests were updated around evaluation behavior.
The fix(eval): save runs commit shows that evaluation is more useful when failures leave artifacts. Without saved runs, debugging repeated behavior is harder.
Squash the completed stage to a clean v1 checkpoint, then refactor maintainability without changing the teaching contract.
After Stage 1 worked, several maintainability issues remained visible in the implementation.
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.
The refactor/v1.5-maintainability branch made six observable changes:
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.
The refactor updated tests in:
tests/test_verification.pytests/test_agent.pytests/test_dispatcher.pyThose tests anchor the behavior that should not change while internals become clearer.
The refactor history does not add a new stage capability. It improves the maintainability of Stage 1 and prepares the code for later stages.
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 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:
v2 checkpoint.Planned curriculum source: docs/stages/02-file-detective.md.
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:
v3 checkpoint.Planned curriculum source: docs/stages/03-self-correcting.md.
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:
v4 checkpoint.Planned curriculum source: docs/stages/04-persistent.md.
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:
v5 checkpoint.Planned curriculum source: docs/stages/05-governed.md.