The course page has a syllabus and the syllabus has six weeks. Week one is prompt engineering. Week two is LangChain. Week three is RAG. Week four is MCP. Week five is “advanced agents.” Week six is a capstone where you deploy a chatbot to a free tier and put it on your CV. Somewhere around week three you notice that every exercise is an import statement and a config dict, and that you could swap the framework for a different one and the shape of your homework would not change at all.
That is the tell. You are not learning agents. You are learning one vendor’s spelling of agents, and the spelling is the part with the shortest half-life. Two years from now the class names will have moved, the decorators will have been deprecated twice, and the thing you will still need is the answer to a question the syllabus never asked: what exactly is your code responsible for once the model has spoken?
The generic course cannot ask that question, because the answer is the framework’s job and the framework is what it is selling. So it teaches you to recognise vocabulary. You come out able to say “chain,” “retriever,” “agent executor,” “tool call,” and unable to say what happens when the retriever returns five chunks of garbage and the executor loops on them nine times before your budget dies. That second thing is the job.
I want to lay out the order I would actually learn this in if I were starting from zero today, which is not the order any curriculum uses, because the curriculum has to sell you a framework and the real path makes frameworks look small.
The harness is the whole subject
Strip the marketing off and an agent is a control-flow problem with a stochastic function call in the middle. The model is a component you rent. It has no memory, no persistence, no ability to act, and no opinion about when to stop. Everything that makes the thing feel like an agent - that it remembers, that it retries, that it uses a tool, that it eventually answers - is code you wrote around a stateless call.
That code is the harness. It is the loop, the state it carries between turns, the tools it exposes, the conditions under which it gives up, the budget it respects, and the shape of what it hands back. It is the part you own, the part that breaks in production, and the part nobody teaches because it does not have a logo. It is also, conveniently, the part that transfers. Swap the model, swap the provider, swap the framework, and the harness questions are identical.
So learn it in stages, and finish one before you start the next. Extraction first: one call, a schema, a parser that refuses malformed output. Then a loop around that call. Then states instead of steps. Then memory that outlives the process. Then a tool registry the model can grow into. Each stage is small enough to write in a sitting, and each one hands you a failure mode you will recognise for the rest of your career.
The reason to build each stage yourself is not purity. Nobody gets points for writing
their own retry logic. It is that every stage contains a decision, and a framework makes
that decision silently at import time. max_iterations=15 is somebody’s guess about your problem. A
default checkpointer is somebody’s guess about your consistency requirements. A default
chunk size of 1000 characters is somebody’s guess about your documents. You cannot evaluate
those guesses until you have made the guess yourself and watched it be wrong.
There is a second reason, which matters more the longer you do this. Debugging an agent is almost never debugging the model. It is working out which of your six layers dropped the thing you needed: the parser that silently coerced a field, the loop that exited a turn early, the memory that returned a stale document, the tool whose description promised something it does not do. If you did not build those layers you cannot see them, and you will spend your time rewriting prompts to fix bugs that live in your own code.
The one rule worth remembering: The next stage is worth nothing until the current one has broken on you. Read about loop guards and you have a fact. Watch your own agent call the same tool eleven times in a row and you have an instinct.
Stage one: where the model is boring
Start somewhere deliberately unglamorous. Get structured data out of prose. A prompt, a target struct, and code that validates the result and retries on failure. No agency, no tools, no loop, nothing that would look good in a demo.
What you are learning here is the only primitive underneath all of it: the model is a function from text to text, and every guarantee beyond that is one you build. It does not return an object. It returns characters that may parse into an object, and the gap between those two sentences is where a surprising share of production incidents live. Write the validation layer and you will feel the gap immediately.
Then push on it until it fails, because the failures are the curriculum. Ask for five fields
and it returns four, with the fifth silently omitted rather than null. Ask for an enum and
it returns a value adjacent to your enum, spelled reasonably, that your parser has never
seen. Ask for a number and it returns "about 40". Give it a document with the answer
missing and watch it produce a beautifully formatted struct full of confident invention.
Each of those is a different repair: a stricter schema, a retry with the validation error
fed back, an explicit “unknown” branch, a confidence field you actually check.
By the end of an afternoon you have a component that turns unreliable text into a typed value or a clean failure, with no third state. That component is the atom. Every stage above this is that same call in a more interesting arrangement, and if the atom is unreliable nothing above it can be made reliable by adding structure.
Stage two: wrap it in a while
Now give the call a second chance to be right. This is the ReAct pattern, and it is genuinely as small as the paper makes it sound: the model reasons about what to do, emits an action, you execute it, you feed the result back, and you repeat until it says it is done.
Reasoning traces help the model induce, track, and update action plans as well as handle exceptions.
Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022)We wrote the whole thing out in the agent loop - a couple of dataclasses and a loop you can read in one sitting. Build it before you touch anything that calls itself an agent framework, because afterwards every framework reads as a variation on one skeleton rather than as magic.
The thing worth noticing while you build it is that the transcript is the state. There is no hidden object holding what the agent knows. There is a list of messages, and what the agent “knows” on turn six is exactly what is in that list, in order, within the context window. Once you internalise that, a large class of agent bugs becomes obvious rather than mysterious. It forgot the constraint because the constraint scrolled out. It repeated the tool call because the observation you appended did not actually say the call had failed. It ignored your instruction because you put it in turn one and there have been forty turns since.
Now break it on purpose. Give it a tool that fails. Give it a question it cannot answer with
the tools it has. Give it a tool whose result is empty rather than wrong. Watch what it does
when it runs out of ideas: it repeats itself, confidently, until your budget runs out. That
is where you discover why a loop guard exists, and
having discovered it, you will never again read max_iterations as boilerplate. You will
read it as somebody else’s answer to a question you now know how to ask.
Then break it the other way. Give it a task that genuinely needs nine steps and set the cap at five. Watch it get truncated mid-plan and produce an answer that reads as finished and is not. Under-termination and over-termination are different bugs with different fixes, and a single integer cap addresses neither of them well. That realisation is what sends you to the next stage.
Stages three and four: state that outlives the turn
A state graph is worth building the day your while loop grows a third if. Not before. If
you reach for a graph framework at stage two you will be configuring an abstraction over a
problem you have not had yet, which is the exact failure the course path produces.
When you do build it, build it small: nodes are functions, edges are predicates, and the runner is a dispatch loop holding a current node and a state object. Thirty lines. What that exercise teaches is not the API, it is the discipline a graph imposes. You must name your states, and naming them forces you to admit how many there really are. Most agent codebases that feel unmanageable are unmanageable because the state is implicit, spread across a transcript and four booleans, and nobody can say what the system is currently doing.
The failure mode here is subtle and worth meeting yourself. Two edges out of the same node are both true, and the run takes whichever one you happened to check first. Nothing errors. The agent simply behaves differently on Tuesday because a field arrived in a different order. If you have written the dispatch loop yourself you will think to assert that exactly one edge matches. If you inherited it, you will spend a week on a heisenbug.
Persistent memory is the day after. It is worth building when you want to resume a run tomorrow, and the reason to build it by hand is that the storage half is trivial and the hard half is precedence. You retrieve four facts about the same entity, written at four different times, and two of them contradict. Which wins? Recency is the obvious answer and it is wrong often enough to hurt, because a carefully derived conclusion from last month should usually beat an offhand mention from this morning. That is what a shared workspace has to arbitrate, and no default implementation can arbitrate it for you, because the answer depends entirely on what your facts mean.
Stage five: the tools you harvest
The last stage is a registry: a place to declare what the agent can do, and a harvester that turns a function signature into the schema the model is shown. Write the harvester. It is introspection over type hints and a docstring, it is less code than you expect, and it makes the central asymmetry of tool use impossible to forget.
@tool derives from it - no schema written by
hand. Each parameter's type comes from its annotation, its
required flag from whether it has a default.The asymmetry is this. Your function has a body, and the model never sees it. The model sees
a name, a description, and a parameter schema, and that is the entire universe of what it
knows about the capability. Every tool bug is a gap between those two things. The function
returns an empty list for “no results” and also for “you passed a malformed date,” and the
description mentions neither, so the model concludes the data does not exist. The parameter
is called q and the description says “query,” and the model passes a sentence where the
implementation wants two keywords. None of that is a model failure. It is documentation
failure, and you are the one writing the documentation.
Then there is the budget. Every tool description is resident in the context on every single turn, before the user has said anything. Forty tools with generous descriptions is a standing tax you pay per call, forever, and it grows as your system grows.
~80-token entrypoint and discovers the
rest on demand via --help. Switch the per-tool weight to see how fast the tax compounds.A tool surface is a product, not a dump: The right number of tools is the smallest set that covers the job. Two well-named tools with sharp descriptions beat nine overlapping ones, and the model’s accuracy at choosing between them falls off faster than most people expect.
Write the vector database before you use one
RAG is the place where the framework-first path does the most damage, because a managed retriever hides four independent decisions behind one constructor and every one of them is where your quality actually lives.
So write your own. An in-memory vector store is a list of embeddings and a dot product. Embed your chunks, normalise them so cosine similarity collapses into a matrix multiply, stack them into one array, and take the top-k by score. It is a page of numpy:
import numpy as np
class Store:
def __init__(self, dim: int):
self.vecs = np.zeros((0, dim), dtype=np.float32)
self.docs: list[dict] = []
def add(self, docs: list[dict], embeds: list[list[float]]) -> None:
v = np.asarray(embeds, dtype=np.float32)
v /= np.linalg.norm(v, axis=1, keepdims=True)
self.vecs = np.vstack([self.vecs, v])
self.docs.extend(docs)
def search(self, embed: list[float], k: int = 5) -> list[tuple[dict, float]]:
q = np.asarray(embed, dtype=np.float32)
q /= np.linalg.norm(q)
scores = self.vecs @ q
return [(self.docs[i], float(scores[i])) for i in np.argsort(-scores)[:k]]
That is the whole thing, and it will retrieve badly on your first try. Good. You now have a rig where every knob is visible and you can turn exactly one at a time, which is the condition under which learning actually happens.
Turn them one at a time and the lessons arrive in a useful order. Add metadata filters and
watch how much of “better retrieval” turns out to be a WHERE clause on date or document
type. Change your chunking strategy and watch a benchmark move further than swapping the
embedding model did, which is not what the marketing implies. Add a reranking pass over the
top fifty and see the cases where cosine was confidently lying, usually because a chunk
shares vocabulary with the question while answering a different one.
Before any of that, though, build the boring thing nobody builds: thirty questions with known answers in your corpus, and a script that reports how often the right chunk appears in the top five. Without it you are tuning on vibes, which is the real reason most retrieval systems plateau. With it, every knob above becomes a measurable experiment that takes two minutes. Thirty questions is not a rigorous eval and it does not need to be. It needs to be sensitive enough to tell you which direction you just moved.
Then, having done it by hand, go use the framework. You will read its options list as a set of positions on questions you have already argued with yourself about, which is a completely different experience from reading it as a menu. You will also know which of its defaults to override on day one, and that knowledge is worth more than the weeks you spent learning its class hierarchy.
Then make retrieval a tool
The last step of RAG is to stop treating it as a preprocessing step. In the standard arrangement you take the user’s question, embed it once, retrieve, stuff the results in the prompt, and generate. The retrieval happens before the intelligence does, which means the query is written by someone who has not yet read anything.
Agentic RAG inverts that. Retrieval becomes a tool the loop can call, repeatedly, with queries the model writes itself after seeing what came back last time. Ask a question that needs three different lookups and it does three, refining the second based on the first. Ask one where the first search returns nothing and it rephrases instead of hallucinating around the void. That is exactly your stage-two loop with your stage-five registry pointed at your own vector store, which is why it belongs here and not in week one.
It costs more per question and it is worth it for anything where the answer is assembled rather than looked up. It also introduces its own failure, which you should meet deliberately: the model searches five times, gets partial results each time, and synthesises a confident answer from fragments that were never meant to sit together. The fix is citation discipline, not a better prompt. Make every claim carry the chunk it came from and the failure becomes visible instead of invisible.
What you can safely defer
Some of the loudest topics on the syllabus are not conceptual jumps, and treating them as milestones costs you weeks you could have spent on the stages that matter.
Learn early
- The loop and its exit conditions
- Structured output and validation
- Your own retrieval, end to end
- State that survives a crash
Defer
- MCP servers
- Knowledge graphs
- Skill and plugin scaffolding
- Multi-agent orchestration
MCP is a transport and a discovery convention for tools you have already had to define anyway. It is a good standard and it solves a real distribution problem: without something like it, every tool has to be re-implemented per client. But conceptually it is an interface description for your tool surface, and if you already understand what a tool schema is you understand MCP in an afternoon. Whether it earns its place in your stack is a plumbing question with real costs on both sides, and we argued those in the abstraction tax. It is not a stage in the progression.
Knowledge graphs are the same shape of distraction: real, occasionally excellent, and used in a small minority of what people actually build. The honest version of the pitch is that they help when your questions are about relationships several hops deep, and that most production retrieval questions are one hop and answered fine by chunks with metadata. Learn them when you have a problem whose structure you cannot express any other way, not because they appear in week five.
Multi-agent orchestration is the most expensive deferral to get wrong. A supervisor spawning five specialists is a genuinely useful pattern for parallel, independent work, and it is a disaster as a first architecture, because every problem you had with one agent is now happening five times with a coordination layer on top and a transcript nobody can read. Get one agent working, measurably, then split it when you can name the reason.
And the agent tooling itself - skill definitions, plugin manifests, the CLI configuration you set up before you start - is developer ergonomics. It makes you faster at building agents. It teaches you nothing about how they work. Set it up in twenty minutes and stop thinking about it.
One honest exception to all of this: this progression is written for people who already write software. If you are coming in without a programming background, the right first move is genuinely the opposite. Drive a coding agent, work through its skill system, build something that runs, and let the curiosity about the harness arrive on its own. Different track, different order, and it is a legitimate one.
A capstone worth a quarter
Toy projects teach toy lessons. A chatbot over your PDFs will teach you that chunking matters and then stop teaching you anything, because there is no way to be wrong that costs you.
The project I would build instead touches every stage at once, uses real data with real dirtiness, and produces an answer you can check against the world: a deep research agent that reads capital expenditure out of public company filings and works out who has to supply it.
The thesis is simple. When a company commits capital, it commits to buying physical things. A data center build is power, land, concrete, accelerators, cooling, and licensed electricians. A fab is lithography tools, ultrapure water, process gases, and trained technicians. A grid buildout is conductor, transformers, and linemen. The capex line in a filing is a demand signal about industries the filer does not operate in, disclosed quarters before that demand shows up as revenue somewhere else. Whether that signal is tradeable is not the interesting question. Whether you can build a harness that extracts it reliably very much is.
The data is free and unusually well-behaved. The SEC exposes company facts as JSON, and the
endpoints compose into a real research surface. companyconcept gives you every reported
value of one concept for one company across all periods. companyfacts gives you every
concept a company has ever tagged. submissions gives you its filing history. And frames
inverts the whole thing: one concept, one period, every company that reported it, in a
single call. That last one is the difference between studying a company and studying an
economy.
curl -H 'User-Agent: Your Name you@example.com' \
'https://data.sec.gov/api/xbrl/frames/us-gaap/PaymentsToAcquirePropertyPlantAndEquipment/USD/CY2023.json'
The User-Agent is not optional, and this is a nice early lesson in reading a data source
on its own terms rather than through a wrapper. Send curl’s default and you get a 403. Send
one with your name and contact address and you get a 200. There is a ticker-to-CIK map at
www.sec.gov/files/company_tickers.json, the endpoints are rate limited so you cache
aggressively and pull once, and everything XBRL does not tag lives in the filing prose,
searchable through the full-text index at efts.sec.gov. Structured numbers on one
endpoint, narrative on another, and the interesting work in joining them.
Not investment advice. This is an engineering exercise that happens to use market data because market data is public, messy, and has a ground truth. Do not trade on it. The value is the harness you build, not the signal you think you found.
Now count what the project forces you to build. Extraction, because the narrative sections explaining what the capex is for are prose and you need them as structs with a source citation. A loop, because one filing leads to a segment note leads to a supplier’s own filing, and you cannot script that traversal in advance. Retrieval, because you are indexing thousands of pages of dense legalese and asking questions across them. Memory, because a quarterly rerun should know what it concluded last quarter and what changed. Tools, because you need EDGAR, a price series, and a units calculator, and the model has to choose between them. Every stage, in one repo, driven by a question you actually want answered.
The hard parts are hard in instructive ways too, and you should expect them. Companies tag the same economic reality with different concepts, so your “capex” is a small taxonomy problem before it is anything else. Segment disclosure is inconsistent, so the split between “data centers” and “everything else” is sometimes stated and sometimes an inference you have to mark as an inference. And the mapping from a dollar of capex to a physical quantity is where your model is most opinionated and least verifiable. Building something that is honest about which numbers are reported and which are derived is most of the engineering.
The gap model, and the backfit
The last piece is what turns the project from a scraper into a research system. For each demand class, compare implied demand against what the suppliers have publicly said they can deliver, and look at the residual. Where committed capital outruns announced capacity, something has to give: price, schedule, or somebody’s guidance. That residual is the output, and unlike a chatbot’s answer it is a number you can be wrong about in public.
Then backfit it. Run the whole pipeline as of two years ago, using only filings that existed then, and see what it would have concluded. This is where the project stops being about agents and starts being about engineering discipline, because doing it correctly is genuinely difficult and doing it incorrectly is effortless.
The trap that makes every backtest look brilliant: Filings get amended, and the API hands you today’s values by default. If your as-of-2024 run reads a number that was restated in 2025, your model is reading the future. Filter by filing date, not fiscal period, and watch your results get worse and become true.
The point of the backfit is not to get rich. It is that you now have a scoring function, and a scoring function is what turns “I built an agent” into “I can tell whether this change made it better.” Every argument you have with yourself after that becomes settleable. Does a larger chunk help? Run it. Does the reranker earn its latency? Run it. Does the expensive model beat the cheap one on this specific task, or only on the benchmark somebody else ran? Run it.
That capability is the actual deliverable of the whole exercise, and it is the thing the six-week course cannot give you, because evals only exist relative to a problem you care about and the course has to pick a problem nobody cares about so that everyone can follow along.
Where to run it
Keep the infrastructure boring, because infrastructure is the most seductive way to avoid doing the work. Cloudflare’s free Workers plan gives you 100,000 requests a day and 10ms of CPU per invocation, which is plenty for a scheduled research job that spends its life waiting on network rather than computing.
When you outgrow the CPU limit, and a document pipeline will, run the heavy half wherever
you like and keep the thin half at the edge. cloudflared opens an outbound-only tunnel, so
a process on your laptop gets a real hostname without opening a single inbound port or
holding a public IP. Local compute, public endpoint, no infrastructure to speak of, and the
same trick works for putting a private dashboard in front of the thing without building auth
into it.
That combination is worth naming because it removes the last excuse. You do not need a cluster to learn this. You need one long-running process, a scheduler, and somewhere to put the results, and the whole stack fits inside a free tier until the project is good enough to deserve money.
The part that compounds
Build it three ways. Once with your own loop and no dependencies. Once with a graph framework. Once as a set of prompt-chained workflows with no agency at all, where you decide the sequence and the model only fills in the steps. Same question, same data, three harnesses.
Then compare them on the things that actually differ. How many quarters of backfit does each one survive? How long does it take you to change your mind about the model once you have a new idea? When it produces a wrong answer, how long until you know which layer produced it? That last question is the one that separates architectures in practice, and it is invisible in every framework comparison you will ever read, because it depends on your ability to see inside your own system.
You will probably find the no-agency version wins more often than you expected. Many tasks that look agentic are a fixed sequence with one hard step in the middle, and the loop is overhead you are paying for flexibility you are not using. Discovering that for yourself, with a scoring function to prove it, is worth more than any framework you could have learned instead.
That comparison is the actual curriculum. Not “which framework is best,” which is boring and situational, but the feel for which decisions your harness is making on your behalf and which ones you left to chance. Nobody can hand you that. It only arrives after you have written the loop, watched it spin, and fixed it yourself.
Start at stage one this week. The framework will still be there when you need it, and you will finally be able to read its source.



