Coding Agents in the Terminal
Install a terminal coding agent and use it to build, check and publish two small data apps in Python or R, each with a three.js page. Pre-work, on your own laptop before Day 1.
No notebook: runs in your terminal Claude Code Codex Gemini CLI git GitHub CLI Python or R three.js
- Duration
- 60 minutes, pre-work; optional clinic on Day 1, 08:00–09:00
- Level
- 100 · Foundational
- Accounts and cost
- GitHub; optional: Claude Code, Codex, Gemini CLI. Claude Code and Codex need a paid plan; Gemini CLI has a free tier with a personal Google account
What You Will Build
In this module you will:
- Install and drive a terminal coding agent
- Build and check two small data apps with it
- Publish them with GitHub and GitHub Pages
Before You Start
- Your own laptop, set up as on the Before Day 1 page.
How This Module Works
You work alone, at your own pace, by giving a coding agent short prompts in a terminal. It is optional pre-work: do it at home, before Day 1. If you get stuck, bring your laptop to the optional drop-in clinic on Day 1 from 08:00 to 09:00, before the first briefing.
A coding agent is a language model that runs in a loop on your machine. It reads files, proposes edits, runs commands, reads their output, and decides what to do next. You give it a goal in plain English; it asks before it does anything risky. The software around the model (the loop, the tools, what the model is shown, the rules for what it may do without asking) is called the harness: Claude Code, Codex and Gemini CLI are three harnesses, each around its maker’s models. Module 14 builds a small harness from scratch. Here you use a finished one.
You drive it with prompts. You type only a few commands yourself: the one-time WSL 2 setup on Windows, the agent’s installer, the command that starts the agent in your project folder, and a local web server to view your pages. You ask the agent to do everything else, in short prompts: set up git and GitHub, write and run the apps, check them, commit and publish. Then you check what it did. When a step needs your password, the agent shows you the command and you run it yourself.
By the end you have a public GitHub repository of your own with two small apps, each with a 3D page that anyone can open:
- App 1, biology. A protein structure explorer for ubiquitin: its backbone in 3D, colored by a property you choose, with a contact map, a radius of gyration and a hydrophobicity score per residue.
- App 2, marketing. A customer segmentation of a synthetic shop: recency, frequency and spend per customer, k-means segments with readable names, and a 3D scatter you can rotate and hover.
Build both apps in one language, Python or R. Pick it in any Python | R tab below and every other tab on the page follows. Building the second language as well is an optional extra. Pick your agent the same way: Claude Code, Codex or Gemini CLI. The prompts you type are the same for all three.
| Minutes | Section | You end with |
|---|---|---|
| 0–10 | 1. Install a coding agent | An agent running in your project folder that answers you |
| 10–20 | 2. Git and GitHub | git and gh set up, you signed in, an empty public repository |
| 20–38 | 3. App 1: protein structure explorer | protein/ checked and committed |
| 38–55 | 4. App 2: customer segmentation | rfm/ checked and committed |
| 55–60 | 5. Publish | Two live URLs |
The minutes are an estimate, not a measurement: no one has yet timed this module on a fresh laptop. Allow about 90 minutes. The table leaves out one-time setup (WSL 2 and its restart on Windows; on a Mac, the Command Line Tools, which the first git command may offer to install), signing up for an agent plan, and the wait for the GitHub Pages build. If time runs short, App 2 is the part to postpone: publish App 1 on its own, and ask the agent to leave rfm/ out of the landing page until you build it.
You need: a laptop or desktop with macOS, Linux or Windows 10/11; a GitHub account; an account for one agent (each tab below says which, and what it costs); and Python 3.10 or later, or a recent R 4.x. On Windows, use WSL 2 (the Windows Subsystem for Linux) with Ubuntu for the whole module. This page gives no native Windows or PowerShell instructions: inside WSL 2 you follow the Linux steps, and every command works as written.
- Open Command Prompt as administrator (Start menu, type
cmd, right-click, Run as administrator) and runwsl --install. Restart when it asks. New installations get WSL 2 with Ubuntu. - Open Ubuntu from the Start menu, choose a Linux user name and password, and run
sudo apt update. - Do the rest of this module in that Ubuntu window, in your Linux home folder (
~), and follow the Linux instructions wherever a step differs by system.
The habit this module teaches is more durable than any one tool: plan, act, check the work yourself, then commit. An agent is fast and often right. It is also confidently wrong in ways you will only notice if you check one number against a source the agent did not produce.
1. Install a Coding Agent (10 min)
Pick one tab. The choice stays selected for the rest of the page. Commands were checked on 2026-10-05 against the sources named at the end of each tab; the versions current that day were Claude Code 2.1.289, Codex 0.160.1 and Gemini CLI 0.62.0. These tools change weekly: if a command fails, check the tool’s own page.
Each tab ends with the command that starts the agent in your project folder, ~/agent-apps. You work in that folder for the whole module.
Made by Anthropic. Command: claude.
Runtime. The native installer needs no Node.js and updates itself. The npm package (@anthropic-ai/claude-code) is an alternative that needs Node.js 22 or later; the docs recommend the native installer.
Install.
macOS, Linux and Windows (inside WSL 2):
curl -fsSL https://claude.ai/install.sh | bashAlternatives:
brew install --cask claude-code(Homebrew; does not auto-update).
Start it. Open a new terminal, make the project folder, and start Claude Code there in Manual mode, so it asks before every command and file edit:
mkdir -p ~/agent-apps && cd ~/agent-apps
claude --permission-mode manualSign in. The first start asks you to sign in: follow the browser prompt. If the browser does not open, press c to copy the login URL. In WSL the browser may show a code instead of returning to the terminal: paste it at the Paste code here if prompted prompt. To switch accounts later, type /login inside a session.
Account and cost. Claude Code needs a Claude Pro, Max, Team or Enterprise plan, or a Claude Console account (pay per token with prepaid credits). The free Claude plan does not include it. On 2026-10-05, claude.com/pricing listed Pro at $20 a month ($17 a month billed annually) and Max from $100 a month. This module fits comfortably in a Pro plan.
Permission prompts. A permission mode sets what Claude Code may do without asking. Recent versions start in auto mode, where a second model reviews actions instead of you. The start command above chooses Manual mode instead, so you see every prompt. Each prompt shows the exact command or file edit and offers Yes, Yes, and don’t ask again for …, and No. Press Shift+Tab to cycle modes: Manual → accept edits (file edits run without asking) → plan (reads only, proposes a plan). Esc interrupts a running step.
Quit and resume. Quit with /exit or Ctrl+D twice. Resume the last conversation in this folder with claude --continue, or pick one with claude --resume (or /resume inside a session). /diff shows the changes in your working tree.
Sources, checked 2026-10-05: code.claude.com/docs/en/setup, quickstart, authentication, permission-modes, sessions, interactive-mode, claude.com/pricing.
Made by OpenAI. Command: codex.
Runtime. The standalone installer needs no Node.js. The npm package (@openai/codex) declares Node.js 16 or later.
Install.
macOS, Linux and WSL:
curl -fsSL https://chatgpt.com/codex/install.sh | shAlternatives:
npm install -g @openai/codexorbrew install --cask codex.Windows: inside WSL 2, as on Linux. Codex’s own system requirements list Windows 11 via WSL 2.
Start it. Open a new terminal, make the project folder, and start Codex there with the “Default” preset (explained below):
mkdir -p ~/agent-apps && cd ~/agent-apps
codex --sandbox workspace-write --ask-for-approval on-requestSign in. The first start asks you to sign in: choose Sign in with ChatGPT. If the browser cannot reach the terminal (common in WSL), quit, run codex login --device-auth instead, and start Codex again. codex login status shows who is signed in. To use an API key: printenv OPENAI_API_KEY | codex login --with-api-key.
Account and cost. The README recommends signing in with a ChatGPT Plus, Pro, Business, Edu or Enterprise plan; an OpenAI API key (billed per token) also works. We could not verify plan prices from an official page on 2026-10-05: see chatgpt.com/pricing and OpenAI’s Codex in ChatGPT article.
Permission prompts. Codex combines a sandbox (what commands can touch) with an approval policy (when it asks you). The start command above is the “Default” preset: Codex reads, edits and runs commands inside the project folder without asking, and asks before it reaches the internet (for example pip install or a download) or writes outside the folder. Because edits are not prompted, ask it to show you the diff after each step, or type /diff. /permissions switches to Read Only (asks before any edit) or back. /plan switches to plan mode. Esc interrupts a running turn. Avoid --dangerously-bypass-approvals-and-sandbox (alias --yolo) on your own laptop.
Quit and resume. Quit with /quit or /exit. Resume with codex resume (a picker) or codex resume --last.
Sources, checked 2026-10-05: openai/codex README and docs/install.md (read through raw.githubusercontent.com), the CLI’s own flag and slash-command definitions in codex-rs/ at the same commit, and the npm registry entry for @openai/codex. The official site, developers.openai.com/codex, was not reachable from the build machine.
Made by Google. Command: gemini.
Runtime. Needs Node.js 20 or later (node --version). If you do not have it, see the Node.js line in the troubleshooting box below.
Install.
macOS, Linux and WSL:
npm install -g @google/gemini-cliAlternatives:
brew install gemini-cli(macOS and Linux), or try it without installing:npx @google/gemini-cli.Windows: inside WSL 2, as on Linux.
Start it. Open a new terminal, make the project folder, and start Gemini CLI there in the default approval mode, which prompts before tools that edit files or run commands:
mkdir -p ~/agent-apps && cd ~/agent-apps
gemini --approval-mode defaultSign in. The first start asks how to sign in: choose Sign in with Google and follow the browser flow. A school, company or Workspace account also needs export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID" before you start gemini. To use an API key from Google AI Studio instead: export GEMINI_API_KEY="..." before gemini (do not commit it). /auth changes the method later.
Account and cost. A personal Google account works on the free tier: up to 1,000 model requests per user per day, per the project’s quota page (the README also mentions 60 requests a minute). Google AI Pro and Ultra subscriptions raise the daily quota; an unpaid API key gives 250 requests a day on Flash models only. Read the terms and privacy notices for how each tier may use your prompts.
Permission prompts. The approval mode sets what runs without asking. The start command above chooses the default mode. Shift+Tab cycles default → auto_edit (file edits approved automatically) → plan (read-only). Avoid --approval-mode yolo, which approves everything. Ctrl+C cancels the current request.
Quit and resume. Quit with /quit (or /exit), or Ctrl+C on an empty prompt. Resume the latest session with gemini --resume, or open the session browser with /resume.
Sources, checked 2026-10-05: google-gemini/gemini-cli README and its docs/ folder (get-started/installation, get-started/authentication, cli/cli-reference, cli/session-management, reference/commands, reference/keyboard-shortcuts, resources/quota-and-pricing), read through a clone of the repository; npm registry entry for @google/gemini-cli. The official site, geminicli.com, was not reachable from the build machine.
No subscription? Claude Code and Codex need a paid plan or an API key billed per token. Gemini CLI has a free tier with a personal Google account. If you have none of these, use Gemini CLI’s free tier, or pair with someone who has an agent and take turns writing the prompts.
Check that it works. Your agent is running in the empty folder ~/agent-apps. Type this prompt:
What is in this folder, and which versions of Python, R, git and the GitHub CLI (gh)
are installed? Do not create or change any files.
What you should see: the agent asks before running at least one command, such as python3 --version (Codex may run read-only commands without asking), then reports what it found. The folder is still empty. Note anything it reports as missing: section 2 starts there. If your language is missing or too old (you need Python 3.10 or later, or a recent R 4.x), ask the agent to install it, with the same rule as in section 2: anything that needs your password, you run yourself. Keep the agent running.
Troubleshooting: macOS
command not foundafter installing. Open a new terminal window first. The native installers for Claude Code and Codex put the command in~/.local/bin; if it is still missing, add that folder to your PATH:echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc, then open a new terminal.- Claude Code does not start properly. Run
claude doctor: it checks the installation and reports what is wrong. - No Homebrew. Install it from brew.sh, or use the
curlinstallers, which do not need it. - Node.js (Gemini CLI only):
brew install node, thennode --version. - Corporate proxy. Set
HTTPS_PROXY=http://proxy.example.com:8080in your shell before starting the tool. If your company inspects TLS, install its root certificate in the system keychain; Node-based tools such as Gemini CLI also readNODE_EXTRA_CA_CERTS=/path/to/corporate-ca.crt.
Troubleshooting: Linux
command not foundafter installing. Open a new terminal. If~/.local/binis not on your PATH, add it:echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc.- Claude Code does not start properly. Run
claude doctor: it checks the installation and reports what is wrong. - Node.js too old (Gemini CLI needs 20 or later; distribution packages are often older). Install the current LTS release with nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash, open a new terminal, thennvm install --lts. The LTS line was Node.js 24 on 2026-10-05. EACCESfromnpm install -g. Do not usesudo npm install -g. Installing Node.js with nvm, as above, puts global packages in your home folder and avoids the error.- Corporate proxy. As for macOS:
HTTPS_PROXY, andNODE_EXTRA_CA_CERTSfor Node-based tools if your company inspects TLS.
Troubleshooting: Windows and WSL
- Install WSL 2. See the box at the top of the page: Command Prompt as administrator,
wsl --install, restart, then open “Ubuntu” from the Start menu and run every command on this page there. - Work in the Linux home folder (
~), not under/mnt/c/. Microsoft recommends keeping files in the Linux file system when you work from a Linux command line; it is much faster. exec: node: not found, or npm reports the wrong platform. WSL is picking up the Windows copy of Node.js (which nodeprints a path starting with/mnt/c/). Install Node.js inside WSL with nvm, as in the Linux box.Exec format errorwhen runningclaude: you are on WSL 1. Convert withwsl --set-version Ubuntu 2in Command Prompt (as administrator).- Browser sign-in does not come back to the terminal. Use the fallback each tool offers: paste the code (Claude Code),
codex login --device-auth(Codex), or copy the printed URL into your Windows browser. - Local web pages. A server started in WSL (section 3, step 7) opens from your Windows browser at
http://localhost:8000.
Cost and Rate Limits
- Subscriptions (Claude Pro or Max, ChatGPT plans, Google AI Pro or Ultra) include agent use up to a usage limit that resets over time. If you hit it, the tool says so; wait, or switch to the free Gemini tier for the rest of the module.
- API keys are billed per token, and an agent sends the files it reads with every turn. Anthropic’s cost page reports an average of about $13 per developer per active day across enterprise deployments; this module uses a small fraction of a day, but set a spend limit in your provider console before you start.
- Each prompt is several model requests. The agent reads, edits and runs commands in a loop, and each step is a request. On free tiers with a per-minute limit, a burst of steps can hit it: wait a minute and continue.
- Keep the context small. Start a new session for App 2 instead of continuing App 1’s conversation.
2. Git and GitHub (10 min)
GitHub Pages publishes a website from a GitHub repository, so your apps need a repository first. git tracks the files. The GitHub CLI (gh) signs you in to GitHub, creates the repository and turns on Pages. From here on you type prompts, not commands: the agent runs the commands and asks you first. (Codex asks before most of the commands in this section, because they use the network or write outside the project folder.)
The agent cannot type your password, and it should never see one. When a command needs sudo (likely on Linux and WSL when software is installed), the prompts below tell the agent to show you the command instead of running it. Run it yourself in a second terminal (open one, then cd ~/agent-apps), then tell the agent it is done. Never paste a password, token or API key into a prompt.
1. Install what is missing.
Install whichever of git and the GitHub CLI (gh) is missing, following each tool's
official instructions for this system. If a step needs sudo or my password, do not
run it: show me the command and I will run it in a second terminal. Finish by
showing me git --version and gh --version.
What you should see: a permission prompt for each install command; read it before you approve it. On macOS the agent will usually use Homebrew. The first git command on a Mac may open a dialog offering to install the Command Line Tools: accept, wait until it finishes (it takes several minutes), then tell the agent to continue. On Ubuntu and WSL, installing gh usually needs sudo, so the agent hands you a few lines to run. Read them before you run them, and ask the agent to explain any line you do not understand. At the end, both tools print a version. If both were already installed, nothing changes.
2. Tell git who you are. Put your own name in place of “Your Name”, and the email address of your GitHub account in place of the example.
Set my global git user.name to "Your Name" and user.email to "you@example.com",
unless they are already set, and show me the result.
What you should see: your name and email. Every commit you make from now on carries them.
3. Sign in to GitHub. You sign in in your browser. The agent only starts the sign-in.
Sign me in to GitHub: run gh auth login --web for github.com over HTTPS, show me
the one-time code it prints, and wait while I finish in the browser. Then run
gh auth setup-git and gh auth status. Never print my token or put it in a file.
What you should see: a line like ! First copy your one-time code: ABCD-1234 and the address https://github.com/login/device. Open that address, sign in to GitHub, enter the code and authorize the GitHub CLI. gh auth setup-git lets git push with the same sign-in. gh auth status then reports that you are logged in to github.com under your user name. It must not show your full token. gh keeps the token in your system’s credential store. Where there is none (common on Linux and WSL), gh itself writes it to a plain text file in your home folder, outside the project; gh auth status says where.
If no code appears within half a minute, the agent may be holding the output until the command ends, which it never does on its own. Interrupt the agent (Esc; Ctrl+C in Gemini CLI). Run gh auth login --web yourself in your second terminal and finish in the browser. Then ask the agent: Run gh auth setup-git and gh auth status.
4. Create the repository.
Make this folder a git repository on a branch named main, with a one-line README.md
as the first commit. Then create a public GitHub repository named agent-apps from
this folder with gh and push that commit; show me the commands and wait for my OK
before you run them. Finish by giving me the repository's URL.
What you should see: a short list of commands, before anything runs, ending in something like gh repo create agent-apps --public --source=. --push. Check that it says --public: on a free GitHub account, Pages works only for public repositories. After your OK, the agent gives you https://github.com/YOUR-USERNAME/agent-apps. Open it and you see the README.
Sources: gh auth login (the --web flag, the one-time code it prints, the git credential setup it skips when it is not run interactively, and the fallback to a plain text file) and gh repo create (--public, --source, and --push, which needs --source), from their definitions in cli/cli (pkg/cmd/auth/login/login.go, pkg/cmd/auth/shared/login_flow.go, internal/authflow/flow.go, pkg/cmd/repo/create/create.go), read through raw.githubusercontent.com on 2026-10-06. Pages availability from the GitHub Docs source (github/docs, data/reusables/gated-features/pages.md), checked 2026-10-05, when docs.github.com was not reachable from the build machine.
Working Habits for Both Apps
Read this once; it applies to every step below.
- Short prompts, with the rules inside. Each prompt below says what to do, what not to touch, and, where it matters, what “done” looks like. Anything that needs your password or leaves your laptop (a push, a publish) comes with “show me first”. Put that in the prompt itself: the three agents’ permission settings differ, but every agent can follow that sentence.
- Plan first. The first prompt of each app asks for a plan and forbids file changes. Use your tool’s plan mode for it if you like (
Shift+Tabin Claude Code and Gemini CLI,/planin Codex). - Review the diff before you accept it. When a permission prompt shows a file edit, read it. After each step, ask “Show me the diff of what you changed” (Claude Code and Codex also have
/diff). If you do not understand a change, ask the agent to explain it before you continue. - Approve commands you understand. Downloads and package installs (
pip install,install.packages) will ask for permission; that is expected. Decline anything that deletes files outside the project or touches your shell configuration, and any push the agent has not shown you first. - Never paste secrets. Do not paste API keys, tokens or passwords into a prompt, and do not keep a
.envfile in the project. Everything the agent reads is sent to its provider. Do not ask it to rungh auth token. If a key ever lands in a commit, revoke it with the provider; deleting the commit is not enough. - Check against numbers the agent did not produce. Each app has a check step. The agent writes a short, separate check, and you compare its output with numbers printed on this page, which come from the reference solutions, not from your agent. That is the step that catches mistakes.
- Commit when a step works. A commit is an undo point: if a later step goes wrong, ask the agent to restore the file from the last commit.
- Run servers in your own terminal. A local web server never stops on its own; if the agent starts one, its turn can hang. Start servers yourself in the second terminal. It is the only command you type while you build the apps.
The repository will look like this when you are done:
agent-apps/
├── README.md the first commit (section 2)
├── index.html links to the two apps (section 5)
├── .nojekyll tells GitHub Pages to serve the files as they are
├── .gitignore
├── protein/ App 1: 1ubq.pdb, analyze.py or analyze.R, data.json, index.html
└── rfm/ App 2: purchases_v1.csv.gz, analyze.py or analyze.R, data.json, index.html
The prompts are numbered boxes: type each one into your agent, changing only what the text around it tells you to, and read “What you should see” before you move on. Where Python and R differ, a tab shows both.
3. App 1: Protein Structure Explorer (18 min)
Proteins fold into compact 3D shapes, and the shape decides what a protein does. Ubiquitin is a small, well-studied protein; its structure, entry 1UBQ in the Protein Data Bank (PDB), was solved by X-ray crystallography. A PDB file lists every atom with its x, y and z coordinates in ångströms (Å; 1 Å = 0.1 nm). Each amino acid (residue) has one central carbon atom, the C-alpha; joining the C-alphas in chain order traces the backbone.
You will compute three things:
- Radius of gyration R_g: how compact the fold is. For the N C-alpha positions \mathbf{r}_i with mean \bar{\mathbf{r}}, R_g = \sqrt{\tfrac{1}{N}\sum_i \lVert \mathbf{r}_i - \bar{\mathbf{r}} \rVert^2}.
- Contact map: which pairs of residues (i, j) have C-alpha atoms at most 8 Å apart.
- Hydrophobicity: the Kyte–Doolittle scale value of each residue’s amino acid (Kyte and Doolittle, 1982), positive for water-avoiding residues. One value per residue, no window averaging.
1. Plan.
Plan only: do not create or change any files yet.
In protein/, I want analyze.py. It downloads ubiquitin, PDB entry 1UBQ, from
https://files.rcsb.org/download/1UBQ.pdb to protein/1ubq.pdb (if that fails, from
https://raw.githubusercontent.com/project-delphi/nlp-llms/main/data/1ubq.pdb).
It parses the C-alpha atoms with NumPy, using the fixed PDB columns (no Biopython),
and computes:
- the radius of gyration of the C-alpha atoms, in angstroms;
- a contact map that counts every pair of residues i < j whose C-alpha atoms are
at most 8 angstroms apart;
- the Kyte-Doolittle hydrophobicity of each residue, no window averaging.
It prints the number of residues, the radius of gyration and the number of
contacts, and writes protein/data.json with one record per residue (number,
name, x, y, z, hydrophobicity, number of contacts), the list of contact pairs,
and the summary numbers.
Use a virtual environment in .venv and add .venv/ to .gitignore. Tell me the
files you will create, the functions, and how we can check each number.
Ask me anything that is unclear.
Plan only: do not create or change any files yet.
In protein/, I want analyze.R. It downloads ubiquitin, PDB entry 1UBQ, from
https://files.rcsb.org/download/1UBQ.pdb to protein/1ubq.pdb (if that fails, from
https://raw.githubusercontent.com/project-delphi/nlp-llms/main/data/1ubq.pdb).
It reads the structure with bio3d, selects the C-alpha atoms, and computes:
- the radius of gyration of the C-alpha atoms, in angstroms;
- a contact map that counts every pair of residues i < j whose C-alpha atoms are
at most 8 angstroms apart;
- the Kyte-Doolittle hydrophobicity of each residue, no window averaging.
It prints the number of residues, the radius of gyration and the number of
contacts, and writes protein/data.json with jsonlite: one record per residue
(number, name, x, y, z, hydrophobicity, number of contacts), the list of contact
pairs, and the summary numbers.
Install missing packages with install.packages(). Add .Rhistory and .RData to
.gitignore. Tell me the files you will create, the functions, and how we can
check each number. Ask me anything that is unclear.
This prompt is longer than the others because it is the specification: the data source, the units and the definitions decide the numbers you check later.
This prompt contains a deliberate flaw in the contact definition. Leave it in. You will catch it in step 4 and make the agent fix it in step 5.
What you should see: a short plan naming one script, data.json, the libraries, and a way to check each number. No files change. If the agent starts writing files anyway, interrupt it and repeat “plan only”.
2. Write the analysis.
The plan looks good. Implement it now. Keep the script short and readable,
with the Kyte-Doolittle values in one dictionary (or named vector) at the top.
What you should see: permission prompts for each new file (Claude Code in Manual mode, Gemini CLI in default mode) and for installing packages. Codex writes inside the folder without asking, so ask it for the diff afterwards. Check that the hydrophobicity table has 20 entries, one per standard amino acid.
3. Run it.
Run the script and show me what it prints.
What you should see: the script downloads 1ubq.pdb (another permission prompt for the network) and prints 76 residues, a radius of gyration near 11.49 Å, and 326 contacts. protein/data.json exists. If the RCSB download fails, the script should fall back to the workshop copy; say so to the agent if it did not.
4. Check the numbers. The agent writes this check, but it must not reuse its own script, and you compare the result with numbers from this page, not with anything the agent says.
Without using your analysis script, write a short one-off check in awk, Python or R
that reads protein/1ubq.pdb directly. It should print the number of C-alpha atoms,
the radius of gyration, and the number of C-alpha pairs at most 8 angstroms apart,
counted separately for |i - j| = 1, |i - j| = 2 and |i - j| >= 3. Show me the code,
then run it, and do not save it in the project.
What you should see: read the code first. It should take the C-alpha (CA) atom lines from the PDB file itself, not from data.json, and compute R_g by the formula above. Then compare its output with these numbers from the reference solutions:
| Quantity | Expected |
|---|---|
| C-alpha atoms | 76 |
| Radius of gyration | 11.49 Å |
| Pairs within 8 Å, \lvert i - j \rvert = 1 | 75 |
| Pairs within 8 Å, \lvert i - j \rvert = 2 | 74 |
| Pairs within 8 Å, \lvert i - j \rvert \ge 3 | 177 |
| All pairs within 8 Å | 326 |
The atom count, R_g to two decimals, and the total of 326 match the agent’s script from step 3. Now look at the rows for \lvert i - j \rvert = 1 and 2. A chain of N = 76 residues has exactly 75 neighbor pairs (i, i{+}1) and 74 pairs (i, i{+}2), and the check finds every one of them within 8 Å. Together they are 2N - 3 = 149 pairs, exactly the difference between the agent’s 326 and the 177 pairs with \lvert i - j \rvert \ge 3. The agent’s contact count includes all of them. That is the flaw. If the check disagrees with the table in some other way, either the check or the script is wrong: ask the agent to show you the lines in each that compute the number, and find out which.
5. Make the agent find and fix its mistake. Do not tell it the answer; make it reason.
Look at the check's pair counts by |i - j|. Is part of your contact count true for
every protein, whatever its shape? Explain why and propose a better definition, then
wait for my OK before changing the code.
What you should see: the agent notices that consecutive C-alpha atoms are always about 3.8 Å apart (the fixed geometry of the peptide bond), so pairs one apart, and pairs two apart (at most two such steps, under 8 Å), are “in contact” whatever the fold. They say nothing about how the chain packs. It should propose counting only pairs with |i - j| \ge 3. Approve that, then:
Make that change, add an assertion that no pair with |i - j| < 3 is ever counted,
and run the script again.
The new count is 177, equal to the \lvert i - j \rvert \ge 3 row of your check.
R Only: If Your Count Was Already Right
bio3d’s cmap() function excludes near neighbors by default (its scut argument is 3) and uses a 4 Å cutoff unless you pass dcut = 8. If your agent used cmap(), the flaw may not have appeared; ask it which dcut and scut it passed, and make sure the cutoff is 8 Å. That is the same lesson: a default you did not choose changed the answer.
6. Add the three.js page. three.js is a JavaScript library for 3D graphics in the browser. An import map lets a plain HTML page load it from a CDN (content delivery network) with no build step.
Create protein/index.html: one self-contained page that loads data.json with
fetch() and draws the C-alpha backbone in 3D with three.js
0.186.1. Load it from cdn.jsdelivr.net with an import map:
"three" -> https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js
"three/addons/" -> https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/
Requirements:
- a small sphere per residue and a line or tube through them in chain order;
- OrbitControls to rotate and zoom;
- a dropdown that recolors residues by hydrophobicity, number of contacts, or
position in the chain, with a small color legend;
- clicking a residue shows its number, name, hydrophobicity and contact count in
a side panel;
- a checkbox that draws the contacts as thin lines;
- the radius of gyration and the contact count in the header.
No other libraries. Keep it under 300 lines.
What you should see: one new file. Skim it: the import map should name version 0.186.1 exactly, and the page should read data.json, not a copy of the numbers pasted into the HTML.
7. View it locally. Browsers refuse to fetch() a file from a page opened straight from disk, so serve the folder over HTTP. This one you type yourself (see “Run servers in your own terminal” above). In your second terminal, from ~/agent-apps:
python3 -m http.server 8000Rscript -e 'install.packages("servr", repos = "https://cloud.r-project.org")' # once
Rscript -e 'servr::httd(".", port = 8000)'Open http://localhost:8000/protein/. Stop the server later with Ctrl+C.
What you should see: the backbone in 3D. Drag to rotate, scroll to zoom. Color by hydrophobicity: the strongly hydrophobic residues should mostly sit inside the fold. Click a residue and compare its contact count with data.json. If the page is blank, open the browser’s developer console (F12, or Cmd+Option+I on a Mac), copy the first error, and give it to the agent.
8. Commit.
Show me what you will commit and wait for my OK. Then commit protein/ and .gitignore
with the message "Protein structure explorer for 1UBQ", and do not push yet.
What you should see: a list with protein/1ubq.pdb, the script, protein/data.json, protein/index.html and .gitignore. It must not include .venv/ (Python) or .Rhistory and .RData (R). If anything else is in it, say no and ask why.
4. App 2: Customer Segmentation (17 min)
Start a new agent session in ~/agent-apps (quit and start again), so App 1’s conversation does not fill the context.
A shop wants to treat customers differently: thank the best ones, win back those drifting away. RFM describes each customer with three numbers:
- Recency (R): days since the customer’s last purchase, counted from a reference date. Lower is better.
- Frequency (F): how many separate orders (invoices) the customer placed.
- Monetary (M): total spend, the sum of quantity × unit price over all their order lines.
The data is the workshop’s synthetic purchases table, purchases_v1.csv.gz: one row per invoice line, with columns invoice_id, invoice_date, customer_id, country, stock_code, quantity, unit_price, covering 2024-01-01 to 2025-12-31. No real customer is in it.
All three numbers are right-skewed: a few customers buy far more than the rest. So you take the log of each, \log(1 + x), then standardize each column to mean 0 and standard deviation 1 (a z-score), so that no column dominates the distances. k-means then splits the customers into k groups around k centers (centroids). To choose k by a stated rule rather than by eye, you use the mean silhouette: for each customer, how much closer it is to its own group than to the next one, from −1 to 1; higher is better.
1. Plan.
Plan only: do not create or change any files yet.
In rfm/, I want analyze.py (pandas, NumPy, scikit-learn) that:
1. Downloads https://raw.githubusercontent.com/project-delphi/nlp-llms/main/data/purchases_v1.csv.gz to
rfm/purchases_v1.csv.gz. Columns: invoice_id, invoice_date, customer_id,
country, stock_code, quantity, unit_price. One row per invoice line.
2. Computes per customer: recency = days since the last purchase, as of today;
frequency = number of distinct invoices; monetary = sum of quantity * unit_price.
3. Transforms each column with log1p, then standardizes to mean 0 and
population standard deviation 1.
4. Runs k-means (20 restarts, random seed 0) for k = 3, 4, 5, 6 and keeps the k
with the highest mean silhouette score. Prints the silhouette for each k.
5. Names each segment from its standardized centroid (zR, zF, zM) with this rule:
recent means zR < 0; value = (zF + zM) / 2.
recent and value > 0.75 -> "Champions"
recent and value > 0 -> "Loyal"
recent otherwise -> "New or promising"
not recent and value > 0 -> "At risk"
not recent otherwise -> "Lost"
If two segments get the same name, add " (2)" to the one with the lower value.
6. Prints each segment's name, size and centroid in original units, and writes
rfm/data.json: per customer (id, R, F, M, the three z-scores, segment) and per
segment (name, size, color, centroid).
Use the .venv from App 1. Tell me the functions and how we can check the numbers.
Plan only: do not create or change any files yet.
In rfm/, I want analyze.R (base R, the cluster package for silhouettes, jsonlite)
that:
1. Downloads https://raw.githubusercontent.com/project-delphi/nlp-llms/main/data/purchases_v1.csv.gz to
rfm/purchases_v1.csv.gz. Columns: invoice_id, invoice_date, customer_id,
country, stock_code, quantity, unit_price. One row per invoice line.
2. Computes per customer: recency = days since the last purchase, as of today;
frequency = number of distinct invoices; monetary = sum of quantity * unit_price.
3. Transforms each column with log1p, then standardizes to mean 0 and
population standard deviation 1 (divide by n, not n - 1).
4. Runs kmeans (nstart = 20, set.seed(0)) for k = 3, 4, 5, 6 and keeps the k with
the highest mean silhouette width. Prints the silhouette for each k.
5. Names each segment from its standardized centroid (zR, zF, zM) with this rule:
recent means zR < 0; value = (zF + zM) / 2.
recent and value > 0.75 -> "Champions"
recent and value > 0 -> "Loyal"
recent otherwise -> "New or promising"
not recent and value > 0 -> "At risk"
not recent otherwise -> "Lost"
If two segments get the same name, add " (2)" to the one with the lower value.
6. Prints each segment's name, size and centroid in original units, and writes
rfm/data.json: per customer (id, R, F, M, the three z-scores, segment) and per
segment (name, size, color, centroid).
Tell me the functions and how we can check the numbers.
Again the plan prompt is the specification: the seed, the number of restarts, the kind of standard deviation and the naming rule decide which segments you get.
Step 2 of this prompt contains a deliberate flaw: “as of today”. Leave it in; you will catch it in step 4.
What you should see: a plan with one script and the functions above, and no file changes. A good agent may already question “as of today” for a historical dataset. If it does, tell it to keep the instruction for now.
2. Write the analysis.
Implement the plan.
What you should see: one new script. In the diff (ask for it if your agent did not show one), find the line that computes recency, and the line that counts frequency: is it counting distinct invoices, or rows?
3. Run it.
Run the script and show me what it prints, then show me the first five rows
of the RFM table.
What you should see: 3,000 customers, a silhouette score for each k from 3 to 6, the chosen k, and one line per segment. Keep the first five rows on screen for step 4.
4. Check one customer. The agent counts from the raw file with a separate check; you do the date arithmetic yourself.
Without using your analysis script, write a short one-off check that reads
rfm/purchases_v1.csv.gz directly and prints, for the first customer in the RFM
table, the number of distinct invoices, the total spend and the last purchase date.
Show me the code, then run it, and do not save it in the project.
What you should see: read the code first. It should keep only that customer’s rows, count distinct invoice_id values (not rows), and add up quantity × unit price. Invoices and spend match the agent’s F and M. Then work out recency by hand, without the agent: the number of days from that last purchase date to 2026-01-01, the day after the data end (a purchase on 2025-12-31 has recency 1).
Recency does not match: the agent’s value is larger by the number of days between 2026-01-01 and the day you ran the script. It measured from the clock, as the prompt told it to. For a historical dataset that is wrong: every customer looks long gone, and because the scores are log-transformed, the shift also changes the clusters. (If recency does match, your agent quietly ignored “as of today”. Run step 5 anyway: an agent that departs from your spec without saying so is a finding too.)
5. Make the agent find and fix its mistake.
My hand calculation disagrees with your recency for the first customer; the data end
on 2025-12-31. What reference date does your recency use, and what does that do to R,
to the log transform and to the clusters? Then fix it: use the day after the last
invoice in the file, never the clock, assert that the smallest recency is at least 1,
and run the script again.
What you should see: the reference date is now 2026-01-01, the smallest recency is 1 day, and your hand-computed recency matches. The chosen k and the segments may change. With the reference solutions they are:
k = 4; segments and sizes: Lost 950, At risk 789, New or promising 644, Champions 617.
k = 4; segments and sizes: Lost 949, At risk 790, New or promising 644, Champions 617 (base kmeans() and scikit-learn can differ by a customer or two).
k-means starts from random centers, and Python and R use different random number generators, so the two languages need not agree exactly. Your agent’s code should match the reference for your language if it follows the rule in step 1.
6. Add the three.js page.
Create rfm/index.html: one self-contained page that loads data.json with fetch()
and draws a 3D scatter with three.js 0.186.1, loaded from
cdn.jsdelivr.net with the same import map as protein/index.html.
Requirements:
- one point per customer at its standardized (zR, zF, zM), with labeled axes
"Recency (lower = more recent)", "Frequency" and "Monetary";
- color by segment, using the colors in data.json;
- hovering a point shows that customer's id and raw R (days), F (invoices) and
M (spend) in a tooltip;
- a side panel lists the segments with color, name, size and centroid in raw
units; clicking a segment highlights its points and dims the rest;
- OrbitControls to rotate and zoom.
No other libraries. Keep it under 300 lines.
What you should see: one new file with the same import map version as the protein page.
7. View it locally. Start the server from section 3, step 7, if it is not running, and open http://localhost:8000/rfm/.
What you should see: a cloud of points in a few colored groups. Hover over a point from the “Champions” segment: recent, many invoices, high spend. Hover over a “Lost” one. Click each segment in the panel. Then hover over the customer you checked in step 4 (its id is in the tooltip) and confirm its three numbers once more.
8. Commit.
Show me what you will commit and wait for my OK. Then commit rfm/ with the message
"Customer segmentation with RFM and k-means", and do not push yet.
What you should see: a list with rfm/purchases_v1.csv.gz, the script, rfm/data.json and rfm/index.html, and nothing else.
Optional: A Real Retail Dataset
The UCI Machine Learning Repository’s Online Retail II dataset holds two years of real transactions from a UK online shop, with the same kind of columns. Real data brings real problems: cancelled invoices (invoice numbers starting with “C”, negative quantities), missing customer IDs, and a different date range, so the reference date changes. Point your agent at it and ask it to adapt analyze.py or analyze.R, handling each of those explicitly.
Not yet cleared for the workshop: we have not verified the dataset’s license (the UCI page was not reachable from the build machine on 2026-10-05). Check the license on the dataset page before you download it, and do not commit the data to a public repository unless the license allows it.
5. Publish (5 min)
1. Add a landing page.
Create index.html at the repository root: a short page titled "Agent apps"
that links to protein/ and rfm/ with one sentence about each. Also create an
empty file named .nojekyll at the root.
.nojekyll tells GitHub Pages to publish the files exactly as they are, without running its Jekyll site builder.
2. Commit and push.
Commit index.html and .nojekyll with the message "Landing page". Then show me
what git push will send, and wait for my OK before you push.
What you should see: three commits (App 1, App 2 and the landing page) going to main on GitHub. After your OK the agent pushes, and the repository’s page on GitHub shows protein/, rfm/ and index.html.
3. Turn on GitHub Pages.
Turn on GitHub Pages for this repository with gh api, publishing the main branch
from the root folder. Show me the exact command and wait for my OK, then tell me
the build status and the site URL.
What you should see: a command like gh api --method POST "repos/{owner}/{repo}/pages" -f "source[branch]=main" -f "source[path]=/", for you to read, not to type. gh api calls GitHub’s REST API as you; it fills in {owner} and {repo} from the repository in the current folder, and source[branch]=... sends the nested source object the endpoint expects. The allowed paths are / and /docs. If the agent proposes a different branch or path, say no and ask why.
4. Wait for the build. It usually takes a minute or two. building means wait, built means done, and errored means look at the Actions tab of your repository. Until it says built, ask again:
Check the GitHub Pages build status again.
5. Open the live URLs. The site is at https://YOUR-USERNAME.github.io/agent-apps/, the URL the agent gave you in step 3. Open it, then …/protein/ and …/rfm/.
What you should see: the same two pages you saw locally, now public. If a page is blank but worked locally, check the browser console: the usual cause is a path that starts with / (for example /data.json), which points at the root of github.io rather than your repository. Ask the agent to make every path relative.
Sources, checked 2026-10-05: the “Create a GitHub Pages site” and “Get a GitHub Pages site” endpoints (POST and GET /repos/{owner}/{repo}/pages, body source.branch and source.path, status values built, building, errored) from GitHub’s official OpenAPI description (github/rest-api-description, api.github.com.json, version 1.1.4); the {owner}/{repo} placeholders and the key[subkey]=value syntax from gh api --help (gh 2.89.0); .nojekyll from the GitHub Docs source. docs.github.com itself was not reachable from the build machine.
Summary
- A coding agent is a model in a loop with tools and a permission policy. You choose how much it may do without asking; for unfamiliar work, choose “ask”.
- You drove the work with short prompts that state the goal, the limits and, where it matters, what done looks like. For anything that needs your password or leaves your laptop, the prompt says “show me first”.
- Plan first, review every diff, and commit each step that works.
- The agent did what the prompt said, including its flaws. The two mistakes you fixed, trivially true contacts and a clock-based reference date, were caught only because you compared the agent’s numbers with numbers it had not produced: the page’s table and your own date arithmetic.
- Prompts carry the spec: units, conventions (population or sample standard deviation, which pairs count), seeds and the rule for choosing k. A rule you state is a result anyone can reproduce.
Common misconceptions.
- “The agent ran it, so it is right.” Running shows the code executes, not that it computes the right thing.
- “The agent checked its own work, so it is checked.” A check the agent writes is only as good as what you compare it with. Here that was the page’s numbers and a date you worked out yourself.
- “Auto-approve saves time.” It saves seconds per step and costs you the moment you would have caught a bad command.
- “The same prompt gives the same code.” It does not; two runs, or two agents, can differ. Your checks are what make the result reproducible.
The reference solutions for both apps, in both languages, are in agents-intro/ in the workshop repository. Look at them after you finish, not before.