Skip to content
quarry Artifact [86c336fced]
Login

Artifact [86c336fced]

Artifact 86c336fcedbfea656a523e14d60c089b7b0ff9d899eaf7f270caaff95a1def16:


- Feature Name: `ask-your-person` (an agent refused for want of
  authority its person holds asks them instead; one approval runs the
  call as that person)
- Start Date: 2026-11-08
- Status: Draft. Decisions settled by the owner on 2026-11-08 (see
  [Decisions](#decisions)).
- Depends on: RFC 0021 (agent accounts, the operator, forge agents and
  their guards); RFC 0026 (an agent inherits its person within its
  scope); RFC 0011 (notifications: in-app, push, email)
- Amends: the sign-off of 2026-10-21 (RFC 0021, deed acts), which this
  generalises: its three deed operations become ordinary cases and its
  allowlist goes. RFC 0021 Forge agents: Guards ("a machine cannot
  multiply machines"), which now yields to a person's signed approval.
- Terms: an agent's **person** is who answers for it: a personal
  agent's operator, a forge agent's answerable site admin, or, for a
  dedicated team or repository agent, that scope's admins (any one of
  them may answer).

## Summary

Whatever a person may do, their agent may ask them to do. When an
agent's call is refused because it lacks authority its person holds, the
agent can ask instead of stopping. The person is told at once (inbox,
phone push, email), approves or denies with one tap, and an approval
runs exactly the call the agent made, as the person, under the person's
own permissions at that moment. The result goes back to the agent, a
secret in it readable once and by that agent only. Every zeitforge user
has this for every agent they register, with nothing to set up.

## Motivation

On 2026-11-08 a QA agent needed an account. Creating it was the only
step a machine could not take, and it cost the owner three manual steps
on a phone: a page to find, a token to copy, a vault to paste it into.
The forge already had the right shape for three deed acts (delete,
un-serve, hand on a repository): the agent asks, the owner approves, the
call runs as the owner. It stopped there for three reasons: an allowlist
of three operations, an approver found through a repository's deed (so
nothing forge-wide could be asked), and a replay that discarded its
result (so a minted token would be lost). This RFC removes all three.

## Design

### Asking

An agent asks by sending the call it wants with `Quarry-Ask: <reason>`.
The reason is one line in the agent's words, shown to the person beside
the forge's own description of the call. Without the header nothing
changes: a refusal is a refusal, so no agent queues requests by
accident.

The router decides before the operation runs:

- the caller is an agent with a person;
- the call is refused for authority only, not because its input is
  invalid, its target does not exist for this agent, or the forge is
  unavailable;
- the person holds that authority now (the same check the call would
  meet as them);
- the call is inside the agent's own scope (below).

When all hold, the answer is `202 Accepted` with the request (id,
summary, state `pending`), and nothing else happens. Otherwise the
ordinary refusal stands, and the agent learns no more than it would
have without asking: a repository it may not see is still answered as
one that does not exist.

### What may be asked

Anything the person may do themselves (decision 1), except:

- the person's own credentials and sessions: password, email, sign-in
  links, their own tokens and sync token;
- deciding sign-offs, and changing who answers for an agent;
- deleting, disabling or suspending the person's own account.

Everything else the person can do is askable, forge-wide operations
included: creating an agent, granting a role, changing a repository
setting, handing a repository on.

### Scope

A request must fall inside the agent's scope as its token states it: an
agent whose token names repositories can ask only about those, and a
token's letter ceiling bounds what it may ask for there. A forge-wide
operation needs a token with no repository list. The person's own
permissions bound the rest, checked again at approval, so an agent can
never obtain through a request what its person could not do.

### Answering

The request goes to the agent's person. For a dedicated team or
repository agent it goes to that scope's admins, and the first of them
to decide closes it. It is delivered at once on the sign-off workflow:
in-app, phone push, and email, on by default because a request waits on
someone. The email's link opens a page with the request and two
buttons, Approve and Deny; it needs the person's own session (a
sign-in link if they have none), so a forwarded email approves nothing.

Approval checks first that nothing it was asked about has moved: the
agent still exists, is open and still answers to this person; a
repository is still the same one (by its project code) and still within
the agent's scope; and the person may still do the call. Then it runs
the stored call, exactly as made, through the ordinary operation
registry as the person. The act is journalled as `agent (signed off by
person)`.

SMS is a notify channel, not a quarry feature: it is asked for upstream
at danmestas/notify and becomes one more step on the sign-off workflow
when notify ships it.

### Results

The result of an approved call is kept with the request for the agent
to read with `signoff.get`. A field an operation marks secret (a minted
token, an invite link) is readable once, by the requesting agent only,
and is never shown on the page, in the email or in the journal; after
the read it is gone. A failed run keeps its error for both to read.

### Limits

- A request expires after 7 days unanswered.
- An agent may have at most 20 requests pending; the same call asked
  again while one is pending returns that one.
- Asking is on for every agent by default. On an agent's page the
  person can turn it off for that agent; a refused agent is then told
  its person does not take requests from it.

### The forge-agent guard

RFC 0021 kept two acts from forge agents so that a machine cannot
multiply machines or raise its own standing: making or altering a forge
agent, and changing any account's site-admin flag. Those acts stay
unavailable to any agent directly, and become askable: with a person's
approval they are that person's act, which is what the guard asked for.

## Build order

1. Requests become forge-wide: the approver is the agent's person, the
   repository optional, the result stored (secret fields once), expiry
   and the pending limit. The three deed operations move onto it and
   the allowlist goes.
2. The router: `Quarry-Ask`, the authority-only refusal test, the scope
   test and the `202`.
3. The page and the email: one request, two buttons, session-bound.
4. The per-agent switch on the agent's page.
5. Secret fields in operation outputs: `agent.token.new`,
   `account.invite`, `agent.token.reissue`.

## Decisions

1. What may be asked: anything the person may do themselves, minus the
   exclusions above, not an allowlist (owner, 2026-11-08: "A").
2. Who has it: every zeitforge user and every agent they register,
   according to the agent's scope and the person's permissions, with
   nothing to set up (owner, 2026-11-08).
3. Reaching the person: inbox, push and email at once; SMS when notify
   has it (owner, 2026-11-08: "Inbox is fine but sms or email is even
   better").
4. Settled with the owner's choices as the frame: the explicit
   `Quarry-Ask` header, approval replaying the exact call as the person,
   7-day expiry, 20 pending per agent, a secret read once, and asking on
   by default with a per-agent off switch.