## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Channels connect external conversations to company tasks and agent execution. > - Slack, Discord, and AgentMail already provide durable delivery and access controls. > - People also need to reach an agent from Apple Messages and send photos. > - Photon provides shared Pro DMs, dedicated numbers, and authenticated event recovery. > - This pull request connects Photon to the existing channel services. > - People can message an agent while Paperclip retains task ownership and approval authority. ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting: channel services, shared contracts, database constraints, Apps, and agent Channels UI. **Problem or motivation** Paperclip has no iMessage channel. A person cannot use Apple Messages to start a task, send a photo, or answer an agent's pending question. **Proposed solution** Add experimental **iMessage Photon** with Pro-compatible shared DMs or a dedicated Photon Cloud number per agent channel. Reuse channel admission, identity links, task generations, publication, and interaction continuation. Keep groups disabled for shared allocation. Dedicated lines support groups that an operator explicitly enables. Require a fresh linked message and a published agent response before setup completes. **Alternatives considered** Shared allocation has no owned phone number, so it reserves one project and allows DMs only. Dedicated allocation reserves one stable number. Local Mac access needs a separate deployment model. The upstream Photon Chat SDK adapter does not persist the poll mappings and send receipts required here. This change uses the lower-level SDK without adding another agent runtime. **Roadmap alignment** This extends Connected Apps and agent communication through the existing channel subsystem. It does not add a parallel tool connection or agent loop. GitHub searches for Photon and iMessage found no matching provider implementation. **Additional context** This ships behind the existing experimental channel gate. Dedicated-line release qualification remains incomplete. Real Photon Pro DMs passed task/reply, native poll, text answers, confirmation rejection, media, restart, pause, reconnect, revocation, and removal tests. An operator-supplied iPhone camera HEIC also passed the full round trip. Dedicated groups remain unqualified. See [the verification record](doc/connections/IMESSAGE-PHOTON-VERIFICATION.md) and [the implementation plan](doc/plans/2026-09-11-imessage-photon.md). ## What Changed - Add the provider catalog entry, shared setup contracts, and a forward migration. A global partial index reserves the dedicated number or shared project until its endpoint is archived. - Add Cloud project inspection, vaulted project credentials, selected-line token renewal, and a leased receiver. Persist checkpoint updates under the receiver lease. Shared project replay accepts sparse increasing sequences only after a complete recovery barrier. - Connect DMs and enabled groups to existing task generations, sender authorization, ordered delivery, and publication services. Keep each iMessage conversation on its task after completion; only explicit `/new` or `/close` releases the binding. Publish committed inbound comments live and label their human bubbles “Sent from iMessage” in both task-chat renderers. - Persist immutable text/file send identities, upload receipts, poll IDs, option IDs, per-person drafts, and canonical interaction continuation proofs. - Add source-bound file recovery, bounded HEIC/HEIF conversion, JPEG previews, and related Live Photo companion video retention. - Add the three-step setup flow and channel management surfaces with official branding. Preserve the experimental gate and existing pause/disconnect behavior. - Add interactive production-component Storybooks for setup, access, recovery, and ongoing conversations. Add provider, integration, catalog, and browser regression coverage. Document setup, recovery, supported boundaries, and qualification gaps. ## Verification - Live Photon Pro, SDK 2.1.0: linked iPhone messages create a task and receive native Codex replies in Apple Messages. Unlinked senders cannot start work. - Three real follow-ups each reopened the same completed task. Incoming bubbles appeared on its open page without reload and showed “Sent from iMessage.” The third follow-up ran after restarting the server on `4d7222110`; the agent correctly repeated its previous reply from before the restart. - Native polls after restart, sequential text drafts, required-field correction, explicit submission, approval rejection with a required reason, and native continuation passed against Photon. - PNG, text documents, synthetic HEIC, and a real iPhone camera HEIC passed in both directions. The camera photo produced a 3024×4032 JPEG preview. The native agent described it and returned the received HEIC byte-for-byte. - Pause/resume, reconnect, identity revocation, removal, `/status`, `/new`, `/close`, and stale answers after close passed live. Messages suppressed by pause did not become work on resume. Removal stopped intake and removed credential bindings. - All 304 focused tests passed on `4d7222110`. These cover Photon unit/integration behavior, both task-chat renderers, live comment hydration, completed-task continuity after restart, enabled groups, duplicate delivery, and explicit reset/close. The selected Teams completion-boundary regression also passed. Full workspace typecheck/build and token gates passed for the conversation fix; the final UI changes passed their affected typecheck/build and tests. - All 26 new Photon Storybook Playwright cases passed in light and dark themes, including the complete shared-DM setup journey and 390px mobile follow-ups. UI typecheck and the Storybook build passed. These stories use simulated Photon responses and do not replace the live evidence above. - The full chat-adapters browser suite previously passed all 39 cases. Migration checks passed, and migration 0275 applied to the isolated live instance with the earlier Photon migration already applied. - The local full Vitest run was previously interrupted by the host's embedded-Postgres shared-memory limit; it is not a full-suite pass. All 30 applicable CI checks passed on preceding head `7a5419cac`, with two skipped checks and Greptile 5/5. Head `24f8e1aae` adds an explicit required-story discovery guard to the 26 passing Storybook cases. Greptile rates this final head 5/5 with no unresolved review threads. All 30 applicable CI checks passed, with two optional checks skipped. - A repeated live send key suppressed the duplicate but returned gRPC 6 / SDK `internalError` without an original receipt. Paperclip keeps unknown delivery unresolved. This provider behavior is covered by a regression test. - See [the verification record](doc/connections/IMESSAGE-PHOTON-VERIFICATION.md) for package versions, redacted live evidence, deterministic coverage, and remaining qualification gaps. ## Risks - Dedicated group qualification remains unrun; groups are disabled for the approved Pro scope. Real iPhone camera HEIC passed transport, preview generation, agent inspection, and return. Keep the channel experimental; the dedicated-line release matrix remains incomplete. - Shared recovery and attachment aliases were verified against the live gateway. Duplicate writes currently return an error without the original receipt; unresolved sends require operator resolution. The implementation fails visibly on invalid replay ordering, a reset cursor, or changed identity. - The HEIF converter passed on macOS arm64 and in Linux CI. Windows HEIF binaries have not been executed in this work. Linux musl has no packaged converter. Unsupported conversion retains the original and reports the missing preview. - The migration adds a global reservation across companies for Photon numbers and shared projects. Paused and revoked endpoints keep that reservation until removal. - Integration touches shared channel services. Existing provider browser coverage passes; broad repository verification is recorded above. - `pnpm-lock.yaml` is intentionally excluded under repository policy. The repository bot owns lockfile updates. The additional Superagent supply-chain scan is neutral/inconclusive because these new dependencies are not yet in the committed lockfile. Its security scan passed; all required CI checks pass. ## Model Used OpenAI Codex, GPT-6 family, with reasoning, repository inspection, code execution, browser testing, and tool use. The exact served model identifier and context-window size are not exposed in this session. No sub-agents were used. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
20 KiB
iMessage Photon verification
Date: 2026-09-11. Branch: codex/imessage-photon.
Base inspected: 1c4bcff2b; updated through master ab15aff39.
Initial implementation checked: 7ada38eb7ef5dff5441f23c02131798b11d57712.
Status: experimental; Pro shared-DM live journeys verified below. Dedicated groups and the remaining release matrix are not yet qualified.
PR #13299 carries the current
CI and review results. The Photon migration is 0275_easy_dragon_man.sql, regenerated after master added its own 0274 agent-chat migration. Greptile reviewed the implementation commit at 5/5 with no
actionable comments. This record distinguishes local evidence from live proof.
Environment and versions
- Fresh worktree:
imessage-photon; separate worktree configuration, instance, database/storage home, and application port 3109. - Browser tests use a disposable local-trusted instance on port 3319 with a new database and storage home. They mock the provider/control-plane responses.
- Database integration tests use disposable embedded PostgreSQL with real channel, identity, task, attachment, publication, and interaction services.
- Advanced SDK 2.1.0; grpc-js 1.14.4; nice-grpc 2.1.17; nice-grpc-common 2.0.4; heif2jpeg 0.1.6. Local converter execution: macOS arm64.
- The synthetic HEIC fixture is generated from a solid-color 16×16 image. It has no personal photo content and does not qualify real iPhone HEIC/Live Photos.
- Production credentials, line tokens, phone numbers, and participant identifiers are absent from this record. Test numbers/IDs in fixtures are synthetic.
The primary-instance seed attempt encountered existing source schema drift
(tool_connections_transport_check missing), so the isolated worktree uses a clean
instance. The primary database was not modified. Several test starts also reached
macOS's 32-segment System V shared-memory limit. Only unattached IPC from this task's
exited browser-test databases was eligible for cleanup; running instances were not
stopped or altered.
Deterministic acceptance evidence
server/src/__tests__/photon/photon.test.ts exercises Basic Cloud authentication,
token redaction, dedicated/shared/missing allocation, immutable line identity,
Unicode multipart publication, receipt recovery, unknown sends and explicit retry,
upload receipt reuse, quota classification, per-part authorization, contiguous
checkpoint recovery, ignored event frames, cutoff history, lease loss, real local
gRPC framing/authentication, scoped state, duplicate-title poll IDs, poll creation
before a local crash, answer parsing, source ownership, image bounds, and actual
synthetic HEIC conversion.
server/src/__tests__/photon/channel.integration.test.ts composes the real channel
service with the Photon adapter and synthetic provider responses. It proves the
fresh linked-message/task/agent-publication setup requirement, restored DM reply,
echo filtering, identity reservation, explicit group enablement, authorized poll
resolution, per-person answer drafts, rejection reasons, exactly one canonical
continuation record, delayed HEIC retry after restart, attachment provenance,
quoted context, task generations, stale controls, retained pending input through
pause, group removal, and a native continuation proof for a second group person.
The checkpoint takeover test verifies the database lease and checkpoint update
share one transaction.
The native continuation test caught a JSON key-order mismatch after JSONB storage. Both the recorded answer digest and reconstructed proof now use the existing canonical hash. This is a native authorization composition test, not evidence of a live model turn through Photon.
The Photon/OpenAPI follow-up also verifies safe setup credential, quota, network, and invalid-response errors, the complete board-only inspection contract, group participant response fields, and the unchanged credential binding after rejected replacement. All 39 Photon/OpenAPI tests and the server build passed after the review fix separating provider outages from invalid setup input.
The Photon browser cases in tests/e2e/chat-adapters-ui.spec.ts cover catalog
discovery, multiple-line selection, password input, keyboard selection, vaulted
credential payload shape, setup completion, group enablement, light/dark themes,
mobile navigation/layout, and pause/resume. The surrounding suite covers existing
Slack, Discord, GitHub, Teams, and Telegram surfaces.
| Check | Result |
|---|---|
| Photon targeted tests | 31 passed, including checkpoint takeover, Live Photo companion retention, and native continuation authorization. |
| Token gates | Passed. All four gates clean. |
| Workspace typecheck | Full pnpm -r typecheck passed before and after rebase. |
| Full chat-adapters browser suite | 38 passed, including Photon light/dark/mobile coverage and existing providers. |
| OpenAPI contract | 8 passed, including mounted-route completeness, board-only inspection, and token-free response schemas. |
| Post-rebase channel/native checks | 96 passed across Photon, OpenAPI, explicit native continuation, and chat-control admission retry. |
| Native session resume | 37 passed after building the required local fake-provider binary. |
| UI Vitest project | 6,008 passed across 582 files after rebase. |
| Shared catalog project | 727 passed, including exact catalog and branding coverage. |
| Repository Vitest suite | The initial pnpm test:run overlapped edits/rebase and was stopped; it is not a final-commit pass. Fresh targeted and CI checks supersede it. The serialized route run found the missing Photon OpenAPI contract, which is fixed and passes its 8-case suite. Full gate status is recorded in the linked PR. |
| Build | Full pnpm build passed before and after rebase. |
| Generated forward migration | Generated through pnpm db:generate; @paperclipai/db check:migrations passed. Disposable database migrations exercised by integration tests. |
| Native HEIF platform packages | macOS arm64 executed; other published platforms not executed. |
Local test prerequisites
The standard pnpm test:run launcher isolates PAPERCLIP_CONFIG, PAPERCLIP_HOME,
and temporary files. Direct heartbeat/continuation tests must use equivalent
isolation; otherwise the worktree preview configuration suppresses execution.
The actual runner-driver fixture also requires:
cargo build --manifest-path packages/paperclip-runner/runner/Cargo.toml --bin fake-codex-app-server
A run without that binary failed at provider startup; the complete 37-case native session-resume suite passed after building it. Catalog assertions were updated for the 42nd visible app, and the focused catalog/Browse/board-gallery tests pass. Some broad package runs encountered host embedded-Postgres startup limits during concurrent local development. These startup failures are not provider proof; inspect the linked PR for the current complete gate results.
Pro shared-DM live qualification (2026-09-12)
The operator approved Pro-compatible shared DMs with groups disabled. The live test uses the isolated instance on port 3109, a Photon Pro project, its enrolled test participant, and the participant's actual iPhone. The test source was fully seeded through the worktree CLI; the primary instance remains untouched.
Observed with SDK 2.1.0 on implementation base e556f7dbefd3ee738bde7830d69d5e30c4e96872
plus the shared-DM changes in this PR:
- Project inspection and vaulted setup succeeded against Photon Cloud's actual shared allocation. Shared credentials select the fixed shared gateway and a project-scoped identity, without inventing an owned phone number.
- At 13:16 UTC, the participant sent a fresh iMessage from their iPhone. Photon delivered it through authenticated recovery. The project-filtered event feed jumped from an empty cursor to a non-adjacent sequence; the dedicated-only adjacency check initially stopped in Attention.
- After the shared recovery fix and an isolated server restart, reconnect replayed the original message at 13:26 UTC. Paperclip discovered the exact sender but created no conversation/task while the identity was unlinked. The normal private confirmation flow then linked that identity to the isolated Board account.
- At 13:27–13:28 UTC, the fresh linked request created a task, ran the native Codex runner, and delivered the requested response back to Apple Messages.
- A native poll created at 13:29 UTC survived restart. Setup initially rejected interaction answers until the endpoint was active, deadlocking a clarifying question before final-reply qualification. Photon now permits those responses during its verified test step with the same identity, generation, and permission checks. After restart, a fresh vote at 13:33 UTC produced exactly one canonical answer and one native continuation. Its final reply arrived in Messages. A late unvote did not undo the answer. Setup then completed normally.
- At 13:35–13:37 UTC, two free-text answers were collected sequentially. Early submission stayed pending; explicit submission of both drafts resumed the native agent with both exact values. The test also corrected the missing-answer hint to identify the unanswered question rather than always question 1.
- At 13:38 UTC, the initial PNG/document import failed visibly because the shared gateway returns project attachment aliases in metadata and native UUIDs in stream headers. Shared downloads now retain authenticated source-message/chat checks and the exact alias-addressed RPC, validate the header metadata, and retain project aliases for provenance and restart. At 13:43–13:44 UTC, a fresh two-file message sent during the outage was recovered, imported, inspected by the agent, and returned as actual PNG and text-file attachments in Messages. The agent correctly identified the image and the document's verification word.
- At 13:46–13:49 UTC, a canonical
request_confirmationrejected a bare Reject reply with an actionable reason request. A correlated rejection with a reason resolved the canonical interaction and resumed the native agent, which returned the exact reason and confirmed that no further action ran. - At 13:49–13:50 UTC, a synthetic HEIC was sent through Apple Messages. Paperclip retained the 676-byte original and created a 633-byte JPEG derivative. The agent correctly described the solid blue 16×16 image and returned the original HEIC through Photon; the file appeared in Messages. This tests the real transport and converter together, but does not substitute for an actual iPhone camera photo.
- At 13:51–13:53 UTC, Pause suppressed a delivered test message without creating a task. Resume did not replay it as work; a fresh request created the next task and received a reply. Reconnect reused the vaulted credentials and preserved project/allocation identity, then completed its fresh-message/reply test.
- At 13:53–13:54 UTC, revoking the linked identity caused the next live message to
be filtered with no task or agent run. The normal private confirmation flow
restored the link. Completed tasks remained idle between fresh requests, and
/statuscorrectly reported no active task./newrequested a fresh message, and/closeclosed the next active conversation. Its late correlated answer left the old interaction unresolved and did not start another task. - At 13:56 UTC, Remove connection archived the test endpoint and its connection, cleared saved secret bindings, and stopped intake. A message sent while removed created no task. The same Photon project remained eligible in new setup. A replacement endpoint was linked normally and completed a fresh native task/reply test at 13:58 UTC. The test channel was left active.
- At 18:13–18:14 UTC, an operator-supplied iPhone camera HEIC passed the same
authorized Apple Messages conversation on code commit
fc4e4f0a3(documentation heada2a9319f3). Messages transformed the 1,432,391-byte source into a 1,132,602-byte HEIC before ingestion. Paperclip retained those received bytes and generated a 783,443-byte, 3024×4032 JPEG preview. The native agent correctly described the photo, then staged the HEIC with the same SHA-256 as the received original. Text and file publications each succeeded on their first attempt with provider receipts, and the returned photo appeared in Apple Messages. The native run succeeded and the task completed. The personal photo is not included in the repository or this report. This closes the real camera HEIC round-trip gap; Live Photo reassembly remains outside scope. - An identical published test send was repeated with its original key, exact
payload digest, and reply target. Photon suppressed it but returned gRPC 6 with
SDK
internalErrorand an empty context, saying the operation was already processed. No new bubble appeared. Contrary to the documented original-result behavior, the shared gateway supplied no receipt. A regression test preserves delivery-unknown state in this case; no text matching or new key is used. - The shared receiver now commits only after the complete ordered replay barrier. Regression cases cover sparse events, interrupted/out-of-order replay, and cursor resets without advancing the saved checkpoint. Dedicated recovery remains strict.
- All 39 chat-adapters browser tests passed, including shared setup after reload and existing provider coverage. The 20 Photon unit cases passed. An integration rerun initially hit the host's embedded-Postgres startup limit; this is a test environment failure, not a provider result.
The expanded unit suite has 22 passing cases, including shared attachment alias
ownership, header validation, and missing duplicate receipts. After merging master
and regenerating migration 0275, all 16 integration cases passed at code commit
fc4e4f0a32d35e41e56f6698404fca64cee3f32b. Full workspace typecheck, build, token
gates, and migration checks passed on that commit. The isolated instance then
restarted successfully, reported startup ready on that commit, and retained the
active shared-DM endpoint and linked identity. A broad pnpm test:run was started and stopped
when the host's shared-memory limit prevented the live isolated PostgreSQL from
restarting. Only this task's exited test database resources were removed. This
interrupted run is not a full-suite pass; current CI must qualify the final commit.
All 30 applicable CI checks passed on a2a9319f3, with two skipped checks. One
unchanged Cursor sandbox command-selection case initially exceeded its 10-second
timeout. The exact case passed locally in 735 ms, and the failed CI server shard
passed on its single rerun. Greptile rated that head 5/5 with no unresolved review
threads. The 22 Photon unit cases also passed in Linux CI, including native HEIC
conversion. Subsequent changes to this record add qualification evidence only;
the linked PR shows their current check status.
Photon's CLI manages projects and users; its terminal provider simulates chat UI. Neither substitutes for actual Cloud iMessage delivery. The local Mac initially classified the assigned number as RCS, while the participant's iPhone sent the observed iMessage. No RCS/SMS fallback was enabled.
Live qualification still required
Dedicated-line credentials were unavailable during the initial implementation. The Pro shared-DM journeys above passed; the remaining matrix must be completed before release readiness. Live inbound receipt alone is not full qualification. Record the tested commit, package versions, redacted project/line/chat IDs, participants, timestamps, and observable results when running it.
| Live case | Status |
|---|---|
| Linked DM creates task and receives actual agent response | Passed with Pro shared DMs and the native Codex runner. |
| Enabled group with two linked people preserves attribution | Disabled for the approved Pro scope; dedicated-line live qualification remains unrun. |
| Unlinked sender cannot start work | Passed for the live shared-DM probe; sender discovered, zero conversations/tasks created. |
| Inbound/outbound photos and real iPhone HEIC | Passed for PNG, text file, synthetic HEIC, and an operator-supplied iPhone camera HEIC. The real photo produced a full-resolution JPEG preview and a byte-identical return of the received HEIC. |
| Native poll and text answer resume correct interaction | Passed, including sequential drafts, incomplete submission, explicit submission, and one poll continuation. |
| Approval rejection reason reaches canonical interaction | Passed, including missing-reason correction and native continuation. |
| Restart preserves DM/group replies and pending questions | Shared DM recovery and pending native poll passed; dedicated groups remain unrun. |
| Pause/resume/reconnect/removal enforce authority | Passed for Pro DMs. Removal archived the endpoint and connection, cleared secret bindings, and stopped intake. |
| Completed turn stays idle until fresh input | Passed. September 12 correction: two successive real follow-ups reopened PHOTON-17, with no new task. |
| Provider ambiguous-send/idempotency behavior | Real repeated key suppressed duplicates but returned no original receipt. Unknown-send recovery remains an operator action; no induced network-timeout test. |
| HEIF conversion on Linux glibc/Windows and deployment packaging | macOS arm64 and Linux CI conversion passed. Windows execution remains unrun. Linux musl has no packaged converter. |
Keep this channel behind the existing experimental gate. Mocked tests, synthetic gRPC, and a visible catalog card do not establish these live results.
September 12: persistent conversation and live task bubbles
The operator reported three messages creating PHOTON-15, PHOTON-16, and
PHOTON-17. Task completion had incorrectly been treated as the end of an
iMessage conversation, and channel admission did not emit the comment event
used by an open task page. The fix preserves the latest task until an explicit
/new or /close, publishes comment activity after its transaction commits,
and labels inbound human bubbles in both task-chat renderers.
Tested the fix in the isolated codex/imessage-photon worktree on September 12,
2026 at 13:56–13:57 America/Chicago, against the operator's existing Pro DM
endpoint (99bebf95…3884) and PHOTON-17 (bd6d8379…ba15). Left the task page
open and sent two authorized messages through Apple Messages to the same Photon
conversation, waiting for completion between sends. Both appeared without a
page reload and both reopened PHOTON-17. Its bubbles showed “Sent from
iMessage”; the agent returned “PHOTON-17 live follow-up received” and
“PHOTON-17 still one conversation” through Photon. The earlier task records
were preserved as history. No live /new was sent to replace the operator's
current conversation; explicit reset, close, stale controls, duplicate delivery,
restart, and dedicated-group continuity are covered by integration fixtures.
The live server was then restarted on 4d7222110. A third message asked the
agent to repeat its previous reply. It appeared live on PHOTON-17 with its
iMessage label, and the agent returned the exact previous reply through Photon.
All 304 focused tests passed on that commit, and the existing Teams completion
boundary passed its separate regression test.
Interactive Storybook coverage lives under Connections / iMessage Photon. It uses the production catalog card, three-step channel wizard, access and management pages, and task message bubbles with explicitly simulated provider actions. Thirteen stories cover catalog discovery, agent selection, credentials, shared setup, multiple dedicated lines, missing allocation, loading, connecting, outage recovery, reconnect, identity access, and persistent task follow-ups. All 26 light/dark Playwright cases passed, including the 390px mobile layout. The credential and mobile screenshots were inspected. Run with:
pnpm build-storybook
pnpm exec playwright test --config tests/storybook-visual/imessage-photon.config.ts