Tools, payments, and trust. A field guide for iMessage assistants
Sep 5, 2025 · 5 min read · guide, product, payments, imessage · 107 reads
Run tools with intent. Offer upgrades at the right moment. Handle time, files, and identity with care.
Tools, payments, and trust. A field guide for iMessage assistants
Practical patterns that keep conversations moving and build trust over time.
Using tools well
- Reason. Call one tool. Verify the output. Then explain the result in plain language.
- If a tool returns URLs, collect them and place a short links block at the end of the message.
- If a tool changes state such as timezone, honor the new setting for the rest of the run.
How we wire it in code
- The agent emits
<command>blocks. We execute one at a time, log the interaction, inject results back into the thread, then produce a single final message. - Files:
tools/agent_cli.pymulti step loop (_handle_commands). Command registry intools/commands.pyand the tool files. - Timezone changes: we detect
set-timezoneand refresh the shared mapper for the remainder of the run. - Files:
tools/agent_cli.pyupdates viaPromptBuilder.update_timezone().utils/date_mapper.pyholds the active timezone.
Payments and plans
- Offer an upgrade only when limits are reached or when the user asks. Never in the middle of a task.
- Send a secure checkout link when the person is ready. If the account already has a plan, send a billing portal link instead.
- Keep it short. State the plan, what it includes, and give one action.
- After you send a link, avoid repeating it. Put it at the end of the next message with any needed context.
How we wire it in code
- URL and rate limit services create the upgrade or portal links on demand. We append the URLs at the end of the final reply.
- Files:
tools/url_commands.py:send-urlandtools/agent_cli.pywhich collects links and appends them. - Validation returns plan name and usage. If over the limit we create a Stripe Checkout Session or a Billing Portal session.
- Files:
services/supabase/subscription_service.py:RateLimiter.validate_rate_limit(),create_checkout_link(),create_portal_session(). - Plan mapping and prices are explicit. We map plan to product ID, pull the active price, then create the session with return URLs and space metadata.
Rate limits and onboarding
- Explain limits with one line. Show daily or monthly usage and the plan name. Add the upgrade or portal link.
- Treat first time users differently. Offer a short intro, ask for timezone, and offer to link their channels.
Conversation history that feels smart
- Bring only the past messages you need. Keep a small context floor so replies never feel lost.
- If history is thin, include a short intro that nudges toward timezone, preferences, and a quick overview of what you can do.
- When people talk to you across email and iMessage, pull the relevant pieces from both so the thread feels continuous.
How we wire it in code
- We select recent messages with token aware limits and include associated spaces for cross channel continuity.
- Files:
secretary_service.py:get_message_history(),services/prompt_builder.py, andtools/prep_agent.py.
Time and timezones
- Ask for the timezone early if you plan to schedule anything. Never assume UTC without a clear signal.
- Parse natural time such as "Jan 10 3PM" into a precise timestamp. Convert back to a friendly time in replies.
How we wire it in code
- Persist timezone per space and treat it as the source of truth.
- Files:
services/supabase/space_service.py:set_timezone(),get_current_time(). - The shared mapper converts between human time and ISO using the active timezone.
- Files:
utils/date_mapper.pyandtools/agent_cli.py. - Capture timezone automatically when possible.
- From IP on the web. Auth links land on a page that can geolocate the visitor and post the guess to
SpaceService.set_timezone(). - During signup. Ask for timezone and set it immediately.
- CLI warnings. Tools warn when running in UTC.
Attachments and rich media
- Acknowledge attachments. If you extract details from images, say what you found. Dates and times matter most.
- Summarize what the files are and what you did with them so the user sees progress.
How we wire it in code
- We fetch metadata and signed URLs. We download images to a temp file, run a vision model, and clean up.
- Files:
services/supabase/attachment_service.py:get_attachments(),prepare_images(),process_files(). - We feed metadata and results into the final prompt so the message cites what we found.
- Files:
services/prompt_builder.py:get_file_metadata_snippet()andprocess_files_snippet().
Identity and trust
- Offer a contact card so users can add the assistant to Contacts. Include a name, a short title, a friendly description, and a photo.
- Use short readable IDs when you need an identifier.
How we wire it in code
- We generate a vCard that users can add.
- Files:
services/supabase/personality_service.py:make_vcf(). - We map long IDs to short stable IDs in prompts and unmap before commands run.
- Files:
utils/id_mapper.py.
Channel linking and verification
- Make linking easy. Generate a short code and clear steps. Confirm when linked.
How we wire it in code
- We generate codes and bind the current channel after verification.
- Files:
tools/url_commands.py:link-channelandverify-channel. Backing serviceservices/supabase/channel_link_service.py.
Sending behavior and safety
- Keep clear modes for development, testing, and production so you avoid accidental sends. In testing, restrict recipients.
- For email, reply in the thread and avoid CC loops. For iMessage groups, use the group identifier.
How we wire it in code
- iMessage sends use dev, test, and prod modes. Groups use `chat_guid" and 1:1 uses the phone buddy.
- Files:
unified_sender.py:_send_imessage(). - Email sends use the Gmail API with delegation. We set thread headers, auto prefix Re on replies, and strip the assistant address from CC.
- Files:
unified_sender.py:GmailServiceListenerand_send_email().
Resilience and graceful failure
- If a model or tool is overloaded, send a short human message that says you are busy and to try again soon. Silence kills trust.
- Sanitize inbound text and track state so errors never cascade.
Observability you can act on
- Log each thinking and command step for analysis, but only share the final message with the user.
- Save recent prompts. They are gold for tone and edge case fixes.
Small patterns that add up
- One merged inbound to one reply. No bursts.
- Post links at the end of the message.
- Ask for timezone once and remember it.
- Keep iMessage short. Keep email structured and skimmable.
- Prefer short confirmations to long essays.
Use these defaults and you will ship something that feels quick, polite, and helpful. The assistant will still act fast. It will simply do so with manners.