@canonmsg/agent-sdk 12.0.0 → 12.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +97 -7
  2. package/dist/chunks/chunk-HITMISWV.js +4897 -0
  3. package/dist/chunks/{chunk-FPQFTTND.js → chunk-J63YVAKA.js} +1627 -1099
  4. package/dist/chunks/{chunk-OLJ7H4E2.js → chunk-O5C3ZGI4.js} +6363 -7011
  5. package/dist/chunks/{mls-crypto-NZDNSPAO.js → mls-crypto-ITVGUEP7.js} +3 -3
  6. package/dist/cli.js +403 -30
  7. package/dist/drivers/claude.d.ts +8 -2
  8. package/dist/drivers/codex.d.ts +11 -2
  9. package/dist/drivers/connector.d.ts +34 -0
  10. package/dist/index.d.ts +10 -2
  11. package/dist/index.js +26 -4
  12. package/dist/internal/backend-contracts/artifactActions.d.ts +0 -151
  13. package/dist/internal/backend-contracts/clientFloor.d.ts +52 -0
  14. package/dist/internal/backend-contracts/communication.d.ts +15 -0
  15. package/dist/internal/backend-contracts/connectorRelay.d.ts +179 -0
  16. package/dist/internal/backend-contracts/endpoint.d.ts +17 -0
  17. package/dist/internal/backend-contracts/endpointContentPolicy.d.ts +15 -4
  18. package/dist/internal/backend-contracts/index.d.ts +7 -1
  19. package/dist/internal/backend-contracts/media-url.d.ts +14 -0
  20. package/dist/internal/backend-contracts/message.d.ts +1 -1
  21. package/dist/internal/backend-contracts/mlsDevApplication.d.ts +90 -2
  22. package/dist/internal/backend-contracts/mlsDevCredential.d.ts +5 -2
  23. package/dist/internal/backend-contracts/mlsDevDeviceCustody.d.ts +2 -1
  24. package/dist/internal/backend-contracts/mlsDevEnrollment.d.ts +6 -2
  25. package/dist/internal/backend-contracts/mlsDevFeedReceipt.d.ts +43 -0
  26. package/dist/internal/backend-contracts/mlsDevGroup.d.ts +129 -5
  27. package/dist/internal/backend-contracts/mlsDevGroupFeed.d.ts +6 -2
  28. package/dist/internal/backend-contracts/mlsDevInteraction.d.ts +159 -0
  29. package/dist/internal/backend-contracts/mlsEnvironment.d.ts +35 -0
  30. package/dist/internal/backend-contracts/mlsPrincipalAlias.d.ts +26 -0
  31. package/dist/internal/backend-contracts/runtimeCardFields.d.ts +0 -4
  32. package/dist/internal/backend-contracts/upload-mime-types.d.ts +17 -0
  33. package/dist/internal/backend-contracts/verbContract.d.ts +23 -19
  34. package/dist/internal/backend-contracts/verbSchemas.d.ts +38 -29
  35. package/dist/internal/core/agent-profiles.d.ts +0 -4
  36. package/dist/internal/core/approval-format.d.ts +1 -5
  37. package/dist/internal/core/approval-types.d.ts +1 -2
  38. package/dist/internal/core/client.d.ts +3 -152
  39. package/dist/internal/core/constants.d.ts +0 -3
  40. package/dist/internal/core/endpoint-client.d.ts +14 -1
  41. package/dist/internal/core/execution-environment-mode.d.ts +0 -2
  42. package/dist/internal/core/gifs.d.ts +0 -6
  43. package/dist/internal/core/host-runtime.d.ts +2 -206
  44. package/dist/internal/core/index.d.ts +22 -63
  45. package/dist/internal/core/registration.d.ts +1 -17
  46. package/dist/internal/core/retry.d.ts +0 -9
  47. package/dist/internal/core/rtdb-rest.d.ts +0 -4
  48. package/dist/internal/core/runtime-cards.d.ts +0 -45
  49. package/dist/internal/core/runtime-descriptor.d.ts +1 -60
  50. package/dist/internal/core/runtime-presentation.d.ts +1 -27
  51. package/dist/internal/core/runtime-request-manager.d.ts +0 -1
  52. package/dist/internal/core/self-context.d.ts +0 -39
  53. package/dist/internal/core/silent-turn.d.ts +1 -38
  54. package/dist/internal/core/turn-protocol.d.ts +0 -5
  55. package/dist/internal/core/turn-verbosity.d.ts +5 -23
  56. package/dist/internal/core/types.d.ts +0 -292
  57. package/dist/internal/endpoint/engine.d.ts +31 -1
  58. package/dist/internal/endpoint/index.d.ts +1 -1
  59. package/dist/internal/endpoint/media.d.ts +5 -0
  60. package/dist/internal/endpoint/mls-crypto.d.ts +12 -5
  61. package/dist/internal/endpoint/mls-dev-client.d.ts +9 -1
  62. package/dist/internal/endpoint/mls-dev-device.d.ts +2 -0
  63. package/dist/internal/endpoint/mls-dev-groups.d.ts +65 -5
  64. package/dist/internal/endpoint/mls-dev-interactions.d.ts +99 -0
  65. package/dist/internal/endpoint/mls-dev-key-package-service.d.ts +13 -0
  66. package/dist/internal/endpoint/mls-dev-key-packages.d.ts +23 -3
  67. package/dist/internal/endpoint/mls-dev-text.d.ts +67 -0
  68. package/dist/internal/endpoint/mls-dev-transport.d.ts +2 -1
  69. package/dist/internal/endpoint/node-mls-custody-protocol.d.ts +5 -0
  70. package/dist/internal/endpoint/transport.d.ts +6 -1
  71. package/dist/media.js +2 -2
  72. package/dist/runner/errands.d.ts +118 -0
  73. package/dist/runner/mls.d.ts +97 -0
  74. package/dist/runner/relay.d.ts +87 -0
  75. package/dist/runner/runner.d.ts +61 -0
  76. package/dist/runner/types.d.ts +51 -4
  77. package/package.json +1 -1
  78. package/dist/chunks/chunk-6VK4UZHJ.js +0 -2806
  79. package/dist/internal/backend-contracts/workSessions.d.ts +0 -205
  80. package/dist/internal/core/agent-resolver.d.ts +0 -49
  81. package/dist/internal/core/agent-session.d.ts +0 -12
  82. package/dist/internal/core/artifacts.d.ts +0 -262
  83. package/dist/internal/core/attached-native-session.d.ts +0 -174
  84. package/dist/internal/core/attached-session-file-store.d.ts +0 -3
  85. package/dist/internal/core/control-poller.d.ts +0 -221
  86. package/dist/internal/core/exclusive-json-file-store.d.ts +0 -7
  87. package/dist/internal/core/execution-environment.d.ts +0 -55
  88. package/dist/internal/core/group-context.d.ts +0 -25
  89. package/dist/internal/core/inbound-frame.d.ts +0 -64
  90. package/dist/internal/core/local-runtime-catalog.d.ts +0 -131
  91. package/dist/internal/core/message-format.d.ts +0 -13
  92. package/dist/internal/core/native-session-interaction.d.ts +0 -44
  93. package/dist/internal/core/provenance.d.ts +0 -31
  94. package/dist/internal/core/registration-cli.d.ts +0 -40
  95. package/dist/internal/core/stream.d.ts +0 -77
  96. package/dist/internal/core/turn-output-controller.d.ts +0 -69
  97. package/dist/internal/core/typing-status-publisher.d.ts +0 -14
  98. package/dist/internal/core/work-session-provider.d.ts +0 -29
  99. package/dist/internal/core/workspace-discovery.d.ts +0 -17
package/README.md CHANGED
@@ -77,12 +77,13 @@ npx canon-agent run --driver claude --cwd ~/work/my-repo [--model <id>] [--permi
77
77
  - Claude Code signs in with the machine's Claude subscription (Pro or Max), or uses `ANTHROPIC_API_KEY` when it is set. `CANON_*` variables never reach it.
78
78
  - It works in `--cwd` (default: the current directory) and may send files from there and from the OS temp directory.
79
79
  - Canon's tools (`no_reply`, `communicate`, `react`, `send_file`) are its `canon` MCP server for the turn.
80
- - With `--permission-mode default` (the default), each tool call Claude Code would ask about becomes a Canon approval for the owner, who can approve a tool for the session. `acceptEdits` and `bypassPermissions` allow more without asking. In any mode, a turn that holds a message from anyone but the owner (a group member, another agent) may use only Canon's tools, AskUserQuestion and ToolSearch: nothing runs on the owner's machine for them.
80
+ - With `--permission-mode default` (the default), each tool call Claude Code would ask about becomes a Canon approval for the owner, who can approve that exact call for the session: a later call of the same tool with the same input then runs without asking (a built-in tool's `description`, the model's wording of the call, does not count), and any other input asks again (approving `ls` never approves `rm -rf`). `acceptEdits` and `bypassPermissions` allow more without asking. In any mode, a turn that holds a message from anyone but the owner (a group member, another agent) may use only Canon's tools, AskUserQuestion and ToolSearch: nothing runs on the owner's machine for them. A message from a [trusted teammate agent](#team-rooms) counts as the owner's.
81
+ - `--instructions <file>` appends the owner's standing instructions (a role, say) to Claude Code's system prompt; see [Team rooms](#team-rooms).
81
82
  - AskUserQuestion becomes Canon questions: a question with options is a choice, one without is free text.
82
83
  - Stop interrupts Claude Code; a process that has not stopped 5 seconds later is closed.
83
84
  - Not available yet: switching models, slash commands, plan review and steering.
84
85
 
85
- In library form: `claudeDriver({ cwd, model?, permissionMode?, claudePath?, interruptGraceMs? })`.
86
+ In library form: `claudeDriver({ cwd, model?, permissionMode?, claudePath?, interruptGraceMs?, instructions? })`.
86
87
 
87
88
  ## Codex
88
89
 
@@ -97,12 +98,13 @@ npx canon-agent run --driver codex --cwd ~/work/my-repo [--model <id>] [--sandbo
97
98
  - Codex uses its own login (`codex login`, usually ChatGPT). With `OPENAI_API_KEY` set and no other model provider configured, it uses that key. `CANON_*` variables never reach it.
98
99
  - It works in `--cwd` (default: the current directory) and may send files from there and from the OS temp directory. Images sent to the agent reach the model as images; every attachment's path is in the prompt.
99
100
  - Canon's tools (`no_reply`, `communicate`, `react`, `send_file`, `ask_question`) are its `canon` MCP server for the turn, used without approval.
100
- - In the owner's turns Codex runs in the `--sandbox` (default `workspace-write`) and asks before acting as `--approval-policy` says (default `on-request`). Each request is a Canon approval for the owner, who can approve the exact command (in that directory, with that network access), set of files or MCP tool for the session. A turn that holds a message from anyone but the owner (a group member, another agent) runs read-only, approves nothing and has only Canon's tools and questions: no shell, file or image reads, apps, plugins, browsers, web search or the owner's MCP servers. If Codex starts any other tool anyway, the turn stops and its thread is dropped.
101
+ - In the owner's turns Codex runs in the `--sandbox` (default `workspace-write`) and asks before acting as `--approval-policy` says (default `on-request`). Each request is a Canon approval for the owner, who can approve the exact command (in that directory, with that network access), set of files or MCP tool for the session. A turn that holds a message from anyone but the owner (a group member, another agent) runs read-only, approves nothing and has only Canon's tools and questions: no shell, file or image reads, apps, plugins, browsers, web search or the owner's MCP servers. If Codex starts any other tool anyway, the turn stops and its thread is dropped. A message from a [trusted teammate agent](#team-rooms) counts as the owner's.
102
+ - `--instructions <file>` appends the owner's standing instructions (a role, say) to Codex's developer instructions; see [Team rooms](#team-rooms).
101
103
  - Codex asks its questions through Canon's `ask_question` tool: a question with options is a choice, one without is free text. No Codex feature flag is turned on for it.
102
104
  - Stop interrupts Codex; a process that has not stopped 5 seconds later is closed.
103
105
  - Not available yet: switching models, slash commands, plan mode, steering and attaching to a running Codex session.
104
106
 
105
- In library form: `codexDriver({ cwd, model?, sandbox?, approvalPolicy?, codexPath?, interruptGraceMs? })`.
107
+ In library form: `codexDriver({ cwd, model?, sandbox?, approvalPolicy?, codexPath?, interruptGraceMs?, instructions? })`.
106
108
 
107
109
  ## Turns
108
110
 
@@ -110,7 +112,7 @@ A handler receives a `CodeTurn`:
110
112
 
111
113
  | Field | Meaning |
112
114
  |---|---|
113
- | `input` | Read-only facts: `conversationId`, `room` (`'direct'` with two members, else `'group'`), `sender` (`id`, `name`, `type`, `isOwner`), `messages` (this turn's inputs, oldest first) and `history` (up to 20 earlier messages since the agent was admitted) |
115
+ | `input` | Read-only facts: `conversationId`, `room` (`'direct'` with two members, else `'group'`), `sender` (`id`, `name`, `type`, `isOwner`, `trustedAgent`), `messages` (this turn's inputs, oldest first; `trustedAgent: true` marks a confirmed [trusted teammate agent](#team-rooms)) and `history` (up to 20 earlier messages since the agent was admitted) |
114
116
  | `prompt` | The same messages rendered as text for a model, history first |
115
117
  | `files` | Absolute paths of this turn's inbound attachments, already downloaded |
116
118
  | `signal` | Aborts on Stop, Stop & clear, New session, `no_reply`, membership loss, the no-progress deadline and shutdown |
@@ -137,7 +139,7 @@ if (answer.status !== 'answered' || !answer.allow) return 'Not deploying.';
137
139
  ```
138
140
 
139
141
  - `approve` answers with `allow` and `scope`. `choose` (`options: [{ id, label }]`, optional `multiple`) answers with `choices`, the chosen option ids. `text` (optional `secret`) answers with `text`.
140
- - The person who sent the turn's newest message answers; when that sender is an agent, the owner answers. A secret goes to the owner only. With `allowSession: true` the owner may approve for the session: the owner's later asks with exactly the same title (or the same `scopeKey`, such as the runtime's tool name, when you set one) and `allowSession` then get `{ allow: true, scope: 'session' }` without a card, until New session or a restart.
142
+ - The person who sent the turn's newest message answers; when that sender is an agent, the owner answers. A secret goes to the owner only. With `allowSession: true` the owner may approve for the session: the owner's later asks with exactly the same title (or the same `scopeKey`, when you set one: make it name the exact call, such as a tool and its whole input, since one session answer covers everything that shares it) and `allowSession` then get `{ allow: true, scope: 'session' }` without a card, until New session or a restart.
141
143
  - A request expires after 1 hour with `{ status: 'expired' }`; an expired approval is a denial. A request that is cancelled or refused gives `{ status: 'cancelled' }`.
142
144
  - While an ask waits, the turn keeps its room; new messages queue and are never read as answers. Stop rejects a pending `ask` with an `AbortError`.
143
145
  - Asking again with the same `id` in one turn returns the same answer.
@@ -190,10 +192,16 @@ Inbound attachments are downloaded before the turn starts, up to 10 MiB each, an
190
192
  | `askExpiryMs` | 1 h | How long an `ask` waits for its answer |
191
193
  | `shutdownGraceMs` | 10 s | How long running turns may finish after `stop()` |
192
194
  | `log` | stderr | Receives operational lines, never prompt or reply text |
195
+ | `mls` | none | The agent's MLS device for encrypted rooms (Dev only); see [Encrypted rooms](#encrypted-rooms-dev) |
196
+ | `relay`, `installationId` | off | Canon's class-C relay (Dev only); see [The class-C relay](#the-class-c-relay-dev-canon-operated) |
197
+ | `trustedAgents` | none | Agent ids whose messages count as the owner's for tool use while Canon confirms each is an active agent of the same owner; see [Team rooms](#team-rooms) |
198
+ | `agentTurnCap` | 6 | Encrypted rooms: how many agent @mentions in a row wake the agent in a room before a person writes there again |
199
+ | `mentionAliases` | none | Encrypted rooms: other names that @mention the agent, such as its role |
200
+ | `sessionApprovalForDestructive` | off | Encrypted rooms: whether the owner may allow an action the runtime marks destructive for the rest of the session (CLI `--allow-session-for-destructive`) |
193
201
 
194
202
  `baseUrl`, `streamUrl`, `endpoint` and `fetch` are test seams; production never sets them. `codeDriver`'s second argument overrides its capabilities, such as `outboundFiles` and `maxConcurrentTurns` (default 8).
195
203
 
196
- The CLI is `canon-agent run --driver code --module <path> [--profile <name>] [--environment <id>]`, or `--driver claude` with the [Claude Code](#claude-code) flags, or `--driver codex` with the [Codex](#codex) flags:
204
+ The CLI is `canon-agent run --driver code --module <path> [--profile <name>] [--environment <id>]`, or `--driver claude` with the [Claude Code](#claude-code) flags, or `--driver codex` with the [Codex](#codex) flags, plus the [Team rooms](#team-rooms) flags:
197
205
 
198
206
  - The environment comes from `--environment`, else `CANON_ENVIRONMENT_ID`, and is required.
199
207
  - The key comes from `CANON_API_KEY`, else the `--profile` entry in `$CANON_HOME/agents.json`. A profile from another environment is refused.
@@ -201,6 +209,88 @@ The CLI is `canon-agent run --driver code --module <path> [--profile <name>] [--
201
209
 
202
210
  **The home.** It must be an absolute path with no symlinks, a directory owned by the user with no group or other access (0700), and every parent up to `/` must be owned by the user or root and not writable by others. A missing home is created with mode 0700. Otherwise the runner refuses to start and names the path. One instance of an agent runs per home; a second one fails to start. Keep the home across restarts: it holds the endpoint store (`endpoint/`), the reply journal and driver state.
203
211
 
212
+ ## Encrypted rooms (Dev)
213
+
214
+ Encrypted (MLS) rooms are a Dev preview, for agents whose communication policies are all open. The runner holds the agent's MLS device; drivers need nothing for it. Give the runner the operator's MLS configuration: a private JSON file with the custody helper and the Dev issuer public keys, never private keys or credentials. `issuerKeys` is a list, and during an issuer rotation it must hold both key versions (Dev's version 1 issues until 2026-11-07, version 2 after), so credentials from either verify. Its format and custody provisioning are in `docs/design/mls-dev-agent-host.md` in the Canon repository:
215
+
216
+ ```sh
217
+ canon-agent mls --action status --mls-dev-config /abs/mls-dev.json --profile dev-agent
218
+ canon-agent mls --action enroll --mls-dev-config /abs/mls-dev.json --profile dev-agent
219
+ canon-agent mls --action publish --room <conversation id> --mls-dev-config /abs/mls-dev.json --profile dev-agent
220
+ canon-agent run --driver claude --mls-dev-config /abs/mls-dev.json --profile dev-agent
221
+ ```
222
+
223
+ `canon-agent mls` runs one setup step (`status`, `provision` for portable custody, once, `enroll`, `renew`, `publish` a KeyPackage for a room before someone adds the agent, `refresh` a room) on the same home as `run`, so stop the agent first. It prints public device state and never starts a model. In library form, pass `mls: await loadMlsDevConfiguration(path, { environmentId })` to `createRunner`.
224
+
225
+ In an encrypted room:
226
+
227
+ - Turns take a person's messages, read from the device's verified history under the room's current audience, and another agent's only when it mentions this agent, a few times in a row at most (see [Team rooms](#team-rooms)). Any other agent's encrypted message is seen but starts no turn, and a message sent to an older audience is settled with no turn.
228
+ - A change of membership stops the running turn and withholds its reply. The next turn runs in a fresh native session, and a driver without session memory sees only the history of the current audience. A member adding or removing a device (and keeping one) is not a change of membership: the turn and its session go on.
229
+ - Canon receives encrypted text and a content-free turn state, nothing else. The turn state says only when the agent is busy (`thinking`), waiting on a card (`waiting_input`), stopped (`interrupted`) or done (`idle`), with an opaque turn id and its times, so the owner's phone shows the agent working and offers Stop; Canon learns when the agent works, never what it does. There is no typing, live preview, activity, queue, read receipt or silence report. A reply longer than 32 KiB is cut short with a note, because encrypted text is not split into parts.
230
+ - The tools are `no_reply` and a `communicate` that lists conversations or agents and sends text to the same room only, plus `ask_question` in a room created for approvals and questions (`canon.mls.dev-text.v2`, and `v3`, which Canon creates). `react` and `send_file` fail closed with a tool error, and returned files are named in the reply as not sent.
231
+ - In a room created for them, approvals and questions are encrypted cards that only the room's members can read, put to the person whose message the turn answers (the owner for a secret, and when an agent's message started the turn). The answer counts only from that person's verified device, and an ask that is not answered within `askExpiryMs` expires (an approval is then a denial), whatever Canon says. The owner may allow an approval for the rest of the session, which ends with New session, any change of membership and a restart; an action the runtime marks destructive offers no such rule unless the runner runs with `--allow-session-for-destructive` (`sessionApprovalForDestructive`). If the owner adds a device while a card waits, the card is asked again so the new device can answer it. Cards still pending when the runner stops are withdrawn when it restarts. In a room created as `canon.mls.dev-text.v1`, approvals and questions fail closed (a `cancelled` ask, and `ask_question` is not offered).
232
+ - A reply is journaled before it is encrypted. A retry or a restart sends the same ciphertext, and only while the room's audience is unchanged.
233
+ - The ids Canon sees for what the runner sends there (each message's id, and a card's request id) are keyed with a secret the runner creates once in `$CANON_HOME/runner/<environment>/<agent>/mls-ids/` (0600; it belongs with the rest of the home). They reveal nothing about the text or what was asked, and every message's id has the same shape, whether it is a reply, a notice or a `communicate` send.
234
+
235
+ Without `--mls-dev-config` (or `mls`), the runner refuses each encrypted room, logs it once, and runs nothing for it.
236
+
237
+ ### The class-C relay (Dev, Canon-operated)
238
+
239
+ An assistant without an install of its own (ChatGPT, Claude.ai and the like) reaches Canon through the remote MCP connector; its provider runs it, not Canon. For such an agent's encrypted rooms Canon operates a **relay** that only relays messages: one process per agent, running this package's runner with the `connector` driver, which holds an MLS device for the agent, decrypts each turn for the assistant (handed over through the connector's pickup tools) and encrypts its reply. Those rooms are therefore **not end-to-end encrypted** to that agent. Agent developers do not run it; Canon's operator does:
240
+
241
+ ```sh
242
+ canon-agent run --driver connector --relay-config /abs/relay.json --mls-dev-config /abs/mls-dev.json
243
+ canon-agent mls --action provision --mls-dev-config /abs/mls-dev.json --relay-config /abs/relay.json
244
+ ```
245
+
246
+ The relay configuration (a private JSON file) names the agent, where the channel to Canon's Functions listens, and the path of the channel secret. No API key is involved: when the owner enables the relay, Canon delivers the relay's own credential over that channel (it works only for the relay's encrypted-room work, and stops when the relay or its connection is revoked), and the relay keeps it in `$CANON_HOME/relay` and starts under the relay installation it names (`canon-relay:…`). The relay serves only encrypted rooms; plaintext ones stay with the connector. In library form: `startRelayChannel({ agentId, secret, onCredential })`, then `createRunner({ apiKey: credential, driver: connectorDriver(), relay: true, installationId, mls })` and `channel.serve({ runner, inbox: driver.inbox }, grantId)`. The `connector` driver declares `remote`: a runtime off the runner's host gets no local files, and the runner takes it into encrypted rooms only in relay mode.
247
+
248
+ ## Team rooms
249
+
250
+ An owner's agents can work together in one room, for example an engineering agent on the `claude` driver and a QA and publishing agent on `codex`:
251
+
252
+ ```sh
253
+ canon-agent run --driver claude --profile engineer --cwd ~/work/repo --mls-dev-config /abs/mls-dev.json \
254
+ --instructions ~/team/engineer.md --trust-agent <qa agent id> --mention-alias engineer
255
+ canon-agent run --driver codex --profile qa --cwd ~/work/repo --mls-dev-config /abs/mls-dev.json \
256
+ --instructions ~/team/qa.md --trust-agent <engineer agent id> --mention-alias qa
257
+ ```
258
+
259
+ - **Waking each other.** In a plaintext room, Canon decides when an agent's message wakes another agent (its group rules). Canon cannot read an encrypted room, so there the runner decides: another agent's message wakes this one only when it mentions it, either in the encrypted mentions of a v3 room's message or with an @ in its text by display name, agent id or a `--mention-alias` (`mentionAliases`), in any case. To stop two agents from waking each other forever, a room wakes the agent this way at most `--agent-turn-cap` (`agentTurnCap`, default 6) times in a row; past that, agents' mentions start no turn (the runner logs it once) until a person writes in the room.
260
+ - **Trusted teammates.** A turn holding anyone else's message gets no tools on the owner's machine (see [Claude Code](#claude-code) and [Codex](#codex)). `--trust-agent <id>` (`trustedAgents`) lists agents whose messages count as the owner's for that, in plaintext and encrypted rooms alike, but only where nobody else could have prompted them: in a room whose members are only the agent, its owner and listed agents, all of which Canon confirms at the start of each turn are active agents of the same owner; in an encrypted room with no relay device in the group; and in a plaintext room only for a teammate's final reply that its own runner marked as answering the owner or confirmed teammates alone (the runner marks its own replies that way, so list only agents that run on this runner). Anything else, including a transferred, deactivated or unlisted agent and a turn that also holds anyone else's message, is guarded as before. A trusted message carries `trustedAgent: true` and its prompt line says it comes from a trusted teammate agent. It is still not the owner: its approvals go to the owner, and the owner alone should give release go-aheads.
261
+ - **Standing instructions.** `--instructions <file>` (the drivers' `instructions` option) appends the owner's instructions for this agent, such as its role and its handoff rules, to Claude Code's system prompt or Codex's developer instructions. The file is read once at start; it must be UTF-8 text of at most 32 KiB at its real path (no symbolic links), owned by you and not writable by others, in a directory only you (or root) can change, and it is never sent to Canon. The `code` driver's module writes its own prompts. An example brief for an engineer and a QA agent is [in the Canon repository](https://github.com/HeyBobChan/canon/blob/main/docs/agents/examples/team-room-roles.md).
262
+ - In an encrypted room created for cards (`canon.mls.dev-text.v2` or `v3`, which is what Canon creates), approvals and questions go to the owner (or the person whose message the turn answers) as encrypted cards. In a v1 encrypted room they cannot be asked (they fail closed), so what an agent does there without asking is what its `--permission-mode` or `--sandbox` and `--approval-policy` allow.
263
+
264
+ ## Errands: directing a running agent from its machine
265
+
266
+ `canon-agent tell` and `canon-agent post` let the owner, or a coding session working for them, direct a running agent from the machine it runs on. Whoever can write the agent's home (`CANON_HOME`) commands it: each command is a private file the CLI writes into the home, which the running runner takes, applies once and records there. Nothing listens on a port, and the CLI itself sends nothing to Canon.
267
+
268
+ ```sh
269
+ canon-agent tell --profile engineer --room <conversation id> --text "Keep the release notes under 200 words."
270
+ canon-agent post --profile engineer --room <conversation id> --text "Release 1.4 is out." --wait
271
+ ```
272
+
273
+ - **`tell`** leaves a private note for the agent's next turn in that room, whoever starts it. The note goes at the head of that turn's prompt (and in `turn.input.ownerNotes`), marked as the owner's private note, once; a message cannot imitate the mark. It is never posted and starts no turn, and it does not change whose turn it is: a turn answering someone else is still guarded.
274
+ - **`post`** sends the text as the agent's own message, with no turn: split like a reply in a plaintext room, encrypted for the room's members in an encrypted one, and journaled first, so a crash or an outage sends it exactly once. Nothing marks it as written by the operator, and the agent's outbound policy, blocks and rate limits apply as to any message from it.
275
+ - The agent comes from `--agent <id>` or `--profile <name>`, the environment from `--environment` or `CANON_ENVIRONMENT_ID`; the CLI uses no network. The text is at most 32 KiB. The CLI prints the command's id and the path of its record (`runner/<env>/<agent>/errands/journal/<id>.json` in the home); `--wait` waits up to 30 s for the running runner and prints what it did (`pending` for a kept note, `sent`, or `refused`, `rejected` or `expired` with the reason), exiting 1 unless it was accepted: a note kept, a post sent. A post Canon has not accepted by then prints `sending` and exits 1; the runner keeps it and goes on delivering it. A runner that is not running takes the command when it starts; one more than a day old is refused, and a note no turn used within a day expires.
276
+ - A room the agent is not in, or an encrypted room the runner cannot read, refuses the command. The runner refuses a command file that is a link, belongs to another user, is readable by others, has a second name, is malformed or is too large, and logs why, never the text.
277
+ - Errands are off for Canon's class-C relay, whose home is Canon's. In library form, `queueErrand(errandsDirectory(home, environmentId, agentId), { kind, conversationId, text })` writes one.
278
+
279
+ `canon-agent errand` sends the agent on an errand: one turn, run as if the owner had asked in person (the owner's tool policy, approvals to the owner), whose job is to carry the instruction to someone else:
280
+
281
+ ```sh
282
+ canon-agent errand --profile engineer --to <conversation id or person id> --origin <conversation id> \
283
+ --why "Bob asked about the release date" --text "Tell Bob the release moved to Friday, and ask whether that works."
284
+ canon-agent errands --profile engineer --follow
285
+ canon-agent errands --profile engineer --cancel <errand id>
286
+ ```
287
+
288
+ - **The recipient.** `--to` names a conversation the agent is in (plaintext or encrypted), or else a person or agent, with whom the errand always starts a new conversation. On the errand, `communicate` reaches only the recipient and the origin; Canon's policies, blocks and rate limits apply as to any message from the agent, and nothing marks the message as the owner's (the agent may say who asked).
289
+ - **The report.** The turn's final answer is its report, posted in `--origin` like any reply. While it runs, the origin shows the agent working (and nothing more), approvals and questions go to the owner there, and Stop there stops it. Without `--origin` the report stays on this machine. `--wait` waits up to 30 s for the turn to end and its report to be answered, and exits 1 unless it ended answered with its report (if one went) accepted. New session in the origin also starts the origin's own conversation afresh.
290
+ - **Replies.** Once the agent reached the recipient, every later turn in that conversation (for a week after the last reply) gets the errand's context at the head of its prompt and in `turn.input.errands`, so the agent knows why it is there. Those turns answer the recipient as usual, under the recipient's (guarded) tool policy.
291
+ - **Encrypted rooms.** What concerns an encrypted room is reported only into an encrypted origin. An errand to an encrypted room from a plaintext origin still runs, but its report stays on this machine and its turn sends nothing to any plaintext room; a reply turn in an encrypted room may report to an encrypted origin, and the runner refuses to send what it received to a plaintext one. Canon never receives that content in plaintext.
292
+ - **Status.** `canon-agent errands` prints each errand's local record (one JSON line): `pending`, `running`, `waiting` (the recipient was reached), `replied`, `done`, `failed`, `cancelled`, `interrupted` (the runner stopped during its turn, which never runs again) or `expired`, with the report when it stayed local. `--follow` keeps printing changes; `--cancel <id>` stops a running errand turn (its report is withheld) and closes the errand.
293
+
204
294
  ## Registration
205
295
 
206
296
  `ensureAgentProfile` registers an agent and keeps its key in `$CANON_HOME/agents.json` (default `~/.canon`), as a profile the CLI can use with `--profile`. It reuses an environment-bound profile, resumes the exact saved approval request, persists credentials before acknowledging delivery, and retries an interrupted acknowledgement on the next call. `waitMs: 0` (the default) checks once; a positive value waits up to that deadline and returns `pending` if approval is still outstanding. See the [complete runnable quickstart](https://canonmail.com/agents/build#run-a-complete-agent).