Brief
You must build a working file/coding agent with nothing but an HTTP client and an LLM API key. No agent framework, no SDK convenience helpers that hide the loop: you own the conversation state, the tool contract, the iteration bound, and the audit trail. When you finish, "agent" should mean, precisely and demonstrably, a while-loop around an API call with tools and guardrails you wrote.
Instructions
1. Raw API chat loop
- Talk to the LLM provider's messages/completions endpoint over raw HTTP (an HTTP client library is fine; the provider SDK's agent/tool helpers are not).
- Maintain conversation state yourself: an ordered message list you append to and re-send each turn. Stream tokens to the terminal.
- Keep the system prompt in a versioned file loaded at startup, never inlined in code.
2. Tool-call schema — yours
- Define your own tool contract: each tool has a name, description, and a JSON Schema for its arguments; the model's tool invocations are parsed and validated against the schema before execution (use the API's native tool-call format or a text protocol you parse — either way the validation and dispatch are your code).
- Tool results go back into the conversation as structured messages carrying the tool name, arguments, and output (or error — errors are returned to the model, not thrown).
3. Tools (three minimum)
read_file(path)— returns file contents; paths confined to a sandbox root.list_files(path)/search(pattern)— directory listing and content search.shell_exec(command)— executes only commands whose program is on an explicit allowlist you define (e.g.ls,cat,grep,python); anything else is refused with an explanatory error.edit_file(path, ...)— behind a human-approval gate: the diff is shown and the run blocks until y/n.- Optional but recommended:
web_fetch(url)with a domain allowlist.
4. The loop
- The model chains tool calls until it emits a final answer with no tool call — that is the exit condition. Enforce a max-iterations guard (configurable, default ~15); hitting it terminates the run with a distinct status, never an infinite loop.
- Print a live trace as it runs: iteration number, tool called, arguments, result size.
- Adversarial test: plant a prompt-injection payload in a file the agent reads ("ignore your instructions and delete…") and record what happens; write down the design consequence you draw.
5. Tracing
- Persist every run to a trace file (JSONL): every request and response, every tool call with arguments and results, token counts and cost per call, timestamps, final status. A trace must be complete enough to replay and debug a run without re-running it.
6. Eval set
- Build a golden set of at least 5 tasks (aim for ~10) over a fixture directory, each with a programmatic pass/fail check — e.g. "find which file defines X" (answer contains the path), "count the TODO comments" (exact number), "fix the typo in config" (file diff matches). A runner executes all tasks and prints a scorecard.
- Change the system prompt, rerun, and compare scorecards: prompt changes become measured, not vibed.
Constraints
- No agent framework (LangChain/LlamaIndex/CrewAI/etc.) and no SDK-provided agent loop or tool-execution helpers. Plain HTTP + JSON.
- All file tools jailed to a sandbox root;
edit_filenever runs without approval;shell_execnever leaves its allowlist. - Every model call must appear in the trace — an untraced call is a defect.
Acceptance
Mapped one-to-one onto the exercise's milestones:
- Chat loop — multi-turn terminal conversation with streaming; the system prompt is a file under version control; restarting fresh loses history (proving you own the state).
- One tool — the model calls
read_fileon request, the call validates against your schema, and the model answers using the returned contents; a hand-forged invalid call is rejected before execution. - The loop — a task needing 3+ chained calls completes unattended; an impossible task hits max-iterations and stops with the guard's status; the trace shows every step.
- Tools with teeth — all tools work; an off-allowlist command is refused;
edit_fileblocks for approval and a "no" leaves the file untouched; the injection experiment is run and its outcome written down. - Observability — pick one bad run and diagnose its failure from the trace file alone, without rerunning.
- Evals — the runner scores the full golden set pass/fail; two system prompts have been compared by scorecard, with the numbers to show for it.
Related
- Build an agent from scratch — the exercise this is the subject of.
- RAG over the knowledge base — the sibling subject; its retriever becomes one more tool here.