You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
22 KiB
503 lines
22 KiB
---
|
|
title: The Delegation Contract — activeUIX agent axis
|
|
type: specification
|
|
audience: human + agent + implementors
|
|
authority: NORMATIVE — this document defines conformance for the delegation axis
|
|
version: 2026-07-28
|
|
status: DRAFT
|
|
doctrine: docs/architecture/agent.md (the WHY; this document is the WHAT)
|
|
plan: docs/process/PLAN-agent.md (§5 · D-AG.12 — the decision that created this document)
|
|
---
|
|
|
|
# The Delegation Contract
|
|
|
|
**An agent is another actor.** This document specifies what an implementation
|
|
must do so that a delegation — a bounded transfer of control from a person to
|
|
another actor — is _observable, attributable, bounded and reversible_.
|
|
|
|
It is deliberately **not** an LLM specification. Nothing here depends on a
|
|
model, a provider or a prompt. A macro, an automation rule, a workflow and a
|
|
language model are all _another actor_, and they conform the same way.
|
|
|
|
---
|
|
|
|
## 0. About this document
|
|
|
|
### 0.1 Conformance language
|
|
|
|
The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**,
|
|
**SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY** and **OPTIONAL** are to be
|
|
interpreted as described in RFC 2119 / RFC 8174, when and only when they appear
|
|
in all capitals.
|
|
|
|
### 0.2 Requirement identifiers
|
|
|
|
Every normative statement carries a stable identifier of the form `AG-n`.
|
|
**Identifiers are never reused and never renumbered.** A requirement that is
|
|
withdrawn keeps its number and is marked `Withdrawn`; the number is not
|
|
recycled. Conformance failures — from either vehicle in §0.5 — **MUST** cite
|
|
the identifier they violate.
|
|
|
|
### 0.3 Stability ladder
|
|
|
|
Each section declares a stability level. The level tells an implementor what
|
|
they may build against:
|
|
|
|
| Level | Meaning |
|
|
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `stable` | Will not change incompatibly within a major version. |
|
|
| `provisional` | Shipped and testable, but may change while this document is `DRAFT`. |
|
|
| `reserved` | The shape is declared and the hole is typed; behaviour is NOT yet specified. Implementations **MUST NOT** repurpose reserved identifiers or wire categories for other meanings. |
|
|
|
|
### 0.4 Versioning
|
|
|
|
This document is versioned **by date** (`YYYY-MM-DD`). The date changes when a
|
|
normative statement changes incompatibly; editorial changes do not move it.
|
|
While `status: DRAFT`, any section may change.
|
|
|
|
### 0.5 Conformance vehicles
|
|
|
|
Conformance is verifiable by two distinct vehicles, because two distinct things
|
|
are being verified:
|
|
|
|
| Vehicle | Verifies | Nature |
|
|
| ------------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
| **Protocol fixture kit** | The wire and the custody machine | **Portable** — a third party runs it against _their_ implementation. Scripted turns in, expected custody transitions out. |
|
|
| **`agent-check`** | The participation contract of a component | Repo lint over the manifest tree. |
|
|
|
|
Both **MUST** cite requirement identifiers in their failures. A vehicle that
|
|
reports "failed" without naming what it failed is not a conformance vehicle.
|
|
|
|
### 0.6 Honest position
|
|
|
|
Stated here rather than discovered later:
|
|
|
|
- Capability calls are **sequential** in v1; concurrency is not specified.
|
|
- The axis is **client-first**. A server-authoritative implementation is
|
|
permitted and its portability is required by §7, but it is not the assumed
|
|
topology.
|
|
- **Generality is under test.** At the version above, the contract has been
|
|
exercised by one presence surface and is being proven against two domain
|
|
shapes (schema and set). Until §A.3's gate is met, the generality of the
|
|
manifest format is a claim, not a result.
|
|
|
|
---
|
|
|
|
## 1. Model and terminology
|
|
|
|
**Stability: `stable`**
|
|
|
|
```
|
|
Session — long-lived; identity; context across delegations
|
|
└── Run — ONE delegation: budget · authorization · undo group · control return
|
|
├── Turn — one exchange with the acting party
|
|
│ └── CapabilityCall — the ONLY thing that mutates the world
|
|
└── Context — snapshot of reads taken when the turn opens
|
|
```
|
|
|
|
- **Run** — a bounded transfer of control. The unit of delegation, of undo and
|
|
of audit.
|
|
- **CapabilityCall** — an invocation of a declared capability. The only
|
|
construct permitted to mutate anything.
|
|
- **Read** — a declared, versioned, origin-tagged view a participant exposes.
|
|
- **Manifest** — the declaration a component makes to participate: its
|
|
capabilities and its reads.
|
|
- **Initiative** — _why_ a run exists: `user | system | scheduled`.
|
|
- **Autonomy** — how far the run may go without a human: `suggest | review |
|
|
auto`.
|
|
|
|
> **AG-1** — Every mutation performed by a non-user actor **MUST** occur inside
|
|
> a CapabilityCall that belongs to a Run. A mutation without the delegation
|
|
> envelope (budget, authorization, undo grouping, control return) is
|
|
> non-conformant.
|
|
|
|
> **AG-2** — A Run **MUST** carry an `initiative`.
|
|
|
|
> **AG-3** — A single auto-authorized CapabilityCall **MUST** be expressible as
|
|
> a valid Run. (The non-model case — a macro, a rule — is not a second system.)
|
|
|
|
---
|
|
|
|
## 2. Custody of control
|
|
|
|
**Stability: `stable`**
|
|
|
|
The run states model **who holds control**, not how the acting party thinks.
|
|
Any internal plan/act/replan loop lives entirely inside `acting`.
|
|
|
|
```
|
|
┌─────────── escalated ──────────┐ (non-terminal)
|
|
▼ │
|
|
offered → planned → reviewing ⇄ authorized → acting ──┤
|
|
│ │ (re-entrant) │ ▼
|
|
└─────────┴──── cancelled ───────────────► returned (control ALWAYS returns)
|
|
```
|
|
|
|
> **AG-4** — Control **MUST** always return. Every Run **MUST** reach a
|
|
> terminal state (`returned` or `cancelled`) — including on failure, on budget
|
|
> exhaustion, on timeout and on kill. A Run that can end without returning
|
|
> control is non-conformant.
|
|
|
|
> **AG-5** — `returned` **MUST** carry a typed outcome from at least
|
|
> `{completed, partial, rolled-back, aborted, error}`, and **SHOULD** carry a
|
|
> typed reason.
|
|
|
|
> **AG-6** — `escalated` **MUST** be non-terminal, **MUST** carry a typed
|
|
> reason, and **MUST** auto-return on timeout. An abandoned escalation still
|
|
> needs an exit.
|
|
|
|
> **AG-7** — Authorization **MUST** derive a capability allowlist and a budget.
|
|
> A CapabilityCall outside that scope **MUST** escalate for re-review. It
|
|
> **MUST NOT** fail silently and **MUST NOT** proceed.
|
|
|
|
> **AG-8** — A mid-sequence failure **MUST** roll back to run-start, unless the
|
|
> capability declares that it accepts partial close. The person authorized the
|
|
> plan, not half of it.
|
|
|
|
> **AG-9** — User interruption **MUST** be modeled as a return, never as an
|
|
> error.
|
|
|
|
> **AG-10** — The implementation **MUST** provide a global kill that sends
|
|
> every active Run to `returned(aborted)`.
|
|
|
|
> **AG-11** — Pause and resume **MUST NOT** introduce new states: a pause is a
|
|
> return with resumable context; a resume is a fresh Run chained in the session.
|
|
|
|
---
|
|
|
|
## 3. The participation contract
|
|
|
|
**Stability: `provisional`** — the manifest format is being proven against two
|
|
domain shapes; see §A.3.
|
|
|
|
> **AG-12** — A participant declares capabilities and reads in a **manifest**.
|
|
> The manifest **MUST** be inert data: it **MUST NOT** have any effect until a
|
|
> running delegation reads it.
|
|
|
|
> **AG-13** — A component carrying a manifest **MUST** behave identically when
|
|
> no agent is present. Conformance to this requirement is verifiable by running
|
|
> the component's own test suite with no agent configured.
|
|
|
|
> **AG-14** — Every capability and every read **MUST** carry a
|
|
> natural-language description intended for the acting party. Implementation
|
|
> types **MUST NOT** be assumed to reach it.
|
|
|
|
> **AG-15** — Capability arguments **MUST** be typed by a schema and **MUST**
|
|
> be projectable to JSON Schema for the acting party.
|
|
|
|
> **AG-16** — The contract **MUST** declare a size limit per argument. Exceeding
|
|
> it **MUST** produce a typed invalid-arguments failure, not a truncation.
|
|
|
|
> **AG-17** — A manifest **MUST** declare a maturity tier from at least
|
|
> `{reads-only, reversible-acts, full-acts}`. The declared tier **MUST** be
|
|
> auditable against what the manifest actually exposes.
|
|
|
|
> **AG-18** — Reversibility **MUST** be declared **per effect**, not per
|
|
> capability. External effects (send, publish, notify) **MUST** be treated as
|
|
> irreversible.
|
|
|
|
> **AG-19** — The reversion authority **MUST** be the participant's own native
|
|
> history. Compensating transactions **MUST NOT** be presented as reversal.
|
|
|
|
> **AG-20** — Each read **MUST** export a version. Each act **MUST** carry the
|
|
> version it was planned against. A mismatch **MUST** be a _recoverable_ stale
|
|
> error that permits re-read and re-plan — not a run failure.
|
|
|
|
> **AG-21** — An invocation of an undeclared capability **MUST** be returned to
|
|
> the acting party as a result it can correct from, bounded by budget. It
|
|
> **MUST NOT** terminate the Run by itself.
|
|
|
|
> **AG-22** — Capabilities **MUST** be mechanical mutations at the
|
|
> participant's own altitude. Cognitive operations (summarize, translate,
|
|
> classify) **MUST NOT** be lodged in the participant.
|
|
|
|
> **AG-23** — When addressing is ambiguous (two instances of the same
|
|
> participant are mounted), the implementation **MUST** elicit. It **MUST NOT**
|
|
> resolve by heuristic.
|
|
|
|
---
|
|
|
|
## 4. Authorization, consent and autonomy
|
|
|
|
**Stability: `stable`**
|
|
|
|
> **AG-24** — Autonomy **MUST** resolve as: the application sets the ceiling;
|
|
> the person may only lower it; a capability may demand _more_ control, never
|
|
> less. Conflicts **MUST** resolve deny-overrides.
|
|
|
|
> **AG-25** — A capability with an irreversible effect **MUST** require at
|
|
> least `review`, regardless of the resolved ceiling.
|
|
|
|
> **AG-26** — Non-user initiative (`system`, `scheduled`) **MUST NOT** run at
|
|
> `auto`; it **MUST** be clamped to at most `review`.
|
|
|
|
> **AG-27** — Every read **MUST** carry an origin tag distinguishing at least
|
|
> author-produced from imported or third-party content.
|
|
|
|
> **AG-28** — An untrusted origin present in the turn's context **MUST** lower
|
|
> the autonomy ceiling. This is the specified answer to prompt injection: it is
|
|
> a policy row, not an open threat.
|
|
|
|
> **AG-29** — Authorization **MUST** bind to a fingerprint of the reads it was
|
|
> computed against, plus a time to live. Drift **MUST** trigger recomputation:
|
|
> an identical diff **MAY** auto-re-approve; a different one **MUST** re-review.
|
|
|
|
> **AG-30** — A persistent grant ("always allow X") **MUST** carry a time to
|
|
> live and an explicit scope, and **MUST** be re-gated when the capability set
|
|
> changes.
|
|
|
|
> **AG-31** — A review surface **MUST** present the concrete proposed change in
|
|
> the application's own domain. Prose descriptions of a change **MUST NOT**
|
|
> substitute for it.
|
|
|
|
---
|
|
|
|
## 5. Actor attribution
|
|
|
|
**Stability: `stable`**
|
|
|
|
> **AG-32** — Every CapabilityCall **MUST** carry an actor reference
|
|
> identifying who caused it. The absence of a reference **MUST** mean the
|
|
> person, never an unattributed actor.
|
|
|
|
> **AG-33** — Only the delegation engine **MUST** be able to produce an actor
|
|
> reference that resolves. A structurally forged reference **MUST** resolve to
|
|
> nothing.
|
|
|
|
> **AG-34** — Code outside the engine **MUST** carry a received actor reference
|
|
> verbatim. It **MUST NOT** construct actor contexts. _(Verified by
|
|
> `agent-check`; the type system alone cannot enforce this, because a cast
|
|
> type-checks.)_
|
|
|
|
---
|
|
|
|
## 6. Presence and accessibility
|
|
|
|
**Stability: `provisional`** — AG-35 carries an open decision; see §D.
|
|
|
|
This section is the axis's distinguishing contract. No surveyed framework
|
|
specifies it, and it is the reason the delegation is _observable_ rather than
|
|
merely _logged_.
|
|
|
|
> **AG-35** — A Run above `suggest` autonomy **SHOULD** have an attached
|
|
> presence — some surface that is expressing its cycle. An implementation
|
|
> **MUST** make this attachment observable, so that a Run entering `acting`
|
|
> with no presence attached is detectable rather than silent.
|
|
>
|
|
> **A presence is not a specific component.** Any surface that renders the
|
|
> cycle satisfies this requirement by attaching; the requirement is that
|
|
> _something is observing_, not that a particular widget is mounted.
|
|
>
|
|
> ⚠ **OPEN — see §D.1.** Whether an implementation **MUST** refuse such a Run,
|
|
> or **SHOULD** warn, is not yet decided.
|
|
|
|
> **AG-36** — The axis **MUST** own exactly one polite live region. Multiple
|
|
> concurrent announcement channels are non-conformant.
|
|
|
|
> **AG-37** — Announcements **MUST** be attributed (they say _who_ acted) and
|
|
> **MUST** be coalesced by plan. They **MUST NOT** be emitted per act or per
|
|
> delta.
|
|
|
|
> **AG-38** — The engine **MUST NOT** move focus. This is an engine invariant,
|
|
> not a courtesy of each surface.
|
|
|
|
> **AG-39** — The announcement that closes a Run **MUST** carry its outcome. A
|
|
> failure that returns control silently is indistinguishable from success and
|
|
> is non-conformant.
|
|
|
|
> **AG-40** — A region being mutated by the acting party **MUST** be marked
|
|
> busy while the mutation is in flight.
|
|
|
|
> **AG-41** — Elicitations **MUST** be dismissible or postponable, and
|
|
> **MUST NOT** be modal by default.
|
|
>
|
|
> ⚠ **NOT YET MET** — see §D.2. The channel that would carry an elicitation's
|
|
> question and its answer does not exist yet.
|
|
|
|
> **AG-42** — A keyboard-reachable exit from the delegation **MUST** exist at
|
|
> all times while a Run is active.
|
|
|
|
> **AG-43** — Under reduced-motion preferences, presence **MUST** be preserved
|
|
> in a static form. It **MUST NOT** be hidden.
|
|
|
|
> **AG-44** — No state of the cycle **MUST** be distinguishable by colour
|
|
> alone.
|
|
|
|
---
|
|
|
|
## 7. Wire protocol
|
|
|
|
**Stability: `stable` for the active categories · `reserved` where marked**
|
|
|
|
> **AG-45** — A transport **MUST** declare the protocol version it speaks.
|
|
|
|
> **AG-46** — The protocol **MUST** carry at least: turn lifecycle, text
|
|
> deltas, tool-call lifecycle, and an escape hatch for implementation-specific
|
|
> events.
|
|
|
|
> **AG-47** — State synchronisation is **reserved**. Its events carry a typed
|
|
> direction, because context flows from the interface to the acting party while
|
|
> agent state flows the other way; an implementation **MUST NOT** collapse the
|
|
> two directions into one channel.
|
|
|
|
> **AG-48** — Elicitation is **reserved** at the wire level, with its shape
|
|
> declared (a request identifier, a prompt, and an optional schema).
|
|
> Implementations **MUST NOT** repurpose it.
|
|
|
|
> **AG-49** — A transport is replaceable. An implementation **MUST NOT** make
|
|
> the delegation machine depend on a specific provider dialect; dialect
|
|
> translation belongs in an adapter.
|
|
|
|
---
|
|
|
|
## 8. Degradation
|
|
|
|
**Stability: `stable`**
|
|
|
|
| Requires | Optional | When missing | Error |
|
|
| ---------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- | ----------------------------------------------- |
|
|
| A transport port (the application supplies it) | orchestration, permissions, preferences, connection, storage | Manifest registration is a no-op; every participant stays fully functional | Only on invoking a capability with no transport |
|
|
|
|
> **AG-50** — With no transport configured, manifest registration **MUST** be a
|
|
> no-op and every participant **MUST** remain fully functional. An error
|
|
> **MUST** be raised only when a capability is actually invoked.
|
|
|
|
---
|
|
|
|
## Annex A — Frontier claims
|
|
|
|
These are the two claims this contract makes beyond the state of the field.
|
|
Each carries an explicit status, because a specification that claims what it
|
|
has not shipped is not a reference.
|
|
|
|
### A.1 — Context by attention · `claimed`
|
|
|
|
Reads are not a flat bag handed wholesale to the acting party. They are an
|
|
always-present index (identity, type, label, summary) plus deep reads pulled on
|
|
demand, **prioritized by what the person is attending to** — focus and
|
|
viewport. This is a signal a user-interface framework has and a server-side
|
|
agent framework does not.
|
|
|
|
**Status: `claimed`.** The push→pull index is specified (§3) and not yet
|
|
implemented.
|
|
|
|
### A.2 — Delegation as a transaction · `claimed`
|
|
|
|
One delegation is one undo step. The Run is the transaction envelope: budget,
|
|
authorization, optimistic concurrency per read, write scope, and supersession
|
|
when the premise changes underneath.
|
|
|
|
**Status: `claimed`.** Requirements AG-8, AG-19 and AG-20 specify it; it has
|
|
not yet been exercised against a set-shaped participant, which is where it
|
|
either holds or breaks.
|
|
|
|
### A.3 — Promotion gate
|
|
|
|
This document leaves `DRAFT` and claims reference status when, and only when:
|
|
|
|
1. **≥2 distinct domain shapes** have conformant manifests — at minimum one
|
|
schema-shaped and one set-shaped participant. Two text-shaped participants
|
|
do **not** satisfy this gate: a manifest format proven only over sequential
|
|
text silently assumes sequential text.
|
|
2. Both frontier claims above are `shipped`.
|
|
3. The protocol fixture kit passes.
|
|
|
|
---
|
|
|
|
## Annex B — Threat model
|
|
|
|
**Stability: `provisional` · informative**
|
|
|
|
The axis constructs, by definition, the classic lethal trifecta: private data,
|
|
untrusted content, and the ability to act. The contract's answer is distributed
|
|
rather than centralized:
|
|
|
|
| Threat | Where the contract answers it |
|
|
| ---------------------------- | --------------------------------------------------- |
|
|
| Prompt injection via context | AG-27 · AG-28 (origin lowers the ceiling) |
|
|
| Time-of-check/time-of-use | AG-20 (versioned reads) · AG-29 (fingerprint + TTL) |
|
|
| Actor impersonation | AG-33 · AG-34 |
|
|
| Silent action | AG-35 · AG-39 · AG-4 |
|
|
| Scope creep mid-run | AG-7 |
|
|
| Approval fatigue | AG-30 |
|
|
| Irreversible surprise | AG-18 · AG-25 |
|
|
|
|
---
|
|
|
|
## Annex C — Conformance checklists
|
|
|
|
### C.1 — For an implementation of the axis
|
|
|
|
**Shipped** as `$agent/conformance` (outside the main barrel):
|
|
|
|
```ts
|
|
import { runConformance, formatConformanceReport } from '$agent/conformance';
|
|
|
|
const report = await runConformance(mySubjectFactory);
|
|
if (report.failed) throw new Error(formatConformanceReport(report));
|
|
```
|
|
|
|
An implementor adapts `ConformanceSubject` — start · run · authorize · reject ·
|
|
resolveEscalation · stop · disable — over their own engine. The kit never
|
|
imports ours; `subject.ts` is the worked example of that adapter, not a
|
|
dependency. Every failure line leads with the `AG-n` it violates.
|
|
|
|
Covered today: `AG-2`, `AG-4`, `AG-5`, `AG-6`, `AG-9`, `AG-10`, `AG-21`,
|
|
`AG-25`, `AG-26`, `AG-50`. `AG-35` and `AG-41` are deliberately absent while
|
|
§D holds their decisions open — a kit that asserted an undecided requirement
|
|
would be inventing the decision.
|
|
|
|
### C.2 — For a participant (a component)
|
|
|
|
`agent-check`; every failure cites its `AG-n`.
|
|
|
|
---
|
|
|
|
## §D — Open decisions
|
|
|
|
Listed here rather than silently resolved. While this document is `DRAFT`, an
|
|
open decision is a legitimate state; on promotion, §D **MUST** be empty.
|
|
|
|
### D.1 — Presence: `SHOULD` or `MUST`? (AG-35)
|
|
|
|
The doctrine states the engine "refuses (or warns)". The parenthetical is
|
|
unresolved.
|
|
|
|
- **Refusing** gives the axis a hard guarantee: a delegation can never act
|
|
invisibly. It also breaks every headless and test usage that does not attach
|
|
a presence, and removes the application's right to choose its own surface.
|
|
- **Warning** preserves both, and makes the violation discoverable in
|
|
development — but a warning enforces nothing in production.
|
|
|
|
**Recommendation:** `SHOULD` by default, with an application-level policy that
|
|
promotes it to `MUST` for applications that want the hard guarantee. This is
|
|
expressible in RFC-2119 without weakening the claim, and it is the only shape
|
|
that does not punish an application for rendering the cycle its own way.
|
|
|
|
**Blocks:** the implementation of AG-35.
|
|
|
|
### D.2 — The elicitation channel (AG-41)
|
|
|
|
The requirement is specified and **not met**, for a reason that is structural
|
|
rather than cosmetic: an escalation carries a typed _reason_ but not a
|
|
_question_, and the resolution channel is binary — it has nowhere to carry an
|
|
_answer_.
|
|
|
|
Materializing AG-41 requires both directions:
|
|
|
|
- the question travelling out (prompt + schema, the shape AG-48 already
|
|
reserves), and
|
|
- a typed answer travelling back in.
|
|
|
|
**Blocks:** AG-41, and with it the completeness of §6.
|
|
|
|
---
|
|
|
|
## To go deeper
|
|
|
|
- **Why** any of this: [`docs/architecture/agent.md`](../architecture/agent.md).
|
|
- The execution record and signed decisions:
|
|
[`docs/process/PLAN-agent.md`](../process/PLAN-agent.md).
|
|
- The semantic vocabulary the cycle is expressed in:
|
|
[`docs/CANON.md`](../CANON.md).
|