Envoys
Envoys
An envoy is the workspace agent reached over someone else’s wire — Telegram or WhatsApp. What arrives is a message from a person the workspace may not have a user row for, so the interesting question is not “how do I speak this protocol” but whose permissions the agent acts under when it answers . That is policies , and it is why an envoy is a declaration in source rather than host configuration: the answer belongs in a diff.
The transport itself stays host-supplied. Holding a socket open is not something a scale-to-zero tenant can do, so the workspace names the transport and the host provides it — Colony owns the credentials and the socket.
The declaration
One file per envoy at src/envoys/+<lower_snake_case>.ts ; the filename is the envoy key.
// src/envoys/+sales_desk.ts
export default {
transport: 'telegram',
audience: 'public',
policies: ['commercial_shared', 'sales_rep'],
groupMessages: 'disabled',
task: 'Answer questions about quotes and accounts for this customer.'
} satisfies Envoy;
| Field | Meaning |
|---|---|
transport | The wire that reaches it ( telegram or whatsapp ). The transport dictates how addresses are canonicalised and compared. |
audience | Who may reach it: `public` (anyone who can message the wire) or `authenticated` (a member who has proven the address is theirs). Reach, not conversation shape — that is what `groupMessages` answers. |
policies | Everything this envoy MAY DO — bound to the generated policy names, so an envoy cannot point at a policy that does not exist. An array, safe because the compiler refuses an unconditional grant beside a narrowed one on the same collection. |
task | The envoy’s standing instruction, on top of the workspace’s `src/+agents.md`. |
groupMessages | How a group message triggers a turn: `disabled`, `mention_or_reply`, or `all`. |
delegation | Whether this envoy may create and coordinate sandbox agent sessions: `enabled` (the default) or `disabled`. Disable it for narrow ingress envoys that must act alone. |
Whose permissions the agent acts under
An envoy may be a group chat, so there is no single person behind it to inherit permissions from. Each declared envoy gets its own agent subject — holding exactly the policies policy names it declares, nothing else — and every inbound message
re-enters the workspace as that subject before the agent loop starts. A public envoy’s threads route to the admin inbox and stay off every member’s; an envoy whose policies grant nothing can do nothing, and the same hooks and approval gates apply as for any other client.
Workbench, documents, and delegation
Every envoy has one shared workbench for delegated agent sessions. Inbound text and documents never enter it: each chat_session owns its transcript, inbound buffer, drain lease, and chat_document bindings. A document read resolves both its key and conversation binding before bytes are returned. The shared workbench may offer spawn_subagent because the workbench contains no sender uploads.
Setting delegation to 'disabled' stops the envoy from spawning subagents at all — the right shape for a narrow ingress surface.
envoy_key `envoy_key` on a chat_session binds the external conversation to the tenant row. Ingress admits and buffers messages without invoking a model; one leased drain orders the burst, persists its complete sender attribution, runs one turn, and sends at most one completed reply.Related guides
- Agent — the loop every envoy message runs through
- Policies — what the envoy’s subject can see and change
- Workspace source — where the envoy role lives in the layout