Setup guide

Action Brain: a personal agent that turns loose thoughts into handled tasks

Record a voice memo, forward an email, or send a link. An always-on agent transcribes it, works out what matters, does the research, reminds you at the right moment, and keeps a live dashboard and app in sync.

Agent: Hermes, on a small home computer Cloud: Cloudflare free tier Interface: iMessage (Telegram fallback), web, native app

Overview

Most to-do systems fail at the capture step. You have to stop, open an app, and type. Action Brain removes that friction: you capture however is easiest, and the agent turns what you captured into something you can act on.

The loop is simple:

  1. Capture: a quick voice memo from your phone, an email to a private inbox, a link or a message in chat. Your phone also reports when you leave or get home, so the agent knows where you were.
  2. Triage: the agent pulls out to-dos, dates and decisions, judges how important each one is, and researches anything that needs it (phone numbers, business hours, how a process works), always with sources.
  3. Remind: reminders arrive by iMessage at sensible times, avoid the times you're busy (including work meetings), and keep coming until you mark the item done. A reminder never moves the day a task is planned for.
  4. Review: a private dashboard (one-tap sign-in that lasts a year) and a native app show what's left today and what's coming later, reading from one shared database.
Design principle: the language model only runs when something actually changed. Polling, scheduling and reminder delivery are plain scripts, which keeps costs close to zero and behavior predictable.

Architecture

Four layers, left to right. The agent runs at home; storage and anything public-facing run on Cloudflare's free tier.

Everything runs as scheduled jobs on the home device (fourteen at last count), and every one of them delivers to iMessage. The main ones:

JobEveryRuns the LLM?What it does
Memo triage1 minOnly when there's a new recordingLists the bucket; wakes the agent only when an untriaged memo appears
Email triage2 minOnly when there's new mailChecks both inboxes; same "wake on change" pattern
Reminders5 minNoSends due reminders, the daily agenda and game alerts; mirrors calendar data; refreshes the dashboard
Work availability6× a dayNoSilent snapshot of work busy blocks from the public booking page
Day planWeekdays 8amYesToday's work blocks, personal events and tasks, slotted into the real gaps
Week look-aheadDaily 7:30pmYesNext 7 days; only mentions new items or conflicts
School summaryWeekdays 3:30pmYesGrades and missing work, by chat and as one ongoing email thread
Location watch1 min (test mode)NoConfirms each leave / arrive home event while the automations are being tested
Weekly choresWeeklyNoFixed nudge emails, and pruning location pings older than 30 days
Quota watchDailyNoSilent unless free-tier usage is on pace to exceed a limit

Voice memo pipeline

The main way things get in. A memo is usually 5–30 seconds: "remind me to…", "add this trip idea…", "email my spouse that…".

  1. Upload. A phone shortcut posts the audio to a small Cloudflare Worker that checks a secret token and writes it to an R2 bucket under a dated key. The shortcut setup is at the end of Build your own.
  2. Tag the place. After the upload finishes, the same shortcut runs a separate location ping marked "memo". Because it runs after the upload, a GPS failure can never lose a recording. In testing the ping landed 1–3 seconds after the recording, so memos are matched to places by timestamp (see Location context).
  3. Detect. A script lists the bucket every minute and compares it with a local "already triaged" list. If nothing is new, its output doesn't change, so the agent never wakes.
  4. Pull only what's new. New recordings are downloaded once, tracked in a local list so nothing is fetched twice.
  5. Transcribe. Audio is converted with ffmpeg and sent to Workers AI (whisper-large-v3-turbo). That costs about $0.0005 per audio minute, and the free daily allowance covers roughly 200 minutes.
  6. Triage. The agent reads the transcript along with recent notes and the open task list, so a memo that repeats an existing task gets merged instead of duplicated. It also looks up where you were when you recorded it and notes the place.
  7. Record. Results go to a dated triage notes file, reminders go to the database, and the memo is marked triaged only after everything is saved.
  8. Recover. If a run fails, the memo is still untriaged after 10 minutes, the monitor's output changes, and the agent retries. A failure lasting 30+ minutes sends one alert.
Speech-to-text gets names wrong. Give the agent a short glossary of the people in your life (stored in its memory) so it fixes mis-heard names before acting.

Triage & importance

Every item gets an importance level, and that sets how persistent its reminders are. Hard things aren't crammed into tonight; they're scheduled for when you can actually act on them.

LevelTypical itemsFirst reminderThen
HighFamily, legal, financial or health obligations; deadlines within ~3 daysNext morning, 9amDaily until done
MediumReal tasks without a pressing deadline: home projects, errands, follow-upsIn 1–3 daysEvery 3 days
LowIdeas, someday/maybe, nice-to-havesIn about a weekOnce
  • Dates you mention explicitly always win over these defaults.
  • If a task needs a business to be open, the reminder is timed for its hours. A "prepare" reminder can still come earlier.
  • Each reminder carries everything needed to act: phone numbers, hours, steps, links. You never have to look anything up.
  • The research the agent does cites its sources and never invents numbers, addresses or facts.

Reminder engine

A plain script, run every five minutes, decides what to send. It respects your time.

When it stays quiet

  • Quiet hours (10:30pm–8am): late or repeat reminders wait for the morning.
  • Busy calendar events: nothing is sent during a meeting or appointment; tasks wait until it ends. In-person events start 45 minutes early to cover travel.
  • Protected time: a favorite team's games block everything from an hour before kickoff to the end. If you're going in person, the window covers travel too.

What it sends

  • Daily agenda at 7am, with warnings when events overlap.
  • Day plan on weekdays at 8am and a 7-day look-ahead at 7:30pm (see Calendar & schedules).
  • Due tasks with full details and a done <id> reply hint.
  • Habit check-ins (medication, supplements, exercise), skipped if already logged today.
  • Game-day morning note and a 30-minute heads-up.

A habit reminder that would land inside a busy block moves just before it, but only if that's within two hours of its usual time. Otherwise it waits until the block ends; an evening medication reminder shouldn't jump to mid-afternoon.

Everything goes out over iMessage on a shared line, with Telegram as the fallback if that channel goes down.

Task day vs. reminder time

Each task has two separate times: due_date, the day it's planned for, and next_due, when the next reminder fires. Repeat nags advance next_due but never touch due_date, so a reminder can't quietly push a task to tomorrow.

  • Unfinished tasks roll to today. A task's day is the later of its planned day and today; the dashboard, the morning agenda and the task list all use this rule.
  • Deferring is explicit. "Push it to the weekend" runs reminders.py move ID WHEN, which sets both fields. The app's snooze does the same.
  • Routines don't pile up. Items marked recurring=1 (a twice-daily stretch, say) show only on the day of the current occurrence and drop off once it passes, instead of rolling forward.

To make changes safely, the engine has a time-travel test mode: copy the data, set a fake "now", and step through a week in 5-minute ticks to see exactly what would be sent and when.

Calendar & schedules

Context makes reminders smarter. Everything here is read-only.

  • Personal calendar through Google Calendar's secret iCal link, which is read-only and needs no OAuth app. Recurring events are expanded 14 days ahead and refreshed every 30 minutes. Events marked "free" don't block anything.
  • Sports schedule from a public sports-data API, refreshed twice a day because game times change. Games with a time still "TBD" are ignored until it's set.
  • Work calendars stay out. Company policy usually forbids sending them to third-party agents, so keep this personal.
  • Work availability, inferred. A public appointment-booking page only offers times you're free. A script asks the page for its open 30-minute slots between 9 and 5 PT, and every missing slot counts as busy at work. No work data is ever shared.

Booking pages hide slots inside their minimum-notice window, so today can look fully booked when it isn't. Silent snapshots run six times a day, and for each day the script keeps the freshest snapshot taken outside that window, labeled "as of" with its time. Two messages use it:

  • Day plan, weekdays at 8am: work busy blocks and personal events in time order, with today's tasks slotted into the real gaps and any clashes flagged (a pickup during a meeting, say).
  • Look-ahead, 7:30pm daily: the next seven days. It remembers what it already said and only speaks up about new items or conflicts.

Location context

Knowing where you were makes memos clearer and reminders better timed. Your phone reports a handful of moments, and nothing else is tracked.

  1. A tiny shortcut sends a ping. An iPhone Shortcut called "Herm Ping" runs Get Current Location, then POSTs {what, lat, lon} to the dashboard Worker at /api/v1/location. It authenticates with a Bearer token whose device name starts with loc:, and the Worker lets that token post locations and nothing else.
  2. Automations decide when. Shortcuts automations fire on "leave Home" and "arrive Home" and pass that text along as what. Working from home means there are no office triggers. The voice memo shortcut ends by running Herm Ping with "memo".
  3. The Worker stores a row. Each ping becomes a row in the D1 locations table: time, event (arrive, leave, memo, task or ping), optional place label, and coordinates.
  4. The agent reads it as context. locations.py recent lists the latest pings, and locations.py near TS finds the ping closest to a timestamp; memo triage uses it to note where a memo was recorded. Coordinates become names through a private places.json of named places, with OpenStreetMap reverse geocoding as the fallback.
POST /api/v1/location
Authorization: Bearer <token>        (location-only, device "loc:…")
{"what": "leave Home", "lat": <lat>, "lon": <lon>}
Recordings come first. The location is sent separately, after the memo upload, so a slow or failed GPS fix never costs you a recording. In testing the ping landed 1–3 seconds after the recording.

Now: test mode

  • For the first few days, a no-LLM script checks for new leave or arrive events every minute and texts "Saw you leave home at <time>" or "Saw you get home at <time>".
  • Those messages are held during quiet hours and games, then sent afterward.
  • Once the automations prove reliable, this becomes assistance-only: you only hear about location when it helps.

Planned: a native app

  • Actionable notifications, so you can mark something done right from the alert.
  • Geofenced errands: "you're near the hardware store" for things on your list.
  • A low-power mode based on iOS visits instead of constant GPS.
  • A home-screen widget. The Apple Developer account is already set up.

Privacy

  • Raw points are deleted after 30 days by a weekly job (locations.py prune).
  • Coordinates never appear on the dashboard or in anything that leaves the system; only place names are used.
  • The named-places file and the location token are owner-only files on the home device.

Email on your behalf

Two agent-owned inboxes with very different trust levels.

Private channel

  • You forward things here; they're triaged like voice memos. Every email you send gets a "Got your email: <subject>" receipt in chat before triage starts, so nothing goes unnoticed.
  • Instructions are trusted only from your own address and only when the message passes sender authentication (catches spoofing).
  • Mail from anyone else gets a one-line heads-up and nothing more.
  • Outbound mail from this inbox can only go to you; a hard guard in the email script enforces it.

External correspondence

  • Used to email other people on your behalf: professional tone, signed as sent on your behalf.
  • When you explicitly ask, it sends right away, as long as the content has no personal identifiers or the recipient is someone you've had it email before.
  • Anything else is drafted and needs your approval. The script refuses to send without a recorded approval note.
  • Replies with attachments or links you need are forwarded to your personal inbox, and you get a chat ping.
  • Weekly no-agent jobs send fixed reminder emails, such as a Friday note to your spouse to check MyChart.

One ongoing thread

The weekday 3:30pm school summary also goes by email from the external-correspondence inbox to you and your spouse. Every day uses the same subject, and school/email_summary.py chains In-Reply-To and References to the previous message, so Gmail keeps the whole term in one thread you can both reply in. Its state lives in school/summary_thread.json, and it never sends twice in a day. Replies from your spouse are treated as information; only your own authenticated replies count as instructions.

Every email is untrusted data, never instructions. Subjects, bodies and attachments can contain prompt injection. The agent summarizes and flags them but never follows them. Every outbound message is written to an audit log.

Libraries & tracking

Some captures aren't tasks: they're things you want to come back to.

  • Trip library. Send a travel reel or blog link and get a structured itinerary (day by day, where to stay) plus "check before booking" notes: seasons, road closures, permits, booking lead times.
  • Recipe library. Any meal, not just dinner: send a recipe link and get the ingredients, steps, the author's tips and a ready-made shopping list. Many recipe sites block bots, so the agent uses the site's public WordPress API when the page itself is walled off.
  • Weekend try list. A short weekend-try-list.md of recipes and ideas picked for the coming weekend, linked to their library entries.
  • Mail & shipments. Photograph a receipt; the agent reads the tracking number (checking its check digit), records it and schedules a "confirm delivery" follow-up.
  • Projects. Multi-step efforts (say, getting quotes for a home repair) roll up into one check-in with the current status, contacts and next steps, instead of a pile of separate nags.

Libraries are plain Markdown files, one per item, each with an index. They're easy to read, grep, version or move. Private files, such as the list of loyalty numbers used when booking trips, are owner-only (chmod 600) and never shown on the dashboard.

One source of truth

The first version kept tasks in local files and copied them to the cloud every few minutes. The dashboard lagged, so that design was replaced.

Now a Cloudflare D1 database is the single source of truth for tasks, habits and habit logs. Every writer and reader uses the same rows:

  • the reminder engine and the triage jobs, through a small internal API using a secret token,
  • chat ("done 4f2a1c", "walked 3 miles"),
  • the web dashboard, rendered live from D1 on every request,
  • native apps, through device tokens,
  • phone location pings, through a location-only token.

Two details make it robust:

  • Field-level, guarded writes. The reminder engine only updates the fields it changed, and only if the item is still open, so it can't bring back something you finished on your phone a second earlier.
  • An offline fallback. If the cloud is unreachable, reminders run from a local cache and their changes are queued and replayed later.
items       (id, kind task|habit, title, details, importance, status open|sent|done,
             next_due, due_date, recurring, repeat_hours, last_sent, done_at, source)
habit_logs  (date, habit, value, ts)
events      mirrored calendar, next 7 days
games       mirrored sports schedule
actions     audit log of app actions
devices     sha256(token), name (web:… browser, loc:… location-only), last_seen
locations   ts, event arrive|leave|memo|task|ping, place, lat, lon (kept 30 days)

Web dashboard

A single Cloudflare Worker, built for a phone screen. You sign in once per device with a texted link or code, and it stays signed in for a year.

  • Today: what's still on the schedule (with a NOW marker), tasks planned for today or rolled over unfinished, habits not yet logged, and a collapsed "done today" list.
  • Later: grouped into Tomorrow, Next 7 days, Later, and Waiting on you.
  • Also: the upcoming calendar, a 7-day habit grid, upcoming games, recent triage notes, and the trip and recipe libraries.

Signing in

  1. Text "dash link". The agent replies with a magic link and a 6-digit code. Both are single-use and expire in 15 minutes.
  2. Tap the link, then "Sign in". Opening the link only shows a button; the token is spent by the button's POST, so iMessage link previews can't burn it. An expired or used link falls back to the sign-in screen with the code box.
  3. Or enter the code. A web app added to the home screen has its own cookie jar and can't receive a tapped link, so it uses the code instead (iOS autofills it from Messages). Five wrong tries kill all outstanding codes.
  4. Stay signed in. Signing in sets an HttpOnly, Secure session cookie that lasts a year. Each browser is its own device, listed with dash_link.py devices and revoked one at a time with dash_link.py revoke ID.
Add the plain dashboard URL to the home screen, never a one-time link. Home-screen icons save the URL you added them from, so an icon saved from a sign-in link would open a spent link every time.

Password fallback

HTTP Basic Auth still works: the sign-in screen has a "Use password instead" link, which also sets the year-long cookie. The agent never sees the password. It creates a one-time setup link that expires in 24 hours. You choose the password on that page, and only a salted PBKDF2 hash is stored. Pages are served with noindex, no-store and a strict Content-Security-Policy that blocks all scripts.

Native app & sync API

The same Worker exposes a small JSON API, so a SwiftUI app for iPhone and Mac can show the same data and act on it.

EndpointPurpose
POST /api/v1/loginSign in once with the dashboard password; returns a per-device token to keep in the Keychain. Only its hash is stored.
GET /api/v1/snapshotTasks with live Today/Later buckets, habits, logs, events and games. Supports ETag, so an unchanged poll costs one row read.
POST /api/v1/actionstask_done, task_reopen, habit_log or task_snooze, applied immediately.
GET /api/v1/actionsHistory of which device did what, and when.
POST /api/v1/locationA location ping from a phone automation. Location-only tokens (named loc:…) can call this endpoint and nothing else.

App tokens, location tokens and web sessions all live in the same devices table, stored only as hashes, and each one can be revoked on its own. iPhone and Mac apps have to be built in Xcode on a Mac. A Linux build server can host the API but can't build the app.

What it tracks

The kinds of things that have flowed through it. The categories matter more than the specifics.

  • Family logistics
  • Kids' school & homework
  • School and sports paperwork
  • Benefits & insurance paperwork
  • Certified mail & tracking
  • Home projects & contractor quotes
  • Household chores
  • Travel plans & documents
  • Trip ideas
  • Recipes
  • Medication adherence
  • Supplements
  • Daily exercise
  • Calendar conflicts
  • Work busy blocks
  • Leaving & getting home
  • Where memos were recorded
  • Weekend plans
  • Sports schedule
  • Side-project infrastructure
  • Cloud quota usage

Habit tracking

Habits are just reminders with a daily repeat and a log. Reply naturally ("took my meds", "walked 3.5 miles") and the agent records it. A logged habit never sends a reminder that day, and the dashboard shows a 7-day streak grid.

Check before you claim. Logs can arrive from chat, memos, email or the app at any moment. The agent re-reads the log before saying something is missing. An early version told the user a habit wasn't logged five minutes after it had been.

Security model

The agent can run commands on a home computer, so who can talk to it, and what it trusts, matters more than anything else.

Access

  • The agent answers exactly one person: your number on iMessage (a shared line) and your user ID on the Telegram fallback. Turn on two-step verification for those accounts; they're effectively the key to the machine.
  • Nothing on the home device is reachable from the internet: no port forwards, no tunnels, no public IPv6. All traffic is outbound.
  • The memo upload endpoint and the private R2 bucket both require tokens; public bucket access is off.
  • Every credential file on disk is readable by its owner only (chmod 600), and secrets are never shown on the dashboard.
  • Dashboard sessions are HttpOnly cookies backed by hashed tokens; sign-in links and codes are single-use and short-lived, and each device can be revoked.
  • Tokens are scoped: the phone's location token can post pings and do nothing else.

Trust

  • Web pages, emails and attachments are data. Only the owner gives instructions.
  • Hard guards live in code, not just in prompts: send allowlists, approval notes, no-reply rules.
  • No personal identifiers go out without explicit approval.
  • Passwords and payment details are never typed by the agent or pasted into chat.
  • Read-only access wherever possible (calendar, schedules, the booking page).
  • Location coordinates stay internal: never on the dashboard, never in anything sent out, deleted after 30 days.

A periodic audit is worth doing. Check open ports from outside, the router's automatic port openings (UPnP), SSH settings (keys only), unneeded network services, the firewall, automatic security updates, and any old agents still running in the background.

Costs & quotas

Everything fits comfortably in Cloudflare's free tier. The bigger risk is other projects on the same account.

ResourceFree limitAction Brain usage (est.)
Worker requests100k / day~1–1.5k (≈1%); ~3k while the per-minute location test runs
D1 rows read5M / day~15k (≈0.3%), plus recent location pings re-read each minute during test mode
D1 rows written100k / day~300–1,000 (≈1%)
KV reads / writes100k / 1k per day~100 / ~10
R2 list operations1M / month~43k (checking once a minute)
Speech-to-text10k neurons / day≈200 audio-minutes free

Free-tier limits are shared across the whole account. A daily script checks usage through Cloudflare's GraphQL analytics and only sends a message when you're on pace to exceed a limit. The LLM is the real cost, which is why it only runs when there's new input.

Lessons learned

Gate the LLM behind cheap change detection.
A monitor script's output is hashed every tick, and the agent only wakes when the hash changes. Make that output grow only (every ID ever seen, plus a retry marker), so marking something done doesn't wake the agent again.
Pick one source of truth early.
Copying data between stores causes "why is the dashboard stale?" bugs. Put the state in one database and make every surface read it live.
Enforce rules in code as well as prompts.
Policy written into a prompt is advice. The email script refusing to send without an approval note is a guarantee.
Simulate time before shipping schedule logic.
Stepping through a fake week caught a bug that would have sent the same habit reminder three times when it moved ahead of an event.
Watch for working-directory collisions.
The Cloudflare CLI (wrangler) creates a .wrangler folder wherever you run it. Run it from your home directory and that folder masks its saved login. Always run tools from a project folder.
Never send undefined to D1.
Older records missing a field broke a bulk write. Give every column an explicit default.
Never quote state from memory.
Re-read the log before saying whether something is done or missing.
Revoke test sessions by ID, never all at once.
Cleaning up after a sign-in test by revoking every web session also signed the user out on their phone. Mark test requests so they're easy to spot, then revoke just that one.
Reminders must not move a task's day.
When one field meant both "when to nag" and "which day", every repeat nag quietly pushed tasks forward. Keep the planned day and the reminder time separate.
Home-screen icons remember the URL they were added from.
Add the plain dashboard URL to the home screen, never a one-time link, or the icon opens a spent link forever.

Build your own

A practical order of operations, roughly one evening per step.

  1. Run an agent on a box that's always on. Hermes on a small single-board computer works well. Connect a chat app (iMessage, with Telegram as a fallback) and allow only yourself.
  2. Set up voice capture. Create an R2 bucket and a token-protected upload Worker, plus a phone shortcut that posts the recording (details below).
  3. Add the memo monitor, transcription and triage job. Write down the importance policy and the reminder cadence explicitly.
  4. Add the reminder engine. Start with quiet hours and repeat-until-done, keeping each task's planned day separate from its reminder time. Add calendar blackouts after that.
  5. Create the D1 database and API. Make it the source of truth from day one.
  6. Build the dashboard. It should be private (texted sign-in link or code, with a password fallback), live, and organized by today versus later.
  7. Connect email. Use separate inboxes for "talk to me" and "talk to others", enforce guards in code, and keep an audit log.
  8. Add context. Work availability from a booking page, then location pings from phone automations with a location-only token and a 30-day retention limit.
  9. Run a security audit and set up the quota alert. Then tune the cadences based on what actually annoys you.

Step 2 in detail: the voice memo shortcut

Press the Action Button and recording starts; tap to stop and the memo uploads. It's five built-in actions in Apple's Shortcuts app, so there's no app to build.

The finished shortcut

  1. 1Record Audio
    Audio Quality
    Normal
    Start Recording
    Immediately
    Finish Recording
    On Tap
  2. 2Save File backup copy
    File
    Recorded Audio
    Ask Where to Save
    Off, saving to a folder such as iCloud Drive/Memos
  3. 3Get Contents of URL
    URL
    https://<worker>.<subdomain>.workers.dev/upload
    Method
    POST
    Header
    Authorization = Bearer <token>
    Request Body
    File: Recorded Audio
  4. 4Show Notification optional
    Text
    Contents of URL
  5. 5Run Shortcut location tag
    Shortcut
    Herm Ping
    Input
    memo

The two values in angle brackets are your own: the Worker address printed by wrangler deploy, and the token you stored with wrangler secret put UPLOAD_TOKEN.

Set it up in Shortcuts

  1. Create the shortcut. In the Shortcuts app, tap + and give it a short name like "Memo". Add each action from the search bar at the bottom of the editor.
  2. Record Audio. Tap the arrow on the action to show its options. Start Recording: Immediately is what makes the button feel instant; On Tap lets you talk as long as you like.
  3. Save File. Set it to save Recorded Audio, turn off Ask Where to Save, and pick a folder. If you stop recording with no signal, the upload fails, but this copy stays in the Files app.
  4. Get Contents of URL. Paste the upload URL and tap Show More. Set Method to POST. Under Headers, add Authorization with the value Bearer, one space, then the token. Set Request Body to File and choose the Recorded Audio variable.
  5. Show Notification. Set its text to the Contents of URL variable, which is the server's reply. You'll see the saved key, or an error such as unauthorized. Use this rather than a fixed "Saved" message, because Shortcuts doesn't stop when the server rejects an upload.
  6. Run Shortcut. Last, run your "Herm Ping" shortcut with the text memo as its input (see Location context). It sends the location separately, after the upload, so a GPS failure never loses a recording.
  7. Assign it to the Action Button. Go to Settings → Action Button, swipe to Shortcut, tap Choose a Shortcut and pick yours.
  8. Run it once from the Shortcuts app. Allow microphone access and tap Always Allow when it asks to connect to your Worker's domain. Otherwise those prompts interrupt your first real memo.

What the upload Worker expects

PartValue
RequestPOST /upload with the raw audio file as the body (not a form). Up to 100 MB, which is about 70 minutes at Normal quality. A 30-second memo is about 0.7 MB.
AuthAuthorization: Bearer <token>. The token is a Worker secret, never in the code, and the Worker compares it in constant time. Anything else gets 401.
Stored asrecordings/YYYY/MM/<UTC time>-<id>.m4a, for example recordings/2026/10/2026-10-04T03-48-11Z-e3de.m4a. Keys sort by time, and the short random suffix prevents collisions.
Reply201 with {"key": "…", "size": 115960}.
Other routesGET /recordings lists recordings and GET /recordings/<key> downloads one. Both use the same token.

Check that it worked

Press the button, say a few words, and tap to stop. Then list the bucket from any computer; the newest recording should be at the top. Times in keys are UTC.

curl -H "Authorization: Bearer <token>" \
  https://<worker>.<subdomain>.workers.dev/recordings

Troubleshooting

SymptomLikely causeFix
401 unauthorizedThe header value is missing Bearer, has a stray space or line break from pasting, or the token was just changed.Retype it as Bearer <token>. After wrangler secret put, allow about 30 seconds for the new token to take effect.
400 empty bodyRequest Body is set to JSON or Form, or the File field is empty.Set Request Body to File and choose Recorded Audio.
A prompt on the first pressShortcuts asks for permission once for each new domain and for the microphone.Run the shortcut once in the Shortcuts app and tap Always Allow.
Upload failed, no signalThe phone was offline when you stopped recording.The backup is in your Save File folder. Make a second shortcut that takes a file from the Share Sheet and runs only Get Contents of URL with Shortcut Input as the body, then share the file to it.
413 file too largeThe recording is over 100 MB, about 70 minutes.Split long recordings into parts.
The token is stored in plain text inside the shortcut. Remove the header before you share the shortcut with anyone. If the token leaks, run wrangler secret put UPLOAD_TOKEN with a new value and update the header.