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.
Register an agent
The registration is the most an agent may ever do. Nothing it does at runtime can exceed it.
Register one
Section titled “Register one”Keep registrations in a repository and change them through review. Two commands do that:
SubactId.Server agent init jira-triage --out agents/# fill in allowed_scopes and allowed_audiences in agents/jira-triage.yamlSubactId.Server agent apply agents/*.yaml --server https://subactid.internal.example.cominit writes the registration, the agent’s key pair and its public key set, with tight defaults
and two empty lists you must fill in. apply reconciles the files with a running control plane
through the admin API. The command line has the details.
The same registration over HTTP, which is what apply sends:
curl -s -X POST https://subactid.internal.example.com/admin/agents \ -H "Authorization: Bearer $SUBACTID_ADMIN_KEY" \ -H 'Content-Type: application/json' \ -d '{ "agent_id": "jira-triage", "display_name": "Jira triage agent", "sponsor_required": true, "allowed_scopes": ["jira:read", "jira:comment"], "allowed_audiences": ["https://jira.internal"], "max_task_ttl": "PT30M", "max_token_ttl": "PT5M", "max_delegation_depth": 1, "jwks_uri": "https://agents.internal.example.com/jira-triage/jwks.json" }'The fields
Section titled “The fields”| Field | What it does |
|---|---|
agent_id |
Required. The agent’s name in its assertion, in act.sub as agent:<id>, and in the ledger |
display_name |
Required. For people reading the registry |
sponsor_required |
Must be true in v0.1, and is true when omitted |
allowed_scopes |
Required, at least one. The ceiling: a token’s scope is the intersection of this, the user’s scopes and the request |
allowed_audiences |
Required, at least one absolute URL. Any other audience is invalid_target |
max_task_ttl |
How long one task may last. Optional; SubactId:Tokens:DefaultTaskTtl when omitted |
max_token_ttl |
How long one token may last. Optional; SubactId:Tokens:DefaultTokenTtl when omitted, cut to the task if shorter. Never more than max_task_ttl |
max_delegation_depth |
Required, 1 to 5. v0.1 issues depth 1 only, so the value does not change what is issued |
high_risk_audiences |
Audiences whose tokens carry introspect_required. Empty by default |
jwks |
The agent’s public keys, held and served by the control plane. Set this or jwks_uri |
jwks_uri |
An absolute HTTPS URL the control plane fetches the agent’s keys from |
Durations are ISO 8601: PT5M is five minutes, PT6H six hours. Both lifetimes must fall within
the server’s SubactId:Agents:* bounds: a task from a minute to a day, a token from thirty seconds to
an hour, by default.
Choosing the values
Section titled “Choosing the values”allowed_scopes: narrow. It limits what a compromised agent can do. List what the agent does, translate that into scopes, and register exactly those.max_token_ttl: five minutes is usually right. It is how long a revoked task’s tokens stay usable for audiences validated locally; see revocation. Longer means fewer refreshes and a longer window for a leaked token.max_task_ttl: as long as the work takes. A token never outlives it, and a task that runs longer than needed is a grant left open.max_delegation_depth:1.high_risk_audiences: every audience you would not want reached after you revoke a task. A token for one carriesintrospect_required, and@subactid/serverand@subactid/mcpthen introspect it on every call, with no configuration on the tool server.
The agent’s keys
Section titled “The agent’s keys”The control plane authenticates an agent by a short-lived signed assertion, checked against the agent’s registered public keys. Set exactly one of:
jwks: the key set inline, asagent initwrites it. The control plane serves it at/agents/{agent_id}/jwks.json, so the agent needs no public endpoint and the control plane fetches nothing. A key with a private member is refused.jwks_uri: a URL the control plane fetches the keys from. It must be absolute HTTPS. The keys are cached for up to 10 minutes, so a removed key can keep working that long. To stop a key at once, hold it inline or disable the agent.
The assertion is a compact JWS signed with RS256, PS256 or ES256:
| Claim | Value |
|---|---|
iss and sub |
The agent_id |
aud |
The control plane’s issuer URL, or that URL plus /oauth2/token |
jti |
Unique, at most 256 characters. A reused one is invalid_client |
exp |
At most five minutes ahead. 60 seconds of clock skew is allowed |
@subactid/client builds and signs the assertion; see Build an agent.
Changing a registration
Section titled “Changing a registration”curl -s -X PATCH https://subactid.internal.example.com/admin/agents/jira-triage \ -H "Authorization: Bearer $SUBACTID_ADMIN_KEY" \ -H 'Content-Type: application/json' \ -d '{"allowed_scopes": ["jira:read"]}'PATCH takes any subset of the fields plus enabled, and validates the merged registration as a
whole. A change applies from the agent’s next request: a narrowed allowed_scopes at the next refresh of each live task, and {"enabled": false} on the
next exchange or refresh. Tokens already issued stay valid until they expire.
Every change writes agent.registered, agent.updated or agent.deleted to the ledger in the
same transaction.
A validation failure answers 400 with the fields named:
{ "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1", "title": "The request is invalid.", "status": 400, "errors": { "display_name": ["is required."], "max_delegation_depth": ["is required."] }}Deleting an agent, revoking its tasks and the admin key are covered in the admin API.
© 2026 Nikola Živković PR Agencija za programerske usluge Novi Sad. Subact ID is its product.