PAF becomes saf/device (history kept), STATE.md dissolves into saf/state.md with the dated era archived, the substrate SAF moves up from souveraine, and every agreement points at saf/INDEX.md and nowhere else. one map, nothing to remember
342 lines
17 KiB
Markdown
342 lines
17 KiB
Markdown
# Agent principals
|
|
|
|
An agent is not a process borrowing the human's account. She has a logical
|
|
identity that can cross machines and, on every admitted machine, a local Unix
|
|
principal that lets the kernel tell her work from the human's and from another
|
|
agent's.
|
|
|
|
This distinction is not implemented yet. Today the server, its agents, and
|
|
their tools normally run as the logged-in human. The per-agent SeedID exists,
|
|
but a signature cannot repair a shared UID: `SO_PEERCRED` still sees the same
|
|
caller and ordinary file permissions still collapse every agent into the same
|
|
authority.
|
|
|
|
## Three identities that must not be collapsed
|
|
|
|
| Identity | Meaning | Lifetime |
|
|
|---|---|---|
|
|
| agent ID + agent SeedID | which being this is | stable across renames and admitted nodes |
|
|
| node commission | which admitted body is speaking for her | local to one commissioned device |
|
|
| Unix account | which local processes and files the kernel attributes to her | local to one device |
|
|
|
|
The numeric UID is not federated. An agent can be UID 1003 on one admitted
|
|
body and UID 991 on another while remaining herself, because both node-local
|
|
account mappings point to the same admitted agent identity. Copying
|
|
`/etc/passwd`, a UID number, or a private key is not commissioning.
|
|
|
|
There is a fourth identity already on the machines: the `souveraine` system
|
|
account. It belongs to the machine tier and runs `souveraine-machined`. It is
|
|
not Souvie, not a default agent, and not a convenient shared account for all
|
|
agents. The name is reserved.
|
|
|
|
## The starting set, by role
|
|
|
|
A human's devices retain that human's own account. Agents do not borrow it.
|
|
|
|
| Role | Required local account |
|
|
|---|---|
|
|
| the default Souveraine agent on a fresh system | `souvie` |
|
|
| the human's primary personal agent, if she is admitted | her own name |
|
|
| any further named resident agent | its own name |
|
|
|
|
Only the first is shipped. `souvie` is what a fresh Souveraine install admits,
|
|
because a Souveraine system running its own agent as the human is the state
|
|
this chapter exists to end. Every other admission is a deliberate act by the
|
|
human on the body in question — including the primary personal agent, whose
|
|
name and cadence are that human's to choose. This chapter describes the
|
|
mechanism; which beings a particular deployment admits is that deployment's
|
|
record to keep.
|
|
|
|
"Each node" includes a development laptop. The laptop is not a development
|
|
exception, and a phone is not a special appliance case. When a resident agent
|
|
runs on either body, her turn, subconscious pass, memory access, and tools
|
|
must cross the worker boundary under the mapped account.
|
|
|
|
The display name is editable. The agent ID, SeedID, and admitted account
|
|
mapping are not derived again when the display name changes. Account names are
|
|
chosen once, checked against the local account database and reserved names,
|
|
then stored in a root-owned node-local mapping. A collision is shown to the
|
|
administrator; creation must not silently attach an agent to an existing
|
|
human or daemon account or invent a suffix that nobody noticed.
|
|
|
|
Souvie is the default, not the only possible agent. Restoring an existing
|
|
Souvie must join her existing logical identity rather than minting another
|
|
Souvie because the display name matches. A further agent is admitted onto each
|
|
body where she is meant to act. Her local Unix account is new on that body;
|
|
her being is not.
|
|
|
|
Primary and subconscious are processing positions inside an agent today.
|
|
Creating an agent therefore does not automatically create a second Unix user
|
|
for her second cadence. The cadences share her principal unless the human and
|
|
the system later admit one as an independently authorized agent. The account
|
|
boundary follows authority, not the number of internal thought streams.
|
|
|
|
## Resident agents and borrowed-user modes
|
|
|
|
Not every named mode needs a Unix account. A hosted or borrowed-user mode runs
|
|
under the human's account rather than claiming a fully admitted resident
|
|
identity. That is a deliberate posture, not the same failure as a `dedicated`
|
|
agent accidentally running as the human.
|
|
|
|
The durable agent record therefore carries a principal intent, not one vague
|
|
boolean:
|
|
|
|
| Intent | Expected runtime | Meaning |
|
|
|---|---|---|
|
|
| `dedicated` | the named agent account | full node admission and kernel-backed identity |
|
|
| `borrowed-user` | the invoking human account | a hosted mode using authority the human process can already reach |
|
|
|
|
Promoting a borrowed mode to a resident agent is an admission ceremony;
|
|
changing a UI label or model does not do it. Which agents in a given
|
|
deployment hold which intent is that deployment's record, not this chapter's.
|
|
|
|
A borrowed-user mode must know, in every model request, that readable does not
|
|
mean owned or shareable. Its operating posture is stricter:
|
|
|
|
- the human's files, groups, sockets, credentials, and decrypted home are
|
|
borrowed reach, not the mode's personal territory;
|
|
- do not widen mode bits, ACLs, group membership, links, remotes, publication,
|
|
or sharing outside the explicit task without the human's confirmation;
|
|
- prefer the named workspace or a narrow temporary copy over exploring the
|
|
rest of the human home;
|
|
- never read or disclose a resident agent's private memory merely because the
|
|
shared human UID permits it;
|
|
- attribute actions to a borrowed mode acting through the human's principal,
|
|
not to an independently admitted agent.
|
|
|
|
This caution is context and policy, not a substitute for containment. A
|
|
borrowed process still has the human's kernel permissions; sensitive verbs must
|
|
remain behind the same capability and consent gates.
|
|
|
|
## The live principal fact
|
|
|
|
The intent in `agent.json` is durable. The live posture is observed afresh from
|
|
the selected agent or mode, node mapping, process credentials, and worker unit.
|
|
The trigger may expose a convenient `operating_as_user` boolean, but that
|
|
boolean is only a projection of the facts and is never an authorization input.
|
|
|
|
The runtime projection contains at least:
|
|
|
|
```text
|
|
agent_id, display_name, principal_intent
|
|
expected_account, effective_account, effective_uid
|
|
node_id, worker_pid, trigger, observed_at
|
|
posture = isolated | borrowed-user | acting-as-human | drift
|
|
```
|
|
|
|
It is injected as a fresh system block into **every model request**, including
|
|
primary and subconscious passes, subagents, resumed conversations, and later
|
|
tool rounds after a mode switch. It is not ordinary conversation history and
|
|
cannot be microcompacted, replayed from an old session, or taught back as
|
|
assistant speech.
|
|
|
|
A machine-generated cache may be exposed as
|
|
`/run/souveraine/agent-principals/{agent_id}.json` and a Markdown rendering for
|
|
inspection. It does not belong in the agent-writable, Git-backed
|
|
`system/dynamic/` memory tree: an awareness file the agent can edit cannot be
|
|
the source of who the kernel says she is. The prompt projection rechecks the
|
|
effective credentials when it reads the cache. Authorization always checks
|
|
the actual peer and process credentials again at the verb boundary.
|
|
|
|
## Creation is logical creation followed by admission
|
|
|
|
The current creation path makes a UUID, an agent seed, memory directories, an
|
|
`agent.json`, and a paired subconscious tree under the human's
|
|
`~/.souveraine`. It has no privileged account step. The complete operation is
|
|
two related but separately observable transitions:
|
|
|
|
1. **Create the logical agent.** Establish or import the agent ID and SeedID,
|
|
initialize her memory, and record the requested local account name. Import
|
|
must verify identity before it mutates local authority.
|
|
2. **Admit her to this node.** A narrow system-tier executor creates or adopts
|
|
the approved Unix account, writes the agent-to-principal mapping, installs
|
|
the node commission, gives only her data to that account, and starts her
|
|
worker as that UID.
|
|
|
|
The split makes failure honest. An agent can exist while admission is pending,
|
|
but the UI and API call that state `unadmitted`; they do not show a green agent
|
|
whose turns still run as the human. Personal- and step-up-tier verbs remain
|
|
closed until admission and execution checks pass.
|
|
|
|
Agent creation should ask for the authority shape, not merely a name and
|
|
model:
|
|
|
|
- create a new identity or admit an existing one;
|
|
- principal intent and, for a resident, the required local account name;
|
|
- which node and role are being admitted;
|
|
- whether the privileged admission completed;
|
|
- which operating skill was projected from the admitted capabilities.
|
|
|
|
Fresh installation proposes Souvie and `souvie`. It must not confuse that
|
|
with the package-created `souveraine` machine account. Adding any further
|
|
agent uses the same path; there is no hand-written exceptional setup for a
|
|
particular deployment's names.
|
|
|
|
## The privileged edge
|
|
|
|
QML, the TUI, and the ordinary server must not run `useradd`, rewrite
|
|
`/etc/passwd`, or recursively `chown` a human home. They submit one exact
|
|
admission request to one package-owned system executor. That executor owns:
|
|
|
|
- account creation and collision refusal;
|
|
- a locked password and non-login shell unless interactive login is later
|
|
admitted deliberately;
|
|
- a stable home/state root tied to the agent ID, not the display name;
|
|
- the root-owned agent-ID, SeedID, node-ID, account-name, UID mapping;
|
|
- precise ownership changes inside that agent's root only;
|
|
- install, start, stop, and inspection of the per-agent worker unit;
|
|
- idempotent repair when the requested mapping already exists and agrees.
|
|
|
|
The low-level account primitive may be `systemd-sysusers` or an equally narrow
|
|
account API. That implementation choice does not create a second authority:
|
|
the admission executor remains the only writer and reports the resulting NSS
|
|
facts rather than assuming its command worked.
|
|
|
|
### Admission is a ceremony
|
|
|
|
The executor is not the interface. A binary reachable only by someone who
|
|
already knows its name is not a way for a human to grant anything, and `sudo`
|
|
in a terminal that already trusts the caller is not a decision — it is a
|
|
formality. Admission is a highly user-directed act and has to be asked for as
|
|
one.
|
|
|
|
It has two occasions:
|
|
|
|
- **initial setup**, where a fresh install admits the default Souveraine agent
|
|
as `souvie`, because that is the shipped default state and the only
|
|
admission the product makes on its own; and
|
|
- **on the human's request**, under specific conditions, for any further
|
|
agent.
|
|
|
|
Both cross the same gate. The grant is a polkit action carrying a written
|
|
reason that names the agent and the account, and it requires step-up
|
|
authentication at the moment of the grant — including from a caller who could
|
|
already reach root by other means. The cost is the point: giving an agent her
|
|
own UID on this body is a deliberate authentication, never an inherited one.
|
|
|
|
Removing an agent record, removing a node admission, deleting a Unix account,
|
|
and destroying memory are four different operations. None implies another.
|
|
Decommission stops the worker and revokes the node commission first. Account
|
|
and data deletion require explicit, separately named confirmation; archival
|
|
is the normal default.
|
|
|
|
## One UID requires one execution boundary
|
|
|
|
A single server process cannot truthfully execute three admitted agents as
|
|
three UIDs. Per-agent accounts therefore require a process boundary:
|
|
|
|
- a controller may inventory agents and route requests;
|
|
- each admitted agent has a worker process running as her mapped UID;
|
|
- her memory, seed interface, conversation state, and tool execution are
|
|
reached inside that worker's boundary;
|
|
- controller-to-worker IPC identifies the peer with `SO_PEERCRED` and binds
|
|
the logical agent ID to the admitted UID;
|
|
- cross-agent consultation is a signed and policy-visible request, not direct
|
|
filesystem access through a shared process.
|
|
|
|
The controller is not allowed to become a universal memory reader merely
|
|
because it launches workers. Filesystem ownership, service sandboxing, and
|
|
capability tokens should leave it only the routing and lifecycle access it
|
|
actually needs.
|
|
|
|
Existing data under a human's `~/.souveraine` is a compatibility layout. The
|
|
admission migration must stop the agent, copy or move only that agent's UUID
|
|
root, preserve and verify the Git repository, install ownership atomically,
|
|
then start the worker under the admitted UID. Never apply a broad recursive
|
|
ownership change to `~/.souveraine` or a human home. Until migrated, health
|
|
labels the agent `acting-as-human`.
|
|
|
|
## Account and authority health
|
|
|
|
Agent Health answers two separate questions: *does she have an admitted
|
|
principal?* and *is the live work actually running as it?* A directory and a
|
|
row in `/etc/passwd` are not runtime proof.
|
|
|
|
The inspection result is structured and available to setup, the agent manager,
|
|
the TUI, the Panel, and `describe`. At minimum it reports:
|
|
|
|
- logical agent ID, SeedID fingerprint, and node commission;
|
|
- expected account name and UID from the root-owned mapping;
|
|
- NSS account existence, home/state root, shell, password-lock state, and
|
|
narrowly allowed groups;
|
|
- owner and mode of the memory, seed, conversation, socket, and runtime roots;
|
|
- worker unit, PID, effective UID/GID, cgroup, and executable;
|
|
- whether live peer credentials match the logical-agent mapping;
|
|
- whether any process handling the turn or its tools is the human,
|
|
`souveraine`, another agent, or an unknown UID;
|
|
- capability-token subject and expiry when an authority verb is exercised;
|
|
- operating-skill presence and whether it matches the current capability
|
|
description.
|
|
|
|
The top-level states are deliberately blunt:
|
|
|
|
| State | Meaning |
|
|
|---|---|
|
|
| `isolated` | account, ownership, worker, commission, and peer identity agree |
|
|
| `borrowed-user` | hosted mode intentionally uses the invoking human's account and receives the cautious borrowed-permission posture |
|
|
| `unadmitted` | logical agent exists; this node has no completed admission |
|
|
| `acting-as-human` | a `dedicated` agent's turn or tool is unexpectedly executing as the human or another human UID |
|
|
| `principal-drift` | mapping exists but NSS, ownership, unit, or peer facts disagree |
|
|
| `identity-drift` | account is healthy but agent key or node commission does not match |
|
|
| `stopped` | admission is sound and no worker is currently expected/running |
|
|
|
|
`acting-as-human`, `principal-drift`, and `identity-drift` are not yellow
|
|
decorations. They refuse personal and step-up authority. Ambient conversation
|
|
may remain available in a visibly degraded compatibility mode while migration
|
|
is unfinished. `borrowed-user` is intentional and may use only the human-granted
|
|
capability surface; it never becomes `isolated` merely because the human's UID
|
|
can read something.
|
|
|
|
The audit trail records both logical and kernel-backed subjects: agent ID,
|
|
node ID, account name, UID, PID, operation, human requestor or consent grant,
|
|
and result. "The agent asked" and "the human authorized this one action" are
|
|
different fields. Running as the agent does not silently inherit the human's
|
|
consent; running as the human does not make a model response the agent's.
|
|
|
|
## The operating skill
|
|
|
|
Admission also projects an agent-local operating skill when it is missing or
|
|
stale. It teaches the agent the boundary she actually has on this node:
|
|
|
|
- her logical and local principal names;
|
|
- her owned paths and worker/service inspection verbs;
|
|
- the ambient, personal, and step-up capabilities returned by `describe`;
|
|
- how to request a narrow user grant instead of impersonating the user;
|
|
- how to interpret and surface health refusals;
|
|
- how to ask for repair when the node mapping has drifted.
|
|
|
|
The skill is generated from the root-owned mapping and the authority's live
|
|
capability description. It is not a credential and grants nothing. Editing a
|
|
Markdown skill cannot change a UID, group, token, or verb. Health compares the
|
|
projection to its sources and can offer an idempotent regeneration. An agent
|
|
may keep her own prose and procedures around it, but machine claims remain a
|
|
generated, marked section.
|
|
|
|
This is why account creation and skill creation belong in one experience but
|
|
not one trust boundary: admission gives the kernel-backed principal; the skill
|
|
gives the newly admitted agent an accurate way to use and explain it.
|
|
|
|
## What is true now
|
|
|
|
- Per-agent SeedIDs and the first-run agent creation path exist.
|
|
- The setup wizard defaults inconsistently between `Souveraine` state and an
|
|
`Ani` form field; neither creates a Unix account.
|
|
- the packaged `souveraine` account exists only for the machine tier;
|
|
- on the laptop, NSS currently has no `souvie`, `annie`, or `vanguard`
|
|
accounts, and all seven agent records live below Casey's home;
|
|
- the Agent settings diagnostics check IDs and paths, not principal or process
|
|
identity;
|
|
- the server hosts multiple agents inside the human's process and home;
|
|
- the admission executor exists and is packaged: one writer, the root-owned
|
|
account mapping, and the runtime principal projection injected per model
|
|
request. It has never run as root on a real system — every refusal has been
|
|
proven only against scratch trees;
|
|
- there is no ceremony around it. No polkit action, no step-up, no first-run
|
|
admission, and no surface anywhere that shows a human the choice. Reaching
|
|
it requires already knowing its name;
|
|
- no per-agent worker boundary, account health surface, or generated operating
|
|
skill exists yet. Without the worker, every admitted agent still honestly
|
|
reads `acting-as-human` and `isolated` is unreachable.
|
|
|
|
Until those pieces land, the system must say **logical identity present,
|
|
kernel principal absent**. That is the live gap this chapter owns.
|