Makit notifies you when an agent needs input, an approval, or finishes a long turn. Three slices work together:
| Slice | Spec | When it works |
|---|---|---|
| Actionable notifications | SPEC-08 | App is alive (foreground or backgrounded with a live/recent socket) |
| Background wake | SPEC-07 | App is force-quit or long-suspended — needs APNs (see PUSH.md) |
| Activity record | SPEC-48 | Always, in-app — the durable copy of what happened |
- Status notifications — session becomes
awaiting-input,awaiting-approval, or finishes while you're away. - Actionable notifications — Approve / Deny / Reply buttons on the lock screen for
confirmActionandaskUserQuestionrequests. - Background wake — a content-free APNs ping when no device has a live socket; the app reconnects over your tailnet and pulls the real request.
- Activity — the bell in the home bar (phone) / sidebar footer (desktop): every outcome the app reported, with the error text still attached and copyable.
All session data stays on your private tailnet. APNs carries at most a generic "you have pending item(s)" alert — never message content. The Activity record is in-memory and never leaves the device except through the diagnostics log you choose to send.
An OS notification is a tap on the shoulder that then vanishes, and until SPEC-48
nothing in the product could answer "which session wanted something, and when?"
So the same judgement that decides whether to buzz your phone
(diffStatusNotifications) now also writes to the in-app record:
| Session moves into | Activity severity |
|---|---|
error |
failure |
awaiting-input, awaiting-approval |
warning |
idle/exited from running |
success |
running |
nothing (silent by policy) |
These posts are silent (StatusCenter.post(..., silent: true)): no toast, and
they do not light the unread badge — a session you are looking at already shows its
own status dot, and the OS notification is what handles "you weren't looking". What
was missing was the history, and that is what lands in Activity.
What the app tells you about its own actions (a failed worktree delete, a refused port kill, a copied URL) posts loudly instead: a toast plus a permanent, copyable row. See SPEC-48.
Not covered: which answer you gave an approval. connection.responded
carries only a request id, and threading the answer through would change the
response path SPEC-08 made idempotent — for a fact the awaiting-approval row
already implies. Deferred deliberately.
Requires a paired iPhone with notification permission granted.
- Pair the phone with a running
makitserver over Tailscale. - Grant notification permission during onboarding (or Settings → Notifications).
- Open a session, then background the app (home button / swipe up).
- On the desktop, trigger a
confirmActionfrom any installed agent extension or tool that needs approval (for Pi, this is actx.ui.confirmrequest). - Within a few seconds the phone shows a notification with Approve and Deny.
- Tap Approve on the lock screen. The agent continues without opening Makit or navigating to the session.
- Repeat with
askUserQuestion→ tap Reply, type an answer → verifyanswers[0]reaches the agent. - Idempotency: trigger an approval, tap Approve on the lock screen,
then open the app and try to approve again in the dialog → exactly one
srv.responseis sent. - Foreground path: with the app open, trigger an approval → an in-app dialog appears (no duplicate notification).
- Android: confirm action buttons and inline reply work when the app process is alive.
- Locked-screen foreground actions may require unlock on some iOS versions before the tap is delivered.
- Requests that arrive while the app is foreground are not re-fired as notifications if you background without answering (fast-follow).
Requires a real iPhone, APNs key configured on the server (PUSH.md), and a TestFlight or development build with push entitlements.
- Configure
~/.makit/push.json(sandbox for dev builds). Restart the server and confirm the log linepush: APNs sender active. - Pair the device; confirm
push.registerwas sent (Settings → Notifications shows "Background wake: registered", ormakit deviceslists a push token). - Force-quit the app and lock the phone.
- Trigger
confirmActionon the desktop. Within a few seconds the phone buzzes with a generic alert. - If the app got background time: the alert upgrades to actionable Approve/Deny; tap Approve → approval resolves on the desktop.
- If not: tap the generic alert → app launches, reconnects, presents the pending request (dialog or notification).
- Force-quit tap capture: while alive, background so an actionable notification is shown; force-quit; tap Approve from the lock screen; relaunch → queued action drains and approval resolves exactly once.
askUserQuestionwake → Reply →answers[0]reaches the agent.- Privacy: capture the APNs payload (Console.app) — no session/message content present.
- Degradation: remove
push.json→ no wake, Slice-1 only; decline push permission → nopush.register, same fallback. - Revoke:
makit devices revoke <id>→ no further wakes to that device.
| Symptom | Check |
|---|---|
| No notifications at all | iOS Settings → Makit → Notifications enabled; permission granted in app |
| Status notifications but no Approve/Deny | Request kind must be confirmAction or askUserQuestion; input has no buttons |
| Actions don't reach agent | WebSocket must be connected or reconnecting; check Connection chip |
| No wake when force-quit | push.json on server, push permission on phone, TestFlight/dev build |
| Wake but no approval after tap | Tailscale must be up on phone; server must still be running |
See also DEVELOPMENT.md for simulator E2E commands.