Nextcloud Talk agents
An agent can live in a Nextcloud Talk room instead of behind its own API. Snag posts what the user says into the room as the user, and reads the agent's reply. This page is the set of conventions a Talk-based agent (or the bridge that relays for it) must follow.
Snag Control is a Pro feature. See Plans.
Set up
- Connect Nextcloud in Snag first: Settings > Connections > Add a connection > Nextcloud. Snag uses the app password it holds for Nextcloud.
- In Talk, make one room per agent. Copy the room token from the room's conversation settings (letters and digits, up to 64).
- In Snag, open the Control tab, add an agent, pick Nextcloud Talk.
- Enter the Room token and Its name (what the user says, for example "ops"). Pick its privacy level. Turn on It asks for approvals if the room carries proposals (see Approvals).
- Tap Connect.
A room can also be declared in a repo's .snag/agents.yaml with connection: talk. See Commands in a repo.
A Talk agent can be asked, given jobs and, with approvals on, approve. Its sensitivity is set on the phone.
The bot account (bot_actor_id), the ignored reference prefixes (ignore_reference_prefixes), aliases and the ask timeout (ask_timeout) are set in Settings > Advanced > Use my own infrastructure > Direct endpoints, in the talk_rooms JSON. The editor holds the whole Control setting: edit it in place, since anything left out is reset.
{"talk_rooms": [{"token": "<room token>", "spoken_name": "ops", "aliases": ["operations"], "approvals": true, "ask_timeout": 45, "bot_actor_id": "ops-bot"}]}What Snag sends
The user's words after the agent's name, posted as an ordinary chat message from the signed-in Nextcloud user.
POST {server}/ocs/v2.php/apps/spreed/api/v1/chat/{room token}?format=json
Authorization: Basic <login:app password>
OCS-APIRequest: true
Accept: application/json
Content-Type: application/x-www-form-urlencoded
message=check%20the%20backups&referenceId=<64 hex characters>The reply
The agent's answer must be a Talk reply to Snag's message: its parent is the message Snag posted. Snag takes the first message, in id order, that is all of these:
| Rule | Detail |
|---|---|
| A reply to Snag's message | parent.id is the id of the message Snag posted |
| A plain comment | messageType is comment and there is no systemMessage (reactions, joins and other Talk events never count) |
| Not from the user | Its actor is not the signed-in user |
| From the bot, when one is set | If the room is configured with a bot account (bot_actor_id), only that actor's messages count |
| Not a card or a confirmation | Its referenceId does not start with agent-card: or agent-confirm: |
Use the reference id prefixes to keep an agent framework's own decision cards and confirmations out of the answer: post them with a referenceId starting agent-card: or agent-confirm: and Snag skips them. The list of ignored prefixes can be changed per room (ignore_reference_prefixes, see Set up).
The reply's text is the answer. Snag removes markdown, links and code blocks before speaking it, and speaks it in chunks of up to 25 words. Write replies for the ear.
Timing
| Step | Behaviour |
|---|---|
| Ask | Snag long-polls the room (lookIntoFuture=1, up to 30 s per poll) until the reply arrives or the ask timeout passes (default 30 s) |
| No reply in time | The item goes In flight: "Sent. I'll check back." |
| Later | Every poll (on opening Snag, every 60 s while open, and on demand) reads the newest 100 messages and looks for the reply |
| Job ("tell ops to ...") | Posted the same way; "Sent to ops." and no wait |
Snag reads with setReadMarker=0 and markNotificationsAsRead=0, so the user's own Talk app keeps its unread state.
A reply that arrives after it has dropped out of the newest 100 messages is not found. Reply before the room moves on.
Notices: "Done" lines
Something the agent says on its own, not as a reply, is a notice. Snag reads a standalone message (no parent) from anyone but the user, and turns each of its lines that:
- starts with
Done:, or - ends with
more done.
into an answer waiting in Waiting on you. The user hears it with "what did ops say" or "anything need me".
Done: backup check - all three copies are current.
Done: certificate renewal - renewed, expires in 90 days.
2 more done.When a room is first linked, Snag starts from the newest message: history is not replayed.
Approvals
With It asks for approvals on, the room carries proposals. Snag finds open proposals in the newest 100 messages. A proposal is one message in exactly this form:
N: <action> for <target> - <evidence>. Verify: <how it will be checked>. Reply go NFor example:
12: restart the web container for web-1 - health check failing for 10 minutes. Verify: health check green. Reply go 12Nis 1 to 6 digits and is the same at both ends.<target>is one word with no spaces.- A proposal is closed by a later message
go N, or by a later message that startsN:and is not a proposal (the outcome line).
Talk proposals are routine approvals. Snag reads back "Ops wants restart the web container for web-1." then asks "Approve or hold?".
| User says | Snag posts | Then |
|---|---|---|
| "approve" | go N, exactly, nothing before or after | Waits up to 20 s for a reply to its go N message. The reply is spoken after "Approved." (25 words at most). No reply in time: "Approved. I'll tell you when it's done." |
| "hold", silence, a mishear | Nothing | The proposal stays pending |
| "deny" | Nothing | The proposal stays pending in Talk; Snag marks it denied on the phone |
The agent side must check that go N came from the right user in the right room, and re-validate the action before running it.
Errors
| Talk status | What the user hears |
|---|---|
| 401 | "Ops needs signing in. Parked." |
| 404 | "Couldn't reach ops. Parked." |
| 5xx, no connection | "Couldn't reach ops. Parked." |
| Other | "Couldn't reach ops. Parked." |
Talk has no park: parking happens on the phone and the message stays in the room.
See also
- Snag agent API: richer approvals (confirm phrases, irreversible actions, expiry) and per-answer privacy.
- Snag Control.