Agents Honestly
Start Here

Setup

Accounts, keys, a local Temporal server, Postgres with pgvector, and the repo layout for both language tracks.

Four things run on your machine for the rest of this book: a key, a database, a server, and a repo with two of everything.

That list is short on purpose, and what is missing from it is an argument rather than an oversight. There is no LangGraph here, no Temporal SDK, no AI SDK. Each of those gets installed in the chapter that can say what it buys, because a dependency you installed during setup reads as a prerequisite, and none of them are.

What you need, and when

First needed inWhy it exists
An Anthropic API keyPart IEverything else is downstream of one messages.create call
The repoPart IIAtlas accumulates across twenty parts; it needs somewhere to live
Postgres with pgvectorPart IVThe warehouse and the policy corpus are the same database
A local Temporal serverPart XYou cannot demonstrate crash survival against a process you never crash

Do all four now anyway. It is about twenty minutes, and the alternative is four interruptions spread across a book you were trying to read.

Version floors

MinimumWhere the floor comes from
Node20LangGraph.js requires it, and so does its TypeScript 5.4 floor
Python3.11Three things want it, and one is the book's own code; nothing needs newer
Dockerany currentOnly used to run Postgres
Temporal CLIany currentShips the server and the Web UI in one binary

Nothing here needs a specific patch version, and pinning one in a book is how you get a chapter that stops working in eight months.

The Python row is worth one more sentence, because the three reasons fail differently. The LangGraph CLI refuses to run; the plan schema is a syntax error on sight; and the Temporal LangGraph plugin loads anyway and emits a warning, then quietly does not honour interrupt(). Only the last one lets you get most of the way through the book before finding out.

1. A model key

Create one at platform.claude.com. console.anthropic.com redirects there. API Keys → Create Key. The full value starts with sk-ant-, and the console shows it exactly once, so paste it somewhere before closing the dialog.

Billing is prepaid credits; there is no free tier. Set a spend cap while you are in there. The API bills per token, and an agent is a loop around a billable function. That combination rewards a ceiling. Running all of Part II end to end against the twenty-ticket sample costs a couple of dollars. The figure is illustrative; Tokens shows how to do the arithmetic yourself rather than trusting a number in a book.

Then export it and install the SDK:

export ANTHROPIC_API_KEY='sk-ant-...'
npm install @anthropic-ai/sdk

Verify with the smallest possible call:

ts/scripts/verify-model.ts
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic(); // reads ANTHROPIC_API_KEY

const response = await client.messages.create({
  model: 'claude-opus-5',
  max_tokens: 64,
  messages: [{ role: 'user', content: 'Reply with the single word: ready' }],
});

const block = response.content.find((b) => b.type === 'text');
console.log(block?.text);
console.log(response.usage);

Print usage even here, on a call that does nothing. It is one extra line, and a number sitting in that field the whole time answers every cost question in the book: context growth, caching, cost accounting. The habit is cheaper to form now than to retrofit in Part XV.

The key is a payment instrument

It is organization-scoped and it bills whoever holds it. Keep it in a gitignored .env, never in .env.example, and never in a code sample you paste anywhere. A leaked key is not a security incident in the abstract. It is an invoice.

2. Postgres, with pgvector

One container, one volume:

docker-compose.yml
services:
  db:
    image: pgvector/pgvector:pg17
    environment:
      POSTGRES_USER: atlas
      POSTGRES_PASSWORD: atlas
      POSTGRES_DB: atlas
    ports:
      - '5432:5432'
    volumes:
      - atlas-pgdata:/var/lib/postgresql/data

volumes:
  atlas-pgdata:

The pgvector/pgvector images are Postgres with the extension already compiled in; the tag names the Postgres major version. Bring it up and enable the extension. The image ships it, but each database still has to ask for it:

docker compose up -d
docker compose exec db psql -U atlas -d atlas \
  -c 'CREATE EXTENSION IF NOT EXISTS vector;'

Verify that vectors actually work, rather than that the container is running:

SELECT '[1,2,3]'::vector <=> '[1,2,4]'::vector AS cosine_distance;
-- ≈ 0.00854

If that returns a number, Part IV will work. If it errors on the <=> operator, the extension is not installed in this database.

One database, two jobs. Meridian's forty-million-row warehouse and its twelve hundred policy documents both live here. That is a position rather than a convenience. The chapter on where the answer lives argues that splitting structured facts into a warehouse and unstructured text into a separate vector service is the most common and most expensive early architecture mistake. Running one Postgres from the start is what makes the alternative easy to try.

Your corpus survives `down`, not `down -v`

The volume is named, so docker compose down stops the container and keeps the data. docker compose down -v deletes the volume, which means re-embedding the corpus. The chapter that makes that expensive is Chunking.

3. A Temporal server you can crash

Install the CLI, one binary containing the server, the Web UI, and the client:

brew install temporal

Without Homebrew, download the archive for your platform from https://temporal.download/cli/archive/latest?platform=darwin&arch=arm64 (substituting linux/windows and amd64/arm64) and put the binary on your PATH.

Then start it:

temporal server start-dev --db-filename .temporal/atlas.db

gRPC on localhost:7233, Web UI on localhost:8233, and the default namespace created for you.

The --db-filename flag is not optional for this book. Without it, start-dev uses an in-memory database and every workflow history evaporates when you press Ctrl-C. That is fine for a tutorial and useless here, because Part X's entire claim is that a workflow outlives the process running it. Killing the worker is the experiment; killing the history along with it is cheating.

This is a dev server, and it says so

start-dev is one process backed by SQLite. Production Temporal is a cluster with a real database, separate frontend, history, and matching services, and its own failure modes. See Scale and QoS. Nothing in this chapter is a deployment recommendation.

4. The repo, and why it is two of everything

A companion repository, breim/atlas-from-agents-honestly, holds a runnable version of what follows: a small, provider-free kernel with shared TypeScript and Python fixtures, plus one exercise directory per chapter that has one. It is not a second copy of every listing below.

git clone https://github.com/breim/atlas-from-agents-honestly atlas

Start there when you want executable checks immediately; grow the full project shape below as the chapters introduce each boundary. The lab environment chapter explains the contract between the companion and the production architecture.

atlas/
├── .env                     # gitignored
├── .env.example             # variable names, never values
├── docker-compose.yml
├── data/
│   ├── policies/            # the policy corpus, sampled
│   ├── tickets.jsonl        # the twenty-ticket evaluation sample
│   └── seed.sql             # warehouse + CRM fixtures
├── ts/
│   ├── package.json
│   ├── tsconfig.json
│   ├── scripts/
│   └── src/
│       ├── atlas.ts         # the loop
│       ├── tools/
│       ├── retrieval/
│       ├── workflows/       # Temporal, from Part X
│       └── eval/
└── py/
    ├── pyproject.toml
    ├── scripts/
    └── atlas/
        ├── agent.py
        ├── tools/
        ├── retrieval/
        ├── workflows/
        └── eval/

The two tracks are siblings, not a primary and a port. They share .env and docker-compose.yml, and the load-bearing part is that they also share data/. Part XIV runs the same twenty tickets against whichever track you built, and an eval that cannot be pointed at two implementations of the same spec is measuring the harness rather than the agent.

The dependency manifests are short, and stay short until Part VII:

ts/package.json
{
  "type": "module",
  "dependencies": {
    "@anthropic-ai/sdk": "^0.110.0",
    "pg": "^8.13.0",
    "pgvector": "^0.2.0"
  },
  "devDependencies": {
    "tsx": "^4.19.0",
    "typescript": "^5.4.0"
  }
}

The pinned versions are illustrative; install current ones. Three packages and a type checker. Everything Part II builds is written against that list: the loop, the tools, the bounds, the outcome union.

What you deliberately did not install

Arrives inBecause
@langchain/langgraph · langgraphPart VIIPart II writes the loop by hand first, so that adopting a framework is a decision with a stated price
@temporalio/* · temporalioPart XDurability is a property you should first feel the absence of
ai · @ai-sdk/anthropic · @ai-sdk/reactPart XIIIThere is no interface until there is something worth streaming
OpenTelemetry · @opentelemetry/*Part XVInstrumenting a system you cannot yet describe produces spans nobody reads

This ordering is the book's argument expressed as a dependency file. If LangGraph were installed here, you would reasonably conclude it is required. It is not. It is a trade, and What a Framework Buys You is where you get to make it with the hand-written version in front of you for comparison.

One command that checks all four

scripts/doctor.sh
#!/usr/bin/env bash
set -uo pipefail
fail=0
ok()  { printf '  ok    %s\n' "$1"; }
bad() { printf '  FAIL  %s\n' "$1"; fail=1; }

[ -n "${ANTHROPIC_API_KEY:-}" ] \
  && ok 'ANTHROPIC_API_KEY is set' \
  || bad 'ANTHROPIC_API_KEY is unset'

docker compose exec -T db psql -U atlas -d atlas -tAc \
  "select 1 from pg_extension where extname='vector'" 2>/dev/null | grep -q 1 \
  && ok 'postgres up, pgvector installed' \
  || bad 'postgres unreachable or pgvector missing'

temporal operator cluster health --address localhost:7233 >/dev/null 2>&1 \
  && ok 'temporal responding on 7233' \
  || bad 'no temporal server on 7233'

exit $fail

Four checks, and each one tests the thing rather than a proxy for it: the key is exported, the extension is registered in this database, and the Temporal frontend answers a health RPC. A container that is Up and a database that can do vector math are different claims, and setup chapters that check the first one are why people lose an afternoon in Part IV.

Reading rather than building?

Every sample in this book is complete on the page. The companion lab exists so you can run the failure cases without reconstructing the surrounding harness, not to hide missing code. You can read the whole thing without running any of it. The two places where a running system genuinely changes what you learn are Part X, where the lesson is what a process dying does to an in-flight agent, and Part XIV, where the lesson is a number you have to produce yourself.

References


Next: Part I, What an Agent Actually Is, which spends its whole length on the one call you just made.

Takeaways

  • Four things: a key, a Postgres with pgvector, a Temporal dev server, and a repo with both tracks. Twenty minutes now beats four interruptions later.
  • Print usage from the very first call. Every cost question later is answered by a field that was always there.
  • The warehouse and the policy corpus share one Postgres on purpose. Splitting them early is the mistake Part IV is about.
  • temporal server start-dev without --db-filename is in-memory. Part X needs a history that survives the process, or its central claim is untestable.
  • Both language tracks share data/, so the same evaluation set can be pointed at either implementation.
  • The breim/atlas-from-agents-honestly companion repository exercises the deterministic failure contracts in both languages without a model key.
  • LangGraph, Temporal's SDK, the AI SDK, and OpenTelemetry are absent by design. Each is installed by the chapter that can name what it buys.
  • Verify the capability, not the container: check that pg_extension has vector, not that Docker says Up.

On this page