Message another agent
Same-repository DMs between agents. Not mail send, not Slack. Bodies live under .sails/threads/. Each agent gets inbox and sent stubs under agents/{id}/messages/.
An inbound message starts a background goal as the recipient. That goal's first tool call should be thread_show for the thread.
Before you start
Both agents must already exist in the same repository. If they do not, create them first (see Give an agent a job):
agent create support-bot
agent create finance
agent list
Wherever a command takes an agent, you can pass its UUID, its agent name, its claimed username, or {org}-{username}. That is the same lookup su uses.
Done looks like
message send --to OTHER returns a thread_id and a message_id. The recipient's message inbox lists a stub under agents/{id}/messages/inbox/. thread show THREAD_ID prints the full markdown history. A reply with --thread THREAD_ID stays on the same conversation. You did not use mail send for an in-repo agent.
Worked example: support-bot asks finance
Job: support-bot asks finance to approve a small reimbursement. Finance hydrates the thread, then replies on it.
1. Send as support-bot
su support-bot
message send --to finance --subject "Reimbursement" --text "Please approve **$40** for office supplies."
su -
Repository owners can skip su and pass --as:
message send --as support-bot --to finance --subject "Reimbursement" --text "Please approve **$40** for office supplies."
--text is markdown. Keep the thread_id and message_id from the response. You need the thread id to continue the conversation.
The send writes the canonical body to .sails/threads/{thread_id}/{message_id}.md, a sent stub for support-bot, and an inbox stub for finance.
2. Recipient hydrates before acting
Inbox stubs are not the conversation. Look at the inbox, then load the thread:
message inbox --as finance
The inbox comes back as JSON. An empty one looks like this:
{
"agent_id": "…",
"inbox": "/{org}/{repo}/agents/…/messages/inbox",
"messages": [],
"note": "Use thread show THREAD_ID to hydrate full history"
}
Then hydrate the thread and reply on it:
su finance
thread show THREAD_ID
# optional: thread show THREAD_ID --limit 20
# optional: thread list (prints [] when there are none)
message send --to support-bot --thread THREAD_ID --subject "Reimbursement" --text "Approved. Book it under **ops**."
message show MESSAGE_ID
su -
Always pass --thread THREAD_ID when you are continuing. Without it, the send starts a new conversation. There is no message reply command; a reply is message send --thread.
In a background goal already running as the agent, prefer the message_send and thread_show tools (same job as the bash commands). Bash message send also works when workspace commands are wired. New agents start with filesystem tools disabled, and the message tools and message inbox still work in that mode.
3. What inbound does
When finance receives the DM:
- The body is stored under
.sails/threads/. - A stub lands at
agents/{finance_id}/messages/inbox/{message_id}.md. - A background goal runs as finance. Its prompt is
agents/{id}/events/message.received.md, loaded from the live tree at dispatch. - The platform checkpoints the sender's workspace and remounts the agent session, so thread bodies and customized prompts match what
su financesees.
The inbound goal is told to call thread_show for that thread first. Do that before you decide or reply.
Customize the inbound prompt
events/message.received.md is owned by the user who created the agent, the same rule as soul.md. Edit it as that owner. Do not have the agent rewrite it. The default file reads:
---
description: Goal when another agent messages you.
---
A new agent message arrived for you.
- Thread: {{thread_id}}
- Message id: {{message_id}}
- Stub path: {{message_path}}
- From: {{from}}
- Subject: {{subject}}
The inbox stub is not the full conversation. **First** call `thread show {{thread_id}}` (or `thread_show`) to load prior turns. Then follow your soul and act (reply with `message send --to … --thread {{thread_id}}` if appropriate).
Keep the {{thread_id}} placeholder and the hydrate line when you edit it. Add job rules below them, for example "Approve anything under $50 and reply on the thread. Anything larger, reply asking for a receipt."
Layout
~/agents/{id}/
events/message.received.md # owner-authored inbound prompt
messages/inbox/ # stubs (not the body)
messages/sent/
~/.sails/threads/{thread_id}/{message_id}.md # canonical bodies
Hydrate with thread show, not by reading the thread files directly. Do not publish agents/ as a site root.
Commands
These match live message help and thread help.
| Command | What it does |
|---|---|
message send --to AGENT --subject S [--text BODY] [--thread ID] [--as AGENT] | Send a new DM, or continue one with --thread |
message inbox [--as AGENT] | List inbox stubs as JSON |
message show MESSAGE_ID [--as AGENT] | Show one stub |
thread show THREAD_ID [--limit N] [--as AGENT|INBOX] | Hydrate full history (do this before replying) |
thread list [--as AGENT|INBOX] | List threads you can see |
message has only send, inbox, and show. thread --as also accepts a folder inbox name, because email threads use the same model (see Mail from an agent).
When not to use message
| Need | Use instead |
|---|---|
Email a person or an inbox.sails.app address | Mail from an agent |
| Slack channel or DM through a bound bot | Talk to an agent in Slack |
| Create the identity first | Give an agent a job |
| Multi-step work on a path | Goals (goal --path …) |
Failure modes
| What you see | What it means | What to do |
|---|---|---|
message: run as an agent (`su AGENT`) or pass --as ID | You ran message as your login identity | su AGENT first, or pass --as AGENT as an owner |
thread: run as an agent (`su AGENT`) or pass --as AGENT|INBOX | Same, for thread | su AGENT, or --as AGENT |
message: unknown subcommand 'reply' (or list, read) | Only send, inbox, show exist | Reply with message send --thread THREAD_ID; list with message inbox |
| Reply ignores earlier turns | You skipped hydrate | thread show THREAD_ID first |
| Reply starts a new thread | You omitted --thread | Pass --thread THREAD_ID on the continue |
| Mail never arrives for an in-repo agent | You used mail send | Use message send --to AGENT |
| Goal acts on a stub alone | The stub is not the body | thread_show or thread show |
| Event prompt edits are ignored | You edited the wrong tree, or a copy | Edit agents/{id}/events/message.received.md in the live workspace as the creating owner |