Skip to main content

Overview

Messaging tools handle sending messages in conversations and retrieving media attachments.

send_message

Send a message in an Arlo Health conversation.

The read-first gate

You must reply with the latest conversation state in hand, so replies never ignore the AI’s or provider’s most recent message. Two ways to satisfy this:
  • Pass lastSeenMessageId (the id of the newest message you’ve seen, from a prior get_conversation / wait_for_reply). Arlo treats it as an assertion about the conversation’s newest message: if it matches, the send goes through with no extra round-trip; if a newer message has arrived, the send is refused even if you read the conversation earlier in the session.
  • Or omit it and call get_conversation first. That read marks the conversation read for your session and satisfies the gate as long as no newer message lands in between.
If you’re stale (a newer message arrived), the tool refuses the send but returns the latest conversation state inline under conversation — review it, then call send_message again. No separate get_conversation call is needed.

Sends and waits for the reply

By default (waitForReply: true) this sends your message and waits for the next reply in one call — the AI’s triage reply, a provider connecting, or a provider’s message. The wait resolves only on replies newer than the message it just sent, so it never hands back the question you were answering. It is event-driven and resumable: if it returns stillWaiting, call wait_for_reply to keep waiting (do not re-call send_message — that would re-send). Set waitForReply: false only to fire-and-forget.

Parameters

Safe retries (idempotency)

Background loops and retrying runtimes can accidentally re-send a write into a medical record. send_message, start_conversation, and confirm_provider_connection accept an idempotency key: a replay of the same key within 24 hours returns the original result (tagged _meta["arlo.health/idempotentReplay"]) instead of executing again, and a replay while the first call is still running returns duplicate_in_flight. A failed call releases the key so a genuine retry re-runs. Two ways to pass it:
  • Runtime-level (preferred): set _meta["arlo.health/idempotencyKey"] on the tool call — works on every host, invisible to the model.
  • Model-level: the idempotencyKey parameter, advertised only on agent runtimes.
Keys are scoped per account + tool. Use a fresh key per intended action (e.g. a UUID minted when the action is decided), and reuse that same key on retries of that action only.

Behavior by conversation status

Text messages — first person

Messages are delivered to the AI and provider as if the patient typed them. Write natural first-person responses (“I have…”, “My throat still hurts…”), never third person. For a child or dependent, stay first person from the account holder’s perspective (“My daughter’s fever is down today.”).
Don’t just answer the specific question — include related details the user mentioned. Richer, natural responses help complete triage faster.

Answering an informationNeed checklist

When a prior wait_for_reply / get_conversation returned an informationNeed list, that’s the full set of what triage still needs. Gather those answers from the patient, then send them all in one call (use the messages array to batch) — this collapses many triage round-trips into one. Two hard rules:
  • Answer redFlagGate items first.
  • Only report what the patient actually said. Never guess or fabricate a clinical answer — these messages become part of a medical record. If the patient doesn’t know, say so in first person (“I’m not sure how long it’s been”).

Photo/Video messages

Behavior depends on the host:
  • Widget hosts (Claude.ai, ChatGPT): sending media yourself generally doesn’t work — these hosts usually give the model no access to the user’s files, so the bytes have to come from their device. Tell the user to tap the + button in the Arlo consultation panel to upload from their camera or camera roll, and don’t offer to attach media you can’t read.
  • Agent runtimes with file access (Claude Code, custom agents): send media via the media object. Preferred: put the file bytes as base64 in media.data — the server uploads and sends it for you. Or omit media.data to receive a short-lived (~60s) presigned uploadUrl and PUT the file yourself.
If you do hold the file bytes on a widget host, media.data still works — the constraint is file access, not the host.

Returns

The shape varies by conversation status and options: Because the wait is folded in, a send can return any wait_for_reply shape — including note, nextStep, and nextStepArgs you can pass straight through.

Triage completed — payment required

This is the transition every consultation hits: your send finished triage, so the same call comes back holding the payment gate instead of a reply.
Relay paymentGate.consultationSummary to the patient, then call confirm_provider_connection once they agree (paymentType: "pay_per_use"), or create_payment_setup first when it is payment_setup_required. Arlo never auto-charges.

Read-gate refusal

Nothing was sent. Read the inline conversation, adapt your message to it if needed, then retry with lastSeenMessageId: latestMessageId.
If the folded-in wait times out (stillWaiting: true), the message is already sent — continue with wait_for_reply; never re-call send_message. When the result carries nextStepArgs.afterMessageId, pass it through so the wait doesn’t return your own send.

get_media_url

Retrieve media attached to a message (photos, videos, or files). Messages from get_conversation show the media type but not the attachment itself — this tool fetches it.

Parameters

Behavior by media type

Signed URLs expire (typically in 1 hour).

Example: View a Photo

Example: Read a PDF