Skip to content

Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.

Tasks and task grants

A task is one piece of work an agent does for one person, against one audience. The first exchange creates it. It has its own expiry, separate from any token.

A task grant is what the agent uses to get new tokens for that task. It arrives in the refresh_token field of the exchange response. It is bound to one task and one agent.

An agent registration may set two limits:

{
"max_task_ttl": "PT30M",
"max_token_ttl": "PT5M"
}
Limit Bounds If not set
max_task_ttl The whole task SubactId:Tokens:DefaultTaskTtl, 30 minutes
max_token_ttl Each token in the task SubactId:Tokens:DefaultTokenTtl, 5 minutes

Both are checked against the server-wide bounds SubactId:Agents:MinTaskTtl, MaxTaskTtl, MinTokenTtl and MaxTokenTtl.

A long task holds only a short token, renewed as it goes. A leaked token is useful for minutes, not for the length of the task.

Each token lives for the shorter of max_token_ttl and what is left of the task, so the last token of a 22-minute task lives two minutes, not five. A token never outlives its task.

The task’s lifetime comes from the registration, not from the subject token: a short-lived session token can start a longer task. The human is checked again at every renewal. If tasks should last no longer than the sessions that start them, set max_task_ttl to match.

The refresh authenticates the agent exactly as the exchange did. Then, in order:

  1. The grant exists and belongs to this agent. Otherwise: invalid_grant.
  2. The task is not revoked. Otherwise: access_denied.
  3. The task has not expired. Otherwise: access_denied.
  4. The task has at least five seconds left. Otherwise: access_denied with reason task_ending. Stop; do not retry.
  5. The human may still be acted for (see the sponsor check).
  6. The resource is the task’s audience. Otherwise: invalid_target.
  7. The requested scope is within what the task holds. Otherwise: invalid_scope.
  8. The agent’s current registration still allows it.

It then issues a new token with the same task_id and a new jti.

Subact ID keeps its own list of humans it will not act for. A human on that list is refused with access_denied at every exchange and every refresh. SubactId:UpstreamIdp:SponsorCheck:Mode sets how Subact ID learns who to refuse:

Mode What Subact ID does
poll (default) Checks its list and asks the identity provider’s admin API (Keycloak’s) on every exchange and refresh. An exchange always asks fresh. A refresh may reuse an answer for up to SubactId:UpstreamIdp:SponsorCheck:CacheTtl (30 seconds by default), and never longer than the token being issued. If the provider cannot answer: temporarily_unavailable.
signals Checks only its list. Operators add to it through the admin API; your identity provider can add to it through SCIM or Shared Signals. If nothing tells Subact ID, a task runs until it expires, which max_task_ttl bounds.

Use signals when your identity provider has no admin API Subact ID can read.

In poll mode, a person disabled or deleted at the identity provider fails the next refresh of every task, within one token lifetime. Back-channel logout and the CAEP session-revoked event work in both modes: they end tasks but do not add the person to the list. See revocation and connecting your identity provider.

Renew at about 60% of expires_in, not after a 401. @subactid/client does this for you and never hands out a token that is about to expire.

A task ends when it expires, when it is revoked, or when the agent stops using it. An expiry sweeper marks expired tasks as ended, revokes their grants and writes one task.expired record. Expiry is never recorded as a revocation.

Subact ID Pre-release. v0.1 is not out yet.

© 2026 Nikola Živković PR Agencija za programerske usluge Novi Sad. Subact ID is its product.

LegalTermsPrivacy