Ask the person an agent acts for (CIBA)

Before an agent takes a chosen action for someone, ask that person in Teams or on their phone. Their approval mints a short-lived token scoped to exactly that action.

After reading this page you will be able to:
  • Choose which tools need the person's own consent
  • Record what would have been asked before you enforce it
  • Approve or deny a request in Teams, on the phone or in the console
  • Verify the action token on the system that receives the call
  • Let a self-managed agent use the CIBA endpoints

Approvals ask a reviewer whether an agent's write may go out. That is the operator's check. This page covers a different check that belongs to the person: before the HR agent submits Alice's leave for 3 to 5 December, it asks Alice.

The standard behind it is OpenID Client-Initiated Backchannel Authentication (CIBA), in poll mode. The action is described with RFC 9396 authorization_details.

Nothing changes until you list a tool. With no tools listed, Liya in Teams, the copilot, the HR and SAP agents and scheduled runs work exactly as they did.

Go to Govern → Access, or call the API:

PUT /api/ciba/tools
{"tools": {"hr/submit_leave": {"mode": "log"},
           "sap/post_expense": {"mode": "enforce", "expiry_s": 900}}}

The key is server/tool. For each one:

  • mode: log (start here): the call goes through as before. A ciba.would_ask audit row records who would have been asked and for what. Use it to check the list before anyone gets a prompt.
  • mode: enforce: the call is held until the person approves.
  • expiry_s: how long the person has to answer. The default is 600 seconds and the maximum is 3600.

Changing the list needs access.write and is audited as ciba.tools.save.

What the person sees

When an enforced tool is called, the person the run is for is asked. That is the signed-in person for the copilot, or the Teams user for Liya. They are asked:

  • in their one-to-one Teams chat with the bot;
  • as a push notification on any phone they registered;
  • in the console, under GET /api/ciba/requests.

The message names the agent, the tool and the server, for example: "hr-agent wants to run submit_leave on hr". The request is bound to a SHA-256 hash of the exact arguments. Approving one leave request does not approve a different one.

Only the named person can answer (POST /api/ciba/requests/{ref}/approve or /deny). Anyone else gets a 404.

What the agent does meanwhile

The tool call returns "not run: asked alice to approve …". A run that can suspend waits. When the model calls the same tool with the same arguments after the person approves, the call goes out.

If nobody is behind the run, such as a scheduled run with no person, an enforced tool is refused with "this run has nobody behind it to ask". The refusal is audited as ciba.refused. Use log mode for tools that scheduled runs call.

The action token

On approval, Liyagent mints one token, once. It is a JWT signed with the platform keyring (typ: liyagent-action+jwt) and carries:

  • sub: the person;
  • act.sub: the agent;
  • authorization_details: the approved call;
  • a jti;
  • a lifetime of 5 minutes.

The call carries it in the X-Liyagent-Action-Token header. The receiving system can verify it against /.well-known/jwks.json. It no longer has to take the agent's word that the person agreed.

Self-managed agents

An agent that runs elsewhere uses the same flow with its gateway credential:

POST /api/gateway/ciba/bc-authorize
login_hint=alice&binding_message=Submit+leave+3-5+Dec
&authorization_details=[{"type":"tool_call","server":"hr","tool":"submit_leave","args_sha256":"…"}]

POST /api/gateway/ciba/token
grant_type=urn:openid:params:grant-type:ciba&auth_req_id=…

The token endpoint answers as the spec does:

  • authorization_pending until the person answers;
  • slow_down for a client that polls faster than interval, which then grows by 5 seconds;
  • access_denied or expired_token;
  • the access token once, after which invalid_grant.

Audit

Every step writes a row: ciba.request, ciba.approve, ciba.deny, ciba.token, ciba.would_ask and ciba.refused. Each row names the agent and the person it acted for.