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