Intentions
Let AI agents and your own app ask one running resource of your product, such as a device, to carry out one action your product declared.
What an intention is
An intention asks one resource of a product (a device, a machine, any
running host) to carry out one tool the product declared, such as
restart. Yayaw decides whether the caller may ask, signs a lease valid for 30
seconds, delivers it to the resource's host, and returns the host's answer:
done with a result, or refused with a reason.
It works the same for every product: the product declares its tools as data in its bundle, and Yayaw provides the checks, the delivery and the trail.
Declare the tools
The bundle's intents section lists, for each resource kind, the tools its
hosts accept, the action a caller must hold on the resource for each, and the
arguments each takes.
"intents": {
"kinds": [
{
"resource": "acme-device",
"type": "acme-agent-intent+jwt",
"audience": "urn:yayaw:acme:agent-intent",
"gateFlag": "acme:agent-intents-enabled",
"tools": [
{ "name": "restart", "action": "update" },
{ "name": "rotate_key", "action": "manage",
"arguments": { "keyId": { "type": "string", "maxLength": 64 } } }
]
}
]
}typeis<product key>-agent-intent+jwtandaudienceisurn:yayaw:<product key>:agent-intent: a lease for one product is never valid for another.Arguments are a fixed, typed list matched exactly: a host never receives a field its product did not declare.
Nothing is issued while
gateFlagis off.
Who can ask
| Caller | How |
|---|---|
| An AI agent (Claude, ChatGPT…) | The MCP tool yayaw_resource_intent_issue with resource, resourceId, tool, arguments. |
| Your signed-in app | POST /api/resources/{resource}/{resourceId}/intents with its device session as the bearer. |
Both wait for the host's answer and return the same result. Everyone reads
their own trail with yayaw_resource_intents_list: the intentions they asked
and those asked of a resource they host.
From your app
POST /api/resources/acme-device/device-1/intents
Authorization: Bearer <device session>
Content-Type: application/json
{ "tool": "restart", "arguments": {} }{ "success": true,
"data": { "outcome": "done", "jti": "…", "resourceId": "device-1",
"tool": "restart", "result": { "restarted": true } } }A refusal by the host is a 200 with "outcome": "refused", a code and a
message. The request waits up to the lease's 30 seconds: give your client a
timeout above 40 seconds. The host's indicator shows "Signed-in app" for these
intentions.
What Yayaw checks
The credential can write.
The resource kind declared intentions and its
gateFlagis on.The tool is declared, and the caller holds its action on this resource through their groups.
The arguments match the declaration.
The resource's host registered its key, and was seen in the last 15 seconds with that key.
| Code | Status | Meaning |
|---|---|---|
write_scope_required | 403 | The credential cannot write. |
forbidden_resource | 403 | Undeclared tool, unknown resource or missing right: one answer for all three. |
intents_unavailable | 404 | The kind declared no intentions, or its flag is off. |
invalid_request | 400 | The body or the arguments do not match. |
resource_identity_unknown | 409 | The resource has no registered host key. |
resource_unavailable | 409 | The host is not running with agent control on. |
intent_timeout | 504 | No answer in time: the outcome is unknown, read the resource again before retrying. |
The host's side
The host signs in with its own device session and:
Beats its presence every few seconds with
PUT /api/resources/{resource}/{resourceId}/presenceand{ "hostFingerprint": "<its key>" }, and removes it withDELETEwhen it stops accepting intentions.Reads its leases from
/api/dynamic-data/runtime-eventswith the topicresource-intent:{resource}:{resourceId}, by polling or as a stream. Only the host's account can read them.Verifies each lease: a JWT signed by the instance (keys at
/api/auth/jwks), with your product'stypeandaudience, not expired, naming its own key, a declared tool and declared arguments.Answers once with
POST /api/resources/intents/{jti}/ackand{ "outcome": "done", "result": {…} }or{ "outcome": "refused", "code": "…", "message": "…" }. The first answer wins.