I moved diary summaries and a short morning note from OpenAI to Apple's Private Cloud Compute (PCC). Counting 41 days of production logs, 65 of 614 requests (10.6%) were refused as "may contain sensitive or unsafe content," and 59 (9.6%) were rate-limited. What got refused was ordinary diary text and conversation summaries. Here are the failures this caused and how I rebuilt around them.
What I wanted to do
Liner Notes, my iPhone diary app, uses a language model for conversation summaries, a short note every morning, titles for records, and the roles of speakers in a conversation. It started out on OpenAI's API.
At the end of September 2026 I moved these to PCC, which lets an iOS app call Apple's server-side language model through the Foundation Models framework. The app pays nothing, and requests are not retained on Apple's servers. For an app that handles conversations, both are big advantages.
Counting the production logs
I noticed memos with no summary and counted the production logs (my own iPhone, 41 days).
| Requests | Failed | Refused as unsafe | Rate-limited | Daily quota | Cancelled, other |
|---|---|---|---|---|---|
| 614 | 130 (21%) | 65 (10.6%) | 59 (9.6%) | 0 | 6 |
Breakdown of 614 production requests (41 days)
Show as a table
| Item | Value | Share |
|---|---|---|
| Succeeded | 484 | 78.8% |
| Refused as unsafe | 65 | 10.6% |
| Rate-limited | 59 | 9.6% |
| Cancelled, other | 6 | 1.0% |
The refusals came with three messages.
| Message | Count |
|---|---|
| Streamed response may contain sensitive or unsafe content | 41 |
| Safety guardrail was triggered. | 18 |
| Response may contain sensitive or unsafe content | 6 |
What got refused was diary drafts and conversation summaries, nothing sexual or violent. A summary of a technical meeting was refused too. For Apple's on-device model, developers have repeatedly reported on the forums that it refuses ordinary requests too often. The same thing happens with server-side PCC.
Refusals are also slow. A success comes back in 1–3 seconds; a refusal took 7.6–12.7 seconds, because the response starts streaming and the filter stops it partway through.
Time until a response
Seconds (drawn at the middle of each range)
Show as a table
| Item | Value |
|---|---|
| Success | 1–3 s |
| Refusal | 7.6–12.7 s |
The problems this caused
Refusals and rate limits caused four problems in the app.
First, summaries went missing silently. When PCC failed, no summary was made, leaving memos with a transcript but no summary. To users that looks like a bug.
Second, a safety switch shut PCC off in production. I had a switch that disables a generation path if the app crashes in the middle of it. It counted a background task that merely expired as a crash, and on October 16 it turned off PCC entirely in production.
Third, I deleted records while over the daily quota. On the day I regenerated person profiles in bulk on a development iPhone, it made 1,158 requests and 689 of them failed with the quota error. Once the quota is hit, every request fails. The job deleted old records before regenerating them, so once they were deleted they could not be rebuilt, and the records dropped to 7.
Fourth, the morning batch hit the rate limit. The limit kicks in at 8–10 requests in the previous 60 seconds. The morning notes were generated together in the 7 o'clock hour, and in production only 3 of 14 requests in that hour succeeded.
Success rate by hour (production, hours with 10+ requests, JST)
%
Show as a table
| Item | Value |
|---|---|
| 0:00 | 83% (10/12) |
| 7:00 | 21% (3/14) |
| 8:00 | 79% (38/48) |
| 9:00 | 70% (14/20) |
| 10:00 | 53% (60/114) |
| 12:00 | 82% (31/38) |
| 13:00 | 86% (38/44) |
| 14:00 | 96% (22/23) |
| 16:00 | 97% (31/32) |
| 18:00 | 76% (25/33) |
| 19:00 | 82% (28/34) |
| 20:00 | 93% (14/15) |
| 21:00 | 97% (31/32) |
| 22:00 | 90% (54/60) |
| 23:00 | 90% (55/61) |
The success rate drops at 7:00 and at 10:00, the busiest hour. Both are hours when generation was batched.
Trying to reduce refusals
Input length made no difference between refused and accepted requests (both 873 and 2,174 characters were refused). I could not tell which words trigger a refusal, and I have not found a way to reduce refusals by rewording the prompt. So I treated refusals as something that will happen and changed the design.
What I rebuilt
| Problem | Change |
|---|---|
| Summaries go missing silently | Fall back to the on-device model. Conversations are not sent to OpenAI by policy, so on-device is the only fallback |
| A missing summary looks like a bug | Save the transcript first and add the summary later. Even if refused, the transcript is readable |
| The safety switch misfires | A background expiration is not counted as a crash |
| Records deleted while over quota | Bulk jobs at most 2–3 times a day; never run delete-then-regenerate while over quota |
| The morning batch hits the rate limit | Spread generation over time |
| No way to tell which feature is refused | Log the feature, the duration and the failure type (refusal, rate limit, quota) for every request |
When you hit the daily quota, the error carries the time it resets (resetDate), and quotaUsage shows what is left. I had not read it and was puzzled that "it still hasn't come back the next morning." The quota is reportedly per iCloud account rather than per app.
Summary
- Before putting PCC in production, measure the refusal rate on your app's real requests. In my app it was 10.6%
- Decide the fallback for a refusal in advance. For summaries, save the transcript first and add the summary later
- The rate limit kicks in at 8–10 requests in 60 seconds. Spread batch generation over time
- When you hit the daily quota, read
resetDatefrom the error. Never run delete-then-regenerate while over quota - Log the feature and failure type for every request so you can see which feature is being refused
More engineering notes
- On iOS, run background audio processing on the Neural Engine
In an iOS BGContinuedProcessingTask, CPU work ran 12-21x slower while Core ML on the Neural Engine stayed within 2x. Three causes and the fix, measured.
- Choosing a speech recognizer by benchmark alone is risky. Measure with the audio your app actually handles
On read speech Apple's recognizer looked best; on conversation its error rate was 1.8x Parakeet's. How to compare Japanese speech recognizers, with data.
- Same transcription accuracy, three times the errors in the summary
Two recognizers with the same CER produced 3x the summary errors. Why a stricter summary prompt made it worse, and how to compare by summary quality.
- I built an AI copy of myself following prior research. Whatever I fed it, agreement stopped at 62–67%
Following prior research, I built an AI copy of myself. Diary or one line, agreement stayed at 62-67%. What identifies a person is how they speak.