atmon docs

FOR-YOUR-AI/THE-FOUR-VERBS.MD

The four verbs

This is what an assistant sees once it is connected, and how each one is meant to be used. Read it if you are writing the prompt around atmon, or debugging a model that is using it badly.

VerbThe jobWhat comes back
search_toolsFind the toolA ranked short list of tool names with compact argument schemas, a flag on each saying whether that person has connected the app, and a resolution id
describe_toolRead the toolOne tool's full definition: description, arguments, what it returns, permissions it needs, and whether it is destructive
call_toolDo one thingThe receipt: status, result, and an error code that is empty on success
submit_jobHand over a whole jobA job id, then, through get_job, the job with its receipts, what it is waiting on, and why it parked

Search by intent, never by guess

The first call takes what the person actually said, not a tool name the model invented. A guessed name fails and costs a turn; worse, a guessed name that happens to exist is the wrong action taken confidently.

{ "intent": "file a bug about the failing build", "entity_id": "user-42", "max_tools": 5 }

The argument names on this page are the ones the MCP verbs declare, which is what an assistant is handed. Calling the same work over HTTP instead uses the RPC's own lowerCamelCase names, entityId and maxTools; the API reference writes those.

What comes back is scoped to that person: the apps they have connected, plus any private tools your project built. Ask for the whole catalog explicitly if you want to show somebody what they could connect.

The resolution id that comes with it is the link between the search and the action. Pass it into call_tool. It is also what closes the learning loop: reporting whether the chosen tool was the right one is what improves the next ranking, and each resolution can be reported once.

Read the arguments before calling

The compact schema in a search result is enough for a simple call. When it is not, describe_tool gives the full one.

Fill arguments from what the person said. Never from a plausible default. An assistant that invents a recipient because none was given is the failure mode this whole section is written to prevent.

One thing at a time, with the two arguments that matter

{
  "tool_slug": "github.create_issue",
  "entity_id": "user-42",
  "resolution_id": "res_...",
  "arguments": { "owner": "rudrite", "repo": "automaton", "title": "build is red" },
  "idempotency_key": "issue-build-red-1"
}

The person. Whose accounts this runs in. Not the project, not the assistant.

The arguments. One object, matching the schema the search result carried. Over HTTP the same object arrives as a JSON string in argumentsJson instead.

The idempotency key. Set it on anything that sends, charges, or creates. The same key returns the original call instead of doing it again, so a retry after a timeout is safe.

Confirmation. A destructive tool called without it is refused, before any credential is unlocked, and the refusal names what would be removed. Confirming means adding "confirm": true inside arguments, beside the tool's own fields:

{
  "tool_slug": "github.delete_repository",
  "entity_id": "user-42",
  "arguments": { "owner": "rudrite", "repo": "scratch", "confirm": true }
}

There is no confirm argument beside tool_slug, and one written there is ignored, so the call is refused again with the same message. The flag means one thing: the person was told what would change, in the app's own nouns, and said yes. It is not a formality and it is not the same as an approval, which is a different person with a different key. See Approvals and questions.

Hand over the whole job when it is bigger than the turn

More than about twenty rows, or any work that outlives the conversation, belongs in submit_job rather than in a loop of calls.

The reason is not tidiness. A loop puts every row through the model's context and leaves no way to know what already happened when it crashes at row four hundred. A job passes collections between steps by reference, derives a key per row so a crash cannot send twice, and answers with a receipt rather than with the rows. See Jobs.

Do not poll for the result in a tight loop. Subscribe to the platform events and read the job when woken, or read it when the person asks.

Read a refusal as an answer

A refused call is a successful call that says no. It arrives as a normal result carrying a status and an error code, not as a transport error.

Three groups, and they call for three different behaviours.

Wait, then retry the same call. The pace limits and the transient failures. Each carries its own interval in the detail; backing off on a timer you invented ignores what you were told.

Ask a person, then continue. A parked approval, and a destructive call that carried no confirmation.

Stop; a person has to act first. No account connected, an account missing a value the app needs, or an authorization that has lapsed. None of these is a retry, and none of them can be fixed from inside the conversation. Say which app it is, in the person's words rather than the tool's, and say what they have to do.

Every code and its treatment is in Error codes, and the limits are in Limits. The atmon skill puts all of this in front of the model directly, which is why installing it is worth more than writing it into your own prompt.

Say what the receipt says

The last rule, and the one that decides whether people trust the thing. Report what came back, not what the plan hoped for. A job whose receipt says the count is incomplete did not do the whole job, whatever the plan asked for.