Amy
Internals

Local development

The inner loop for contributors working on Amy itself — the Worker, the CLI, the agents, the schemas. Edit a file, see the change in under 2 seconds, no remote anything.

If you just want to call the Amy backend, you want Getting started. This page is for contributors changing how Amy works.

What you'll run

ProcessPortPurpose
wrangler dev (Miniflare)8787The 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 scipy

You 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 dev

Wrangler prints something like:

⛅️ wrangler 3.99.0
[mf:inf] Ready on http://localhost:8787

localhost: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 --local

Re-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:8787

Then run the CLI in watch mode from the repo root:

bun --watch src/cli.ts          # = bun run dev

Every 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

WhereWhat
cloud/.wrangler/state/v3/d1/amy-db.sqliteLocal 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.sqliteThe CLI's local mirror of your data
data/local/memory.jsonlThe CLI's local memory log
~/.amy/credentials.jsonYour signed-in session token
~/.amy/logs/YYYY-MM-DD.jsonlPer-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:webhook

It 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

CommandWhat it doesCostRuns in
bun scripts/robustness.ts36 deterministic tests of parsers, validators, and pure functions$0~3s
bun scripts/validation_e2e.tsValidator gate fixtures, every gate against known-good and known-bad inputs$0~3s
bun scripts/e2e.tsA live 3-turn conversation against your configured backend$1-35-10 min
bun run typechecktsc --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

ChangeWhat 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
.envThe CLI (next restart). The Worker reads cloud/.dev.vars for local secrets, set those once and they stick
cloud/wrangler.tomlWrangler reloads; bindings re-bind

wrangler dev does not reload on schema changes, migrations are one-shot.


Where logs live

SurfaceFile / 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:8787

Port 8787 already in use

Another wrangler dev is still running. Find and kill it:

lsof -ti :8787 | xargs kill

Or 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:

BackendSetup
Claude subscription (easiest)claude login once. No env vars.
Anthropic API keyexport ANTHROPIC_API_KEY=sk-ant-…
OpenRouterFill 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:local

Python 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

On this page