Say It Once

A voice-first AI product, designed and built end to end

Project poster — “HEARD.” A hand loom on a mango-yellow field weaving scattered notes, receipts and a spoken red waveform into finished cloth, beside the line “ASIS — Say it once. Let the day find its shape.”

01
Year — 2026

Role

  • Independent product — ASIS, submitted to the App Store
  • Product and UX, SwiftUI client, realtime voice agent, Bun/Hono API
  • Memory system, calendar sync, subscriptions, privacy and legal surfaces
  • Submitted to Apple — August 2026

iOS 17+ · iPhone · v0.8.8 (build 8) · ASIS Pro & Max

Every calendar app can store an event. None of them can absorb the sentence you actually say. ASIS is a voice-first life assistant for iPhone: you talk for ten seconds, a realtime agent grounds what you said in your real schedule and what it already knows about you, performs the action through nine bounded tools and puts the result somewhere you can correct it. I designed the product and built all of it — the SwiftUI client, the Node voice agent, the Bun API, the memory system, the calendar sync, the subscription layer and the public site. Submitted to Apple in August 2026.

Problem

Life organisation is scattered across a calendar, a task list, a reminders app, a notes file and the person’s own head. Every one of those tools can store information. Not one of them removes the actual work, which is translating a messy intention into structured entries, reconciling it against what is already there, carrying the context that made it matter, and deciding what should happen next.

Voice looks like the obvious fix and mostly is not. Dictation moves the typing without moving the work: something still has to know that “move it to after the gym” refers to Thursday, that the gym block ends at seven, that the thing being moved is a provider-managed recurring event, and that shifting it collides with something you already promised someone. The hard part was never transcription. It is everything that has to be true before ten seconds of speech is safe to act on.

Two ASIS App Store frames forming one panorama: a tilted iPhone crossing the seam, showing the live voice sphere mid-answer, under the headlines “You talk.” and “One tap to start.”
The way in — one tap, then ordinary speech, in the one language the session is pinned to
Two ASIS App Store frames: the Today plan with a running Push Workout card under the headline “It’s handled.”, and the Today timeline showing a free window between events under “Free time, found.”
And what comes back — the plan it acted on, and the free window it found inside a real day

The bet

The bet is that a life assistant only becomes useful when three things hold at once: conversation fast enough for real life, actions reliable enough to trust, and memory selective enough to feel familiar without becoming invasive. Any one of them missing makes the other two worthless — a fast assistant that books the wrong thing is worse than a form, and an assistant that remembers everything is a surveillance product with a friendly voice.

Scoping it was mostly deciding what not to build. It is not a project-management tool for teams, not a chatbot with no action layer, not a coach, and not a clinical, legal or financial advisor. It has no streaks, no badges, no urgency theatre and no dashboard aesthetic, because the failure mode of productivity software is making the user feel behind. The assistant’s persona is a competent chief of staff — warm but not gushing, sharp but not showy — and the spoken contract is two or three sentences with no markdown, no emoji and no narration of its own machinery.

Everything that follows is the cost of that promise. Voice is the fastest way in, but Plan, Memory and Settings exist so nothing the assistant does is only knowable by asking it again.

Similarity is not permission. A private memory can score highest in retrieval and still stay silent until you name the topic yourself.

The voice loop

A turn runs through a realtime pipeline, not a request cycle. iOS streams encrypted room audio into LiveKit; Deepgram Nova-3 transcribes in the one pinned speech language; Gemini 3.1 Flash Lite reasons over a grounded prompt of local time, the current plan, connected events and policy-allowed memory, and either answers or calls a tool; Google Chirp 3 HD streams the reply back. Every stage has a rollback wired behind a flag — Claude Sonnet 4.6 for the model, xAI for speech-to-text, Deepgram Aura 2 or Cartesia for the voice — so a provider outage is a configuration change, not a redesign.

The split that took the longest to accept is the boring one. The API is Bun + Hono; the agent is a separate Node 22 process, because LiveKit’s rtc-node binding does not run under Bun. Rather than compromise the API runtime for the agent’s dependency, the two ship as separate Fly apps and a shared TypeScript schema package keeps their contracts honest. The API suspends and auto-starts on a shared CPU; the agent runs on two gigabytes with a restart policy and no HTTP surface at all.

Realtime tuning is where the product is felt. Endpointing waits 300 ms with a 250 ms end-of-utterance threshold; preemptive speech is off so the assistant never starts talking before its grounded answer is stable; the false-interruption resume heuristic is off in favour of deterministic behaviour. When something stalls, a staged watchdog forces a commit at 8 s, offers manual recovery at 16 s and closes the session at 24 s — bounded silence beats a spinner, because a voice interface with no timeout simply looks broken.

Sessions are capped at 30 minutes, onboarding gets 50% more room to finish its structured first conversation, and a rolling ceiling of four hours per 24 stops a runaway client from turning a subscription into an unmetered voice line.

The system

Bounded agent tools
9
Speech languages
22
Canonical memory types
11
Event intents
14
Canonical Postgres tables
22
Ordered migrations
28

Bounded tools

The model is never allowed to improvise an action. Nine tools with explicit schemas are the entire surface it can touch: five run as RPC on the device — read the merged plan, calculate real free slots, write events, write to-dos, write reminders — and four run on the server, against memory and conversation history. Onboarding swaps the reminder-oriented set for two of its own so a setup conversation cannot pretend the user is already in a normal session.

Two rules inside those schemas do most of the safety work. A conflicting write is never silently committed — the allow-conflict flag turns true only after the person says so. And creating an event on a connected Google or Outlook calendar requires the user to name that calendar; otherwise it lands in ASIS’s own calendar, where a mistake is cheap. Credentials and secrets are rejected outright rather than stored as personalisation.

Memory that has to earn it

Memory is the part of the product most likely to be faked and the part I spent the longest making real. It is not a transcript archive presented as personalisation — it is a governed dossier of durable facts, preferences, routines, priorities, people, projects and commitments, and every row is searchable, editable, archivable and deletable by the person it describes.

A candidate has to pass two independent gates before it exists. An evidence score decides whether it may be written at all: 0–4 is discarded, 5–7 stays an invisible candidate, 8–10 can only be proposed, and 11+ may go active. A separate confidence band decides visibility: below 0.40 it stays invisible to the UI, 0.40–0.909 can be proposed at most, and only 0.91 and above is eligible for active use. Identity and health additionally require explicit evidence, an inferred routine requires user confirmation, and a single turn may extract at most six candidates.

The exclusion list is the part I would defend hardest, because it is where most memory systems quietly overreach. Temporary moods, passing names, completed tasks, reminders and to-dos, unconsented health detail, intimate, financial or location detail that was not asked for, credentials, and even a user testing the interface in another language are all explicitly not memories. Assistant guesses are excluded too — memory must come from the user’s evidence, not from the model reinforcing itself.

Writes go through a merge judge: exact type/key first, then semantic neighbours above a 0.75 floor, then a model classifying duplicate, merge, extension, contradiction or new fact, with a 0.85 similarity fence to stop easy duplicates. A contradiction never overwrites — it becomes a pending correction linked by supersession metadata, and the previous memory survives until the user confirms the replacement. Recall fuses dense similarity (Voyage 4 Lite at 512 dimensions), Postgres full-text search and person-mention retrieval through reciprocal rank fusion, at k=8 for voice and k=16 above 0.40 for the search UI. Hygiene runs on its own cadence — consolidation, weekly quality control, monthly compaction, quarterly tidy, type-specific decay — but hygiene is never permission to rewrite the person: evidence links, confirmation state and user corrections survive all of it.

Two ASIS App Store frames: the Memory dossier listing identity and preference rows, each captioned with whether the user said it or corrected it and how long ago, and a tilted device showing an “ASIS would like to confirm” card offering Confirm, Edit or Not quite
Every memory names its own source, and an inference has to ask — the confirm card is the moment the system admits it is guessing

A plan that cannot lie about time

Plan is where automation becomes inspectable. It merges first-party ASIS events, to-dos and reminders with Apple Calendar and with mirrored Google and Outlook events across Today, Week and Month, and it keeps provenance intact the whole way: a mirrored event is never silently promoted into an ASIS event, and ASIS metadata layers on as an annotation rather than mutating the provider’s content. Every event carries one of fourteen intents and a provenance flag, and a user-set intent locks out silent AI replacement — inference outranks nothing the person has stated.

Apple Calendar stays on the device through EventKit. A relevant slice enters the plan and voice context, but there is no server-side copy of anybody’s Apple calendar account, which is a real constraint to work under and the right one. Google and Outlook are mirrored server-side across a rolling window from 60 days past to 400 days future, through Google’s updatedMin change probe and Microsoft’s calendarView delta link with tombstones, on a roughly 20-minute loop with a two-minute debounce, single-flighted per account behind a five-minute cross-machine lease.

The synchronisation rule I am most attached to is a deletion rule. Mirrored events are swept only after a successful per-calendar full listing — never on a delta-only tick. The alternative is an assistant that reads a delta gap as intent and quietly empties a calendar to stay in sync, and no amount of interface polish recovers from that. Writes are conditional on the provider’s ETag or change key, so a lost update surfaces as a 412 that becomes a 409 the client can resolve instead of a silent overwrite. Whole-series time edits on provider-managed recurrence are explicitly refused rather than approximated. Provider notes are stripped of HTML and control characters and capped at 2,000 characters. OAuth tokens sit in a versioned AES-256-GCM envelope — version, 12-byte IV, ciphertext, 16-byte tag — behind a per-account mutex and a token-version compare-and-swap, and are destroyed on account deletion.

Then there are dates, which are never as simple as they look in a planner. All-day items are stored as UTC civil-date boundaries with end-exclusive semantics and reinterpreted in the device’s own calendar, and every daily key — recurrence, usage, widget validity — is built against a pinned Gregorian calendar so a Thai phone running the Buddhist era does not shift a user’s week. For a life-planning product that is not pedantry; a beautiful schedule that expires on the wrong local date is simply broken.

A delta gap is not a deletion. The mirror only sweeps after a full listing succeeds — anything less is an app that empties your calendar to stay in sync.

Failing closed

ASIS holds no push tokens and runs no push infrastructure. The backend grounds a briefing in the real schedule and a model phrases it, but the notification is scheduled and fired locally on the phone — which is why the privacy policy can state plainly that there is no notification-device token to hold. A morning briefing runs on a three-day horizon at 8:00 by default, a pre-event briefing two minutes ahead, and if generation fails the client still schedules a truthful static fallback assembled from plan data rather than saying nothing.

Widgets never touch the network. The app writes a versioned JSON snapshot into the App Group carrying a schema version, a hash of the owning account, an authorisation bit, the local Gregorian day and a validity that expires at local midnight; the extension contains no auth SDK at all. A sign-out, an account change or a lost entitlement writes a redaction envelope before the widget can render again, and the Lock Screen listen intent rechecks authentication and entitlement before the microphone can start. Under uncertainty the widget shows nothing, because the failure mode of guessing is showing one person’s day to another.

What a spoken turn costs

Realtime AI bills by the second, so the product carries a ledger. Provider usage — model tokens, transcription minutes, synthesis characters, room time, embeddings, reranking — is converted into internal micro-dollars against pricing constants held in code, and charged against a daily budget of 350,000 microUSD for Pro and 590,000 for Max, reset atomically at the user’s local midnight. None of that arithmetic is ever shown as dollars; the user sees an allowance, because a person mid-sentence should not be doing unit economics.

The accounting deliberately fails open. If the ledger itself errors, the conversation continues — stranding a paying user on an infrastructure fault is a worse outcome than an unbilled turn. Briefings are debited but ordinary plan budget will not refuse them, and a separate 5× ceiling exists for abuse rather than for ordinary heavy use.

Packaging falls out of that. ASIS Pro is $14.99 a month or $159.99 a year and Max is $24.99 or $269.99, and the two tiers differ by allowance, not by features — segmenting a life assistant by capability would mean shipping a version that deliberately forgets things. There is no free tier and no free trial, which is an uncomfortable decision for a consumer app and an honest one: every conversation costs real money at the instant it happens. Reaching the limit pauses conversation only; the plan, the calendars and the memory stay open, because the user’s own data is not the thing being metered.

The asis.life home page: a pixel-art bonsai on a cream dotted field beside the headline “The Life Assistant you talk to.” with the subhead about planning your calendar in 22 languages, and buttons reading Get ASIS and See how it works
asis.life — positioning, the language list, the trust promises and the packaging, written and shipped alongside the app

Build

Minimum iOS
17.0
Build at submission
0.8.8 (8)
Stored voice recordings
0
Push tokens held
0
ASIS Pro — monthly, annual
$14.99 · $159.99
ASIS Max — monthly, annual
$24.99 · $269.99

Auditing my own product

Before writing any of this down I reconciled ASIS against itself — the live site and legal pages, the running code, and my own planning notes — with implementation outranking marketing and marketing outranking my notes, and I kept the disagreements instead of resolving them quietly.

It found real drift, and the useful findings are the uncomfortable ones. Both the website and the code claim 22 speech languages and they are not the same 22: the code carries Czech, Romanian and Russian where the site lists Hebrew, Norwegian and Finnish. The terms of use say iPhone and iPad; the Xcode target is device family 1, iPhone only. Entitlement enforcement and several retrieval flags default off in code as a dark-launch posture, which means “what the code can do” and “what production does” are two different sentences and the reference is careful to say so. And the backend has a genuine test spine — calendar delta and write series, memory policy and judges, subscription webhooks, briefing ceilings — while the iOS workflow is still a placeholder, which is exactly the wrong way round for the half of the product a reviewer actually touches.

Not every mismatch is a defect. The interface presents twelve memory sections over eleven canonical types because Commitments and Moments are two views of the same episodic row; writing that down as intentional is worth as much as writing the bugs down. The remediation is one owned launch registry — device support, languages, providers, tiers, prices, permissions, active flags — that the app and the site both read from, so a public claim cannot drift away from the build again. A register you keep is worth more than a number you defend.

Outcome

ASIS went to Apple in August 2026: an iPhone app on iOS 17 and later, behind asis.life with its own privacy policy, terms and support, served by a Bun API and a separate Node voice agent on Fly over Neon Postgres with pgvector — 22 canonical tables, 28 ordered migrations, and a subscription that pays for the seconds it spends.

What the project actually taught me is that the model is not the hard part of an AI product. Ten seconds of speech is the easy part. The difficulty is everything that has to be true for those ten seconds to be safe to act on: whose account this is and how the server knows without asking the client; which calendar owns the event and what happens when the provider disagrees; whether a collision is a real conflict or an artefact of a sync gap; whether a sentence deserves to be remembered at all, and who gets to overrule that; and what the product does at second sixteen when a provider simply stops answering.

Every one of those is a boundary somebody has to draw and then defend in code. Building it alone meant drawing all of them — and then writing the privacy policy that promises them to a stranger.

Next case study

02 / 11