This folder contains everything you need to run the Ralph agentic loop and build the Executive Assistant app from scratch — one user story at a time, by your own Claude Code instance, with no human in the inner loop.
It contains a simplified version of the Ralph Loop designed to work with Claude Code only. Visit the Ralph Loop Repository to learn more or use the original version with AMP or other Models/LLM Ecosystems. Read the original Ralph Loop post here
| File | Role |
|---|---|
ralph.sh |
The bash loop that spawns one fresh Claude Code instance per iteration |
ITERATION.md |
The per-iteration prompt — fed to Claude Code as input each iteration |
prd.json |
The executable task list — 24 user stories, all passes: false, ready to be built |
progress.txt |
Empty append-only log; Ralph writes learnings here between iterations |
CLAUDE.md.template |
A starter project-root CLAUDE.md (rename and place at the repo root before you run) |
seed/ |
Python utility + fixture data that populates your test Google account with fake emails and calendar events |
README.md |
This file |
A single-screen dashboard over your test Google account's Calendar and Gmail, with capabilities grouped into three categories:
- Schedule — Today's Schedule, Next Free Block, Tomorrow Preview, Back-to-Back Alert, Week at a Glance, Meeting Context
- Inbox — Needs a Reply (with triage tags), VIP Watch, Noise Report, Draft Replies
- Smart (LLM-backed) — Action Items, Morning Brief, Day in a Sentence, What to Tackle First
The integration is live REST via the googleapis SDK. Drafts are the only thing the app writes back to Gmail.
- Node.js 22+ — check with
node -v. Install or upgrade from nodejs.org or via nvm. - Python 3.10+ (for the seeder only) — check with
python3 --version. - Claude Code CLI:
npm install -g @anthropic-ai/claude-code(then runclaudeonce and complete sign-in). - git — check with
git --version.
Never run this against a real account. Create a fresh Gmail account just for this build — the seeder injects fake emails and calendar events, and the EA will create real Gmail drafts in it.
The EA reads Gmail and Calendar through Google's REST APIs, which means you need a Google Cloud project that owns the API credentials. GCP is separate from your Gmail account — Gmail gives you a mailbox; GCP gives you a developer console where you create OAuth clients and turn APIs on/off.
- Go to console.cloud.google.com and sign in with the throwaway Google account from step 2 (important — the credentials and the mailbox must belong to the same account).
- If this is your first time, Google will prompt you to accept the GCP Terms of Service and pick a country. Do that.
- GCP requires a billing account on the parent organization for some services, but this project only uses free-tier APIs (Gmail + Calendar read/draft). You should not need to add a credit card. If Google nags you for one to "activate" something, the Gmail and Calendar APIs themselves do not require it — skip that step.
Note: Google rearranges Cloud Console menus every few months. The conceptual steps below stay the same, but the exact button labels or nav paths may have shifted slightly by the time you read this. If something is named differently, look for the closest match — the screenshots in Google's official docs at developers.google.com/workspace/guides/create-credentials are kept current.
- Create the project:
- In the Cloud Console top bar, click the project selector (says "Select a project") → NEW PROJECT.
- Project name: anything memorable, e.g.
ea-build. - Organization: "No organization" is fine for a personal Gmail account.
- Click CREATE, then wait ~10 seconds for the project to provision.
- Make sure the top-bar project selector now shows your new project — everything you do next happens inside that project.
- Enable the Gmail API:
- Left nav (☰) → APIs & Services → Library.
- Search for Gmail API → click the result → click ENABLE.
- Wait for the enable to complete (you land on the Gmail API's overview page).
- Enable the Google Calendar API:
- Back to APIs & Services → Library.
- Search for Google Calendar API → click the result → click ENABLE.
If you ever see a "this API is not enabled" error at runtime, come back here — the API was probably enabled in a different project than the one your OAuth client lives in.
Before you can create OAuth credentials, Google requires you to declare what your app is and who can use it.
- APIs & Services → OAuth consent screen.
- User type: External → CREATE. (External just means "any Google account can consent" — Internal is only for Workspace orgs.)
- App information page — fill in only the required fields:
- App name: e.g.
Executive Assistant (local) - User support email: your throwaway account address
- Developer contact email (at the bottom): your throwaway account address
- Leave the optional logo / homepage / domains fields empty.
- Click SAVE AND CONTINUE.
- App name: e.g.
- Scopes page — click SAVE AND CONTINUE without adding any. (The scopes are requested by the seeder at OAuth time, not declared here. Declaring them up front means Google will require app verification, which you don't want for local-only use.)
- Test users page — click + ADD USERS and add your throwaway Gmail address. Click SAVE AND CONTINUE.
- Summary page — review and click BACK TO DASHBOARD.
- Confirm Publishing status: Testing. This is what lets your throwaway test user authorize the app without Google verification. Leave it in Testing forever — there is no reason to publish.
- APIs & Services → Credentials → + CREATE CREDENTIALS → OAuth client ID.
- Application type: Desktop app. (Desktop app is what the seeder's local OAuth loopback flow expects — do NOT pick Web application or anything else.)
- Name: anything, e.g.
ea-seeder. - Click CREATE. A dialog shows the new client ID and secret — click DOWNLOAD JSON. If you miss the dialog, you can re-download from the Credentials list (download icon ⬇ on the right side of your client's row).
- Save the downloaded JSON somewhere temporary for now — you'll move it into
seed/credentials.jsonin Project setup step 2. - Treat this file like a password. It contains your OAuth client secret. The
.gitignorestep in Project setup step 4 makes sure git ignores it; do not commit it.
The four LLM-backed capabilities (Draft Replies, Action Items, Day in a Sentence, What to Tackle First) need an API key. Get one at console.anthropic.com → Settings → API Keys → Create Key. Copy it — you'll paste it into .env in step 4 of project setup. The other 10 capabilities work without it.
Before you start: these steps assume you already have this folder on your machine. Everywhere you see
your-test-account@gmail.comorsk-ant-..., replace with your actual address / API key.
mkdir ea-build && cd ea-build
git init -b main # on older git (< 2.28) use: git init && git symbolic-ref HEAD refs/heads/mainRalph commits after every passing story — without git, the loop can't function.
Move your OAuth client JSON (downloaded in Prerequisites step 6) into seed/credentials.json. For example, if your browser saved it to Downloads:
mv ~/Downloads/client_secret_*.json seed/credentials.json(seed/token.json will appear automatically after the first seeder run in step 5.)
You should now have:
ea-build/
├── loop/
│ ├── ralph.sh
│ ├── ITERATION.md
│ ├── prd.json
│ ├── progress.txt
│ └── CLAUDE.md.template
└── seed/
├── seed.py
├── requirements.txt
├── credentials.json ← you provided this (secret!)
├── emails/ ← fixture emails
└── calendar/ ← fixture events
mv loop/CLAUDE.md.template CLAUDE.mdThis tells Claude Code the quality commands (npm run typecheck, npm test) and the project structure conventions Ralph will follow.
In the project root, create .env (substitute your real values):
cat > .env <<'EOF'
INTERN_USER_EMAIL=your-test-account@gmail.com
ANTHROPIC_API_KEY=sk-ant-...
EOFNot comfortable with the heredoc syntax? Just open
.envin any text editor and paste the two lines, replacing the placeholders.
Then create .gitignore to keep secrets and noise out of git:
cat > .gitignore <<'EOF'
.env
node_modules/
seed/credentials.json
seed/token.json
EOFseed/credentials.json in .gitignore — it contains your OAuth client secret. Committing it would expose your credentials in git history.
This populates your test account with fake emails and calendar events, AND produces seed/token.json (the cached OAuth refresh token the EA reuses at runtime).
cd seed
pip install -r requirements.txt # or: python3 -m pip install -r requirements.txt
export INTERN_USER_EMAIL=your-test-account@gmail.com
python seed.py # or: python3 seed.pyWant a clean Python environment?
python3 -m venv .venv && source .venv/bin/activatebefore thepip installkeeps the seeder's deps out of your global Python. Addseed/.venv/to your.gitignoreif you do this.
A browser opens for OAuth consent. You will see a screen titled "Google hasn't verified this app" — this is expected because your OAuth app is in Testing mode without app verification (which is exactly what step 5 of the Prerequisites set up). To proceed:
- Click Advanced (or Show advanced).
- Click Go to (unsafe).
- Sign in with your throwaway test account.
- On the permissions screen, leave all scopes checked and click Continue. (Unchecking any will break the seeder or the EA at runtime.)
When the flow completes, the seeder runs, prints what it inserted, and seed/token.json appears next to credentials.json.
Verify in your test account's web Gmail and Google Calendar that the fake emails and events are there. Then come back to the project root:
cd ..git status # sanity check — .env and seed/credentials.json should NOT appear
git add .
git commit -m "chore: initial scaffold (seed + loop)"
git checkout -b ralph/executive-assistant-v1If
git statusshows.envorseed/credentials.jsonas files to commit, stop and re-check.gitignore— those must be ignored before you commit anything.
The branch name matches branchName in loop/prd.json. Ralph checks out this branch each iteration.
From your ea-build/ project root:
./loop/ralph.sh # default: up to 10 iterations
./loop/ralph.sh 30 # plenty of headroom — there are 24 storiesIf you get
permission denied: ./loop/ralph.sh, the executable bit didn't survive the copy: runchmod +x loop/ralph.shand try again.Always invoke from the project root, not from inside
loop/. The scriptcds to the project root internally, but it needs to be launched from a directory whereloop/ralph.shresolves.
Expect the loop to run for a while — each iteration spawns a fresh Claude Code agent and lets it implement one user story end-to-end (typically several minutes per story). 24 stories at a few minutes each means a multi-hour run; budget accordingly or leave it going in a terminal while you do other things.
Each iteration:
- Spawns a fresh Claude Code instance (no memory of previous iterations).
- Feeds it
ITERATION.mdas the prompt. - The agent picks the highest-priority story with
passes: false, implements it, runsnpm run typecheckandnpm test, commits, flipspasses: true, and appends toprogress.txt. - The loop reads the agent's output; if it contains
<promise>COMPLETE</promise>, the loop exits. Otherwise it spawns the next iteration.
Memory between iterations lives in git history, prd.json, and progress.txt. That's the entire state.
The agent's output streams live to your terminal. After each iteration:
git log --onelineshows the new commit.loop/prd.jsonshows which stories now pass.loop/progress.txtshows what the agent learned.
After the last story passes:
npm startOpen http://localhost:3001 — you should see the Executive Assistant dashboard pulling live data from your test account.
- "Claude Code CLI not found" — install it with
npm install -g @anthropic-ai/claude-codeand runclaudeonce to sign in. permission denied: ./loop/ralph.sh—chmod +x loop/ralph.sh, then retry.- Loop crashes immediately — check that
loop/prd.jsonexists, you're running from the project root (not insideloop/), you're inside a git repo (git statusworks), andclaudeis on your PATH. - The agent can't typecheck/test on US-001 — the very first story creates
package.json,tsconfig.json, and installs deps. If it fails, check that Node 22+ is installed and that you started from a truly empty project (no leftovernode_modulesor stalepackage.jsonconfusing things). - OAuth
invalid_grantortoken expiredat runtime — re-runpython seed/seed.pyto refreshseed/token.json. Confirm the Gmail API and Calendar API are both enabled in your Cloud project and your test account is listed as a test user on the consent screen. - "This API is not enabled" at runtime — usually means the OAuth client lives in a different project than the API. Go back to Prerequisites step 4 and confirm both APIs are enabled in the same Cloud project that owns your
credentials.json. - LLM-backed capabilities show "unavailable" —
ANTHROPIC_API_KEYis missing or invalid in.env. Fix it and restartnpm start. - The loop hit max iterations without completing — read
loop/progress.txt's last entry to see what blocked the agent. Bump the max (./loop/ralph.sh 50) or fix the blocker manually and re-run. - Accidentally committed
.envorseed/credentials.json— your secrets are now in git history. The safe move: rotate the secrets (delete and recreate the OAuth client in the Cloud Console; rotate your Anthropic API key in the Anthropic Console), then add the files to.gitignoreand re-commit.
ralph.sh is heavily commented but the actual logic is a few dozen lines of bash. The pattern is the whole point:
- A loop spawns a new agent per iteration with no shared context.
- The prompt (
ITERATION.md) tells the agent the protocol: pick one story, implement it, prove it works, commit, log learnings, repeat. - State lives in files on disk (
prd.json,progress.txt) and in git history — not in any agent's memory. - The agent self-terminates by emitting a sentinel string the loop greps for.
That's it. The "intelligence" is in the per-iteration agent; the loop is a dumb scheduler. The discipline is in the prompt protocol and the quality gates.