Local development
The inner loop for working on Amy itself, the Worker, the CLI, the agents, the schemas. Edit a file, see the change in <2s. No deploys, no remote anything, everything on your laptop.
If you're integrating against a running Amy backend, you want Getting started instead. This page is for people changing how Amy works.
What you'll run
| Process | Port | Purpose |
|---|---|---|
wrangler dev (Miniflare) | 8787 | The Worker, with local D1, R2, KV bindings |
bun --watch src/cli.ts | , | The CLI, hot-reloaded on every save |
Both run on your machine. The CLI talks to the local Worker over plain HTTP. No Cloudflare account needed for day-to-day work.
Prerequisites
# 1. Bun — the runtime everything uses
curl -fsSL https://bun.sh/install | bash
# 2. Wrangler — installed transitively via the cloud workspace,
# but a global install is useful for one-offs
bun add -g wrangler
# 3. Python 3.11+ with pandas — the Data Science Agent runs pandas
# in a sandbox. The CLI shells out to your system python.
python3 -m pip install pandas numpy scipyYou do not need a Cloudflare account to develop locally. Miniflare
provides D1, R2, KV, Queues, and Workflows in-process from a SQLite
file under cloud/.wrangler/.
Clone and install
git clone https://github.com/yatendra2001/amy_health_assistant.git
cd amy_health_assistant
bun install
cd cloud && bun install && cd ..Two package.jsons, two bun installs. The root is the CLI; cloud/
is the Worker. They share .env but each owns its own dependencies.
Run the Worker locally
From inside cloud/:
cd cloud
bun run dev # = wrangler devWrangler prints something like:
⛅️ wrangler 3.99.0
[mf:inf] Ready on http://localhost:8787localhost:8787 is the Worker. All your D1 / R2 / KV bindings are
live, Miniflare puts them in .wrangler/state/ so they persist
between restarts. Smoke it:
curl -s http://localhost:8787/healthz
# → {"ok":true,"env":"development"}Apply migrations to local D1
The first time you run the Worker, the D1 schema is empty. Apply migrations:
cd cloud
bun run db:migrate:local
# = bunx wrangler d1 migrations apply amy-db --localRe-run this any time you add a new migration under
cloud/migrations/.
Logs
wrangler dev prints every request to stdout. Filter with grep, or
crash the Worker and look at the stack trace inline. For structured
logs from the deployed Worker, use bun run cloud:tail from
cloud/, that hits live production.
Point the CLI at the local Worker
In your root .env, set:
AMY_CLOUD_URL=http://localhost:8787Then run the CLI in watch mode from the repo root:
bun --watch src/cli.ts # = bun run devEvery save under src/ re-spawns the process. The chat session
restarts; in-memory state is lost, but ~/.amy/credentials.json
and the local SQLite persona persist, so you don't have to re-sign-in.
For a one-shot question without onboarding:
AMY_SKIP_ONBOARDING=1 bun src/cli.ts ask "what's my avg HRV?"Local storage layout
| Where | What |
|---|---|
cloud/.wrangler/state/v3/d1/amy-db.sqlite | Local D1, users, sources, biomarkers, summaries |
cloud/.wrangler/state/v3/r2/amy-lab-reports/ | Local R2, uploaded lab PDFs |
cloud/.wrangler/state/v3/kv/CACHE/ | Local KV, idempotency keys, stream buffer |
data/local/persona.sqlite | The CLI's local mirror of your data |
data/local/memory.jsonl | The CLI's local memory log |
~/.amy/credentials.json | Your signed-in session token |
~/.amy/logs/YYYY-MM-DD.jsonl | Per-day CLI structured logs |
Everything under cloud/.wrangler/ and data/local/ is gitignored
and safe to delete, re-running migrations + onboarding rebuilds it.
Trigger a fake Terra webhook
To exercise the ingest pipeline without a real wearable, sign a
payload with TERRA_WEBHOOK_SECRET (the same one in .env) and POST
it to the local Worker. There's a script for this:
cd cloud
AMY_CLOUD_URL=http://localhost:8787 bun run smoke:webhookIt builds a fake "daily" payload for a synthetic Terra user, HMAC-signs
it as t=<unix>,v1=<sha256> in the terra-signature header, POSTs to
/webhook/terra, then sends two more requests (unsigned and tampered)
that should both 401. End-to-end it asserts the row landed in D1.
If you want to do it by hand:
SECRET="$(grep TERRA_WEBHOOK_SECRET .env | cut -d= -f2-)"
BODY='{"type":"daily","user":{"user_id":"u_test","provider":"WHOOP"}}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
curl -X POST http://localhost:8787/webhook/terra \
-H "content-type: application/json" \
-H "terra-signature: t=$T,v1=$SIG" \
-d "$BODY"Tests
| Command | What it does | Cost | Runs in |
|---|---|---|---|
bun scripts/robustness.ts | 36 deterministic tests of parsers, validators, and pure functions | $0 | ~3s |
bun scripts/validation_e2e.ts | Validator gate fixtures, every gate against known-good and known-bad inputs | $0 | ~3s |
bun scripts/e2e.ts | A live 3-turn conversation against your configured backend | $1-3 | 5-10 min |
bun run typecheck | tsc --noEmit across the CLI | $0 | ~10s |
bun run cloud:typecheck* | Same, for the Worker (run from cloud/) | $0 | ~5s |
* Run from cloud/: cd cloud && bun run typecheck.
The first three are free of LLM cost; only e2e.ts hits a real
backend. Run robustness + typecheck on every change; run e2e.ts
before merging anything that touches the orchestrator or an agent.
The Worker has its own smoke suite, bun run smoke:all from
cloud/ exercises webhook, normalize, CLI auth, sync, labs, and
admin endpoints against your deployed Worker (not the local one).
Use it before pushing changes you're not sure about.
Hot reload behavior
| Change | What restarts |
|---|---|
File under src/ | The CLI process (bun --watch) |
File under cloud/src/ | The Worker (wrangler dev) |
File under cloud/migrations/ | Nothing, run bun run db:migrate:local manually |
.env | The CLI (next restart). The Worker reads cloud/.dev.vars for local secrets, set those once and they stick |
cloud/wrangler.toml | Wrangler reloads; bindings re-bind |
wrangler dev does not reload on schema changes, migrations are
one-shot.
Where logs live
| Surface | File / command |
|---|---|
| CLI (structured) | ~/.amy/logs/YYYY-MM-DD.jsonl, one JSON object per line |
| CLI (uncaught throws) | Same file. Stack trace prints to stderr when AMY_DEBUG=1 |
| CLI (per-turn trace JSON) | Inside chat: type /trace for the last turn |
| Worker (local) | stdout of wrangler dev |
| Worker (deployed) | cd cloud && bun run cloud:tail (live tail) or bun run cloud:logs (recent) |
| D1 (local) | cd cloud && bunx wrangler d1 execute amy-db --local --command "SELECT * FROM users" |
| D1 (remote) | Same, drop --local |
The CLI log file is grep-friendly. To pull every error from today:
grep '"level":"error"' ~/.amy/logs/$(date +%F).jsonl | jq .Common issues
"AMY_CLOUD_URL isn't set in .env"
The CLI talks to the Worker by URL. Set it in .env:
AMY_CLOUD_URL=http://localhost:8787Port 8787 already in use
Another wrangler dev is still running. Find and kill it:
lsof -ti :8787 | xargs killOr start on a different port: bunx wrangler dev --port 8788,
then set AMY_CLOUD_URL=http://localhost:8788 in .env.
"No data yet at data/local/persona.sqlite"
You haven't onboarded against the local Worker yet. Run bun run amy
once and walk through sign-in → connect. The persona file is created
on first sync.
Anthropic auth fails
Amy talks to Claude three ways, same as in production. Pick one:
| Backend | Setup |
|---|---|
| Claude subscription (easiest) | claude login once. No env vars. |
| Anthropic API key | export ANTHROPIC_API_KEY=sk-ant-… |
| OpenRouter | Fill the OpenRouter block in .env |
See the backends table in the README for
full env-var details. The active backend prints in the CLI banner,
look for backend anthropic (oauth) / (api key) / openrouter.
Migrations applied but D1 still empty
You applied to remote, not local. Always pass --local (or use
bun run db:migrate:local) for the Miniflare DB:
cd cloud
bun run db:migrate:localPython sandbox fails
The Data Science Agent shells out to python3 with pandas. Check:
python3 -c "import pandas; print(pandas.__version__)"If that fails, pip install pandas numpy scipy into whichever python
your python3 resolves to.
Where to next
- Using the CLI, every command, every flag.
- Architecture, what each layer of the system does.
- Deploying, when your changes are ready to ship.
- API reference, every endpoint your Worker exposes.
Getting started
Ten minutes from zero to your first turn against a live Amy backend. Pick whichever path matches you.
Deploying to Cloudflare
End-to-end deploy of the Amy backend to Cloudflare. ~30 minutes first time, ~2 minutes for subsequent updates. Everything here is idempotent, re-running a step is safe.