{"ok":true,"content":"# Personal AI Messaging Bridge Reference Architecture\n\nStatus: portable implementation and validation blueprint\nAudience: engineers building an operator-controlled personal messaging agent\nReference date: 2026-08-12\n\n## 1. Scope\n\nThis document specifies a local-first bridge that lets an AI assistant read,\nreason about, propose, approve, and send messages over iMessage, SMS, and MMS.\nIt covers text, attachments, animated GIFs, exact group audiences, native\niMessage replies, multi-topic memory, durable work, an observation-only personal\nmessage replica, and an authenticated operating dashboard.\n\nThe bridge is safety infrastructure, not a prompt wrapper. Provider I/O is\npermitted only after durable identity, audience, freshness, approval, and\nidempotency checks. A recreation is complete only after the validation gates in\nthis document pass against the installed artifacts and owner-only canaries.\n\n## 2. Required behavior\n\n1. Use an AI-owned iMessage account as the normal outbound identity.\n2. Treat carrier SMS/MMS as an explicit policy fallback, never a silent fallback.\n3. Permit outbound provider calls on one configured host only.\n4. Represent people, endpoints, physical threads, topics, tasks, proposals, and\n   deliveries as separate objects.\n5. Allow many topics in one physical thread and one topic across several\n   audience-scoped threads.\n6. Bind every outbound proposal to exact normalized participants, final text,\n   final media bytes, optional native reply target, source context, and expiry.\n7. Require authenticated owner approval for external recipients when the\n   external approval gate is enabled.\n8. Make late, duplicate, ambiguous, expired, and superseded approvals fail closed.\n9. Revalidate audience and freshness immediately before provider I/O.\n10. Never automatically retry an ambiguous send.\n11. Read actual attachment bytes. A blank text body does not mean an empty message.\n12. Send GIF/media bytes as attachments, not human-visible URLs.\n13. Keep personal-account replica input observation-only and incapable of action.\n14. Preserve exact processing state across crashes and restarts.\n15. Expose conversations, topics, memory, work, approvals, deliveries, policies,\n    health, and audit evidence on an admin-only dashboard.\n16. Separate private owner-control delivery from conversation delivery. An\n    approval prompt sender must not accept a route, chat ID, or caller-selected\n    audience, and conversation senders must reject approval-control payloads.\n\n## 3. Reference services and responsibilities\n\nThe pattern is vendor-replaceable. The reference stack uses these categories:\n\n| Component | Reference option | Responsibility |\n| --- | --- | --- |\n| Primary message transport | Apple Messages on an operator-controlled Mac | iMessage read/send, exact chat IDs, attachment storage, native replies |\n| AI messaging identity | Dedicated Apple Account | Keeps assistant-authored messages distinct from the operator's identity |\n| Local bridge | Python service plus signed narrow macOS helpers | Routing, policy, persistence, approval, transport attestation |\n| Durable state | SQLite in WAL mode | Transactional source of truth |\n| SMS/MMS fallback | Twilio Programmable Messaging | Explicit one-to-one fallback where iMessage is unavailable or disallowed |\n| Group MMS fallback | Twilio Conversations | Multiparty carrier messaging when explicitly selected and supported |\n| Optional webhook transport | Make.com | Stateless webhook forwarding only; no AI reasoning or durable authority |\n| AI workers | A tool-capable model runtime | Classification, drafting, media interpretation, and task execution |\n| Admin web app | Small server-rendered app on Cloud Run | Authenticated dashboard, controls, and architecture page |\n| Public edge | Firebase Hosting or equivalent | TLS, routing, and public architecture delivery |\n| Export storage | Cloud Storage or equivalent | Read-only dashboard JSON, policy snapshots, and public Markdown |\n| Personal-message replica | Apple-silicon macOS VM using Virtualization.framework | Read-only observation of a second Messages account |\n| Monitoring | Local heartbeats plus dashboard/inventory exporters | Detects stale pollers, workers, queues, VM state, and deploy drift |\n\niMessage, Twilio, and Make.com are transports. They do not own identity,\nconversation memory, approval state, duplicate suppression, topic assignment, or\nfallback decisions.\n\n## 4. Trust boundaries\n\n### 4.1 Owner channels\n\nConfigure a small allowlist of authenticated owner endpoints. Only messages from\nthose endpoints, an authenticated admin session, or another explicitly defined\nowner channel can authorize disclosure, approval, policy mutation, spending, or\nexternal action. A participant claiming to be the owner is not authorization.\n\n### 4.2 External contacts\n\nExternal participants can supply conversational input but cannot approve a send,\nchange policy, reveal private context, or grant new authority. Share only facts\nwhose audience label permits disclosure to the exact target thread.\n\n### 4.3 Workers\n\nWorkers receive leased, bounded context and may create immutable proposals.\nWorkers cannot call Messages, Twilio, webhook transports, or approval mutation\nAPIs directly. Every action-capable API verifies the active lease.\n\n### 4.4 Personal-account VM\n\nThe VM can export bounded recent observations through a private shared directory.\nIt has no send endpoint, worker launcher, approval resolver, policy mutation, or\ncredential path to host outbound transport. Imported events set\n`observation_only=true` and cannot wake a worker.\n\n### 4.5 Public and admin web surfaces\n\nPublic pages contain only generic architecture. The dashboard, raw message data,\nmemory, policies, approval controls, health evidence, and implementation paths\nrequire an admin session and CSRF protection.\n\n## 5. Domain model\n\n### 5.1 Identity and endpoint\n\nAn identity is a person or agent. An endpoint is a normalized phone number,\nApple ID, provider identity, or other address owned by that identity. Store trust\nlevel, verification provenance, and active dates separately from display names.\n\n### 5.2 Physical transport thread\n\nA transport thread is a provider conversation such as an iMessage chat GUID, a\nTwilio Conversation SID, or a direct endpoint route. It stores:\n\n- transport and provider thread ID\n- complete normalized participant set and audience hash\n- capability flags such as media, native reply, and exact-group creation\n- provider and local revisions\n- health and outbound-block state\n\nA physical thread is not a topic.\n\n### 5.3 Topic\n\nA topic is logical context. A thread can carry multiple topics, and a topic can\nappear in several exact-audience projections. Topic links record confidence,\nsource, state, and last-active time.\n\n### 5.4 Message\n\nA message is an immutable provider observation or outbound attempt. It contains:\n\n- event ID, provider GUID, and physical thread\n- direction, sender endpoint, text, and provider timestamp\n- message kind, reaction relation, edit/retract state\n- native `reply_to_guid` and thread-originator relation\n- attachment links and immutable content hashes\n- topic links with one optional primary topic\n- observation-only and provenance flags\n\n### 5.5 Job\n\nA job is a durable, leased attempt to process one normalized event. Jobs are\nordered per physical thread. Long-running topic work may proceed independently,\nbut conversational release remains serialized by thread and outbox.\n\n### 5.6 Proposal\n\nA proposal freezes one candidate outbound:\n\n- exact physical thread and audience hash\n- topic and source message dependencies\n- final text and content-addressed media asset\n- optional exact native-reply target GUID\n- intent key, freshness class, and validity window\n- deterministic payload hash and idempotency key\n\n### 5.7 Approval and delivery\n\nAn approval prompt references exactly one immutable proposal. A delivery record\nreferences the approved proposal and captures provider evidence. Neither object\ncontains a mutable draft.\n\n## 6. Transactional schema\n\nUse one SQLite database in WAL mode with `synchronous=FULL` for authority. JSON\nand web exports are read-only derivatives.\n\nMinimum tables:\n\n- `identities`, `endpoints`\n- `transport_threads`, `thread_participants`\n- `topics`, `thread_topics`\n- `messages`, `message_topics`\n- `media_assets`, `message_media`, `media_semantic_reviews`\n- `memory_facts`\n- `jobs`\n- `proposals`, `proposal_dependencies`\n- `approval_prompts`, `approval_prompt_messages`, `approval_events`\n- `approval_prompt_deliveries`\n- `outbox_items`, `delivery_attempts`, `delivery_reconciliations`\n- `owner_notices`\n- `media_policies`\n- `vm_checkpoints`, `component_heartbeats`, `audit_events`\n\nRequired uniqueness includes provider GUIDs, normalized inbound event IDs,\nproposal idempotency keys, proposal payload hashes where appropriate, approval\nprovider events, prompt message GUIDs, one prompt-delivery claim per prompt, and\noutbox proposal IDs.\n\nNever store a reusable plaintext lease or delivery-claim token. Store a hash and\nreturn the secret only to the in-memory claimant.\n\n## 7. Ingress\n\nIngress performs bounded work:\n\n1. Parse the provider payload or read new Messages rows from a cursor.\n2. Normalize transport, exact thread, full participants, sender, relations, and\n   attachment metadata.\n3. Copy readable media into a private content-addressed store.\n4. Insert the immutable message idempotently.\n5. Advance the cursor with replay-safe semantics.\n6. Enqueue at most one job if wake policy allows it.\n7. Return immediately.\n\nBodyless relations are retained. GIFs, images, reactions, and replies commonly\nhave little or no plain text.\n\nOn macOS, keep the Messages privacy authorization boundary explicit. The\nalready-authorized reader process should own every direct Messages database\nopen and expose only bounded, named, read-only operations through a private\nmode-0600 local socket. Typical operations are maximum row ID, bounded rows,\nexact-chat metadata, reply-target lookup, and attachment evidence. The ordinary\nbridge must not fall back to opening the protected database itself, and the\nreader interface must expose no send, approval, policy, or worker operation.\n\nAttest the reader's loaded code version in its heartbeat. Compare hashes of the\nloaded bridge/core bytes with the installed artifacts, hot-reload safe read and\nnormalization modules when they change, and mark health degraded on any mismatch.\nOtherwise a long-lived privacy-authorized process can continue executing the\nold routing architecture after a deployment.\n\nWake policy should include direct questions, an explicit assistant mention, a\nnative reply to the assistant, owner commands, or action-worthy media. Ambient\nreactions and duplicate provider shadows are stored without spawning workers.\n\n## 8. Topic and context assembly\n\nTopic assignment precedence:\n\n1. Topic inherited from a native reply target in the same physical thread.\n2. Explicit topic language or stable task identifiers.\n3. Referenced entities and open work.\n4. Recent continuity in the same physical thread.\n5. A classifier result with persisted confidence and rationale.\n\nNever inherit a topic through malformed reply metadata across physical threads.\n\nWorker context contains:\n\n- bounded recent physical-thread messages\n- reply, reaction, edit, and attachment analysis\n- all active topic links for that thread\n- selected audience-safe topic memory\n- current jobs, proposals, and unresolved questions\n- global operator preferences permitted for that audience\n\nMemory facts store provenance, confidence, expiry, and audience policy. A fact\nfrom a private thread is not exposed to a broader audience merely because the\ntopic label matches.\n\n## 9. Scheduler and crash model\n\nUse a durable job queue with:\n\n- one running conversational job per physical thread\n- a short per-thread quiet window that supersedes older queued conversation\n  events with the latest event while retaining every normalized message\n- bounded global worker concurrency\n- claim tokens stored as hashes\n- heartbeat and lease expiry\n- explicit success/failure completion\n- operating-system process IDs only as observability, never authority\n\nEvery proposal and transport entrypoint checks the current job lease. When a\nlease expires, a worker cannot propose, approve, send, or finish successfully.\nBind a worker proposal to its source message revision, not the thread revision\nobserved later when drafting finishes. A worker that started before newer\nsubstantive context must therefore fail freshness checks even if it read a newer\ncontext snapshot while completing.\n\nMissing worker output is a recoverable queue condition. Retry the same event at\nmost once with a fresh run identity and instructions to detect partial prior\nwork. If a newer event is already queued for the same physical thread, revoke\nthe stale worker and attach its unanswered message to that successor's bounded\ncontext instead. The successor must cover both requests before the recovery is\nconsidered resolved. Exhausted recovery remains a durable visible failure.\n\nAn expired in-flight provider attempt is not put back in the queue. Mark it\n`ambiguous_delivery` for operator reconciliation because the process may have\nsent before crashing.\n\n## 10. Freshness and supersession\n\nProposal freshness uses the physical-thread revision, topic revision, source\nmessage, dependencies, validity window, and freshness class.\n\n- `conversational`: superseded by substantive movement in the physical thread.\n- `threaded`: tied to an exact older message; unrelated topics may not invalidate\n  it, but relevant topic movement does.\n- `standalone`: survives unrelated conversation while explicit preconditions\n  remain true.\n\nWhen new relevant context arrives, atomically mark the old proposal\n`superseded`. Generate at most one replacement after a quiet period. A delayed\napproval of the old prompt records the owner event but enqueues nothing.\n\n## 11. Approval protocol\n\nThe approval gate is a durable ledger, not conversational inference.\n\n1. Create an immutable proposal.\n2. Queue an approval prompt in a private owner conversation or dashboard.\n3. Atomically claim prompt delivery once.\n4. Bind the actual prompt provider GUID to the proposal.\n5. Accept a control reply only if it is a unique, post-prompt owner event.\n6. Atomically claim the proposal and enqueue one outbox item.\n\nResolve the final transport-specific route before hashing the approval\nenvelope. Hydrate any route-derived session, route code, workflow, display, and\nexact-chat metadata so the release round trip reproduces one canonical payload.\nKeep a true payload or audience mismatch fail-closed, but classify the rejection\nas a typed pre-submit failure and record only the mismatched field names. Do not\nturn harmless route hydration into an ambiguous delivery or weaken content and\naudience binding to avoid the mismatch.\n\nNatural shorthand such as `YES`, `NO`, or `WHY` applies only to the one focused,\nunexpired prompt in that same private owner conversation. A native reply to an\nolder prompt can resolve that exact prompt by GUID. Duplicate provider events\nreturn their prior result and cannot advance to the next queued approval.\n\nAny intervening private-owner turn clears ambiguous shorthand focus. Human codes\nare a diagnostic fallback, not the normal interface.\n\nApproval prompts and status notices use iMessage or the authenticated dashboard.\nDo not silently send them by carrier SMS.\n\nTreat approval delivery as a separate control-plane capability. Its API accepts\nconfiguration, immutable control text, and optional immutable media only. It\nmust not accept a route, chat ID, reply target, or caller-selected recipients.\nDerive exactly one configured owner address; zero or multiple addresses fail\nclosed. Send text through an address-bound direct-iMessage primitive and media\nthrough a structurally validated one-address vCard primitive. Neither may\nsearch for, select, focus, or submit through the currently open Messages\nconversation. Require one unique post-baseline provider row with the exact\nowner audience, canonical text, media digest when present, zero error, terminal\nsent state, chat ID, and provider GUID before binding the prompt.\n\nKeep the approval system route as metadata only and delete any inherited\nprovider conversation ID, participant binding, or group-creation flag whenever\nit is loaded. If the private address-bound capability is unavailable before\nsubmission, queue the prompt on the authenticated dashboard. Do not fall back\nto carrier SMS or to a generic conversation sender.\n\nEnforce the separation again at every data-plane boundary. Direct SMS, carrier\nAPI, Group MMS, relay, direct/group/new-chat iMessage, generic Shortcut, and\ngeneric Messages-helper senders must classify and reject approval-control\npayloads before provider I/O. Only the private owner-control wrapper may invoke\nthe lower-level address-bound primitive with control content.\n\nPersist the provider baseline and owning process ID inside the one-shot prompt\nclaim. A replacement worker may recover an orphan only after proving the prior\nprocess is gone and inspecting the complete bounded post-baseline provider\nwindow. Reconcile one exact prompt row without sending, requeue only on proven\nabsence, and hold on multiple matches or an incomplete inspection window.\n\nPreflight the iMessage read/attestation capability before atomically claiming a\none-shot prompt delivery. A preflight failure proves that submission has not\nstarted, so retain the same immutable proposal and expose it on the authenticated\ndashboard. Reserve `ambiguous` for failures after provider submission may have\nbegun; a known pre-send permission failure must not consume the claim or block\nthe approval queue indefinitely. Release a pre-submit claim only with its exact\none-use token and a typed transport error that explicitly attests submission was\nnot attempted; ambiguous failures remain claimed until provider evidence or a\nseparate provider-absence reconciliation resolves them.\n\nTreat signed helper booleans as a strict wire contract. Accept native JSON\nbooleans and platform number objects only when serialized as exact `0` or `1`;\nreject strings, missing fields, and generic truthy values.\n\nA provider may store an approval caption and media preview in one message row.\nTreat that row as satisfying both roles only after exact owner audience,\ncanonical text, one attachment, sniffed content type, attachment digest,\nprovider GUID/time, zero error, and sent/delivered state are verified. If a\nnative owner reply arrived while that delivery was still ambiguous, retain it as\nunmatched. A no-send reconciliation may later bind that same persisted event\nonly when it is an exact reply to the verified prompt GUID, arrived afterward,\nstill targets the focused pending proposal, and remains fresh. Atomically update\nthe original event and enqueue one outbox item; never synthesize an approval or\nsend the prompt again.\n\nApproval acceptance and provider delivery must share one observable lifecycle.\nEvery terminal `failed_before_send` or `ambiguous_delivery` outcome inserts one\nidempotent owner-notice record in the same authority database. That record is\nimmediately dashboard-visible and is independently delivered through private\niMessage. The notifier has its own lease, baseline, and exact provider\nattestation so a notifier crash cannot create either silence or duplicates.\n\n## 12. Outbox protocol\n\nThe transactional outbox is the only path to provider I/O.\n\n1. Claim one queued item with a short lease.\n2. Re-read proposal, audience, thread, policy, freshness, media hash, and reply\n   target inside the claim transaction.\n3. Revalidate the exact provider chat immediately before I/O.\n4. Renew the hashed send lease while the same bounded transport process remains\n   alive; stop renewal before terminal completion.\n5. Perform one provider operation.\n6. Persist provider GUID, attachment hash, participant evidence, and terminal\n   state while the lease remains valid.\n\nTerminal distinctions:\n\n- `sent` or `delivered`: provider evidence confirms success.\n- `failed_before_send`: evidence proves no provider submission; consume the\n  approval and require a fresh proposal before any retry.\n- `ambiguous_delivery`: submission may have occurred; never auto-retry.\n- `cancelled`: proposal became stale or policy denied it before provider I/O.\n\nAn ambiguous item may be corrected to `sent` only by a no-send reconciliation\ntransaction backed by one unique post-approval provider emission. Exact\naudience, transport, canonical text shape, media hash, every provider GUID, zero\nerrors, and terminal sent/delivered states must all match. Preserve the original\nambiguous attempt in the audit trail, append a reconciliation record, bind the\nprovisional audience thread to the verified provider chat, and never claim a new\nsend lease.\n\nAn independent watchdog expires transport leases even when no later send is\nattempted. It marks the send ambiguous, consumes the approval, and queues the\nowner notice. Do not rely on a future outbox claimant to discover a crashed\ntransport.\n\nThe watchdog must distinguish a crashed worker from a slow but live bounded\nhelper. Lease renewal uses the original in-memory claim token; no other process\ncan prolong the attempt. A static lease shorter than a helper's maximum runtime\ncreates a race and is invalid. Failure completion and its idempotent owner notice\nmust commit atomically, with compatibility mirrors forbidden from creating a\nsecond notice.\n\n## 13. iMessage transport\n\nApple does not provide a supported public server-side iMessage send API or web\nclient. The reference design uses Messages on an operator-controlled Mac and\nnarrow signed local helpers. Expect macOS releases to alter scripting and\nAccessibility behavior; treat helper capabilities as deploy-time probes, not\nassumptions.\n\nThe host should keep the system iCloud account independent from the Messages\naccount. Sign Messages into the dedicated AI messaging account where operational\nrequirements permit it.\n\n### 13.1 Existing exact chat\n\nFor an unbound multi-person route, ask the authorized read-only reader for all\niMessage chats whose normalized participant set exactly equals the requested\naudience. Reject partial, superset, and non-iMessage matches. If several exact\nmatches exist, select the most recently active one, then bind its GUID and full\nparticipant set to the immutable proposal before approval. Preserve that binding\nthrough the approval round trip.\n\nFor a bound route, look up the provider chat by GUID, read its complete\nparticipant set, normalize every endpoint, compare with the approved audience\nhash, and send by chat GUID. Use one signed, transactional existing-chat helper\nfor ordinary direct and group conversation messages. The helper must open a\nunique recent transcript anchor, exit search, prove the intended conversation is\nselected, focus the composer, stage immutable text and typed media, revalidate\nthe chat, and submit once. Approval prompts and owner notices must never call\nthis selected-conversation capability; they use the separate address-bound\ncontrol plane described in section 11.\nBefore creating an external approval prompt, invoke that same helper in\ndiscard-only mode and independently require structured evidence that it verified\nthe exact target and immutable payload, restored the composer, and never\nattempted submission. A database-only route lookup or boolean preflight marker\ncannot authorize approval, and text-only messages must not fall back to a\nless-auditable scripting path.\nAnchor candidates must be ordinary transcript messages; exclude tapbacks,\nreactions, retractions, and system actions at both the database query and final\nvalidation boundary.\nWhen proving search uniqueness, however, count recent search-visible tapback and\nreaction summaries as collisions: the Messages UI can display both a normal\nmessage and a reaction that quotes it even though only the normal row is eligible\nto serve as the anchor.\nSearch snippets may elide a leading match and actionable controls may be parent\ncontainers, so accept only one deterministic direct-start or exact-substring\nresult and fail closed on ambiguity. Recipient-list Shortcuts are not an\nexact-thread substitute because they may select or create a parallel\nconversation.\n\nMessages may expose a pasted HTTP(S) URL as an object-replacement marker while\nbuilding a link preview. Keep the immutable text on the pasteboard until the\ncomposer is observable, restore the prior clipboard afterward, and accept that\nmarker only when it exactly replaces every detected URL. The provider may add or\nremove whitespace immediately adjacent to that marker; canonicalize only that\nseparator whitespace while still requiring the exact surrounding prose and the\nexact marker count. Count link-preview and typed-media markers separately.\n\nDerive a deterministic provider-emission manifest from the immutable payload.\nAccept either one normal row containing the complete canonical text or one\nunique, contiguous, ordered sequence whose nonempty prose and exact HTTP(S) URL\nfragments reconstruct it. Every row must be post-baseline, outbound, normal,\niMessage, on one exact chat and audience, within a short emission window,\nerror-free, and sent or delivered. Reject interleaved, reordered, changed,\nduplicated, wrong-audience, delayed, or nonterminal sequences. Persist every row\nID and GUID; do not equate one logical send with one provider row.\n\nPass the complete expected participant set for text-only payloads as well as\nmedia. A missing participant binding must fail before submission and must never\nselect an older opaque sender. Before creating an external approval prompt for\nan existing chat, run the signed helper in discard-only mode with the exact\naudience, text, and optional media; solicit approval only after that payload and\ntransport path prove ready.\n\nPreserve a real media extension and sniffed content type while staging. Collapse\nany active text selection before inserting the attachment so media cannot\nreplace the caption. Require exact canonical text plus exactly one typed\nattachment marker before submission, then attest one unique provider emission\nwith the exact audience, text shape, attachment digest/type, zero errors, and\nsent/delivered states. A successful Accessibility call alone is never proof of\nchat selection or delivery.\n\nFor a one-recipient route that does not yet have a bound chat GUID, take a\ndatabase baseline before the provider operation and accept the result only when\nexactly one post-baseline provider emission has the immutable text shape, the\nexact one-person audience, zero provider errors, and terminal sent/delivered\nstates. Zero or duplicate matches are ambiguous and must not be retried\nautomatically.\n\n### 13.2 New exact group\n\nOn macOS versions where supported scripting cannot reliably construct a group,\nuse one serialized, attested transaction through a signed narrow Accessibility\nhelper:\n\nBefore requesting approval, prove that no existing exact-audience iMessage chat\nis available and invoke the exact installed helper in a no-UI, no-send capability\nmode. Require the helper to report a current macOS Accessibility grant. The probe\nmust not request authorization, open a composer, stage content, or submit a\nmessage. Measure the grant from the same persistent process identity that will\nperform the final UI transaction. If the ordinary daemon lacks Accessibility but\nan operator-controlled automation host already has it, use a narrow broker under\nthat authorized host rather than changing TCC programmatically. Authenticate\neach local broker request, bind it to the exact staged-field and media hashes,\nreject stale or replayed requests, restage verified files privately, and invoke\nonly a fixed code-signed helper. The broker must not mutate approval state.\nNever trust a one-off CLI probe because its parent application may confer\npermissions the service does not have. A missing or unreachable live-runtime\nprobe disables new-group creation and raises operator health, while already-bound\nexact chats remain independently usable. Dashboard health must read that live\nruntime endpoint rather than recomputing capabilities in an exporter subprocess.\n\nBefore creating the approval prompt for a new group, run a second discard-only\ntransaction with the exact proposed participants, text, and media bytes. Resolve\nand verify every address chip, stage the immutable payload, verify it, then\ndelete the draft without invoking Send. This catches payload-specific composer\nbehavior that a capability-only probe cannot detect.\n\nTreat the composer's attachment placeholder as typed media rather than message\ntext. For one attachment, require exactly one object-replacement marker, exact\ncanonical text before that marker, and only layout whitespace after it. Never\nloosen text comparison merely because media is present.\n\n1. Freeze and approve exact participants plus final text/media bytes.\n2. Consume one authenticated, content-bound broker transaction under an\n   exclusive send lock, then open a new Messages composer.\n3. Enter every normalized endpoint and require one address-bearing recipient\n   chip for each endpoint, with no extra chip.\n4. Require an iMessage composer, stage the immutable payload, and revalidate the\n   complete chip set immediately before one submission.\n5. Require one unique post-baseline provider emission whose text shape/media\n   hash, service, complete participant set, error states, and terminal states\n   match the proposal.\n6. Bind the resulting chat GUID only after attestation succeeds.\n\nFailure before submission discards the draft and sends nothing. Ambiguity after\nsubmission is quarantined and never retried. A recipient-list Shortcut, Group\nMMS, unsigned AppleScript, database mutation, or guessed chat is not a fallback.\n\nMessages attachment files may be readable only inside the authorized reader's\nprivacy boundary. Request bounded SHA-256 evidence from that read-only process;\nthe transport worker compares the returned digest and must not reopen protected\nattachment paths. Keep text-match, media-match, hash-unavailable, hash-mismatch,\naudience, and provider-state diagnostics distinct so a permission-boundary\nfailure cannot masquerade as a missing outbound row.\n\nReturn structured transport evidence with a phase and a tri-state\n`submission_attempted` value. Only explicit `false` is\n`failed_before_send`; `true` or unknown is quarantined as ambiguous. A terminal\nfailure consumes its approval. An identical retry must become a fresh proposal\ngeneration with a new approval and idempotency key, never a revival of the old\nprompt or response.\n\n### 13.3 Native reply\n\nRepresent a native reply as `(chat_guid, target_message_guid, payload)`.\nBefore sending, verify that the target GUID exists exactly once in the exact\nchat, remains unretracted, and is suitable for the installed reply helper.\n\nA supported local implementation can search with a bounded phrase derived from\na globally unique visible target, explicitly exit search mode, normalize to one\nselected conversation, and re-resolve one exact full-body target in the opened\ntranscript. It must support standard text bubbles and platform variants such as\nmention rows that omit the normal text child. Open the exact row's action\npalette, invoke its native Reply action, and require exactly one target match in\nthe isolated reply transcript even when other replies are already present.\nVerify caption-plus-media as exact canonical text plus exactly one\ntyped attachment marker, not as plain text alone. A rejection before the single\nsubmission keystroke must explicitly record `submission_attempted=false`; an\nunknown value remains quarantined. Submit once, then require one unique\npost-baseline provider emission whose rows all have the native thread-originator\nrelation, exact chat, text shape or attachment hash, zero provider errors, and\nterminal sent/delivered states. Do not confuse a schema's ordinary linear\npredecessor field with its native reply-thread authority. If pre-send identity\nor post-send relation cannot be proven, fail closed. Never fake a reply with\nvisible prose such as \"Replying to...\".\n\n## 14. SMS and MMS fallback\n\nCarrier transport is explicit. A route records why fallback is allowed, which\nsender is used, whether the owner must be included, and whether one-to-one SMS or\nverified Group MMS is required.\n\nTwilio webhooks should be idempotent by provider message SID. Validate webhook\nauthenticity, normalize segments and media, and forward quickly to the local\nbridge. Make.com, when present, is transport-only and stores no reasoning state.\n\nNever silently change audience or transport after approval. A fallback requires\na proposal whose approved payload explicitly permits that transport and audience.\n\n## 15. Media and GIF pipeline\n\n### 15.1 Inbound media\n\nCopy bytes to a mode-restricted content-addressed store. Record SHA-256, sniffed\nMIME type, dimensions, duration, frame count, source GUID, attachment GUID, and\nreadability. For animation, inspect representative frames across the timeline.\nClassify ISO media containers by `ftyp` brands so HEIC/HEIF, AVIF, QuickTime,\nand MP4 do not collapse into one generic MP4 signature.\n\nSemantic analysis separates:\n\n- literal subject and action\n- visible text\n- recognizable source or meme family\n- emotional tone\n- likely conversational target and subtext\n- uncertainty and plausible alternate readings\n\nUse native reply/reaction metadata, sender, local ordering, direct address, and\ntopic context before deciding whom a GIF targets.\n\n### 15.2 Outbound selection\n\nChoose a relevant media item from a private index or bounded acquisition service.\nNever ask the recipient for a URL. Search queries should use abstract concepts,\nnot copied private messages. Download, size-limit, type-sniff, inspect, and stage\nthe final bytes before approval.\n\nIn an established casual, trusted thread where the assistant is an actual\nparticipant, the assistant may proactively prepare one contextual reaction when\nan inbound directly addresses it, completes a running task, lands a strong joke,\nunexpectedly breaks a silence, or creates another unmistakably reply-worthy\nbeat. Prefer a native reply to the triggering GUID and media-first humor when a\nreviewed asset genuinely sharpens the moment. Do not answer every message,\ncompete with the humans, pile on, or revive stale banter. External-recipient\napproval, exact-audience checks, freshness, privacy, blocking, and safety remain\nunchanged.\n\nFor GIFs, require a persisted semantic review of the actual content hash. Caller\nlabels, filenames, URLs, and model assertions are not policy evidence.\n\n### 15.3 Exact-audience media policy\n\nContact-specific rules are keyed by the complete normalized audience hash,\nmedia kind, and direct/group scope. They are not global attributes of one person.\n\nFor example, a direct two-human thread can require a particular actor or meme\ntheme. The same contact in a group does not activate that rule. If no relevant\nreviewed media satisfies the direct-thread rule, use text or send nothing.\n\n### 15.4 Approval and sending\n\nMedia approval binds exact staged bytes, caption, audience, chat, topic revision,\nreply target, and expiry. Show the actual attachment in the private approval\nsurface. Keep it readable until approval expires or resolves.\n\nSend local bytes as an attachment. Attestation requires the exact chat, one\nexpected attachment, matching type/hash, no human-visible source URL, zero\nprovider error, and sent/delivered state. A local attachment row alone proves\nstaging, not recipient delivery.\n\n## 16. Observation-only personal Messages replica\n\nWhen the host Messages app uses the AI account but personal-history context is\nalso required, run a separate Apple-silicon macOS VM signed into the personal\naccount. Use a signed exporter that:\n\n- opens the guest Messages database read-only with query-only pragmas\n- exports a bounded recent window and attachment bytes\n- writes only into a dedicated mode-restricted shared directory\n- emits source identity, observation-only attestation, heartbeat, and cursor\n- has no send, worker, approval, or policy API\n\nThe host importer validates schema, path containment, size limits, source ID,\nheartbeat freshness, and monotonic cursor before recording observations. Keep\nhost import and VM auto-start disabled until the exporter reports a fresh\nheartbeat, readable database, observation-only mode, and real message progress.\nA readable zero-row database proves only that the exporter is installed. Publish\na separate `message_store_initialized` signal and reject activation, polling,\nand healthy status until the guest has observed real provider rows.\n\nDo not persist row zero as a final initial cursor. If account sync populates a\ndatabase after an empty installation probe, recalculate the first cursor from the\nconfigured retention cutoff before exporting. Otherwise a nominally bounded\nobserver can accidentally replay the account's complete history.\n\nKeep the host import supervisor alive even while every optional observer is\ndisabled. It must reload configuration on each idle cycle so a newly enabled,\nready observer starts importing without a bridge restart or silent dead period.\n\nTreat the shared inbox as a durable spool rather than the history archive. The\nhost must persist the normalized observation and monotonic cursor before it\nacknowledges and removes a source file. Replays remain idempotent, invalid files\nmove to a private quarantine, and attachment retention runs on a bounded cadence\ninstead of recursively scanning all retained files on every poll. This keeps\nsteady-state import work proportional to newly exported messages.\n\n## 17. Dashboard\n\nThe admin dashboard should expose:\n\n- aggregate health and degraded reasons\n- exact physical threads and participants\n- multiple topics per thread and topic links across threads\n- retained messages with relations and attachment previews\n- audience-filtered memory facts and provenance\n- queued/running/completed jobs with leases and errors\n- immutable proposals, freshness, dependencies, and payload hashes\n- focused and queued approvals plus prompt delivery evidence\n- outbox and provider delivery attempts\n- owner incident notices with dashboard visibility and private-delivery state\n- media policies and semantic reviews\n- personal-VM checkpoints and import gaps\n- routing and automation permissions\n- component heartbeats, warnings, audit events, and source revisions\n\nControls must require admin authentication and CSRF protection. At minimum,\nsupport external approval-gate toggle, routing policy edit, exact-audience media\npolicy edit, topic reassignment, approval/rejection, and safe route block/reopen.\nAll mutations produce audit events and refresh read-only exports.\n\n## 18. Health model\n\nHTTP liveness is insufficient. Aggregate health is degraded when any required\ncomponent is stale or unsafe. Monitor:\n\nKeep the local watchdog's liveness response independent of persistent storage\nand request logging. Expensive aggregate readiness belongs in component health\nand the dashboard; storage pressure must not make the watchdog restart a live\ningress process.\n\nIf HTTP readiness is delayed but the loopback TCP listener still accepts a\nconnection, classify the bridge as degraded but live and do not restart it. A\nrestart requires the HTTP endpoint and listener to remain absent across a\ndurable grace interval, not one slow probe.\n\n- host Messages poller heartbeat and database access\n- protected-reader RPC readiness and loaded-versus-installed code hashes\n- ingress cursor age and backlog\n- queued job count and oldest age\n- running worker heartbeats and expired leases\n- focused approval age and prompt-delivery state\n- queued/sending/ambiguous outbox items\n- queued/sending/ambiguous owner incident notices\n- transport capability probes and account state\n- VM heartbeat, cursor, message progress, and attachment health\n- dashboard/export age and installed-source hash drift\n\nTreat a worker as active only when its durable lease and a matching live process\nor heartbeat agree. On bridge startup, reconcile old `launching`, `running`, and\n`retrying` records whose grace window elapsed and whose process disappeared.\nThe dashboard must independently render such records as orphaned when the bridge\nis unavailable; a stale status file is audit history, not active work.\n\nSuggested service levels for a personal deployment:\n\n- durable ingress p95 under 3 seconds\n- simple proposal p95 under 15 seconds\n- required-component failure detection under 15 seconds\n- durable restart recovery under 30 seconds\n- zero unbounded worker fan-out\n\n## 19. Security requirements\n\n1. Enforce the primary-host lock at every final provider boundary.\n2. Verify active worker leases at proposal and transport APIs.\n3. Normalize and compare complete audience sets, never labels or partial handles.\n4. Authenticate provider webhooks and admin sessions.\n5. Require CSRF tokens for dashboard mutation.\n6. Keep secrets in Keychain/secret storage, never config exports or logs.\n7. Use mode `0700` private directories and `0600` request/media files.\n8. Resolve and validate paths against approved roots after following symlinks.\n9. Bound network fetch size, redirects, MIME types, and timeouts; never forward\n   credentials to media hosts.\n10. Treat VM, email, calendar, and other automation input as untrusted data, not\n    owner authorization.\n11. Redact public artifacts and mask endpoints in admin summaries where full\n    values are unnecessary.\n12. Record every approval, rejection, supersession, policy change, lease claim,\n    provider attempt, and reconciliation in append-style audit history.\n13. Do not modify operating-system privacy databases or bypass TCC/SIP controls.\n14. Fail closed when helper identity, signature, capability, or attestation is\n    missing.\n\n## 20. Build plan\n\n### Phase 1: Canonical source and fixtures\n\n1. Create one source repository for bridge, core, helpers, exporter, tests, and\n   documents.\n2. Capture sanitized provider fixtures for direct text, groups, replies,\n   reactions, edits, retractions, GIFs, and media-only messages.\n3. Back up installed scripts, helper bundles, configuration, and state.\n\n### Phase 2: Transactional core\n\n1. Implement repeatable schema migrations.\n2. Add identity, endpoint, thread, topic, message, media, and memory records.\n3. Add durable jobs and hashed leases.\n4. Add immutable proposals, freshness, approvals, prompt-delivery claims, and\n   the transactional outbox.\n5. Add audit and health snapshots.\n\n### Phase 3: Ingress and context\n\n1. Normalize host Messages and carrier webhooks into the same event contract.\n2. Preserve exact audiences and all reply/reaction/media metadata.\n3. Add topic links without binding contacts to topics.\n4. Assemble bounded, audience-safe shared context.\n5. Replay historical events in an isolated action-free database.\n\n### Phase 4: Transport\n\n1. Build and sign narrow read/send helpers.\n2. Add exact-chat validation for existing groups.\n3. Add transactional exact-group composition with pre-submit audience checks and\n   post-submit exact-payload/audience attestation.\n4. Add native reply with pre-send target checks and post-send relation\n   attestation.\n5. Add attachment-byte sends and delivery verification.\n6. Add explicit Twilio fallback routes.\n\n### Phase 5: Approval and operations\n\n1. Deliver private approval prompts through iMessage/dashboard.\n2. Support focused shorthand and exact native-reply resolution.\n3. Implement stale proposal suppression and quiet replacement.\n4. Add dashboard controls, health, policies, memory, and active work.\n5. Add personal-VM observation only after its trust-boundary tests pass.\n\n### Phase 6: Migration and release\n\n1. Shadow replay retained history without workers or provider calls.\n2. Install new artifacts behind a disabled outbox-release gate.\n3. Import normalized history and policies.\n4. Run owner-only direct, group, reply, media, approval, and restart canaries.\n5. Enable release only after all gates pass.\n6. Preserve timestamped rollback artifacts and audit history.\n\n## 21. Validation plan\n\n### 21.1 Unit and property tests\n\n- repeatable schema creation and migration\n- provider-event and message-GUID idempotency\n- many topics in one thread and one topic across threads\n- no cross-thread reply inheritance\n- immutable proposal and deterministic payload hash\n- stale, expired, rejected, and superseded proposals cannot enqueue outbox\n- duplicate approval event cannot approve the next proposal\n- one-shot prompt delivery under worker races\n- owner-control sender has no route, chat, or caller-audience parameter\n- stale approval-route conversation bindings are removed\n- approval-shaped payloads cannot traverse any conversation sender\n- expired worker cannot propose, send, heartbeat, or finish\n- exact-audience media policy and direct/group isolation\n- actual-byte semantic review requirement\n- media sniffing, hashing, frame extraction, path containment, and retention\n- observation-only event cannot enqueue a job\n- owner-notice idempotency, lease recovery, and exact provider reconciliation\n\n### 21.2 Replay tests\n\nReplay at least seven retained days twice into an isolated database. Require:\n\n- second pass creates zero new messages\n- stable thread/audience/topic/reply relationships\n- no cross-audience inheritance\n- media hashes and relations remain stable\n- no jobs, proposals, approvals, outbox items, or provider actions\n- no production state file changes\n\n### 21.3 Bridge integration tests\n\n- exact group is verified before payload send\n- audience mismatch produces zero send\n- native reply request uses only private contained files\n- native reply relation is attested after send\n- ambiguous native reply has no ordinary-message fallback\n- media delivery contains the exact approved bytes and no visible URL\n- text-only exact groups use the participant-bound transactional helper\n- every accepted-send failure creates one durable owner notice\n- concurrent prompt dispatch produces one prompt\n- selected Messages conversation cannot affect private approval delivery\n- direct/group/new-chat iMessage, SMS, and MMS reject approval payloads before I/O\n- one-address media preview is delivered before address-bound control text\n- primary-host mismatch blocks every provider\n- owner routine carrier SMS is suppressed\n\n### 21.4 Chaos tests\n\nKill or restart ingress, scheduler, worker, approval processor, and transport\nexecutor around every state transition. Duplicate webhooks, replay cursors, delay\napprovals, expire leases, and force provider timeout. Require no lost inbound\nevent, duplicate prompt, duplicate send, stale authority, ambiguous retry,\nsilent accepted-send failure, or duplicate incident notice.\n\n### 21.5 Live owner-only canaries\n\nUse only configured owner endpoints:\n\n1. Direct iMessage text.\n2. New exact owner-endpoint group.\n3. Media-only animated GIF attachment.\n4. Caption plus attachment.\n5. Native reply to an exact canary message.\n6. Two queued approvals with bare shorthand.\n7. Approval by native reply to the older prompt.\n8. Supersession after new context.\n9. Bridge restart with queued work.\n10. Verify provider GUIDs, exact participants, attachment hashes, relations, and\n    zero carrier SMS.\n\nDo not use an external contact or production group as a canary.\n\n### 21.6 Security audit\n\n- public document contains no personal identifiers, endpoints, domains, tokens,\n  route IDs, credentials, or private message examples\n- admin pages and JSON return denial without authenticated admin state\n- mutation rejects missing/invalid CSRF\n- helpers reject broad permissions and paths outside private roots\n- webhooks reject invalid signatures\n- VM data cannot reach approval or outbound APIs\n- owner-control and conversation transport are separate capabilities\n- approval-payload firewalls exist at every provider data boundary\n- exact audience is checked at proposal, approval, and provider boundary\n- installed helper signature and source hash are recorded\n\n### 21.7 Release gate\n\nRelease only when all automated tests pass, historical replay is deterministic\nand action-free, owner-only canaries are attested, required health stays green for\nan observation window, dashboard state matches the database, public redaction\npasses, automation inventory is current, rollback is exercised, and no unresolved\ncritical or high finding remains.\n\n## 22. Cost model\n\nProvider prices change. Verify the linked official pages before budgeting. The\nreference values below were checked on 2026-08-07.\n\n- iMessage: no per-message provider charge, but it requires Apple hardware,\n  account administration, electricity, storage, and operational monitoring.\n- Apple-silicon VM: Virtualization.framework and an open-source runner can avoid\n  a separate hypervisor license; hardware resources and Apple licensing/account\n  eligibility still apply.\n- Twilio US long-code messaging: reference base rates were $0.0083 per outbound\n  or inbound SMS segment, $0.022 per outbound MMS, and $0.0165 per inbound MMS,\n  before carrier, number rental, registration, failed-message, and compliance\n  fees. See https://www.twilio.com/en-us/sms/pricing/us.\n- Twilio Conversations: first 200 monthly active users were free, then pricing\n  started at $0.05 per active user per month, plus channel rates; media storage\n  started at $0.25/GB-month. See\n  https://www.twilio.com/en-us/messaging/pricing/conversations-api.\n- Make.com: usage is credit-based; most module actions consume one credit, and\n  the free plan listed 1,000 credits per month. Keep scenarios transport-only.\n  See https://www.make.com/en/pricing.\n- Cloud Run: request-based services include monthly free allowances, then charge\n  for requests, CPU, memory, and network use. Keep minimum instances at zero for\n  low-traffic dashboards. See https://cloud.google.com/run/pricing.\n- Firebase Hosting and object storage: low-volume Markdown/JSON dashboards may\n  remain inside no-cost allowances; media retention and egress are the likely\n  storage cost drivers. See https://firebase.google.com/pricing and\n  https://cloud.google.com/storage/pricing.\n- AI runtime: usually the largest variable cost. Budget by input/output tokens,\n  media analysis, tool calls, context length, retries, and duplicate work. See\n  the selected provider's current pricing.\n\nA practical monthly estimate is:\n\n`fixed infrastructure + phone numbers + SMS segments + MMS messages + active conversation users + stored media + webhook credits + AI inference + egress`\n\nMeasure actual counts in the dashboard and billing export. Do not estimate from\nroute count alone.\n\n## 23. Cost controls\n\n- Prefer iMessage for policy-allowed reachable recipients.\n- Keep carrier fallback explicit and suppress repeated notifications.\n- Keep serverless minimum instances at zero and maximum instances low.\n- Use retention limits and content-addressed media deduplication.\n- Coalesce inbound bursts and avoid launching workers for ambient reactions.\n- Keep webhook automation transport-only.\n- Bound worker context and model selection by task complexity.\n- Add provider budgets and billing export; budgets alert but do not hard-cap.\n- Delete old build artifacts and container images according to retention policy.\n\n## 24. Portable configuration\n\nKeep configuration separate from source and public documentation:\n\n- primary outbound host identity\n- owner endpoint allowlist\n- AI iMessage account handle\n- helper bundle paths and expected signatures\n- Messages reader and poller settings\n- SMS/MMS provider credentials and senders\n- approval gate, TTL, and owner channel\n- routing, disclosure, and automation policies\n- exact-audience media policies\n- retention and media size limits\n- VM source identity and shared-directory path\n- dashboard export destination and admin auth\n\nSecrets belong in Keychain or a secret manager. Public docs may name service\ncategories but must not include account IDs, project IDs, bucket names, domains,\nphone numbers, Apple IDs, credentials, or private paths.\n\n## 25. Rollback\n\nRollback order:\n\n1. Disable outbox release.\n2. Stop scheduler and transport executor.\n3. Quarantine queued and in-flight items; do not replay approvals.\n4. Restore timestamped installed artifacts and configuration.\n5. Start the previous bridge in read-only or known-chat-only mode.\n6. Retain the new database and audit history for reconciliation.\n7. Verify no queued proposal from either version can send automatically.\n\nRollback is incomplete until inbound capture, dashboard health, host lock, and\nzero-unintended-send checks pass.\n\n## 26. Known limitations\n\n- iMessage automation depends on local Apple software with no supported public\n  server API. Exact group creation and native reply helpers require validation\n  after every relevant macOS update.\n- A post-send attestation failure is inherently ambiguous. It cannot make the\n  already attempted provider operation reversible; the safe response is to hold\n  and reconcile, not retry.\n- Classification and media interpretation remain probabilistic. Durable policy,\n  audience checks, approval, and actual-byte inspection contain that uncertainty.\n- No document guarantees a bug-free one-shot recreation. The validation suite,\n  owner-only canaries, live health window, and rollback exercise are part of the\n  architecture, not optional follow-up work.\n"}