Skip to content

Acting on a holder's instruction

You can tell an agent working as a team worker, "change Iris's signature to this text", and it can do it. The platform allows that for two edits, and for listing and revoking an App's credentials, and only when the person who holds that worker manages the App and the worker names what they told it. The doors that staff a team (casting, retiring and deleting a worker) and change who is a member of an App accept the same claim on slightly different terms, set out further down. This page explains what that naming is, why the rule is built this way, and what it does and does not prove. For the exact refusals, see Worker signature and trailer.

Why there is a rule at all

Most changes to a team's staffing are a person's act: a person who manages the App, as themselves. A worker session is an agent acting for a person, and the platform cannot tell by itself whether the person asked for a particular edit or the agent decided on its own. A blanket refusal kept that safe, and it also made a plain request ("update my signature") impossible to hand to the agent you were already talking to.

The rule for these two doors (changing a worker's signature or trailer, and changing the agent a worker casts) keeps the person accountable and lets the agent do the typing. The worker's holder must manage the App, and the call must carry an instruction claim: a typed pointer to the place where the holder told the worker to do it.

What an instruction is

An instruction is a pointer, never the words themselves. It has a kind and a reference:

Kind The reference is The platform checks
TEAM_CHAT_MESSAGE A team chat message, as its hrn:node: URN The caller can read it, and it is in this App.
TASK A team task, as an hrn:agenttask: URN The caller can read it, and it is in this App.
LIVE_CHAT An opaque address your client gives to a live conversation Its shape only: 1 to 512 UTF-16 code units, with no whitespace or control characters. It is never message text.

"The caller can read it" includes a message or task the worker wrote itself. Pointing at your own message is accepted, and the platform does not try to tell that apart from pointing at the holder's.

A person may send an instruction too. It is checked the same way, so a bad, unreadable or other-App reference refuses the person as well. It is never recorded for them.

What is recorded

When a worker is admitted, the platform writes one team event that names the worker, the person who holds it, the kind and the reference. It records the pointer and never the content of the message or task. The signature edit is recorded as worker.signature-changed and a casting change as worker.casting.

What is not verified

The platform records the claim. It does not check that:

  • the instruction was ever given, or was clear;
  • the person wrote it, or that it is recent;
  • it is being used only once.

The holder answers for the claim. That is the point of the record: if an agent says it was told to and it was not, the event shows whose worker it was and what it pointed to.

Two limits follow, and they are accepted rather than hidden:

  • The platform does not detect a headless session. At the doors above it refuses a worker session that carries no claim, and a session launched by a host even when it carries one. (The staffing and member doors differ for a launched session; see below.) A session someone started by hand, which a ticker later wakes with a claim, is admitted, and the record is what holds its holder to account.
  • Nothing gives an instructed edit more reach than its holder has. If the holder manages the App, the worker may make these two edits. A holder who is a plain member gets the same refusal through the worker as they would get themselves.

Where it applies

This page covers sixteen doors that accept an instruction. Two edit a worker: updateWorker (the signature and trailer) and changeWorkerAgent. Seven list or revoke an App's credentials. Seven staff a team or change its members: castWorker, castWorkerPreview, retireWorker, deleteWorker, addAppMember, removeAppMember and leaveApp. The MCP tools for the two worker edits take no instruction, so a worker session that calls them stays refused. The specifications behind them are cor:dmo:050:12 (the rule), cor:agt:040:04 and cor:dmo:050:11 (the two worker doors) and cor:agt:080:24 (the two grants below); see Product specs and citations.

Listing and revoking an App's credentials

You can tell an agent, "list this App's keys" or "revoke that key", and it can do it, on the same terms. The seven doors are the three lists (appKeys, appKeysPage and appMemoryWriteGrants) and the four revokes (revokeAppKey, revokeAppKeyScope, revokeAppGrant and revokeAppMemoryWriteGrant). Each takes the same instruction claim.

Who is admitted. A person who manages the App, or that person's held worker on a validated instruction. A worker whose holder does not manage the App gets the holder's own refusal. A worker with no instruction is refused, as before.

A refused worker learns nothing about what exists. It gets the same answer whether or not the key, key scope or grant exists. A person keeps the more useful answer for most of them: "not found" for a missing key (including the key a scope revoke names) or a missing App grant. A missing write grant is the exception, and refuses everyone, a person included.

At these seven credential doors, a session launched by a host is refused whatever it claims, and nothing is recorded. Unlike the staffing doors, no grant admits it at these seven.

What is recorded.

  • An instructed revoke that changed a row records one app.credential-act event. A revoke that changed nothing records none (a key already revoked, or a scope the key did not hold), and a person acting directly records none.
  • An instructed list records one event per call, so one per page of appKeysPage.
  • The event never names a key, a scope or a grant id.

What stays person-only. Minting, rotating and giving keys, scopes, App grants and write grants. The nested App.keys field also refuses a worker session, with or without an instruction, because a nested field carries no instruction to check.

For the table of who may list and revoke what, see App keys: who lists, mints, revokes and rotates them.

Staffing and membership doors

You can tell an agent, "cast a backend engineer called Iris", "retire Iris" or "add Dara to this App as a member", and a worker session may do it. These seven doors take the same instruction claim, and a worker session also has a second way in.

Merged, not necessarily deployed

This is built in hadron-server at 517eb1db (hadron-server#3308). A server older than that still admits any worker session at these doors on its holder's right, as it did before. The rest of this section describes the server at 517eb1db and later.

The two ways in. A worker session is admitted at these doors in one of two ways, and refused if it has neither:

  • An instruction, as above: the call carries the claim, and the worker's holder must be allowed to make the change themselves.
  • Its own grant. A worker that holds the grant for the door may act without an instruction. workers.staff covers casting (and its preview), retiring and deleting workers. app.members covers adding and removing App members, including a member leaving.

The holder's own rights still apply on both ways. Neither the instruction nor the grant gives a worker more than its holder has. A holder who is a reader in the App cannot staff, so their worker cannot either, whether it carries an instruction or holds the grant. For the member doors, a holder who may not manage members may still remove only themselves, and the worker gets the same.

A session launched by a host. At these doors a launched session's claim is ignored, neither checked nor recorded. It acts only on its own grant: refused without one, admitted with one. This differs from the credential doors above, where a launched session is refused whatever it claims (the credential doors above).

Refusal comes first at the member doors. A worker session that has neither an instruction nor the grant is refused before the platform looks up the user you named, so the refusal says nothing about whether that user exists. Retiring and deleting look up the worker first, because the worker's App is the scope of the call. Casting is scoped by the App you name.

leaveApp is a second way to leave. A member can leave an App with leaveApp as well as through removeAppMember, and both are gated alike. A worker session leaving for its holder goes through the same rule (an instruction or app.members) and records the removal. A person leaving directly is unchanged.

What is recorded. An admitted instruction is recorded with the worker, the holder, the kind and the reference, as at the other doors. Three cases that change nothing still record the claim: an instructed cast preview, a cast of a worker that already exists, and a member add or remove that changes nothing.

The two grants

A grant is a narrow, named permission that one worker holds. These two exist for workers that act with no human watching, such as a host-launched session.

Grant Covers
workers.staff Casting, previewing a cast, retiring and deleting workers.
app.members Adding and removing App members, and leaving an App.
  • Only a person who manages the App gives one (ruling 0099). A worker never gives a grant to itself or to another worker, in any shape. Taking one back also admits the holder's worker on an instruction, as in Listing and revoking an App's credentials.
  • A grant counts only for the worker it was given to. Another worker of the same holder, or the holder themselves, does not inherit it.
  • None is given by default. Until a manager gives one, a worker with no instruction is refused at these doors.

From the command line the five commands that use this are team worker cast (and --dry-run), team worker retire, team worker rm, app member add and app member remove; see the CLI reference. The MCP staffing tools take no instruction, so a worker session calling them needs workers.staff.