Hearafter is conversation memory for Apple Watch and iPhone. A one-second hold starts a bounded recording, another hold stops it, and the audio moves to the iPhone, where it is transcribed on device and turned into structured notes. In the free tier the notes are written on device too. In Premium the transcript text, and only the text, goes to a Hearafter service that asks Claude. Audio never leaves the two devices. The premise is narrow on purpose: let me stay with this conversation while the product keeps the details I may want later. It is not an ambient listener, and it records only what the microphone can hear in the room.
I started it under the working name Quick Capture as a Watch-first recorder for meetings, with a five-step path: record on the Watch, transfer to the phone, transcribe on device, normalize into notes, keep the original audio for verification. Within two days the frame had widened to everyday conversations, the name had changed, and the thing I was actually building turned out to be a system for proving that a recording exists before anything else is allowed to move or delete it.
I directed the build. AI agents wrote the Swift under that direction, and separate AI agents reviewed it.
The first hardware run proved the pipeline and broke two files
The first commit had the whole architecture in one slice, and none of it had touched hardware. The Series 7 run the next day was the first real evidence. Watch microphone to WatchConnectivity to SpeechAnalyzer to Claude to stored audio, transcript, and notes on the phone: the complete path worked with real speech.
It also corrupted two of the first three recordings. The files arrived at plausible sizes and the phone could not decode them; AVFoundation returned kAudioFileInvalidFileError, the 'dta?' code. The implementation had treated AVAudioRecorder.stop() as though it finished the MPEG-4 container synchronously, then handed the file to WatchConnectivity on the next line. Transfer moved behind the recorder's completion delegate that day. The lesson looked simple: a file existing on disk does not mean the process that created it is done with it.
Convenience kept trying to become the interface
The earliest Watch interaction used taps, and I triggered recording state changes by accident often enough that stop moved to a one-second hold, then start did too. Releasing early cancels, and the same waveform shows the hold progressing in either direction. That friction is now written into the behavior contract as part of the product, and a tap, short press, shortcut, or hardware button is not allowed to bypass it.
The Action Button was the sharpest version of the choice. The scaffold exposed start and stop as App Intents, which is the conventional setup. On the Ultra 3 I decided the button should launch Hearafter and nothing else, because a hardware button that starts a recording is a way to start one without meaning to.
A call that set the stakes without explaining itself
I used the Watch during a call with the IRS about a business EIN. The phone was on speaker; the Watch only had to record the room. The return call ran roughly twenty-five minutes, and when it ended there was no usable recording in the app.
I never established the cause. The missing capture did not leave enough behind to diagnose, and I am not going to attach a mechanism to it in hindsight. What it did was change the category of failure. A recorder for conversations cannot treat a lost file as an ordinary failed operation, because there is no retry button for the original call. The product exists to take the burden of holding details off a person, and failing at that exact moment hands the entire burden back after it is too late to do anything with it.
“No audio captured,” and 112.6 seconds that said otherwise
The next controlled test was about two minutes of clearly audible dictation, and the Watch display slept at least once during it. Stopping produced “No audio captured.”
The obvious explanations were that background capture had ended when the wrist went down, or that the voice was too quiet for the dead-air gate. Pulling the failed file off the device contradicted both. The failed file held 112.6 seconds of coherent, continuous speech, including speech after the display slept, at a mean level around -35.1 dB and a peak near -9.3 dB, comfortably above the voice threshold. It had AAC payload but no final MPEG-4 moov atom, and its phone-side sidecar reported zero duration, which is the signature of the retry path rather than the normal finalized transfer.
So the Watch had not stopped recording. The audio existed and continued through wrist-down execution. Something had gone wrong with who owned the file.
A retry queue that could delete the file still being written
The best-supported reconstruction goes like this. There is no runtime trace of the sequence, so it stays a reconstruction. Live recordings and retryable recordings shared one directory, Documents/Recordings. When the Watch woke and its scene became active, the retry routine scanned that directory for anything not already in flight, found the still-open file, and queued it. WatchConnectivity copied the unfinished container, delivered it, and deleted the pathname on success. AVAudioRecorder kept writing to the unlinked handle, so the sound was real and the path was gone by the time I stopped.
The fix was not a longer wait. It was ownership. A new recorder writes only into Documents/InProgress, which the retry path never scans. A file is promoted to Documents/ReadyToTransfer only after the completion callback has arrived and AVAudioFile can open it with a positive frame count. WatchConnectivity queues from that directory alone and deletes only there, only after confirmed delivery. Anything interrupted, unreadable, or uncertain moves to Documents/Recovery and is never automatically deleted or classified as silence. The elapsed timer stopped reading the wall clock and started reading the recorder's own audio time, so the screen cannot imply progress the recorder did not make.
The dead-air rule got the same treatment. The gate had discarded a finalized file when its meter history showed less than half a second above threshold. That rule now applies only when metering covered the complete capture. If a lifecycle gap interrupted sampling, the absence of samples cannot prove the room was quiet, and the recording is kept. That coverage rule is in place. Whether the tally underneath it was honest is a question that came back later.
Review found the three folders were not enough
The ownership model sounded correct, and an independent AI code review, separate from the agent that wrote it, found two places where ownership still went ambiguous.
The missing-callback fallback waited 750 milliseconds and then could promote a readable file to the transfer queue. A readable file can still belong to the recorder. That was the original mistake in a narrower form. The fallback now waits three seconds and moves any callback-less capture only to Recovery, never to transfer.
The retry deduplication took a snapshot of WatchConnectivity's outstanding transfers to avoid queuing a file twice. There is a gap between calling transferFile and the new transfer appearing in that snapshot, and a second retry inside the gap would enqueue the same file again. A main-actor set of locally queued filenames closes it.
Both catches changed the implementation before another physical test. The directories already had the right names. What made the model hold was checking every transition where ownership could go ambiguous.
The repair survived the recording that had broken it
The next controlled run used a fictional insurance-claim script written as an oracle: a named adjuster and contractor, an address, a corrected appointment time, a claim number, dollar amounts, instructions, deadlines, decisions, open questions, contact details, and a surname the speaker was unsure of. It was single-speaker, close-range, scripted dictation, designed to include Watch sleep, and it ran 2:31. The file reached the iPhone as playable audio, transcribed, and produced notes that retained the corrected time, the claim number, the amounts, the deadlines, the decisions, the contact details, and the uncertain surname as uncertain.
A later speakerphone conversation also completed through the same path, and its transcript got names and relationships wrong because the source was quiet and overlapping, and those errors carried straight into the notes. I keep that result next to the fixture because together they separate two claims the product must never blur. Hearafter preserved the audio. The interpretation was only as good as what the microphone heard, and a scripted dictation is a narrower thing than a conversation.
Correct source produced the wrong Watch
Between those tests the Watch appeared to regress. Taps started recordings again and dead air was being saved, behaviors that had been removed from source days earlier. An old build artifact had been installed. At one point the iPhone showed Build 21 while the Watch showed Build 16, in the corner labels I keep visible during testing.
The design was already correct, so the fix went into the release process. Build numbers move together across the iPhone, Watch, and widget targets. Every install starts from a fresh signed artifact, reads the version from the built bundle, installs the Watch explicitly by identifier, and queries the installed version on both devices before a build is called current. Installing the iPhone app, which embeds the Watch app, does not prove the Watch received it.
The iPhone started recording too
Widening the premise from meetings to everyday conversations meant Watch ownership could no longer be the price of admission. Native iPhone recording now enters the same local library and processing path as a Watch recording, and the phone version of the interaction preserved the intent without copying the Watch literally: starting still requires the one-second hold, and stopping is an immediate tap, because ending a recording that is already running should not have friction.
The first iPhone dead-air gate trusted the live meter and let an empty recording into the library on hardware. The replacement decodes the finalized file into 33-millisecond windows and requires half a second of accumulated audio at or above -40 dBFS before persistence. Launch recovery runs the same analyzer, so relaunching the app cannot become a second path around the rule.
The second review found the same mistake in several disguises
Build 37, on August 13, was the first paired install where both devices behaved. The next day a second independent AI review read the whole worktree, and the one defect I would call serious was on the iPhone, in code that had never had the Watch's history. When the recorder's finish delegate reported success == false, the controller deleted the file. A false flag from the recorder is not evidence about the audio; the file can hold most of a real conversation with a bad final write. The Watch already knew this, and probed readability on a failed callback before routing to Recovery. The phone controller, written later, trusted the signal.
The smaller findings were the same shape. On the Watch, the dead-air tally added a fixed 33 milliseconds of voiced time per meter sample no matter how long the loop had actually slept between samples, so if watchOS stretched the interval, a short quiet utterance could be counted at a fraction of its real length and deleted. That is the exact false rejection the coverage rule was written to prevent, hiding under the rule. The iPhone's elapsed timer read the wall clock, the pattern the Watch had already banned. And a Watch process that died mid-recording left the Smart Stack widget showing a running “Recording” timer indefinitely, a reassuring signal for a capture that had ended.
The serious defect and three others went into Build 38; the rest, including the tally, into Build 39. The tally now accumulates real inter-sample time capped at the coverage limit and takes one final sample at stop so the last word is counted; the phone timer reads the recorder; the Watch clears stale widget state at launch. The analyzer that decides deletion on the phone gained fixtures for a kept conversation, silence, sub-threshold sound, and an unreadable file, which it did not have when it was first trusted with the decision. On Build 39 one Watch capture of about 48 seconds went end to end on the physical pair, which is one capture, not a matrix.
The keys could not ship, so the product went on device
Until August 15 the notes were written by Claude on a key I had entered by hand. That cannot ship. A key bundled in an app is public within minutes of release, both providers treat keys as server credentials, and one leaked shared key turns the feature off for every customer at once when it is revoked.
So the app that ships writes its notes on device, with Apple's models. Three real recordings went through that path on hardware. One of them, an 89-second banking conversation, came back with the generic “couldn't create notes.” Two builds later every failure had its own copy and the underlying error was persisted in the recording's sidecar: Apple's on-device guardrail had declined ordinary banking content as unsafe. The identical transcript, prompt, and shape produced full notes on my Mac's copy of the same model, whole and in every one-sentence slice. The difference was the phone's beta guardrail, and there is no looser setting. The response was not a workaround. A declined passage is split and retried in halves, whatever survives is kept in order, omissions are counted, and the notes carry a plain line saying they may be incomplete. On the next pass the guardrail declined the second half; the app kept the first and said so.
Claude did not leave the product. It moved behind a service: the on-device app is the free tier, and Premium's one feature is notes written by Claude through a Hearafter endpoint that holds the key. That change retired three absolutes from every customer surface in one pass: “made on your devices,” “no server,” and “nothing leaves your devices.” The claim that survived, checked against the shipped service rather than against intent, is that audio never leaves the devices.
The tests were right and the deployment was wrong
The service is one endpoint on Vercel with App Attest in front of it: a Secure Enclave key attested with Apple, verified server-side, and then used to sign every request. Before it touched hardware it had passed its tests, an independent AI review, and a live smoke test that returned complete notes for two real transcripts.
The first attempt from my iPhone reported “Device registered” and, immediately after, “The service didn't accept this device's signature.” Nothing cryptographic was wrong. Register and notes were separate serverless functions with separate memory, so a registration written by one could never be seen by the other. The in-memory store that passed every test and a full review was structurally incapable of working in the deployment it shipped to. A shared store fixed it. A second review of the result found the same shape once more: the assertion counter was check-then-set, so one assertion could be served many times under concurrency. It is now an atomic single-use claim with a concurrent test that proves exactly one is served.
I keep this beside the Watch story because it is the same defect at a different altitude. A passing test and a clean review are signals about the code. They are not proof of the system in the topology it actually runs in.
What the physical pair had established was one path: a 2:31 scripted Watch recording delivered as playable audio, transcript, and detailed notes; a 48-second capture on the post-review code; the only copy retained whenever finalization or transfer ownership is uncertain; the installed Watch version matching the source under test; three real recordings through on-device notes. Native iPhone capture entered the same library on hardware. It did not establish a reliability claim. Twenty builds of interaction work since had been judged in the simulator, not in a hardware pass. Long sessions with repeated sleep and wake, interruptions, route changes, distant-room speech, and multi-speaker conversation were unproven as a matrix. The Watch microphone at conference-room distance was unproven, and if it turned out weak that would narrow the Watch case and raise the importance of the phone without invalidating nearby capture. The IRS call remained an observed loss with no established cause, and the later mechanism does not get borrowed to explain it. Hearafter was installed on two devices I own and had not been submitted to the App Store.
The most durable thing the month produced is a rule. A signal that the system generates about a recording, whether a completion callback, a meter history, a loop cadence, a success flag, a widget's last known state, a passing test suite, or a file's presence in a directory, is not proof about the recording. Only the file is. Every serious defect that month was some component trusting a signal instead.
