Orkestia
Blog
Staff & Agents

Agents that serve people

Let a Staff agent do work for the person who asked it, in chat or from your own agent harness. The agent keeps its own identity and seat; the person is checked on every start, reads ride on their message and every write needs their tap

When a person asks an agent to do something, the agent runs as itself: its own Staff actor, its own paid seat, its own roles. The person who asked is the requester. The platform works out who the requester is from their signed chat message, records them on the run, and checks on every start (the run itself and every step it starts) that the requester may ask for it. The agent never holds the person's sign-in, token or seat.

What the agent can do for someone is the overlap of two things: what the agent itself may do, and what the requester may do. Nothing is open by default. An app admin opts each workflow in for agents, the requester's group must be granted it, and every write waits for a tap from the requester.

Live since 2026-11-04. If you want an assistant that acts as the person instead, use a DGI responder or Agents over App Data. Those run as the person who is talking; this page is for your own Staff agents.

The seat rule

Only a live, paid Staff agent can work for a requester. Every request checks that the calling actor:

  • is active and has exactly one live identity with an active agt_ token;
  • is in RBAC-scoped mode, which takes a paid actor seat (see Identity and tokens).

The agent's seat pays for the work, and runs, cost and audit are attributed to the agent. The requester's own seat is not touched, and the requester check takes no extra end-user seat. (In the platform chat, path A below, the actor still uses its usual chat attachment, which holds an end-user seat in the app. Path B needs none.) An organization API token, an integration key, or a chat key with no actor behind it cannot name a requester (REQUESTER_REQUIRES_AGENT).

Two ways a request arrives

Path A: platform chatPath B: your own harness
Who listens to the chatThe platform chat bridge, for an actor attached to the spaceYour agent process, connected to the chat with its own key
Who works out the requesterThe platform, from the message that woke the actorThe platform, from the signed message your harness forwards
What your code sendsNothing. The session is bound to the requester at launch_orkestia_requested_by on each start (see the harness contract)
CredentialThe agent's hosted sessionThe agent's agt_ token
How long one request lasts15 minutes after the message, for reads and approved writes. Longer with a task lease5 minutes after the message and at most 20 read starts. A write keeps its request usable while its approval lives. Longer with a task lease
Approval cardPosted by the agent as its answerPosted by your harness

Both paths use the same rules. A request message is always a chat message (kind 9) in one channel, mentioning the agent, written by a person who is an active member of the space.

What is checked on every start

For each start, top level and every step below it, the platform refuses unless all of these hold:

  1. The message is genuine. Its id and signature verify, it is a chat message in one channel of an active space of the agent's organization, and it mentions the calling agent's own key.
  2. It is fresh. Not older than the path's window, and not more than 60 seconds in the future.
  3. A person wrote it. The author is an active member of the space and a human end user of the space's app: active, seated, with a live account and app grant, and not signed out by an admin after the message was sent. Actors, the organization's agent keys, relay bots and unknown keys are refused.
  4. This agent claimed it. The first agent that uses a message claims it. Another agent presenting the same message is refused.
  5. The workflow is an exposed composition of the requester's app. Raw catalog workflows run only as steps inside an allowed composition.
  6. An app admin enabled it for agents (agent_on_behalf on that exposure).
  7. The requester's group is explicitly granted it. A capability no group manages is refused for agents, even though a person clicking it in your app may still run it.
  8. The requester is in the workspace the request belongs to, when there is one.
  9. Read or approved write. A read-only exposure rides on the message; anything else needs an approval tap (next section).

The check reads the database each time and caches nothing. If any part of it cannot be read, the start is refused.

Reads ride on the message, writes need a tap

A workflow is a write unless its exposure is marked read_only. An app admin sets the mark, and the platform accepts it only when every workflow the composition can start, nested compositions included, declares that it has no side effects. A wrong or missing mark costs the person a tap; it never lets an unapproved write through.

  • Reads. A read-only exposure runs on the request message alone, inside the window: path A for 15 minutes after the message, path B for 5 minutes and at most 20 read starts.
  • Writes. Every other start needs a per-action approval: the requester taps approve on a card that names the workflow and shows its input. The approval is bound to that exact composition version and that exact input. It is single use, expires 10 minutes after it was asked for, and covers that one run and everything it starts.
  • Inheritance. A step started by a read-only run must itself be read-only. A step started by an approved write is covered by that approval, but it is still checked against the requester (exposure, agent opt-in, grant).
  • Only the requester approves. A tap from anyone else is refused. A typed "ok", "yes" or "pode" approves nothing.
Few catalog workflows declare that they have no side effects yet, so most compositions cannot be marked read_only. Expect a tap for most actions, reads included, until more workflows carry the declaration. When the mark is refused, identity.exposure.set-agent-policy lists the steps that block it.

Set it up

A person in your organization does every step marked human only. The platform refuses those workflows when an agent starts them, so an agent can never widen its own reach.

Hire the agent with a seat

Hire the actor, switch it to RBAC-scoped mode (a paid actor seat) and mint its agt_ token under Admin → RBAC seats. See Identity and tokens.

Expose the compositions

Expose each composition the agent may run for people on your app, with identity.app.expose-virtual-workflow. This includes compositions nested as steps of another one: each is checked against the requester when it starts.

Enable each exposure for agents (human only)

start_workflow("identity.exposure.set-agent-policy", {
  "identity_app_uuid": "<app>",
  "composition_uuid": "<any version of the composition>",
  "agent_on_behalf": true
})

The setting applies to every exposed version of that composition on the app, and new versions keep it. Add "read_only": true for a composition that only reads. It is refused, and nothing changes, unless every step declares no side effects; the output then lists them in read_only_blockers. A new version that adds a step with side effects loses read_only on expose. "agent_on_behalf": false closes the exposure to agents again.

Grant the requesters' groups

The requester's group must hold an explicit grant for the composition: vw:<lineage_uuid>:invoke (or vw:<lineage_uuid>:*, or vw:*). The lineage_uuid comes from composition.save. The owner's * does not count.

start_workflow("identity.end-user.group.set-capabilities", {
  "identity_app_uuid": "<app>",
  "group_slug": "sales",
  "capabilities": ["vw:<lineage_uuid>:invoke"]
})

set-capabilities replaces the group's whole list, so send the existing capabilities too. Put people in the group with identity.end-user.assign-group, or give them that role in a workspace.

Agent-only grants, for admin groups (rolling out, see below). A vw: grant also decides what people in your app may click, and granting vw:* to one group takes the open default away from every other group. To let a group (your admins, for example) ask agents for every agent-enabled composition without changing what anyone can click, grant an agent-only capability instead:

CapabilityLets the group's people ask an agent for
agent:vw:<lineage_uuid>:invoke (or agent:vw:<lineage_uuid>:*)That one composition
agent:vw:*Every composition of the app that is enabled for agents

Agent-only grants are read only by the requester check. They never change who may run a composition directly, and the exposure must still be enabled for agents. set-capabilities is human only and refuses any other agent: form, or one naming a composition your app does not expose.

Require a requester (human only)

start_workflow("staff.set-actor-requester-required", {
  "organization_uuid": "<your organization>",
  "actor_uuid": "<actor uuid>",
  "requester_required": true
})

From then on every start this actor makes must name a requester, and is refused with REQUESTER_REQUIRED otherwise. It needs the admin role on the actor. The flag stays on the actor when its token is rotated.

In chat (path A) this flag is also what turns the requester check on: an attached actor without it answers exactly as before, as described on Actors in chat.

Connect the chat

  • Path A. Attach the actor to the space as on Actors in chat: bind its end-user seat, buzz.actor.attach, buzz.bridge.sync.
  • Path B. Your harness signs with its own key, which the platform never holds. An organization admin or owner registers that key, adds it to the space, and binds it to the actor (human only):
start_workflow("buzz.org.set-agent-keys", {
  "add": [{"public_key": "<64 hex>", "display_name": "Sales agent"}]
})

start_workflow("buzz.member.add-pubkey", {
  "space_uuid": "<space uuid>",
  "pubkey": "<64 hex>"
})

start_workflow("buzz.org-agent-key.bind-actor", {
  "public_key": "<64 hex>",
  "staff_actor_uuid": "<actor uuid>"
})

One active key per actor. An empty staff_actor_uuid unbinds. Without the binding, no request to that agent resolves (REQUESTER_AGENT_KEY_UNBOUND).

Path A: in the platform chat

When a person mentions a requester-required actor, the platform resolves them from that message and binds the actor's session to them. Inside that session:

  • the agent may call only compositions your app exposes (each checked as above) and the person's own paired Orkestia Browser. Other skills, MCP servers and generic workflow tools are refused with REQUESTER_RAW_TYPE_REFUSED;
  • only the requester may send messages into the session (REQUESTER_SESSION_MISMATCH for anyone else);
  • the session can act for 15 minutes after the message. After that, the person sends the request again;
  • a call your organization's tool policy sends to an org reviewer is refused (REQUESTER_ORG_APPROVAL_UNSUPPORTED): the person's own tap is the only approval here.

For a write, the agent asks for an approval card and posts it as its answer, with approve and deny buttons. When the person taps approve, the actor wakes again on the same request and makes the call with the approval.

A message the actor may not act on gets no answer, so nobody outside the rules learns that an agent sits in the space. An organization admin sees why in data.buzz.inbound.list: the reason is REQUESTER_REQUIRED:<code>, with a code from the table below.

Path B: the harness contract

Your harness reads the person's message from the chat, then starts the workflow with the agent's agt_ token and the message itself under the reserved key _orkestia_requested_by:

curl -X POST https://workflow-api.orkestia.dev/api/workflows/start \
  -H "Authorization: Bearer agt_…" \
  -H "Content-Type: application/json" \
  -d @start.json
{
  "workflow_type": "virtual.<composition_uuid>@3",
  "initial_data": {
    "customer_id": "C-1042",
    "_orkestia_requested_by": {
      "kind": "buzz_event",
      "space_uuid": "<space uuid>",
      "event": {
        "id": "<64 hex>",
        "pubkey": "<the person's key, 64 hex>",
        "created_at": 1793782800,
        "kind": 9,
        "tags": [
          ["h", "<channel id>"],
          ["p", "<your agent's bound key, 64 hex>"]
        ],
        "content": "@Sales agent what is the status of C-1042?",
        "sig": "<128 hex>"
      }
    }
  }
}
RuleDetail
CredentialOnly an agt_ token may send the key. Any other caller is refused (REQUESTER_REQUIRES_AGENT)
The eventThe person's message exactly as signed: all seven fields, unchanged. Any edit breaks the signature (REQUESTER_BAD_SIGNATURE)
Channel and mentionOne h tag with the channel. A p tag with your agent's bound key
Keyskind, space_uuid, event, and approval_uuid for a write. Any other key is refused (REQUESTER_MALFORMED)
AgeAt most 5 minutes old for a read and for asking an approval, unless a live task lease covers the request
Never sent to the workflowThe key is removed before the input is validated. Your composition never sees it, and the approval digest is computed without it
Do not name the requester yourselfrequested_by, _requested_by or similar keys in the input are refused (REQUESTER_OVERRIDE_REJECTED). The requester comes only from the signed message
VersionThe platform starts the exact composition version it checked. When that differs from the type you sent (an ORN, for example), the answer says so in requested_workflow_type

A refused start answers 403 (503 when the check is unavailable) with the code:

{
  "code": "REQUESTER_NOT_GRANTED",
  "message": "The requester may not ask this agent for this start.",
  "error": "ForbiddenError"
}

To ask before acting, call identity.requester.check with request_reference (the same object as _orkestia_requested_by) and workflow_type. It answers allowed, code, served_workflow_type, read_only and needs_approval, and spends nothing. It is advice only: the start is checked again.

Before you post an answer, check the person again. Your harness posts its answers to the chat itself, so the platform never sees them before they appear. Call identity.requester.check with the request and the composition the answer came from, and post only when the answer is allowed: true. On any other code, post nothing in that channel. This is your harness's side of the contract: the platform cannot enforce it, because your harness signs its own messages. See When a person loses access.

Writes: the approval flow

  1. Ask. The agent calls identity.requester.approval.request with request_reference, workflow_type, the exact input it will send, and optionally locale (pt-BR or en). The person must be allowed to run the composition, or the answer is status: "refused" with a code and no card is made.
  2. Post the card. The answer has status: "pending", approval_uuid, served_workflow_type, expires_at (10 minutes), the channel to post in, and the card. On path A the agent posts reply_text as its answer. On path B, post card.text in that channel with one ["buzz-quick-reply", "<label>"] tag for each entry of card.quick_replies. Values whose names look sensitive (passwords, tokens, tax ids) are masked on the card; the approval still covers their real values.
  3. The person taps. The buttons are rq:<approval_uuid>:approve and rq:<approval_uuid>:deny. A tap is the person's own signed message in that channel. It counts only from the requester, while the approval is pending and unexpired.
    • Path A: the platform chat records the tap and wakes the agent; nothing to do.
    • Path B: your harness receives the tap. Forward it unchanged to identity.requester.approval.record-tap with space_uuid and event (the full signed tap; approval_uuid is optional and must match the token). The answer is recorded (true or false), status (approved, denied or refused) and, when refused, code and reason. The person's signature authorizes the tap, not your harness, and only the agent the approval was issued to can record it. Recording the same tap twice answers REQUESTER_APPROVAL_USED.
  4. Start. Start served_workflow_type with the identical input and approval_uuid added to _orkestia_requested_by. Changing any value refuses the start (REQUESTER_APPROVAL_DIGEST_MISMATCH). The approval is spent by that start, even if the run then fails: ask again for a retry.
"_orkestia_requested_by": {
  "kind": "buzz_event",
  "space_uuid": "<space uuid>",
  "event": { "…": "the same request message" },
  "approval_uuid": "<approval_uuid>"
}

Before the tap, the start is refused with REQUESTER_APPROVAL_PENDING; after deny, with REQUESTER_APPROVAL_DENIED.

Long tasks

Rolling out. Task leases, the agent-only grants above, and the stopping of work already running and of path-A answers described in the next section reach your organization with the next platform release. Until then identity.requester.lease.request is not in your catalog, and the windows in Two ways a request arrives apply as they are.

One request message lets an agent read for a short time: 15 minutes on path A, 5 minutes and 20 read starts on path B. Work that lasts longer has three options. The windows themselves are never raised.

A task lease: one tap for up to 2 hours. Inside the request's window, the agent asks the person to let the task keep reading:

start_workflow("identity.requester.lease.request", {
  "organization_uuid": "<your organization>",
  "request_reference": { "kind": "buzz_event", "space_uuid": "<space uuid>", "event": { "…": "the request message" } },
  "minutes": 120,
  "purpose": "Analysing the 40 contracts you sent",
  "locale": "en"
})
  • The answer has the same shape as an approval request: status: "pending", code: "LEASE_PENDING", lease_uuid, minutes, expires_at (10 minutes to tap), the channel, card, quick_replies and reply_text. Post the card as you post an approval card.
  • minutes is 1 to 120. The lease runs from the person's tap, not from the request.
  • It must be asked inside the request's window, by an agent the person may still ask. A late ask answers REQUESTER_REQUEST_EXPIRED.
  • Path A: the agent asks for it itself when a task needs more time, and posts the card as its answer. When the person taps, the agent continues.
  • Path B: forward the person's tap to identity.requester.approval.record-tap exactly like an approval tap (approval_uuid is the lease_uuid), then keep sending the same _orkestia_requested_by.
  • While the lease lives, read-only starts under that request are allowed after the window ends, and path B may make up to 500 read starts instead of 20.
  • Writes still need a tap each. A lease never approves a write.
  • The lease ends at its time, or at once when the person loses access.

A new message from the person. On path B, send the person's newer signed message: it opens a new window. On path A, a new message from the same person in the same conversation also gives the agent's work still running for them a fresh 15 minutes.

One composition for the whole task. Steps started by a run the request allowed are not limited by the window: one exposed composition started inside it runs to the end. Use this when the steps are known in advance.

When a person loses access

A person loses access when they are disabled or signed out by an admin, lose their seat or app grant, leave the workspace, or are removed from or suspended in the space. What stops, and when:

WhatWhen it stops
A new request, or a new composition started for themAt once: every start checks the person again
A step that may change data, inside work already runningAt its next such step: the step is refused and the run fails with the reason (REQUESTER_INACTIVE, REQUESTER_NOT_MEMBER, REQUESTER_NOT_IN_WORKSPACE, ...)
Work that only reads, task leases and agent sessions working for themWithin seconds: the platform cancels them
The agent's answer in the platform chat (path A)Never posted. An organization admin sees requester_revoked:<code> in data.buzz.inbound.list
Your harness's answer (path B)Only if your harness checks before posting, as described in the harness contract

Path B: stop work you hold outside the platform. The platform cannot cancel work your harness does on its own. Check the person before each answer with identity.requester.check. When a person is removed from or suspended in the space, their key also leaves the space's membership list (kind 13534, signed by the chat server), so a harness subscribed to that list can stop their work at once. Other ways of losing access (an admin disabling the person, for example) are not announced in the chat yet: your next start or check is refused instead.

What the run records

  • metadata.requested_by on the run and on every step below it: the requester's end user, app, workspace, space, the request message id, and the approval when there was one. No step can change or drop it.
  • The run's actor is the agent, and triggered_by is agent:<staff actor uuid>, whatever the caller sent.
  • Rows the run writes in App Data are owned by the agent, never by the requester.
  • Refused starts are written to your audit log as identity.requester.refused, sampled to about one entry a minute per agent and code.

Refusal codes

The request message

CodeMeaningWhat to do
REQUESTER_MALFORMEDThe reference or event has the wrong shape: unknown keys, not a chat message, missing fieldsSend kind, space_uuid, event (and approval_uuid) only, with the full event
REQUESTER_BAD_SIGNATUREThe event id or signature does not verifyForward the event exactly as it came from the chat
REQUESTER_SPACE_UNAVAILABLEThe space is not active, or belongs to another organizationUse a space of the agent's organization
REQUESTER_WRONG_CHANNELNot exactly one channel tag, an archived channel, or a tap outside the approval's channelUse the message's own channel; post the card where channel says
REQUESTER_AGENT_KEY_UNBOUNDThe agent has no key in this space: not attached (path A) or no bound key (path B)Attach the actor, or bind its key with buzz.org-agent-key.bind-actor
REQUESTER_NOT_ADDRESSEDThe message does not mention this agentThe person must mention the agent
REQUESTER_STALE_EVENTThe message is older than the window or dated in the future, or a tap falls outside the approval's lifeAsk the person to send the request again
REQUESTER_NOT_MEMBERThe author is not a member of the spaceAdd the person to the space
REQUESTER_NOT_HUMANThe author is an actor, an organization agent key, a relay bot, or the agent itselfOnly people can be requesters
REQUESTER_INACTIVEThe person, their seat, account or app grant is not active, or they were signed out after the messageRestore the person's access, then ask them to send the request again
REQUESTER_EVENT_CLAIMEDAnother agent already used this messageEach agent answers its own mentions
REQUESTER_WRONG_ORGANIZATIONThe run's organization is not the requester'sStart in the organization that owns the space

The workflow and the requester

CodeMeaningWhat to do
REQUESTER_NOT_EXPOSEDNot a composition your app exposes, or not the version it serves (served_workflow_type says which)Expose it, or start the served version
REQUESTER_NOT_AGENT_ENABLEDThe exposure is not enabled for agentsA person runs identity.exposure.set-agent-policy with agent_on_behalf: true
REQUESTER_NOT_GRANTEDNo group of the app explicitly grants this compositionGrant vw:<lineage_uuid>:invoke to the right groups
REQUESTER_NOT_PERMITTEDGroups are granted it, but not the requester'sMove the person to a granted group, or tell them they cannot
REQUESTER_NOT_IN_WORKSPACEThe person is not an active member of the request's workspace, or the workspace is inactiveAdd them to the workspace
REQUESTER_RAW_TYPE_REFUSEDA raw catalog workflow or another tool was called for the requesterWrap the step in an exposed composition
REQUESTER_READ_ONLY_VIOLATIONA read-only run tried to start a writeMark the step read-only only if it is; otherwise ask for an approval
REQUESTER_REQUEST_EXPIREDThe read window is over or the read budget (path B: 20) is spent, or a task lease was asked after the windowAsk the person to send the request again. For long work, ask for a task lease inside the window

Approvals

CodeMeaningWhat to do
REQUESTER_APPROVAL_REQUIREDA write with no approvalCall identity.requester.approval.request and wait for the tap
REQUESTER_APPROVAL_PENDINGThe card has not been tapped yetWait for the tap; do not start again until then
REQUESTER_APPROVAL_DENIEDThe person tapped denyDo not retry. Ask the person what they want instead
REQUESTER_APPROVAL_EXPIREDPast the 10 minutesAsk for a new approval
REQUESTER_APPROVAL_USEDAlready spent by an earlier startAsk for a new approval for each start
REQUESTER_APPROVAL_INVALIDUnknown approval, or one for another agent, person, request, composition or versionUse the approval_uuid and served_workflow_type from your own request
REQUESTER_APPROVAL_DIGEST_MISMATCHThe input differs from the approved inputSend exactly the input you asked approval for
REQUESTER_APPROVER_MISMATCHSomeone other than the requester tappedOnly the requester can approve

The agent and the call

CodeMeaningWhat to do
REQUESTER_REQUIRES_AGENTThe caller is not a Staff agent's agt_ token or hosted sessionCall with the agent's own credential
REQUESTER_AGENT_INELIGIBLEThe agent is not active, has no live identity or token, or is not on a paid RBAC-scoped seatFix the actor's seat and token
REQUESTER_REQUIREDThis actor must name a requester and none was given or resolvedSend _orkestia_requested_by, or have the person mention the actor
REQUESTER_OVERRIDE_REJECTEDThe input tries to set the requester, or a hosted session sent a second oneRemove the field; the requester comes only from the message
REQUESTER_SESSION_MISMATCHSomeone other than the requester messaged a requester-bound sessionThe other person starts their own request
REQUESTER_ORG_APPROVAL_UNSUPPORTEDThe org's tool policy sends this call to a reviewer, which cannot apply to a call made for a personAllow that workflow for the agent in the tool policy
REQUESTER_START_NOT_RECORDEDThe start was not recorded against its approval or read before it ranStart again through the normal start call
REQUESTER_UNAVAILABLE, REQUESTER_CHECK_UNAVAILABLE, REQUESTER_CHECK_FAILEDThe check could not run, so the start was refusedRetry later. If it persists, contact support

Not supported yet

  • Person- and workspace-owned tables. Runs made for a requester reach only the app-owned App Data tables granted to the agent. Tables owned by a person or a workspace stay closed to them, so the agent can reach neither the requester's own rows nor anyone else's. Writes that land in such tables are refused until a narrowing design ships.
  • Read-only exposures are rare. See the warning above: most actions need a tap until more catalog workflows declare that they have no side effects.
  • Approval card preparation. On path A the agent waits up to 120 seconds for the card to be prepared. If it is not ready in time, nothing is done and the person can ask again.
  • Approvals by someone else. Only the requester approves. An app admin cannot approve for another person.
  • Other work by a requester-required actor. Such an actor works only for people. Its sessions started by a schedule or a Staff event, with no person behind them, cannot call tools.
  • Who asked, on rows. App Data rows do not record the requester yet; the run does.

Ask your AI assistant

prompts
Check whether the person who sent this chat message may have my agent run virtual.<uuid>@<version>. Use identity.requester.check with the message as request_reference and tell me the code.

List the compositions my app exposes and which of them have agent_on_behalf on. Prepare (do not run) the identity.exposure.set-agent-policy call for the one called "<name>".

My agent got REQUESTER_NOT_PERMITTED for "<composition>". Find the group that grants vw:<lineage_uuid>:invoke and the group of the requester, and explain the gap. Do not change anything.

For AI agents

RuleDetail
Who you areYou run as your own actor. Never ask for or use a person's token
Human onlyidentity.exposure.set-agent-policy, staff.set-actor-requester-required and buzz.org-agent-key.bind-actor are refused for agents: each checks who started it and answers a permission error (identity_admin_requires_human for the identity one) to an agent, an agent session or an API key. Prepare the call; a person starts it
WritesCall identity.requester.approval.request, post its card, wait for the tap (path B: forward it to identity.requester.approval.record-tap), then start with the identical input and approval_uuid. Never treat a typed "ok" as approval
RetriesREQUESTER_APPROVAL_DENIED, REQUESTER_NOT_PERMITTED and REQUESTER_NOT_GRANTED do not change on retry. Tell the person
ExpiredREQUESTER_REQUEST_EXPIRED or REQUESTER_STALE_EVENT: ask the person to send the request again
Long tasksAsk identity.requester.lease.request inside the window, post its card, and wait for the tap. A lease never approves a write
Before answering (path B)Call identity.requester.check; post nothing unless allowed is true
Raw toolsFor a requester, use exposed compositions only

Identity and tokens

RBAC-scoped seats and agt_ tokens.

Actors in chat

Attach an actor to a space (path A).

Compositions

Save and expose what the agent may run.

Workspaces

Roles and groups that grant capabilities.