docs: roles design round 2, rails layers and autonomy levels (#1873)
This commit is contained in:
1 parent
9f0fcf8690
commit
3bcdee0926
1 file changed
+269
-58
@@ -2,16 +2,22 @@
|
||||
|
||||
Date: 2026-11-10. Lane org-roles-design, issue #1873. Status: proposed, doc
|
||||
only. Nothing here changes `AGENTS.md`, `roles/`, `contracts/`, rights policy
|
||||
or code. The slices in section 12 that touch role or rights policy land only
|
||||
or code. The slices in section 13 that touch role or rights policy land only
|
||||
with Jason's approval.
|
||||
|
||||
Round 2 follows ruling 13. Merge and policy decisions stay with the arbiter
|
||||
agent. The rails split into system rails and role rails (section 3), and a
|
||||
per-organization autonomy setting decides where each class of decision goes
|
||||
(section 9).
|
||||
|
||||
Read with `docs/plans/2026-11-10_agent-levels-design.md` (levels, isolation,
|
||||
Phase A) and `docs/plans/2026-11-10_accounts-design.md` (rights, host trust
|
||||
boundary). This design replaces agent-levels section 7 and the A1 roles patch,
|
||||
which widened the chief's `hireable` list. That patch is withdrawn and nothing
|
||||
here extends it.
|
||||
|
||||
Line numbers are against `origin/refactor` at `50be33355`.
|
||||
Line numbers are against `origin/refactor` at `f98f278fb`. Its code is the
|
||||
same as at `50be33355`.
|
||||
|
||||
## 1. Rulings
|
||||
|
||||
@@ -29,6 +35,7 @@ Jason, 2026-11-10:
|
||||
10. Credentials: the hire decision lists each credential and scope the agent needs; approving the decision binds them.
|
||||
11. Template updates: an imported role is a frozen copy. When the shipped template changes, the system offers a diff; it never changes the copy by itself.
|
||||
12. Personality: the chief may draft personality and traits inside the hire decision. After the hire only a human changes them.
|
||||
13. Trust, verbatim, answering whether merge and policy decisions should go to a person instead of the arbiter agent: "I need to be able to trust the agents to do their jobs. The agent role should establish the rails they operate upon. Tooling and instructions should be clear. We are aiming for a move from "Human in the loop" to "Human near the loop" to "Human out of the loop" operations. We are currently HEAVILY in Human in the loop." They stay with the arbiter.
|
||||
|
||||
Also binding, Jason 2026-11-07: the system is modules joined by documented
|
||||
calls. Swapping or disabling one must not break core, and adding one must not
|
||||
@@ -69,28 +76,59 @@ need a core rewrite (`docs/plans/2026-11-07_organizations-owner-rulings.md:43-47
|
||||
|
||||
## 3. The rails
|
||||
|
||||
A rail is a rule no role, template, plugin, approval or setting can change
|
||||
while the system runs. Only a reviewed commit changes one. Each row says where
|
||||
the code enforces it today and which slice closes the gap.
|
||||
Rails come in two layers. System rails hold for every role at every autonomy
|
||||
level, and only a reviewed commit changes one. Role rails are what each role's
|
||||
definition says its agent may do. Ruling 13 asks for both: agents trusted to
|
||||
do their jobs, inside rails their role sets.
|
||||
|
||||
### System rails
|
||||
|
||||
Nothing changes these while the system runs: no role, template, plugin,
|
||||
approval or autonomy setting. Each row says where the code enforces the rail
|
||||
today and which slice closes the gap.
|
||||
|
||||
| # | Rail | Enforced today | Gap, and the slice that closes it |
|
||||
|---|---|---|---|
|
||||
| R1 | Secrets never enter the repository, images, role files, decision text or the trail. A credential reaches an agent only through a human-approved binding, scoped and revocable. | Message and record bodies pass `credentials.assertClean` (`packages/bus/src/runtime.mjs:833`, `broker.mjs:840,1313,2081`). Credential verbs are human-only (`broker.mjs:6144`). `credential.mint` decisions route to the human (`broker.mjs:2266-2268,2368`). Fire revokes (`instance-settings.mjs:396`). | Role definitions, personality text and hire decisions are not scanned. P1 runs the same check on every string in a composition and a hire decision. |
|
||||
| R2 | The trail and run records are write-once. | `events` and `decision_events` refuse update, delete and replace (`packages/bus/src/migrations/3b.sql:131-132,137-138,144,147`). | None. Hire, deny, expiry and binding entries use the same tables (A4). |
|
||||
| R3 | Fail closed: an unknown action, tool, right, plugin kind or role field refuses. | Role keys, actions and tools (`role.mjs:190,42,24-29`). Rights (`org-rights.mjs:122-129`). Plugin kinds (`packages/plugins/src/kinds.mjs:147-148`). | Inline roles and plugin-declared tools and actions have no check yet. P1 and PL1. |
|
||||
| R4 | Hard waits stay with the human. A role can never hold an action outside `ROUTED`. | `vocabulary.mjs:109`, `role.mjs:43`. This covers `git.push.protected`, `git.push.remote`, `host.sudo`, `credential.mint`, `spend`, `deploy` and `data.delete` (`vocabulary.mjs:22,26,29,49,50,51,55`). | Added: the brief's list did not name it, and "any capacity" must not dissolve it. P1 keeps it for every composition. |
|
||||
| R5 | Human-only acts: approving a hire, fire or role change (`org.hire`), binding a credential, changing personality after hire, installing or enabling a plugin, changing a rail, and resolving a merge or policy decision. No agent approves its own request, and no agent approves a request that changes authority. | No agent resolves its own arbitrated raise: it goes to the human (`broker.mjs:7075`). Plugin enabling needs a human session (`broker.mjs:6008`). Hard waits always go to the human (R4). | Corrected. The brief's "no agent approves another agent's request" is false today. A cross-role raise goes to the domain arbiter, the chief by default, and an agent of that role resolves it (`broker.mjs:7074,7212-7215`, `packages/organization/src/org-template.json:5`). PRD 1.5 REQ-DEC-2 put `git.merge.protected` and `policy.change` on that path (`vocabulary.mjs:69-70,83,93`), and merge-gate holds merge cross-role (`roles/merge-gate.json:10`, `broker.mjs:5198-5201`). This design keeps agent arbitration only for one-off routed work that grants no lasting authority: task, review, record and message actions. A4 routes every decision whose action is `role.hire`, `role.fire`, `credential.mint`, `git.merge.protected` or `policy.change` to the human, whatever its class. That reverses part of REQ-DEC-2, so it needs Jason's approval. The dev tester may enable some plugin kinds (`broker.mjs:6013`); that stays dev-only. Personality has no field yet (T1). |
|
||||
| R6 | Every agent runs sandboxed as the instance uid, with only the tools its role names. A tool exists only if core or an installed, enabled plugin declares it. | bwrap with user, pid and ipc namespaces and a read-only root (`packages/seat/src/sandbox.mjs:76,176`). Tools are a closed core list (`vocabulary.mjs:142`) and the typed bus tools are fixed in core (`packages/harness/src/tools.mjs:96`). | Corrected: no plugin declares a tool today. PL1 adds plugin-declared tools. A sandbox escape holds the instance uid, which equals root for the instance (accounts design, "Host trust boundary"). |
|
||||
| R2 | The trail and run records are write-once. | `events` and `decision_events` refuse update, delete and replace (`packages/bus/src/migrations/3b.sql:131-132,137-138,144,147`). | None. Hire, deny, expiry, binding, setting and reverse entries use the same tables (A4, AU). |
|
||||
| R3 | Fail closed: an unknown action, tool, right, plugin kind, role field or autonomy value refuses. | Role keys, actions and tools (`role.mjs:190,42,24-29`). Rights (`org-rights.mjs:122-129`). Plugin kinds (`packages/plugins/src/kinds.mjs:147-148`). | Inline roles and plugin-declared tools and actions have no check yet. P1 and PL1. AU refuses an unknown class or level. |
|
||||
| R4 | Hard waits stay with the human. A role can never hold an action outside `ROUTED`. | `vocabulary.mjs:109`, `role.mjs:43`. This covers `git.push.protected`, `git.push.remote`, `host.sudo`, `credential.mint`, `spend`, `deploy` and `data.delete` (`vocabulary.mjs:22,26,29,49,50,51,55`). | The brief's system-rail list did not name it, and "any capacity" must not dissolve it. A hard wait leaves the list only when a PRD version moves it into `ROUTED` (REQ-WAIT-3, `vocabulary.mjs:67-69`), never through the autonomy setting. P1 keeps it for every composition. |
|
||||
| R5 | No agent widens its own authority. No agent resolves a decision it raised, a decision whose subject is itself or a definition it runs, or anything that changes the autonomy setting or a rail. | An arbiter's own raise goes to the human (`broker.mjs:7075`). PRD approval refuses an arbiter that is the PM or an author (`broker.mjs:1060`). | Rewritten in round 2. Round 1 also sent merge and policy decisions to a person; ruling 13 reverses that, and REQ-DEC-2 stays as ratified. Nothing checks a decision's subject against its resolver today. AU adds that check, which sends such a decision to a person. |
|
||||
| R6 | Every agent runs sandboxed as the instance uid, with only the tools its role names. A tool exists only if core or an installed, enabled plugin declares it. | bwrap with user, pid and ipc namespaces and a read-only root (`packages/seat/src/sandbox.mjs:76,176`). Tools are a closed core list (`vocabulary.mjs:142`) and the typed bus tools are fixed in core (`packages/harness/src/tools.mjs:96`). | No plugin declares a tool today. PL1 adds plugin-declared tools. A sandbox escape holds the instance uid, which equals root for the instance (accounts design, "Host trust boundary"). |
|
||||
| R7 | Organizations are isolated. Cross-organization reads go only through the assistant's Console read broker. | Each organization has its own bus and every non-assistant agent's scope is one organization (agent-levels design section 4). | The read broker is not built. It is A6. Until then nothing reads across organizations. |
|
||||
| R8 | No grant beyond the granter, except through a flagged line in a human-approved decision. | People: `canGrant` subset (`org-rights.mjs:122-129`). Agents: a hire is bounded by `HIRE_ALLOWED` and the chief's list (`broker.mjs:6303-6305`). | A4 replaces that bound with the approval itself. The broker computes the flagged lines; the chief cannot omit them. |
|
||||
| R9 | No hire chains. Only the chief raises hires, and a hired agent cannot hire. | `role-not-chief` (`broker.mjs:6193`). `role.hire` is not on `HIRE_ALLOWED` (`vocabulary.mjs:114-121`). | Added from ruling 8. P1 refuses `role.hire` and `role.fire` in any hired composition, so the rail survives `HIRE_ALLOWED` going away. |
|
||||
| R10 | Plugin code runs with the host's authority, so installing one is an instance-level human act. | The loader reads only its own tree and takes no path from config (`packages/plugins/src/loader.mjs:1-5`). | Added: the loader says a module "runs with the host's authority" (`loader.mjs:5`). Install from GitHub or a zip has to keep that visible. No hash pin exists. PL1. |
|
||||
| R8 | No grant beyond the granter, except a flagged line the owner approves. This holds at every autonomy level. | People: `canGrant` subset (`org-rights.mjs:122-129`). Agents: a hire is bounded by `HIRE_ALLOWED` and the chief's list (`broker.mjs:6303-6305`). | A4 replaces that bound with the approval itself. The broker computes the flagged lines; the chief cannot omit them. |
|
||||
| R9 | No hire chains. Only the chief raises hires, and a hired agent cannot hire. | `role-not-chief` (`broker.mjs:6193`). `role.hire` is not on `HIRE_ALLOWED` (`vocabulary.mjs:114-121`). | From ruling 8. P1 refuses `role.hire` and `role.fire` in any hired composition, so the rail survives `HIRE_ALLOWED` going away. |
|
||||
| R10 | Plugin code runs with the host's authority, so installing one is an instance-level human act. An organization's setting cannot move it. | The loader reads only its own tree and takes no path from config (`packages/plugins/src/loader.mjs:1-5`). | The loader says a module "runs with the host's authority" (`loader.mjs:5`). Install from GitHub or a zip has to keep that visible. No hash pin exists. PL1. |
|
||||
| R11 | System config is written only by bootstrap. | `scripts/bootstrap.sh:5`, AGENTS.md invariant 2. | None. Nothing in this design writes system config. |
|
||||
| R12 | Rails change only by reviewed commit, and an organization's autonomy setting only by its owner. | The vocabulary and `roles/` change by reviewed commit (AGENTS.md invariant 7). | New in round 2. The setting does not exist yet; AU adds it as an owner-only Console verb. |
|
||||
|
||||
Dropped: none. Two items changed meaning. R5 now says "no agent approves its
|
||||
own request" and lists what always goes to a human, because the code routes
|
||||
cross-role work to an agent arbiter by design. R6 now says "core or a plugin"
|
||||
declares a tool, because core declares all of them today.
|
||||
Round 1 changed R5 and R6 from the brief. Round 2 rewrites R5 again and adds
|
||||
R12.
|
||||
|
||||
### Role rails
|
||||
|
||||
A role's definition is its rails. Each part says what that agent may do, and
|
||||
the broker enforces it:
|
||||
|
||||
| Part | What it says | Enforced today | Gap |
|
||||
|---|---|---|---|
|
||||
| `tools` | The tools the agent has | Typed bus tools follow the held actions (`tools.mjs:97`); built-ins follow the policy (`packages/harness/src/bundle.mjs:368`) | None |
|
||||
| `authority.withinRole` | Actions the agent takes alone | The broker's classification (`broker.mjs:7048`) | None |
|
||||
| `authority.crossRole` | Actions it raises to its domain's arbiter | `broker.mjs:7072-7075` | The raiser picks the domain (`broker.mjs:7067`), so it picks its arbiter. RR fixes the domain for merge and policy. |
|
||||
| `decision.resolve.<domain>` in `withinRole` | Which decisions it may resolve for other agents | Only for PRD approval (`broker.mjs:1059`) | An ordinary resolve checks only that the caller's role is the route (`broker.mjs:7212-7215`). The organization's `arbiters` map names the arbiter, not its role (`org-template.json:5`), and the default arbiter, the chief, holds no resolve right (`roles/chief-of-staff.json:8-9`). RR makes an arbiter hold the right for its domain. |
|
||||
| `credentials` | Each service and scope | Bound by a person only (`broker.mjs:6144`) | None |
|
||||
| `messaging` | Whom it may message | `#checkMessaging` (`broker.mjs:7082`) | None |
|
||||
|
||||
**The validator carries them.** P1's `checkComposition` checks every part
|
||||
against the registry, and treats every `decision.resolve.*` action as a
|
||||
flagged line, because it lets the holder decide for other agents.
|
||||
|
||||
**The instructions state them plainly.** The contract keeps the duties and
|
||||
says what to do on a refusal. The list of what the agent may do is generated
|
||||
from the resolved definition into its prompt (section 10), so the wording
|
||||
cannot drift from what the broker enforces. Every shipped contract has an
|
||||
`## Authority` section today; RR makes the role check refuse a version 2
|
||||
contract without one (`role.mjs:146-157`).
|
||||
|
||||
## 4. The primitives
|
||||
|
||||
@@ -125,8 +163,7 @@ replaces `HIRE_ALLOWED` and `unhireableAction` at hire time. It:
|
||||
organization;
|
||||
3. refuses any action outside `ROUTED` or a plugin's routed set (R4);
|
||||
4. refuses `role.hire` and `role.fire` (R9);
|
||||
5. allows `git.merge.protected` and `policy.change` only in `crossRole`, so
|
||||
each use is its own decision that a human resolves (R5);
|
||||
5. returns every `decision.resolve.*` action as a flagged line (role rails);
|
||||
6. refuses any string that fails `assertClean` (R1), and any field over its
|
||||
length bound;
|
||||
7. returns the flagged lines: every action, tool, network, credential scope
|
||||
@@ -139,8 +176,8 @@ roles and no list of hireable definitions.
|
||||
any mix of registered tools and routed actions, any credential scope a service
|
||||
grants, any personality, any number of agents. A human approves each one.
|
||||
|
||||
**What it cannot mean.** A hired agent cannot hold a hard wait, hire, approve
|
||||
a human decision, read another organization, name a tool nothing declares,
|
||||
**What it cannot mean.** A hired agent cannot hold a hard wait, hire, resolve
|
||||
its own request or a decision about itself, read another organization, name a tool nothing declares,
|
||||
carry a secret in its text, or run outside the sandbox. A new capability that
|
||||
needs new enforcement, for example a new kind of external write, needs a
|
||||
plugin that declares and enforces it. A name with no code behind it gives an
|
||||
@@ -218,13 +255,15 @@ The broker adds, and the chief cannot set or edit:
|
||||
- `expiresAt`: seven days.
|
||||
|
||||
**Raise.** Among agents, only the instance in `launch.by` may raise one, as
|
||||
today (`broker.mjs:6193`). The decision always routes to the human, whatever its
|
||||
class, so the arbiter path at `broker.mjs:7074` never applies. A hire needs no
|
||||
today (`broker.mjs:6193`). It routes by the organization's autonomy setting
|
||||
for authority changes (section 9). At the default, in the loop, it goes to a
|
||||
person, so the arbiter path at `broker.mjs:7074` does not apply. A hire needs no
|
||||
approval count and has no cap (ruling 3). Spend stays bounded by the
|
||||
organization's limits.
|
||||
|
||||
**Approve.** A human whose membership in that organization holds `org.hire`
|
||||
approves in the Console, against the current digest. Two limits in the MVP:
|
||||
**Approve, in the loop.** A human whose membership in that organization holds
|
||||
`org.hire` approves in the Console, against the current digest. Two limits,
|
||||
at every autonomy level:
|
||||
|
||||
- a decision with any flagged line or any credential line needs the owner.
|
||||
This mirrors `decide` stopping short of credential mints (accounts design,
|
||||
@@ -235,6 +274,12 @@ approves in the Console, against the current digest. Two limits in the MVP:
|
||||
The human may change the route, narrow the role or drop credential lines
|
||||
before approving. Widening means a new decision.
|
||||
|
||||
**Near and out of the loop.** Only a plain decision moves: no flagged line, no
|
||||
credential line, organization or project level. An instance whose role holds
|
||||
`decision.resolve.authority` resolves it. It is never the chief that raised it
|
||||
and never the agent the decision names (R5). Near the loop, the owner is told
|
||||
and can reverse it (section 9).
|
||||
|
||||
**What approval writes.** One writer call, with the human as actor and the
|
||||
decision id in the context:
|
||||
|
||||
@@ -334,36 +379,147 @@ roles (companion, continuity, fact-checker, ideas, proofreader, research).
|
||||
a hire refuses (`broker.mjs:6303`), and `conductor-policy.json` is not a role.
|
||||
Neither changes.
|
||||
|
||||
## 9. Usability and security
|
||||
## 9. Autonomy, usability and security
|
||||
|
||||
Read this first. It says who can change what.
|
||||
Read this first. It says who decides what, and what changes as trust grows.
|
||||
|
||||
Everything in the first two columns is done by a person. No agent can do any
|
||||
of it.
|
||||
Ruling 13 sets the direction: from a person in the loop, to near the loop, to
|
||||
out of the loop. Today most decisions sit in the loop.
|
||||
|
||||
| A person changes it directly | It needs an approved decision | Only a reviewed code change |
|
||||
**The three levels.**
|
||||
|
||||
- **In the loop.** A person approves before anything happens.
|
||||
- **Near the loop.** An agent whose role holds the resolve right decides, and
|
||||
the decision takes effect. A person is told in the Console and can reverse
|
||||
it within a window the owner sets. I propose 24 hours as the default.
|
||||
- **Out of the loop.** An agent whose role holds the resolve right decides.
|
||||
The trail records it, and nobody is told.
|
||||
|
||||
**The setting.** An organization's file gets an `autonomy` key with one level
|
||||
per class below. A missing key means today's routing. Only the owner changes
|
||||
it, in the Console: the verb maps to `null` in `VERB_RIGHTS`, the explicit
|
||||
owner-only mark (`org-rights.mjs:28-29`), and it is not on the tester's verb
|
||||
list (`broker.mjs:192`). Each change is a trail event. No agent changes it at
|
||||
any level (R12). The owner can pull a class back toward the loop at any time
|
||||
with no preconditions.
|
||||
|
||||
**Classes and levels.** "Default" marks where each class sits today.
|
||||
|
||||
| Class | In the loop | Near the loop | Out of the loop |
|
||||
|---|---|---|---|
|
||||
| Cross-role work, such as a task scope change | A person resolves | The arbiter resolves; a person is told | The arbiter resolves (default, `broker.mjs:7072-7075`) |
|
||||
| Merge, `git.merge.protected` | A person resolves | The technical arbiter resolves; a person is told | The technical arbiter resolves (default). Acceptance 314 step 5 has the CTO do it (`docs/acceptance/314.md:116-120`) |
|
||||
| Policy, `policy.change` | A person resolves | The arbiter resolves; a person is told | The arbiter resolves (default). No shipped role holds this action |
|
||||
| Plain hire, fire or role change | A person with `org.hire` approves (default, ruling 1) | An agent holding `decision.resolve.authority` resolves; the owner is told | The same agent resolves |
|
||||
| Flagged grants and credential bindings | The owner approves (default) | Does not move (R8) | Does not move |
|
||||
| Root-level agents | The instance owner (default) | Not an organization setting | Not an organization setting |
|
||||
| Plugin install | The instance owner, at the host (default) | Not an organization setting (R10) | Not an organization setting |
|
||||
| Hard waits: push, deploy, spend, mint, delete, sudo | A person, every time (R4) | Not a setting | Not a setting |
|
||||
| The autonomy setting | The owner (R12) | Never | Never |
|
||||
|
||||
PRD approval already has its own switch: a request in `autonomous` mode sends
|
||||
it to the technical arbiter, and one in `guided` mode to a person
|
||||
(`packages/bus/src/request-lifecycle.mjs:308`, `broker.mjs:1057-1060`). A
|
||||
person sets the mode (`broker.mjs:5918-5919`). This design leaves it as is.
|
||||
|
||||
**What reverse can undo.** Near the loop, reverse is a one-step act by the
|
||||
owner, recorded against the decision. After the window it is an ordinary new
|
||||
decision.
|
||||
|
||||
| Class | Reverse does | It cannot undo |
|
||||
|---|---|---|
|
||||
| Organizations and projects (owner) | Hiring, firing or changing an agent's role (`org.hire`) | The rails in section 3 |
|
||||
| Drafting a role copy, or inventing a role from known parts. A draft does nothing until a decision uses it (`org.hire`) | Any agent power above the chief's own, shown flagged (owner approves) | The core list of tools and actions |
|
||||
| An agent's model, limits and route (`org.hire`) | Giving an agent a password, key or token (owner approves) | The shipped role templates |
|
||||
| An agent's personality and traits after hire | Applying a template update to your copy | The rights table, including `org.hire` |
|
||||
| Enabling a plugin in an organization (owner) | Each merge or policy change an agent asks for | Plugin kinds |
|
||||
| Installing a plugin on the instance (instance owner, at the host) | Adding an agent at the root level (instance owner) | System config, which only bootstrap writes |
|
||||
| Cross-role work | Restores the task's prior scope or state | Messages sent, records written (write-once, R2), work done in the window |
|
||||
| Merge | Lands a revert commit through the normal path | The merge in history (no force push, AGENTS.md invariant 8), or anything that ran or shipped the merged code in the window |
|
||||
| Policy | Restores the prior policy | Actions taken under the new policy in the window |
|
||||
| Hire | Fires the agent, which revokes its credentials (`instance-settings.mjs:396-414`) | What the agent did or spent in the window |
|
||||
| Fire | Rehires the same definition `rev` | The run and session state the fire ended |
|
||||
| Role change | Returns the agent to the prior `rev` (section 5) | What the agent did under the new `rev` |
|
||||
| Credential binding | Not offered: it does not move | Each use of the token at the service. That is why it stays in the loop |
|
||||
|
||||
What never moves, whoever asks:
|
||||
**Before a class moves a level.** The organization needs:
|
||||
|
||||
- to move any class to near the loop: a resolver instance whose role holds
|
||||
that class's resolve right and is not the raiser (RR), the class's reverse
|
||||
built and tested (AU), and Console notices on for the owner;
|
||||
- to move any class to out of the loop: time at near the loop, and the
|
||||
owner's review of its reversals in the trail;
|
||||
- for merge, in addition: a reviewer role whose approve verdict a landing
|
||||
needs, and the land gate's tests. Acceptance 314 has both, which is why
|
||||
merge stays out of the loop by default;
|
||||
- for policy, in addition: a reviewer role that sees the policy diff first;
|
||||
- for plain hires, in addition: organization spend limits that cover a new
|
||||
agent, and a resolver other than the chief. Out of the loop also needs
|
||||
per-agent limits in every definition the resolver can approve.
|
||||
|
||||
Flagged grants, credential bindings, root-level agents and plugin installs do
|
||||
not move in this design. Moving one needs a new ruling.
|
||||
|
||||
**A person changes directly.** Organizations and projects (owner). Drafts of
|
||||
role copies and invented roles, which do nothing until a decision uses them
|
||||
(`org.hire`). An agent's model, limits and route (`org.hire`). An agent's
|
||||
personality and traits after hire (ruling 12). Enabling a plugin in an
|
||||
organization (owner). Installing a plugin (instance owner, at the host).
|
||||
|
||||
**Only a reviewed code change.** The rails in section 3, the core list of
|
||||
tools and actions, the shipped role templates, the rights table including
|
||||
`org.hire`, plugin kinds, and system config, which only bootstrap writes.
|
||||
|
||||
**What never moves, at any level:**
|
||||
|
||||
- An agent cannot push to a protected branch, push to a remote, deploy, spend,
|
||||
mint a credential, delete data or use sudo by itself. A person decides each
|
||||
of those every time.
|
||||
- An agent cannot hire. Only the chief asks, and a person says yes.
|
||||
- An agent cannot merge into a protected branch or change policy unless a
|
||||
person approves that one merge or change.
|
||||
- An agent cannot resolve its own request or a decision about itself, and it
|
||||
cannot change the autonomy setting or a rail.
|
||||
- An agent cannot grant more than the chief holds, or bind a credential,
|
||||
without the owner.
|
||||
- Only the chief raises hires, and a hired agent cannot hire.
|
||||
- An agent cannot see another organization's work. The assistant's read
|
||||
across organizations is planned (A6) and not built yet.
|
||||
- A plugin runs with the same power as the Mosaic service itself. Install
|
||||
only code you trust.
|
||||
|
||||
## 10. Threat model
|
||||
## 10. Tooling and instructions
|
||||
|
||||
An agent trusted to work without a person has to know its own limits without
|
||||
asking one. Three things today fall short.
|
||||
|
||||
**Its rails, readable at runtime.** The prompt names the agent's built-in and
|
||||
bus tools (`packages/harness/src/bundle.mjs:283-284`) and the held actions
|
||||
that have no bus tool (`bundle.mjs:308`). It does not say which actions the
|
||||
agent takes alone, which go to which arbiter, which go to a person, or what it
|
||||
may resolve. Six contracts tell the agent "the launch record you received
|
||||
shows the resolved list" (`roles/coder.md:41`, `roles/cto.md:46`,
|
||||
`roles/merge-gate.md:54`, `roles/pm.md:109`, `roles/reviewer.md:38`,
|
||||
`roles/security-reviewer.md:27`). No session receives one. Launch records are
|
||||
host-side, for principal checks (`packages/bus/src/runtime.mjs:885-901`,
|
||||
`broker.mjs:2047`).
|
||||
|
||||
Fix, in TI: the prompt gets a generated "## Your rails" section from the
|
||||
resolved definition. It lists each held action with where it goes (alone,
|
||||
which arbiter instance, or a person), each resolve right with its class's
|
||||
autonomy level, the network, each credential service and scope by name, and
|
||||
the messaging targets. A read-only `my_rails` bus tool returns the same from
|
||||
the broker, since a narrowing takes effect at the next call (section 5). The
|
||||
six contracts drop the launch-record sentence and point at that section.
|
||||
|
||||
**Refusals that say what to do next.** The broker's `fail(code)` throws a
|
||||
bare code (`broker.mjs:251-253`), and 653 lines of `broker.mjs` call it. A
|
||||
reason travels only where a call builds one (`broker.mjs:243-249`, for
|
||||
example `941`, `961` and `6468`). The prompt says only that "a refusal names
|
||||
its reason" (`bundle.mjs:284`).
|
||||
|
||||
Fix, in TI: one table maps each refusal code to the rail or role rail it
|
||||
enforces and the next step, for example "raise a decision for the delivery
|
||||
arbiter" or "a person decides this; raise it with blocking false". The
|
||||
envelope carries both. A test fails when a `fail` code has no entry.
|
||||
|
||||
**The tools it holds.** These are covered. The prompt names each tool
|
||||
(`bundle.mjs:283-284`) and the bundle writes each tool's description and
|
||||
schema to `tools.json` for the harness (`bundle.mjs:372`). What was missing
|
||||
is the routing behind each tool, which the rails section adds.
|
||||
|
||||
## 11. Threat model
|
||||
|
||||
| Threat | What stops it | What is left |
|
||||
|---|---|---|
|
||||
@@ -371,11 +527,13 @@ What never moves, whoever asks:
|
||||
| A custom role names an unknown tool | `checkComposition` refuses it at the decision and at the writer. Launch refuses it again (R3). | None. |
|
||||
| A plugin from an untrusted source | Instance owner only; hash pin; the install screen lists host-run tools. | Approved plugin code runs as the instance uid (R10). A pin does not make code safe. |
|
||||
| A credential scope wider than the task | Each scope is its own line; owner-only approval; the approver may drop lines; admin and all never granted. | The service's own scope granularity. |
|
||||
| An arbiter agent approves another agent's merge or policy change | A4 routes those decisions to the human whatever their class (R5). | Other routed work, such as task scope, still has an agent arbiter by design. |
|
||||
| An arbiter agent resolves a bad merge | Its role must hold `decision.resolve.technical` (RR). It never resolves its own raise (R5). The review verdict and the land gate come first (acceptance 314). The owner can pull merge back toward the loop. | A wrong merge is reverted, not undone. |
|
||||
| An agent widens its own authority through a decision | No agent resolves its own raise or a decision about itself (R5). Flagged grants and credentials stay with the owner at every level (R8). Only the owner changes the setting (R12). | One agent raises a plain change for another. Plain means within the chief's own authority, and near the loop tells the owner. |
|
||||
| A prompt-injected resolver near the loop | The owner is told and can reverse within the window. | What reverse cannot undo (section 9). |
|
||||
| Approval fatigue on flagged grants | Flagged lines shown apart from plain ones; credential and flagged decisions go to the owner; no batch approval; seven-day expiry. | A busy owner who approves without reading. A later slice could limit how many flagged decisions one approval screen holds. |
|
||||
| An imported template drifts from its source | The copy is frozen with its source hash. A changed template produces a diff offer, never a silent change. | An owner who keeps an old copy keeps its old behaviour. That is the ruling. |
|
||||
|
||||
## 11. Invariant 7 amendment
|
||||
## 12. Invariant 7 amendment
|
||||
|
||||
Proposed only. Jason approves it through a reviewed commit.
|
||||
|
||||
@@ -385,9 +543,11 @@ Replace AGENTS.md invariant 7 (`AGENTS.md:45-47`) with:
|
||||
> rails, the core vocabulary of tools and actions, and the shipped role
|
||||
> templates in `roles/` change only by reviewed commit. An organization's
|
||||
> own roles are copies or compositions of known parts. One takes effect
|
||||
> only through a decision a human with `org.hire` approves, which lists
|
||||
> every grant above the chief's own as a flagged line. Only the chief
|
||||
> raises hires, and a hired agent cannot hire.
|
||||
> only through a decision, resolved where the organization's owner-set
|
||||
> autonomy level sends it, by default a human with `org.hire`. The
|
||||
> decision lists every grant above the chief's own as a flagged line,
|
||||
> which only the owner approves. Only the chief raises hires, and a hired
|
||||
> agent cannot hire.
|
||||
|
||||
Touched role-model lines:
|
||||
|
||||
@@ -396,10 +556,15 @@ Touched role-model lines:
|
||||
- `AGENTS.md:209`: change to "`roles/`: shipped role templates and the
|
||||
conductor policy".
|
||||
|
||||
## 12. Slices
|
||||
## 13. Slices
|
||||
|
||||
Phase A after A3, in order. A6 and A7 stay as agent-levels section 9 has
|
||||
them.
|
||||
Phase A after A3, in order: R1, P1, T1, A4, RR, TI, AU, PL1. A6 and A7 stay
|
||||
as agent-levels section 9 has them.
|
||||
|
||||
Slices that change `roles/`: A4 (the chief), RR (the chief, and any role an
|
||||
organization names as delivery arbiter) and TI (six contracts). Slices that
|
||||
change rights policy: R1 and AU. P1 changes no file in `roles/` but defines
|
||||
authority policy. All of them need Jason's approval.
|
||||
|
||||
**R1. The `org.hire` right.** Add `'org.hire': flag(true)` to `RIGHTS` and a
|
||||
verb entry for the hire resolution in `VERB_RIGHTS`. The owner bundle picks it
|
||||
@@ -414,7 +579,7 @@ unknown right still refuses.
|
||||
file but it defines authority policy, so Jason's approval. Tests: an unknown
|
||||
key, action, tool, service or scope refuses; each hard wait refuses;
|
||||
`role.hire` and `role.fire` refuse; a secret in instructions or personality
|
||||
refuses; `git.merge.protected` or `policy.change` in `withinRole` refuses;
|
||||
refuses; a `decision.resolve.*` action comes back as a flagged line;
|
||||
flagged lines match a hand-built expected set for coder against the
|
||||
shipped chief.
|
||||
|
||||
@@ -427,22 +592,68 @@ agent cannot call the edit verbs.
|
||||
|
||||
**A4. Hire as a decision, all levels.** The decision action, raise, Console
|
||||
approval, the writer calls, credential lines, expiry, fire and change, the
|
||||
Console path that raises and resolves in one step, human routing for
|
||||
`role.hire`, `role.fire`, `credential.mint`, `git.merge.protected` and
|
||||
`policy.change` decisions (R5), and removal of the direct path and
|
||||
`#hireable`. Org and project land first; root
|
||||
Console path that raises and resolves in one step, and removal of the direct
|
||||
path and `#hireable`. Every hire decision sits in the loop until AU. Org and project land first; root
|
||||
waits for A2's root writer. This edits `roles/chief-of-staff.json` and `.md`
|
||||
(drops `hireable`, rewords hire), the vocabulary and AGENTS.md invariant 7,
|
||||
and reverses part of PRD 1.5 REQ-DEC-2, so Jason's approval. Tests: a hired agent's raise refuses `role-not-chief`; a
|
||||
so Jason's approval. Tests: a hired agent's raise refuses `role-not-chief`; a
|
||||
chief decision cannot carry its own `flagged`; a member with `org.hire`
|
||||
approving a credential or flagged decision refuses; a stale digest refuses;
|
||||
deny and expiry write nothing; approval writes the role, the route and the
|
||||
trail, and launch refuses `credential-unbound` until binding; fire revokes;
|
||||
the old `instance.hire` verb refuses; a cross-role `git.merge.protected` or
|
||||
`policy.change` decision routes to the human and an arbiter's resolve refuses
|
||||
`not-resolver`; a hire a person starts in the Console leaves a decision record
|
||||
the old `instance.hire` verb refuses; a cross-role `git.merge.protected`
|
||||
decision still routes to the technical arbiter and the CTO resolves it, as
|
||||
acceptance 314 step 5 expects; a hire a person starts in the Console leaves a decision record
|
||||
with its flagged lines.
|
||||
|
||||
**RR. Role rails and resolve rights.** An arbiter must hold
|
||||
`decision.resolve.<domain>` for its domain, checked when the organization
|
||||
file is written and again at resolve, which extends `broker.mjs:1059` to
|
||||
every decision. The vocabulary adds `decision.resolve.delivery`. A fixed
|
||||
domain per action for `git.merge.protected` (technical) and `policy.change`
|
||||
(delivery), so a raiser cannot pick its arbiter. The chief gets both resolve
|
||||
rights, since the template names it arbiter for both
|
||||
(`org-template.json:5`). The role check refuses a version 2 contract with no
|
||||
`## Authority` section. Changes `roles/chief-of-staff.json` and the
|
||||
vocabulary, so Jason's approval. Tests: naming an arbiter without the right
|
||||
refuses when written; an arbiter whose role lost the right refuses
|
||||
`not-resolver`; a merge raised with domain `delivery` still routes to the
|
||||
technical arbiter; acceptance 314 step 5, the CTO resolving the merge, still
|
||||
passes; a contract with no `## Authority` refuses.
|
||||
|
||||
**TI. Tooling and instructions.** The generated "## Your rails" prompt
|
||||
section, the read-only `my_rails` tool, the refusal table and envelope
|
||||
fields, and the six contract edits (section 10). Changes `roles/*.md`, so
|
||||
Jason's approval. Tests: coder's prompt lists each held action with its
|
||||
route; `my_rails` matches the broker after a narrowing; every `fail` code in
|
||||
`broker.mjs` has a table entry; a refused cross-role call's envelope names
|
||||
its rail and next step.
|
||||
|
||||
**AU. Autonomy setting and routing.** The `autonomy` key, the owner-only
|
||||
Console verb, routing per class, the subject check that sends a decision
|
||||
about its resolver to a person (R5), `decision.resolve.authority`, the
|
||||
Console notice, reverse per class with its window, and the trail events.
|
||||
Needs A4 and RR. Changes rights policy (a `VERB_RIGHTS` entry) and the
|
||||
vocabulary, but no role file, so Jason's approval. Acceptance tests:
|
||||
|
||||
- with no `autonomy` key, routing is exactly today's: a merge goes to the
|
||||
technical arbiter, a hire to a person;
|
||||
- a member, an agent and the dev tester each refuse to change the setting;
|
||||
an unknown class or level refuses;
|
||||
- with merge in the loop, a merge decision goes to a person and the
|
||||
arbiter's resolve refuses `not-resolver`;
|
||||
- with merge near the loop, the arbiter's resolve takes effect, the owner
|
||||
gets a notice, and reverse within the window lands a revert commit
|
||||
recorded against the decision;
|
||||
- with hires near the loop, an instance holding `decision.resolve.authority`
|
||||
resolves a plain hire; a hire with a flagged or credential line still goes
|
||||
to the owner; the chief cannot resolve its own raise; no agent resolves a
|
||||
change that names itself;
|
||||
- reverse after the window refuses and names the ordinary decision path;
|
||||
- each setting change is a trail event with the owner as actor;
|
||||
- no setting value moves a hard wait, a flagged grant or a credential
|
||||
binding away from a person.
|
||||
|
||||
**PL1. Plugin manifest, install and declared tools.** Manifest, pinned
|
||||
install root, lock file, the registry merge, and `plugin-disabled` refusals.
|
||||
Changes lead decision 143 and the loader's trust rule, so Jason's approval.
|
||||
|
||||
Reference in new issue
Block a user