arizuko

arizuko › reference › MCP tools

MCP tools

Every Model Context Protocol tool the arizuko platform registers on a per-group unix socket. Generated from ipc/ipc.go and ipc/inspect.go. What the caller holds in acl filters the tool set that a session sees. A tool that the caller holds nowhere is never registered.

Every tool is a grant. Calling <tool> requires an acl row covering the action mcp:<tool>, checked by auth.Authorize against the target as scope. The seeded role:member role — which every folder gets at creation — covers the thirteen messaging verbs, disengage included; read tools and set_work need no grant at all. Everything else is a row somebody delegated. What tools/list advertises is auth.EffectiveActions over those same rows, so the list and the per-call gate cannot disagree. See grants.

One socket, one gate

Every tool below lives on one unix socket that routd binds for the turn. The figure shows the path of a frame and the tools that the socket registers. A tool that the turn does not hold is not on the list. A call that reaches a tool still checks the same rows.

┌─────────────────────────────────────────────────────────────────────────┐
│ ipc/<folder>/gated.sock, bound by routd before runed boots the guest;   │
│ ServeMCP takes the folder as a parameter, never from the path           │
└────────────────────────────────────┬────────────────────────────────────┘
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ accept: at most 8 connections; SO_PEERCRED uid must equal expectedUID   │
└────────────────────────────────────┬────────────────────────────────────┘
                                     ▼ one JSON-RPC frame per line
┌───────────────────┬───────────────────┬─────────────────────────────────┐
│ submit_turn       │ submit_status     │ tools/call                      │
│ not in tools/list │ not in tools/list │ hold gate (CheckHold), then     │
│ records the turn  │ updates the       │ an mcp_tool span, then the      │
│ result once       │ turn's progress   │ registered handler              │
│                   │ row               │                                 │
└───────────────────┴───────────────────┴────────────────┬────────────────┘
                                                         ▼
┌────────────────────┬────────────────────────┬───────────────────────────┐
│ tool source        │ registered at bind if  │ checked on each call      │
├────────────────────┼────────────────────────┼───────────────────────────┤
│ hot-tier verbs:    │ the turn holds         │ mcp:<tool>, then JID      │
│ reply, send,       │ mcp:<tool>             │ containment for a chat    │
│ engage, ...        │                        │ verb                      │
├────────────────────┼────────────────────────┼───────────────────────────┤
│ reads: inspect_*,  │ always                 │ no grant; the handler     │
│ find_messages,     │                        │ binds rows to the folder  │
│ get_work, set_work │                        │ subtree                   │
├────────────────────┼────────────────────────┼───────────────────────────┤
│ cold tier: resreg  │ the turn holds the     │ AuthorizeTurn on the      │
│ via postBuild      │ tool's mcp: action     │ target the handler        │
│                    │                        │ resolves                  │
├────────────────────┼────────────────────────┼───────────────────────────┤
│ mcp connector:     │ the folder's secret    │ mcp:<name>; secrets       │
│ <name>_<tool>      │ reads the catalog, and │ resolved per call and     │
│                    │ the turn holds         │ scrubbed from the result  │
│                    │ mcp:<name>             │                           │
├────────────────────┼────────────────────────┼───────────────────────────┤
│ rest connector:    │ the turn holds the     │ mcp:<name>; secrets       │
│ <name>_<tool>      │ tool's ext: scope      │ resolved per call and     │
│                    │                        │ scrubbed                  │
└────────────────────┴────────────────────────┴───────────────────────────┘
 a call check is AuthorizeTurn(sender, turn folder, target, action, params);
 "holds" at bind is EffectiveActions over the same acl rows; an elevated
 /root turn sees every tool and passes AuthorizeTurn and JID containment

Contents

messages

post

Create a new top-level post on a platform (discord channel message). Use for broadcast/announcement content that is not replying to anyone. Not for replies (reply), direct messages (send), or file delivery (send_file).

Source: ipc/ipc.go:1427 · Grant action: post(jid=...).

nametypereqdescription
chatJidstringyesTarget chat JID; grant-checked + folder-authorized.
contentstringyesPost body.
mediaarraynoFilepaths under ~/; validated by mountsec.

Returns: JSON {"ok": true, "id": "<platformId>"}; on adapter "unsupported", returns error text with platform hint.

reply

Use reply for answers to the user. Omit replyToId to use the current conversation, or supply an earlier message to quote. On a GitHub chat replyToId is required: the call fails and sends nothing without it. Existing threads take precedence.

Source: ipc/ipc.go:1312 · Grant action: reply(jid=...) · In role:member, granted to every folder at create.

nametypereqdescription
chatJidstringyesTarget chat JID.
textstringyesReply body.
replyToIdstringnoPlatform message id to thread to; defaults to db.GetLastReplyID(jid, ""). Required on a GitHub chat: the id from the location header of the message you answer.

Returns: JSON {"ok": true, "id": "<platformId>"}; routd records the outbound row in messages with that id. Pass the id to edit to change the message in place.

send

Use send for a notification or an explicit different chat. send posts in the main channel of the chat. An explicit foreign chat keeps its own destination. Use reply for answers and send_file for files.

Source: ipc/ipc.go:1286 · Grant action: send(jid=...) · In role:member, granted to every folder at create.

nametypereqdescription
chatJidstringyesTarget chat JID; grant-checked + folder-authorized via authorizeJID.
textstringyesMessage body.

Returns: JSON {"ok": true, "id": "<platformId>"}; routd records the outbound row in messages with that id. Pass the id to edit to change the message in place.

send_file

Deliver a file from the group workspace (~/) to a chat. Works on every platform whose channel registered the tool. The caption IS the accompanying message — never follow with send. Not for inline text the user can read in-chat.

Source: ipc/ipc.go:1347 · Grant action: send_file(jid=...) · Path validated via mountsec.ValidateFilePath.

nametypereqdescription
chatJidstringyesTarget chat JID.
filepathstringyesMust be under /home/node/ (the group workspace mount).
filenamestringnoDisplay filename in chat.
captionstringnoMessage text accompanying the file — this IS the message; do not output separate text.
replyToIdstringnoPlatform message id to thread the file to.

Returns: JSON {"ok": true, "id": "<platformId>"}.

send_voice

Deliver text as a synthesized voice message on the platform — push-to-talk on Telegram/WhatsApp, audio attachment on Discord. voice defaults to PERSONA.md frontmatter or the instance default; pass an explicit name to override.

Source: ipc/ipc.go:1390 · Grant action: send_voice(jid=...) · Max 5000 chars; requires TTS_BASE_URL (otherwise UnsupportedError propagates).

nametypereqdescription
chatJidstringyesTarget chat JID.
textstringyesText to synthesize; non-empty, ≤ 5000 chars.
voicestringnoVoice name (e.g. af_bella for Kokoro). Omit to use PERSONA.md or instance default.

Returns: JSON {"ok": true, "id": "<platformId>"}; on adapter unsupported, error text with platform hint.

social actions

All registered via the regSocial/socialAct helper at ipc/ipc.go:1390. All take string args; the first arg (or jidArg) is grant-checked + folder-authorized. All adapter errors are rendered through toolMaybeUnsupported, surfacing the platform hint to the agent.

delete

Delete a post/message previously created by this agent (platform enforces authorship). Use to retract an incorrect or superseded post. No edit tool — delete and re-post.

Source: ipc/ipc.go:1539 · Grant action: delete(jid=...)

nametypereqdescription
chatJidstringyesChat the message lives in.
targetIdstringyesPlatform id of message to delete.

Returns: text "ok".

dislike

Endorse-negative on a message (Discord 👎 reaction). Native only — most platforms have no native downvote and return unsupported with a hint.

Source: ipc/ipc.go:1594 · Grant action: dislike(jid=...) · See user-pref: on emoji-reaction platforms, prefer like(reaction='👎').

nametypereqdescription
chatJidstringyesChat the message lives in.
targetIdstringyesPlatform id to downvote.

Returns: text "ok".

edit

Modify a message previously sent by this agent in-place (Discord, Slack, Telegram own bot messages). Preserves the platform message id. Email is unsupported.

Source: ipc/ipc.go:1606 · Grant action: edit(jid=...)

nametypereqdescription
chatJidstringyesChat the message lives in.
targetIdstringyesPlatform id of message to edit.
contentstringyesNew content.

Returns: text "ok".

forward

Redeliver an existing message to a different chat with provenance preserved (Telegram forward, WhatsApp forward, email Fwd:). Not for replying within the same chat (reply) or amplifying on a public feed (repost / quote).

Source: ipc/ipc.go:1551 · Grant action: forward(jid=<targetJid>) — jidArg is targetJid, not the source.

nametypereqdescription
sourceMsgIdstringyesPlatform id to forward.
targetJidstringyesDestination chat JID; grant-checked.
commentstringnoOptional accompanying text.

Returns: JSON {"ok": true, "id": "<platformId>"}.

like

Like an existing message (unicode emoji on Discord). Use when acknowledging or endorsing a specific earlier message without sending text. Platform decides what reaction strings are valid; unsupported platforms return an error.

Source: ipc/ipc.go:1527 · Grant action: like(jid=...)

nametypereqdescription
chatJidstringyesChat the message lives in.
targetIdstringyesPlatform id to react to.
reactionstringyesPlatform-specific reaction (emoji on Discord; like on Slack).

Returns: text "ok".

quote

Republish a message on your own feed with added commentary (a quote post). Native only — Discord has no quote primitive and returns unsupported with a hint to use post(content=..., source_url=...).

Source: ipc/ipc.go:1566 · Grant action: quote(jid=...).

nametypereqdescription
chatJidstringyesDestination feed JID.
sourceMsgIdstringyesMessage to quote.
commentstringyesYour commentary.

Returns: JSON {"ok": true, "id": "<platformId>"}.

repost

Amplify a message on your own feed without added text (a repost). Use to endorse-and-share. Not for commentary (quote) or sending a copy to a different chat (forward).

Source: ipc/ipc.go:1580 · Grant action: repost(jid=...).

nametypereqdescription
chatJidstringyesDestination feed JID.
sourceMsgIdstringyesMessage to amplify.

Returns: JSON {"ok": true, "id": "<platformId>"}.

pin_message

Pin a message to a chat/channel (Slack pins.add, Discord channel pin, Telegram pinned message). Use to mark a live status surface (deploy progress, standup link) or anchor a reference. Targets any message in the chat — not author-scoped like edit/delete. Email and WhatsApp return unsupported.

Source: ipc/ipc.go:1621 · Grant action: pin_message(jid=...)

nametypereqdescription
chatJidstringyesChat the message lives in.
targetIdstringyesPlatform id of the message to pin.

Returns: text "ok".

unpin_message

Remove the pin on a specific message (Slack pins.remove, Discord channel unpin, Telegram unpinChatMessage). Use to retire a status surface or rotate the pinned reference. Not for clearing every pin (unpin_all) or deleting the message (delete).

Source: ipc/ipc.go:1633 · Grant action: unpin_message(jid=...)

nametypereqdescription
chatJidstringyesChat the message lives in.
targetIdstringyesPlatform id of the message to unpin.

Returns: text "ok".

unpin_all

Clear every pin in a chat/channel (Slack iterates pins.list + pins.remove; Telegram unpinAllChatMessages). Use when wholesale resetting a channel's pinned set. Discord has no bulk primitive — call unpin_message per id.

Source: ipc/ipc.go:1645 · Grant action: unpin_all(jid=...)

nametypereqdescription
chatJidstringyesChat whose pins to clear.

Returns: text "ok".

history & inspection

fetch_history

Pull authoritative conversation history from the channel adapter and cache it. Use to reconstruct context before replying, especially on first contact or after a reset_session. Falls back to local cache if the adapter is down. Not for DB/routing audits (inspect_messages) or single-thread slices (get_thread).

Source: ipc/ipc.go:2389 · Registered only when routd.FetchPlatformHistory != nil. An ordinary caller must have the JID routed to its folder; an elevated /root turn is unrestricted.

nametypereqdescription
chat_jidstringyesChat to fetch.
limitnumbernoDefault 100; a value of 0 or less, or over 200, gives the default.
beforestringnoRFC3339 cursor.

Returns: JSON {messages, count, oldest, source, cap}. source is platform / platform-capped / cache-only / unsupported / cache; messages rendered by router.FormatMessages.

get_thread

Return rows from the message store scoped to one thread (chat_jid + topic). Use when a chat fans out into per-topic conversations (Telegram forum topics, web-chat topics).

Source: ipc/ipc.go:2345 · Registered only when db.MessagesByThread != nil.

nametypereqdescription
chat_jidstringyesChat to query.
topicstringyesTopic / thread key.
limitnumbernoDefault 50; a value of 0 or less, or over 100, gives the default.
beforestringnoRFC3339 cursor.

Returns: JSON {messages, count, oldest, source:"local-db"}.

inspect_messages

Return rows from the message store for one chat_jid, including outbound/bot rows and errored entries. Use for routing/delivery audits or to verify what the store recorded.

Source: ipc/ipc.go:2266 · Registered only when db.MessagesBefore != nil.

nametypereqdescription
chat_jidstringyesChat to query.
limitnumbernoDefault 100; a value of 0 or less, or over 200, gives the default.
beforestringnoRFC3339 cursor.

Returns: JSON {messages, count, oldest, source:"local-db"}.

find_messages

Ranked search over every message the instance has recorded. query is words and all of them must appear: budget review needs both. A "quoted phrase" matches those words adjacent and in order; a leading - excludes a word or a phrase. There is no OR, no prefix* wildcard and no NEAR. Use to find a past message by what it said. Not for whole-chat scroll (inspect_messages), single-thread slices (get_thread), or platform-truth fallback (fetch_history).

Source: ipc/ipc.go:2275 · Registered only when db.FindMessages != nil. An ordinary caller sees only rows whose chat_jid is routed to its folder (post-fetch ACL via db.JIDRoutedToFolder); an elevated /root turn bypasses the filter. Answered from an in-memory BM25 index routd keeps over the messages entity (routd/msgsearch), walked from the store at boot and fed by routd's own writes. A search made while that walk is running is refused, never answered empty.

nametypereqdescription
querystringyesWords, all required; "quoted" for an exact phrase; -word to exclude. An unclosed phrase or a dangling - is an error the caller reads, not an empty hit list.
scopestringnoOne chat_jid (contains :) or a folder subtree (no :; matches routed_to and its children).
senderstringnoExact match on the sender.
sincestringnoRFC3339 lower bound (inclusive) on timestamp.
limitnumbernoMax rows; default 20; a value of 0 or less, or over 200, gives the default.

Returns: JSON {messages, count, source:"local-db"}. Each row carries chat_jid, sender, timestamp, content (the matched fragment with «» around each hit, not the full body), and rank (BM25 — lower is a better match). Ordered by rank, then newest first.

inspect_routing

Return routes visible to this group plus the errored-chat aggregate; pass jid to also resolve that JID to its folder. Use when a message is not reaching the expected group, or to triage delivery failures.

Source: ipc/inspect.go:18 · Registered only when db.ListRoutes and db.DefaultFolderForJID are set.

nametypereqdescription
jidstringnoJID to resolve; non-root callers may only resolve JIDs routed to their folder.
limitnumbernoDefault 50; a value of 0 or less, or over 200, gives the default.

Returns: JSON {routes, errored?, jid?, resolved_folder?}.

inspect_session

Return current session_id for this group/topic plus recent session_log rows (message counts, last error, last reset). Use to check whether context is healthy. Not for clearing state (reset_session).

Source: ipc/inspect.go:87 · Registered only when db.GetSession and db.RecentSessions are set.

nametypereqdescription
topicstringnoTopic key; default empty (root topic).
limitnumbernoDefault 10; a value of 0 or less, or over 100, gives the default.

Returns: JSON {folder, topic, session_id, recent}.

inspect_tasks

Return tasks visible to this group; pass task_id for that task's recent run logs (durations, errors). Use to debug why a scheduled prompt did not fire or failed.

Source: ipc/inspect.go:55 · Registered only when db.ListTasks != nil. Non-root callers gated by auth.Authorize on the task's owner folder.

nametypereqdescription
task_idstringnoSpecific task; if omitted, returns the full visible list with no runs.
limitnumbernoDefault 50; a value of 0 or less, or over 200, gives the default.

Returns: JSON {tasks, runs?}.

groups & delegation

delegate_group

Hand a prompt down to a specific child group for async execution; the child runs in its own session and workspace. Depth capped at 1.

Source: ipc/ipc.go:2070 · Grant action: delegate_group (delegated, not a default) · auth.Authorize on the target folder.

nametypereqdescription
groupstringyesTarget child folder.
promptstringyesPrompt body delivered as a synthetic message.
chatJidstringyesJID used as ForwardedFrom provenance.
depthnumbernoCaller-passed depth counter; rejected when ≥ 1.

Returns: JSON {"queued": true}; enqueues a message-check for the target.

escalate_group

Hand a prompt up to this group's parent folder; the parent responds back through this child. Use when the request exceeds this group's authority or needs operator review. Depth capped at 1.

Source: ipc/ipc.go:2015 · Grant action: escalate_group (delegated, not a default) · Wraps the prompt in <escalation_origin/> with reply_to provenance.

nametypereqdescription
promptstringyesPrompt to bubble up.
chatJidstringyesOriginating chat JID (for reply_to in the wrap).
depthnumbernoCaller-passed depth counter; rejected when ≥ 1.

Returns: JSON {"queued": true, "parent": "<folder>"}.

refresh_groups

Return the folder of every registered group. Use to find delegation targets or to audit the group tree. The REST face limits the list to the subtree of the caller.

Source: resreg/resources/groups.go:48 · Registration gated on holding mcp:refresh_groups.

Params: none. Returns: JSON array of {folder}.

register_group

Create a child agent group and route a JID to it. The agent makes a group only with this tool. arizuko group add, the dashboard and onboarding are the operator paths.

Source: resreg/resources/groups.go:47 · Grant action: register_group (delegated, not a default) · Holding the grant, scoped to cover the child folder, is the whole authority to create.

nametypereqdescription
folderstringyesFull path of the group to create, e.g. acme/support.
jidstringyesSource chat JID to route into the new group.

Returns: JSON {"registered": true, "folder": "<f>", "jid": "<jid>"}; SetupGroup seeds skills/settings/tasks.

set_group_open

Toggle this group's visibility to sibling folders. When open=true, sibling folders' ambient messages surface in this group's <observed> block (and this group's messages surface to open siblings). Default: open. Use to seal off a sensitive folder from cross-folder ambient context. Concept: scopes.

Source: ipc/ipc.go:1880 · Grant action: set_group_open · auth.Authorize re-run with the calling folder as the scope.

nametypereqdescription
openboolyestrue to expose to siblings, false to seal off.
folderstringnoA folder in the subtree of the caller; defaults to the own folder.

Returns: JSON {"ok": true, "open": <bool>}.

set_observe_window

Override this group's ambient observe-window caps (messages and/or chars). Per-group caps win over instance env defaults (OBSERVE_WINDOW_MESSAGES, OBSERVE_WINDOW_CHARS); per-route caps still win over both. Pass -1 to clear an override; omit a field to leave it unchanged.

Source: ipc/ipc.go:1834 · Grant action: set_observe_window.

nametypereqdescription
messagesnumbernoMax ambient messages surfaced per turn; -1 clears override. At least one of messages / chars must be present.
charsnumbernoMax ambient chars per turn; -1 clears override.
folderstringnoA folder in the subtree of the caller; defaults to the own folder.

Returns: JSON {"ok": true, "messages": <n>, "chars": <n>}.

sessions & work

get_work

Read this group's work.md — current work, blockers, next steps. Use at the start of a turn to recover what was in-flight. Returns empty content when the file does not exist.

Source: ipc/ipc.go:2428 · Unconditionally registered; not grant-filtered.

Params: none. Returns: JSON {content, exists}.

inject_message

Write a synthetic inbound message into the store as if received from chat, triggering the normal agent loop. Use for programmatic prompts, tests, or scheduling one-off runs from tool code.

Source: ipc/ipc.go:1972 · Grant action: inject_message

nametypereqdescription
chatJidstringyesJID the synthetic message is bound to.
contentstringyesMessage body.
senderstringnoDefaults to "system".
senderNamestringnoDefaults to "system".

Returns: JSON {"injected": true, "id": "<mid>"}.

fork_topic

Branch a topic from another’s current state. The gateway copies the parent’s Claude Code session file to a fresh session_id, so the child resumes natively with the parent’s history already in its session — no prompt-injection block. Use when starting a focused side-conversation that needs the parent’s recent state but should not pollute it. Pass force=true to overwrite an existing child.

Source: ipc/ipc.go · Grant action: fork_topic · auth.Authorize re-run with the caller’s folder as the scope.

nametypereqdescription
parentstringyesParent topic to fork from ("" = main).
childstringyesNew child topic name.
forceboolnoOverwrite the child if it already exists.

Returns: JSON {"folder", "parent_topic", "child_topic"}. Error "topic_exists" when child exists and force=false.

pane_set_prompts

Slack only — stage suggested-prompt buttons shown at the bottom of the assistant pane after your next reply lands. Fire-and-forget; the buttons appear once and persist until your next call. Use after a reply when you can anticipate the user’s likely follow-ups (e.g. “dig deeper”, “summarise”, “export”). 3-4 prompts is the visible cap. Adapters without pane semantics return chanlib.ErrUnsupported. Concept: slack pane.

Source: ipc/ipc.go:1657 · Grant action: pane_set_prompts.

nametypereqdescription
chatJidstringyesJID of the pane DM channel (slack DM).
prompts[{title, message}]yesTitle shows on the button; message is sent as user input on click.

Returns: text "ok".

pane_set_title

Slack only — override the title shown at the top of the assistant pane. Fires after your next reply lands. Use to reflect the active topic (e.g. “atlas — debugging the build”). Defaults to <assistant> — chat when never set.

Source: ipc/ipc.go:1681 · Grant action: pane_set_title.

nametypereqdescription
chatJidstringyesJID of the pane DM channel.
titlestringyesTitle (max 256 chars; non-empty).

Returns: text "ok".

engage

Mark (jid, topic) engaged for the ENGAGEMENT_TTL window so subsequent messages from people fire even when the route table would not route them here. Use before a scheduled or autonomous turn, or to recover after a failed reply. The caller may engage a conversation nobody holds, one it already holds, or one whose chat routes to its folder; the active turn’s own chat counts as routed. Claiming another folder’s live window is refused.

Source: ipc/ipc.go:1762 · Grant action: engage.

nametypereqdescription
jidstringyesChat JID to engage.
topicstringnoTopic to engage. Defaults to main topic when omitted.

Returns: JSON {"ok": true}.

disengage

Clear engagement for (jid, topic). Subsequent messages need a fresh mention to re-fire. Use when the bot is done helping or when a corrective fork is closing. The caller must hold the live window, or the chat must route to its folder.

Source: ipc/ipc.go:1789 · Grant action: disengage. Included in role:member.

nametypereqdescription
jidstringyesChat JID to disengage.
topicstringnoTopic to disengage. Defaults to main topic when omitted.

Returns: JSON {"ok": true}.

reset_session

Drop the Claude session for a group so the next message starts fresh context. Use when the user asks for /new, when context is confused/polluted, or before a topic switch. Not for injecting content (inject_message).

Source: ipc/ipc.go:1813 · Grant action: reset_session · auth.Authorize on target folder.

nametypereqdescription
groupFolderstringyesFolder whose session to clear.

Returns: text "ok".

set_work

Overwrite this group's work.md with a fresh snapshot of current work, blockers, and next steps. Use at turn end to checkpoint state. This replaces the file — read with get_work first if merging.

Source: ipc/ipc.go:2445 · Always registered — no grant needed. Atomic rename via .tmp.

nametypereqdescription
contentstringyesNew work.md contents.

Returns: text "ok".

scheduling

cancel_task

Permanently delete a scheduled task. Use when nobody wants the task. Not for temporary suspension (pause_task) — this cannot be undone.

Source: resreg/resources/scheduled_tasks.go:54 · Grant action: cancel_task (delegated, not a default) · auth.Authorize on the task's owner folder. REST twin: DELETE /v1/tasks/{taskId}.

nametypereqdescription
taskIdstringyesTask to delete.

Returns: JSON {"ok": true}.

list_tasks

Return scheduled tasks visible to this group. Use for a plain task dump; prefer inspect_tasks when you also want task_run_logs or per-task history.

Source: resreg/resources/scheduled_tasks.go:55 · Grant action: list_tasks (delegated, not a default). REST twin: GET /v1/tasks.

Params: none. Returns: JSON array of core.Task.

pause_task

Mark a scheduled task paused so it stops firing but is preserved. Use when suspending a task temporarily.

Source: resreg/resources/scheduled_tasks.go:52 · Grant action: pause_task (delegated, not a default) · auth.Authorize on task owner. MCP only; the operator pauses with PATCH /v1/tasks/{taskId} and a new status.

nametypereqdescription
taskIdstringyesTask to pause.

Returns: JSON {"ok": true}.

resume_task

Re-activate a paused task so it resumes firing on its schedule. Use to undo pause_task. No effect on already-active or cancelled tasks.

Source: resreg/resources/scheduled_tasks.go:53 · Grant action: resume_task (delegated, not a default). MCP only; the operator resumes with PATCH /v1/tasks/{taskId} and a new status.

nametypereqdescription
taskIdstringyesTask to resume.

Returns: JSON {"ok": true}.

schedule_task

Create a scheduled prompt that fires against a target chat. cron accepts a 5-field cron expression, an integer millisecond interval, or an RFC3339 one-shot timestamp.

Source: resreg/resources/scheduled_tasks.go:51 · Grant action: schedule_task (delegated, not a default) · Deduplicates against existing active tasks with same (folder, cron, prompt). MCP only.

nametypereqdescription
targetJidstringyesChat the task fires against; folder derived via DefaultFolderForJID.
promptstringyesPrompt body delivered at fire time.
cronstringno5-field cron, millis interval, or RFC3339 one-shot.
contextModestringnogroup (default) or isolated.

Returns: JSON {"taskId": "task-<ms>-<uuid8>"}.

routing

add_route

Append one routing rule. Preferred over set_routes for everything except full rewrites.

Source: resreg/resources/routes.go:49 · Grant action: add_route (delegated, not a default) · auth.Authorize with RouteTarget. REST twin: POST /v1/routes.

nametypereqdescription
routestring (JSON)yesJSON object: seq (int), match (key=glob pairs; keys: platform, room, chat_jid, sender, verb), target (folder path, or folder: / daemon: / builtin: prefix).

Returns: JSON {"id": <routeId>}.

delete_route

Remove one routing rule by id. Use after list_routes/inspect_routing to surgically drop a rule. Cannot delete the caller's own default route (seq=0, target=own folder).

Source: resreg/resources/routes.go:51 · Grant action: delete_route (delegated, not a default). REST twin: DELETE /v1/routes/{id}.

nametypereqdescription
idnumberyesRoute id.

Returns: JSON {"deleted": true, "id": <id>}.

list_routes

Return the routing table rows this group can see. Use for a raw route dump; prefer inspect_routing when you also want JID→folder resolution or errored-chat context.

Source: resreg/resources/routes.go:52 · Grant action: list_routes (delegated, not a default). REST twin: GET /v1/routes.

Params: none. Returns: JSON {"routes": [...]}.

set_routes

Bulk-overwrite the full routing table for this folder subtree. Use only for wholesale reconfiguration; prefer add_route/delete_route for targeted edits.

Source: resreg/resources/routes.go:50 · Grant action: set_routes (delegated, not a default) · Rejects targets outside the caller's folder subtree; refuses to drop the caller's seq=0 self-default. REST twin: PUT /v1/routes.

nametypereqdescription
routesstring (JSON)yesJSON array of route objects (same shape as add_route).

Returns: JSON {"updated": true, "count": <n>}.

egress

Manage the egress allowlist each spawn’s own egress proxy is started with. The proxy is default-deny — a folder only reaches hosts it (or an ancestor) has been allowed. Rules cascade down the tree: a rule at a parent folder opens the host for every descendant. All three require the matching mcp:network_* grant. network_allow and network_deny check it again with the folder argument as the scope, so you manage egress only inside the subtree that your grant covers. No grant, no egress management. Backed by the network_rules table. Agent-only by design — there is no REST twin, and network_rules is not mounted at /v1/network_rules or listed in routd’s /openapi.json.

network_allow

Append an allowlist rule so folder and every descendant can reach host. Use when an agent needs a host the default-deny proxy blocks — a vendor API, say. host is a bare domain without scheme or port, e.g. example.com, or a *.example.com subdomain glob. The rule cascades to all children.

Source: resreg/resources/network_rules.go:31 · Grant action: network_allow · auth.Authorize re-run with the folder argument as the scope; host validated by validHostname.

nametypereqdescription
folderstringnoFolder the rule applies to, own folder by default; covers this folder and all descendants.
hoststringyesBare hostname, e.g. example.com — no scheme or path.

Returns: JSON {"allowed": true, "folder": "<f>", "host": "<host>"}.

network_deny

Remove an allowlist rule set on folder itself, closing access to host. Only drops a rule owned by this folder — a host inherited from an ancestor must be removed at that ancestor, not here.

Source: resreg/resources/network_rules.go:32 · Grant action: network_deny · auth.Authorize re-run with the folder argument as the scope.

nametypereqdescription
folderstringnoFolder the rule was set on, own folder by default.
hoststringyesHost to remove from this folder's own rules.

Returns: JSON {"denied": true, "folder": "<f>", "host": "<host>"}.

network_list

Read the egress allowlist of the own folder. resolved is every host the folder can reach — its own rules, those inherited from ancestors, and the instance base. own is only the rules set on the folder itself. Use to see why a host is reachable (own rule vs inherited) before adding or removing one.

Source: resreg/resources/network_rules.go:33 · Grant action: network_list · Reads the own folder of the caller.

Params: none. Returns: JSON {"folder": "<f>", "resolved": [...], "own": [...]}.

acl

list_acl

Audit the unified acl rows whose scope equals the target folder. Writes go through add_acl, remove_acl, arizuko group grant or the dashboard.

Source: resreg/resources/acl.go:42 · Registration gated on holding mcp:list_acl. Authorization: auth.Authorize re-run with the target folder as the scope.

nametypereqdescription
folderstringyesFolder to read.

Returns: JSON {"folder": "<f>", "acl": [{principal, action, scope, effect, params, predicate}, ...]}.

add_acl

Grant a principal access to a folder scope (writes an acl row). scope='**' confers the operator role. You can only grant within your own authority. Not for routes (add_route) or invites (invite_create). REST twin: POST /v1/acl.

Source: resreg/resources/acl.go:40 · Grant action: add_acl · auth.Authorize re-run with the scope argument as the scope — you can only write rows inside a subtree you were granted.

nametypereqdescription
principalstringyesSubject receiving access, e.g. user:alice.
scopestringyesFolder path or ** for operator role.
actionstringnoGrant action.
effectstringnoGrant effect; defaults to allow.
paramsstringnoNarrow the row to matching call arguments: name=glob[,name=glob].
grant_optionboolnoLet the grantee grant this row to others; only a holder who has the option can set it.

Returns: JSON {"ok": true, "principal": "<p>", "scope": "<s>"}.

remove_acl

Revoke a principal's access to a folder scope (drops the acl row). scope='**' revokes the operator role. You can only revoke within your own authority. REST twin: DELETE /v1/acl.

Source: resreg/resources/acl.go:41 · Grant action: remove_acl · auth.Authorize re-run with the scope argument as the scope — you can only write rows inside a subtree you were granted.

nametypereqdescription
principalstringyesSubject whose access to remove.
scopestringyesFolder path or **.
actionstringnoAction filter for the row to remove.
effectstringnoEffect filter for the row to remove.
paramsstringnoThe exact narrowing of the row; omit it for a row without narrowing.

Returns: JSON {"ok": true, "principal": "<p>", "scope": "<s>"}.

invites

invite_create

Issue an invite token granting access to a path glob. The recipient accepts via /invite/<token> and gets an acl(admin, target_glob) row. The agent's authority must cover target_glob — you cannot issue access you do not have.

Source: ipc/ipc.go:2118 · Grant action: invite_create · Issued by agent:<folder>.

nametypereqdescription
target_globstringyesPath glob the invite confers access to.
max_usesnumbernoDefault 1; min 1.
expires_atstringnoRFC3339 timestamp.

Returns: JSON {token, target_glob, max_uses, expires_at?, accept_url?}.

invite_list

List the invites this agent has issued: reference, target glob, expiry and use count. The raw token is never readable again. Read-only; shows only this folder's invites. Use to audit outstanding invites before revoking. REST twin: GET /v1/invites.

Source: ipc/ipc.go:2174 · Grant action: invite_list · Scoped to issued_by="agent:<folder>"; no caller-supplied folder arg.

Params: none. Returns: JSON {"invites": [{ref, target_glob, max_uses, used_count, expires_at?}, ...]}.

invite_revoke

Revoke an invite that you issued, so nobody can redeem it. You can revoke only invites of your own folder. invite_list gives the reference. REST twin: DELETE /v1/invites/{ref}.

Source: ipc/ipc.go:2201 · Grant action: invite_revoke · auth.Authorize re-run with the caller's folder as the scope; ownership enforced in routd closure.

nametypereqdescription
refstringyesReference of the invite, from invite_list.

Returns: JSON {"ok": true, "ref": "<r>"}.

web routes & web presence

Each group folder has a derived public hostname (<folder>.<HOSTING_DOMAIN>) that 302-redirects to its /pub/<folder>/ slot. No vhosts.json, no per-host config — the host is purely folder + "." + HOSTING_DOMAIN. An explicit WEB_VHOST_ALIASES entry overrides for the rare case where the folder label and the desired hostname differ. Use get_web_presence to discover the canonical host. The web_routes table (managed by set_web_route/del_web_route) is the inner layer: it controls access and redirects within /pub/<folder>/ after the vhost 302 lands.

del_web_route

Delete a web route by path. Only routes owned by this folder may be deleted (operators can delete any).

Source: resreg/resources/web_routes.go:37 · Grant action: del_web_route.

nametypereqdescription
pathstringyesPath prefix to remove.

Returns: text "ok", or error if not found / not owned.

get_web_presence

Report a folder's public web presence: derived canonical hostname, public/private base URLs, and the /pub/<folder>/ path that works even without HOSTING_DOMAIN. Read-only. You may query any folder your mcp:get_web_presence grant covers. REST twin: GET /v1/web_presence?folder=<f>.

Source: ipc/ipc.go:2474 · Grant action: get_web_presence · Computed by ipc.WebPresenceFor from WEB_HOST, HOSTING_DOMAIN, WEB_VHOST_ALIASES.

nametypereqdescription
folderstringnoFolder to query; defaults to caller's folder.

Returns: JSON {hosting_domain, derived_host, alias_host?, canonical_host, public_base_url, private_base_url, pub_path}. pub_path is the vhost-independent canonical path.

list_web_routes

List all web routes owned by this folder.

Source: resreg/resources/web_routes.go:38 · Grant action: list_web_routes.

Params: none. Returns: JSON array of {path_prefix, access, redirect_to, folder, created_at}.

set_web_route

Upsert a web route: control whether a URL path is public, auth-gated, denied, or redirected. Scoped to the caller's folder.

Source: resreg/resources/web_routes.go:36 · Grant action: set_web_route.

nametypereqdescription
pathstringyesURL path prefix; must start with /.
accessstringyesOne of public / auth / deny / redirect.
redirect_tostringnoRequired when access=redirect.

Returns: text "ok".

identity

inspect_identity

Resolve a platform sender sub (e.g. tg:123, discord:abc) to its canonical identity and the full set of subs claimed by that identity. Advisory only — agents query, never enforce. Returns {identity:null, subs:[]} when the sub is unclaimed.

Source: ipc/inspect.go:108 · Registered only when db.GetIdentityForSub != nil; bypasses the grant-rule filter.

nametypereqdescription
substringyesPlatform sender sub to resolve.

Returns: JSON {sub, identity, subs}.

system / JSON-RPC

submit_turn

Per-turn agent result delivery. Not a tool — a JSON-RPC method on the same MCP unix socket. Hidden from tools/list; the agent loop calls it directly with the turn payload. Idempotency enforced by routd on (folder, turn_id): the first call wins, a repeat answers ok and changes nothing.

An empty result is the agent deciding to stay quiet, and that is the point of a method call: a marker on stdout cannot tell “nothing to say” from “crashed”. A non-empty result goes out only when the turn did not already reply or send, and never from an isolated cron turn. <internal>, <think> and <status> blocks are stripped before anyone sees it.

Source: ipc/ipc.go:534 (method dispatch) and ipc/ipc.go:534 (handler).

nametypereqdescription
turn_idstringyesUnique per-turn id; enforces dedupe.
session_idstringnoClaude session id.
statusstringnosuccess or error.
resultstringnoFinal result text (on success).
errorstringnoError text (on failure).

Returns: JSON-RPC {"result": {"ok": true}}, or a JSON-RPC error.

Twenty-two more tools have the same gate

These tools follow the same rule as all others: the turn must hold mcp:<tool>. Their parameters are in the declaration that the source column names, and the resreg tools also appear in the /openapi.json of their daemon.

toolsourcewhat it does
observe_groupipc/ipc.go:1843Subscribe this folder to the inbound messages of another folder, as <observed> context.
unobserve_groupipc/ipc.go:1872Cancel an observe_group subscription.
resreg/resources/route_tokens.go:41Make a route token for the anonymous web chat at /chat/<token>/. See route tokens.
issue_webhookresreg/resources/route_tokens.go:42Make a route token for webhooks at /hook/<token>.
resreg/resources/route_tokens.go:43Make a one-time link that binds a channel identity to the account behind it.
list_tokensresreg/resources/route_tokens.go:44List the route tokens that the own folder owns.
revoke_tokenresreg/resources/route_tokens.go:45Revoke a route token by JID.
unpairresreg/resources/membership.go:36Undo a pairing, so the channel identity stops acting as the linked account.
list_pending_actionsresreg/resources/pending_actions.go:41List the tool calls held for approval in a folder.
approve_pending_actionresreg/resources/pending_actions.go:42Approve a held tool call by id.
reject_pending_actionresreg/resources/pending_actions.go:43Reject a held tool call by id.
query_auditresreg/resources/audit.go:31Read the audit log of the daemon, newest first.
list_dead_lettersresreg/resources/dead_letters.go:43List the outbound messages that routd stopped trying to deliver.
replay_dead_letterresreg/resources/dead_letters.go:44Put one dead-lettered message back on the delivery queue.
list_packagesresreg/resources/installed_packages.go:43List the installed packages of the instance.
get_packageresreg/resources/installed_packages.go:44Read the record of one installed package.
register_gdrive_fileresreg/resources/gdrive_files.go:35Bind a Google Drive file and route its comments to a folder.
list_gdrive_filesresreg/resources/gdrive_files.go:36List the Drive files that the own folder bound.
revoke_gdrive_fileresreg/resources/gdrive_files.go:37Unbind a Drive file by id.
register_github_sourceresreg/resources/github_sources.go:33Register a GitHub webhook source: a repository, an organisation or an App.
list_github_sourcesresreg/resources/github_sources.go:34List the GitHub sources of the own folder.
revoke_github_sourceresreg/resources/github_sources.go:35Revoke a GitHub source by its key.

cost accounting

There is no cost tool. Where an instance runs a model gateway, the gateway meters every call that passes through it and is the only ledger of what was spent — arizuko stores none of it. A number the agent reported about itself is exactly what a bypassed gateway produces, so the harness logs the agent’s self-report and stores nothing from it.

A call the agent makes outside the gateway — codex for /oracle, say — is metered by nobody, and no spend cap covers it. The operator sees it on that provider’s invoice.

external REST tools

Beyond the hand-written MCP tools above, arizuko can expose a REST API as an MCP tool from a declarative [[connector]] TOML descriptor — no Go code per endpoint. A descriptor maps a tool name to one HTTP call; when the agent invokes it, routd makes the request directly on the host, injects the credential from the secrets table, and hands back only the response body. The token never enters the guest. Loader: routd/ext.go; dispatcher: ipc/extcall.go CallExtTool.

the descriptor

Each provider is one [[connector]] block with transport = "rest": a URL, the secret keys it may read, where those secrets go, and one [[connector.tool]] per endpoint. The tool's MCP name is <provider>_<tool> (e.g. cloudflare_dns_list). Path params in {braces} are filled from the call arguments; remaining args become the query string on GET/DELETE or the JSON body on POST/PUT/PATCH.

# connectors.toml (operator) or a built-in routd/extproviders/*.toml
[[connector]]
name      = "cloudflare"
transport = "rest"
url       = "https://api.cloudflare.com/client/v4"
secrets   = ["CF_API_TOKEN"]
headers   = { Authorization = "Bearer {secret:CF_API_TOKEN}" }

  [[connector.tool]]
  name        = "dns_list"
  description = "List DNS records for a Cloudflare zone."
  scope       = "ext:cloudflare:dns:read"
  method      = "GET"
  path        = "/zones/{zone_id}/dns_records"

credential injection

There is no auth-method enum. You write {secret:KEY} where the value belongs, and it is rendered per call into one of three sinks:

sinkexample
headers{ Authorization = "Bearer {secret:CF_API_TOKEN}" }
query{ ApiKey = "{secret:NAMECHEAP_KEY}" }
body{ apikey = "{secret:PB_API_KEY}", secretapikey = "{secret:PB_SECRET}" }

A bearer is a literal prefix, not a mode, so a provider with an unusual scheme needs no new case — and a provider with four credentials is written the same way as one with one. The value is looked up per call for the triggering user (their own key overrides the folder default), never held in the descriptor, and scrubbed out of the response text before it returns to the agent. A declared key the store resolves to nothing has its whole entry DROPPED, so a placeholder never travels upstream as literal text.

built-in DNS providers

arizuko ships descriptors for four DNS registrars, ready to use once the operator sets the provider's token: Cloudflare (CF_API_TOKEN), Porkbun, Gandi, and Namecheap. Each covers the list/create/delete DNS operations. Operators add their own providers by dropping a [[connector]] block into connectors.toml in the instance dir.

grant gating

Every [[connector.tool]] carries a scope of the form ext:<service>:<operation> (e.g. ext:cloudflare:dns:write). Registration is grant-filtered like every other tool: the tool joins tools/list only when Authorize(folder, scope) passes, so an agent sees an external tool only where its folder is granted that scope. A call-time grant check stays as defense in depth.

notes & gotchas