Skip to content
quarry Artifact [b963145ed5]
Login

Artifact [b963145ed5]

Artifact b963145ed5f41f41262ff7a2db536e5b76ec0ffdd54a6702aa74a06cd82af515:


- Feature Name: `agents-inherit` (what an agent may do follows from what
  owns it: a person's agent inherits that person within its scope, a
  site's, team's or repository's own agent holds what it is granted)
- Start Date: 2026-10-31
- Status: Draft. Every decision settled by the owner on 2026-10-31 (see
  [Decisions](#decisions)); worked out first as a page of questions with
  a recommendation each, all taken as recommended except token upkeep,
  which the owner tightened. All six build steps are built.
- Depends on: RFC 0021 (agent accounts: the operator, the intersection,
  tokens, revoke-all, the deed rules, forge agents); RFC 0008 (roles over
  the letters); RFC 0018 (teams, their owners, a team's row on a
  repository); RFC 0011 §9 (the chat bridge, whose reader rule this
  keeps)
- Amends: RFC 0021 decision 3 and [Granting](0021-agent-accounts.md#granting)
  (a grant narrows instead of opening); [Tokens](0021-agent-accounts.md#tokens)
  and [Lifecycle](0021-agent-accounts.md#lifecycle) (no required expiry;
  idle lapse; one revoke-and-reissue per agent); [Team-owned
  agents](0021-agent-accounts.md#team-owned-agents) (replaced by team
  agents of two kinds); [Migration](0021-agent-accounts.md#migration)
- Terms: as RFC 0021, **operator** is the person, team, repository or
  forge an agent answers to, and **site admin** is the person who runs
  the forge.

## Summary

An agent is a personal agent or a dedicated agent, at site, team or
repository scope. A **personal** agent belongs to a person and holds
everything that person holds within its scope, live, unless the person
narrows it; it can never hold more. A **dedicated** agent belongs to the
site, a team or a repository itself and is a general account: it holds
what that scope's admins grant it. Tokens need no upkeep: one button
revokes and reissues an agent's tokens, one revokes all of a person's,
and an unused token lapses by itself.

## Motivation

RFC 0021 made an agent its own account that never holds more than its
operator, but gave it nothing by default: the operator grants it each
repository in turn. In practice an operator wants their agent to do what
they do, and they are the one responsible for it. Working repository by
repository is upkeep that buys little, since the intersection already
keeps the agent under its operator.

The second gap is ownership. 0021 knows a person's agent and a forge
agent and defers a team's. A release bot that belongs to a repository,
or a triage bot a team runs, has no home today except some person's
account, which ties a shared tool to whoever happened to create it and
breaks when they leave.

## Decisions
[decisions]: #decisions

Settled by the owner on 2026-10-31.

1. **The owner is responsible, so a personal agent inherits.** On every
   repository within its scope, a personal agent's letters are its
   operator's effective letters, computed at each check, unless the
   operator narrowed it there. It never holds more than its operator; an
   operator losing a letter takes it from the agent in the same instant.
2. **Two kinds, three scopes.**

   | Scope | Personal agent | Dedicated agent |
   |---|---|---|
   | Site | its owner's letters on every repository, and site powers if the owner has them and opted in (decision 4) | what site admins grant it (`zeitgeist` today) |
   | Team | its owner's letters on the team's repositories | what the team's owners grant it, on the team's repositories |
   | Repository | its owner's letters on that repository | what the repository's admins grant it, there only |

3. **A grant narrows.** Today's per-repository grant (`agent.grant`)
   keeps its storage and its clip to the operator, and changes meaning:
   where the operator wrote a string, the agent holds that string
   (intersected with the operator, as now); where none is written it
   inherits. Each personal agent has one switch, **inherit** or **only
   where set**, the second turning the grants into an allow-list.
4. **Site powers by inheritance are opt-in.** A personal site agent of a
   site admin is a site admin only when its owner chose that at
   creation, behind a confirmation, and the fleet's People page marks it
   "site admin by inheritance". A leaked token of such an agent can
   manage the whole forge, so it never happens by default.
5. **Existing agents do not widen.** Every person's agent today becomes
   a personal site agent set to **only where set**, keeping its grants.
   Its operator switches it to inherit when they choose.
6. **Dedicated agents answer to someone.** A repository agent is created
   by a holder of `a` on the repository; a team agent by one of the
   team's owners; a site agent by a site admin (RFC 0021's path for forge
   agents). The creator is answerable and can hand that to another admin
   or owner of the same scope. The agent goes with its repository or
   team; anyone who could create one may disable it.
7. **A dedicated team agent is held under its team.** On each of the
   team's repositories it may be granted at most the role the team holds
   there.
8. **Tokens need no upkeep.** One **Revoke tokens** button per agent
   revokes every token it has and issues a fresh one in the same step.
   One **Revoke all my agents** button on a person's own account (0021's
   revoke-all). No expiry date to choose and nothing to renew: a token
   lives while it is used and lapses after 90 idle days. When a person
   leaves, their personal agents' tokens end with them (decision 10); a
   dedicated agent's tokens stay with it. A site admin may revoke any
   agent's tokens.
9. **The record says who it was for.** Wherever an action is shown, a
   personal agent reads "`curator` for `dmestas`" and a dedicated one
   "`release-bot` for `quarry`". A check-in keeps the agent as its user,
   with the operator shown beside it, never substituted.
10. **An owner leaving ends their agents.** Disabling or deleting a
    person disables their personal agents and their tokens at once
    (0021's cascade, unchanged). A dedicated agent whose answerable
    person leaves passes to its scope's other admins or owners; with
    none, a site admin is asked.
11. **Agents do not make agents.** Only a person creates a personal
    agent, and only a person holding the scope's admin letters creates a
    dedicated one, so every line of responsibility is one step long.
12. **Limits.** 0021's cap on agents per person applies to personal
    agents; dedicated agents get a cap per repository and per team, set
    by site admins.
13. **`zeitgeist` is unchanged:** a dedicated site agent, answerable to
    `dmestas`, holding what it holds today.

## The model
[model]: #the-model

### Scope and kind are columns of the agent

An agent account gains two facts beside 0021's `operator`:

| column | values | meaning |
|---|---|---|
| `kind` | `personal`, `dedicated` | who it belongs to |
| `scope` | `site`, `team:<name>`, `repo:<name>` | where it reaches |

For a personal agent the operator is a person and the scope bounds what
it inherits. For a dedicated agent the operator is the scope itself
(`forge`, the team, the repository), and `answerable` names the person
who answers for it, as forge agents do today.

### Effective letters

For a personal agent P with operator O on repository R:

- outside its scope: what any signed-in account holds on R;
- **only where set** and no string on R: what any signed-in account
  holds on R;
- a string S on R: `S ∩ effective(O, R)`, as 0021 computes now;
- **inherit** and no string on R: `effective(O, R)`.

For a dedicated agent D on R inside its scope: its own string on R, and
for a team agent at most the team's row on R (decision 7). Outside its
scope: nothing.

Both are computed at each check, never copied, so there is no stored
state to fall out of date when an owner, a team or a role changes.

### Site powers

The site-admin flag is not a letter, so inheritance does not carry it on
its own. A personal site agent carries it only with the opt-in from
decision 4, and only while its owner holds it. A personal team or
repository agent never does.

### What does not change

The deed rules (0021 [What an agent may hold](0021-agent-accounts.md#what-an-agent-may-hold)),
run tokens for CI, the token value's shape, per-token narrowing by
repositories and ceiling, sync and API scopes, and the audit trail are
0021's as written. A token can still narrow its agent further; it can
never widen it.

## Tokens
[tokens]: #tokens

- **Revoke tokens** on an agent: one transaction revokes every token of
  that agent and mints one replacement with the same name, scope,
  repositories and ceiling as the most recently used; the value is shown
  once. A person with nothing to paste it into can ignore it, and it
  lapses on its own.
- **Revoke all my agents** on a person's own account: 0021's
  `agent.token.revoke_all`, reissuing nothing.
- **Idle lapse** replaces required expiry. `last_used` already exists; a
  token unused for 90 days fails every door with the refusal a revoked
  token gets, and stays in the list marked, as an expired one does. No expiry is asked for at
  minting. 0021's one-year maximum goes with it.
- A personal agent's token is as strong as its owner within its scope.
  That is the price of inheritance, and one-click revocation is what
  keeps it acceptable.

## Bridged chat
[chat]: #bridged-chat

The chat bridge's rule stands: a bridged channel is readable only by
those holding chat (`C`) on the repository, and the bridge stops if chat
is opened to more than named people. A personal agent holding `C` by
inheritance reads it exactly when its owner may. Linking a person's
upstream Fossil or GitHub login to their account, so that reading can
follow who holds chat upstream, is a later RFC.

## Migration
[migration]: #migration

- Every agent with a person operator gets `kind = personal`,
  `scope = site`, and **only where set**. Its grants are unchanged, so
  its reach is unchanged.
- Every forge agent gets `kind = dedicated`, `scope = site`, operator
  `forge`, `answerable` as today.
- Existing tokens keep working. A token with an expiry keeps it; a token
  with none lapses after 90 idle days from the day this ships.
- The "Your agents" section gains the inherit switch, the scope at
  creation, and the Revoke tokens button.

## What the forge shows

- **Your agents** (personal): each agent's scope, its switch, its grants
  as narrowings, Revoke tokens, and one Revoke all my agents.
- A repository's and a team's settings gain **Agents**: their dedicated
  agents, who answers for each, a create form for those who may, and
  Revoke tokens.
- The People pages show an agent with its operator and kind ("for
  `dmestas`", "for `quarry`"), and "site admin by inheritance" where it
  applies.

## What is not proposed

- Linking upstream identities (Fossil, GitHub) to forge accounts: a later
  RFC.
- An agent as a team member in its own right.
- Agents creating agents.
- Inheriting the deed, which no agent holds.

## Build order

1. The effective-letters rule with the inherit switch, behind the
   existing intersection; migration as above. Built: an `inherit`
   column whose default of 0 is the migration, `agent.inherit`, and
   narrowing on the agent's page. `kind` and `scope` wait for step 3,
   the first step that reads them: until then every person's agent is
   personal at site scope, and the operator column already says which
   kind an agent is.
2. Idle lapse and Revoke tokens; remove the required expiry from minting.
   Built: a token minted without a date lapses 90 days after its last
   use, at every door and for the sessions it opened; `agent.token.reissue`
   and the agent page's Revoke tokens. A date may still be asked for
   through the API, a year at most. Lapsed rows stay in the list, marked,
   rather than being swept.
3. Personal team and repository scopes in "Your agents". Built: an
   `agent_scope` column ('' the site, `team:NAME`, `repo:NAME`, and
   `gone` once that team or repository is deleted); `agent.new` takes
   `scope`, refused for a team the operator is not in or a repository
   they cannot read. Outside its scope an agent holds what it would with
   no string. `kind` is still read from the operator column until the
   dedicated agents of steps 4 and 5 need it.
4. Dedicated repository agents: creation, answerable, the repository's
   Agents section. Built: an agent whose operator is `repo:NAME`, made by
   an admin of the repository (who answers for it) from Settings, Agents.
   It holds the string the repository's admins grant it under People, on
   that repository only, never the site-admin flag; ten per repository.
   Deleting the repository disables it, revokes its tokens and moves its
   operator off the name.
5. Dedicated team agents, held under the team's row. Built, with one
   change from decision 7: a team has no role per repository, only each
   member's string on all of its repositories, so a team agent is made by
   a team owner and given its role by being added as a member, like any
   member. It holds that on the team's repositories only.
6. Site powers by inheritance, behind its opt-in. Built: a site admin
   creating their own site-wide agent may tick "also a site admin" with a
   confirmation (`site_admin` and `confirm` on `agent.new`). It holds the
   flag at forge scope and in its tokens while its owner holds it, and
   the fleet's People page marks it "site admin by inheritance".

## Testing

- A personal agent set to inherit holds exactly its owner's letters on a
  repository with no string, and loses one in the same check its owner
  loses it.
- **Only where set** holds no more than any signed-in account where no
  string is written; a migrated
  agent's reach before and after migration is identical on every
  repository.
- A personal repository agent holds nothing outside its repository; a
  personal team agent nothing outside the team's repositories.
- A dedicated team agent granted admin on a repository where the team is
  a developer holds developer.
- A personal site agent of a site admin is not a site admin without the
  opt-in, is one with it, and stops being one when its owner does.
- Revoke tokens leaves one fresh token and fails every earlier one;
  Revoke all my agents fails every token of every agent of that person.
- A token idle for 90 days is refused; one used on day 89 lives on.
- Disabling an owner disables their personal agents and tokens; a
  dedicated agent whose answerable person is disabled passes to another
  admin of its scope.
- An agent's attempt to create an agent is refused.