Graine AI

Actions

Let the agent fill a field, move to a step or retry an upload on the customer's behalf — and hear honestly whether it worked.

An action is something the agent can do inside your app. It calls one, your code runs, and it waits to hear what happened before saying anything. That wait is the whole design: what it says next depends on whether the field accepted the value.

The agent“set the tenureto 24 months”The SDKfinds your handlerby nameYour codechanges the app,returns a statusIt speaksabout whatactually happenedapp_actionhandlerok | refused | errorNo handler for that name? The SDK refuses it and says so. The agent never waits forever, and neverclaims something it did not do.

In one minute: declare the action on the agent, write a handler with the same name in your app, return ok, refused or error with one sentence. Both halves are required.

1. Declare it

Agent → Embed & Widgets → Actions → What this agent may do in your app. Add a name, a description written for the model, and JSON-Schema parameters. Press Save — that is what pushes the catalogue to the agent.

KeyRules
nameLowercase, digits and underscores, starting with a letter. This is what the model calls.
descriptionFor the model. Say when to use it, not how it works.
parametersJSON Schema. An empty object when it takes no arguments.

An enum is worth adding wherever the value is one of a known set: an action declared with enum: ["light","dark","system"] is one the model cannot get wrong.

An argument can be a list or an object too — a set of answers, a whole plan with its days and items. In the panel, give that argument the type list or object and write its shape as JSON Schema: a list needs items, an object its properties. An imported file keeps every argument exactly as written, and the panel names anything a model provider would refuse (a list with no items, a shape with no type) before you save. Your handler receives lists and maps.

Names must match your useGraineAction handlers exactly. A name one underscore out declares a tool your app refuses for the life of the release, and the agent keeps trying it.

Already keep the list in source control? Actions → Import from JSON takes a bare array, or an object with appActions or actions around one — the SDK's convention is docs/agent-actions.json. Import replaces the panel rather than merging, so the dashboard ends up matching the file rather than keeping a stale action nobody implements.

2. Implement it

In the screen that performs it, not at the root:

PanScreen.tsx
useGraineAction("fill_pan_from_aadhaar", async () => {
  const value = await lookupPanFromAadhaar();
  setPan(value);
  return { status: "ok", data: { pan: value } };
});

It registers on mount and unregisters on unmount, so the agent is never offered "retry upload" on a screen with no upload.

Answer honestly

The return value decides what the customer is told.

return { status: "ok" };                                        // "Done."
return { status: "refused", message: "PAN is locked after submission." };
return { status: "error",   message: "The lookup service is down." };

Returning ok when the field silently rejected the value is the worst failure this feature has: the customer is told their form is complete and finds out later that it is not.

A refusal with a reason is often more useful than a success — "that field is locked after submission" is exactly what they need to hear, and only your code knows it. message is read by the agent, not shown to the customer; it decides how to phrase it.

Answer quickly

Six seconds. A handler that has not resolved by then is answered as a timeout, and the agent tells the customer it was not done rather than guessing. A handler that throws is answered as error, for the same reason.

For anything slower, return immediately and report the outcome as screen state:

useGraineAction("retry_upload", () => {
  startUploadInBackground();
  return { status: "ok", message: "Upload restarted — I'll watch it." };
});

The next useGraineScreen update tells the agent how it went. A handler that blocks for ten seconds produces an agent that has already apologised.

What people build with it

ActionWhat the agent does
Fill a fieldCompletes details it can look up
NavigateMoves them to the step they are asking about
HighlightDraws attention to the control they cannot find
RetryRe-runs a failed upload or mandate
EscalateHands over to a person, with context attached

An action declared but missing from the running build is refused immediately and the agent explains what to tap instead — so your dashboard and your app releases do not have to ship together.

One agent per surface

Actions are declared on the agent, and the model is offered every one of them on every surface that agent serves.

availableActions in the screen context narrows what the agent is allowed to do — one the current screen does not list is refused. It does not narrow the tool list the model sees; that is built once per call from the whole declared catalogue (capped at 24).

So a shared agent offers the model your website's tools inside your app. It spends tokens on tools that cannot work, picks worse because the real ones are buried, and occasionally calls one and has to recover mid-conversation.

SurfaceAgentActions
Marketing sitesite-assistantscroll_to_section, open_faq, download_app
Mobile appapp-assistantopen_setting, set_language, set_app_lock

Share a knowledge base. Never share a tool list.

Next steps

On this page