seat, cli, harness: role sessions confined to their own files (#1586)
Row 92, round 2. Darkwing built it. A role session runs in a bwrap sandbox that sees only its own workspace, a private /proc, /dev and IPC namespace, and the variables ENV_ALLOW passes. The launcher checks the session credential before a Claude launch, records a runner killed from the host as killed rather than failed, and the bus unit template carries the credential file (decision 119). The harness gate and the cli, seat and harness READMEs say what the sandbox does and does not hide. Candidate manifest 8bf4141cbe69fc7bed188555bcc88dcab0b1ea2149912cb6f970ba8c2629dd97 (13 paths), baseac37cd17, packet agents/darkwing/work/queue-92/r2/. Approvals: Dewey 27978, Filbert 27984 (round 1: Dewey 27942, Filbert asked for changes in 27951). Sage's gate rerun 22:27:37Z-22:30:35Z on1b7f3781plus the patch, manifest 13/13: seat 33, cli 120, harness 65, bus 108, webui 86, control-board 124, and every scripts/test-*.sh exited 0 (test-release 4, Docker cases skipped at a dead socket). Live at the next bus restart and the next role session launch (decision 109). Installing the new unit and restarting is Mos's call. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+36
-3
@@ -220,6 +220,8 @@ is `{"ok": true, "result": {run, instance, harness, model}}` or
|
||||
| the instance isn't running | `instance-running` |
|
||||
| the instance resolves to `pi` or `claude-code` and a model | `unsupported-harness` |
|
||||
| its role has a contract file | `no-role-contract` |
|
||||
| a Claude Code session has `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, or `ANTHROPIC_AUTH_TOKEN` with `ANTHROPIC_BASE_URL` to log in with | `no-session-credential` |
|
||||
| bwrap runs here | `sandbox-unavailable` |
|
||||
| its model names exactly one `launch.max` family | `unknown-model-family` |
|
||||
| that family has room; every running session counts, the PM's too | `capacity-full` |
|
||||
| the broker authorizes `role.launch` for the caller | the broker's code (`launch-revoked`, `decision-required`, …) |
|
||||
@@ -229,13 +231,31 @@ Every refusal is an `action.refused` event (the broker's `refuse`, or its
|
||||
own refusal for the last broker check) and a line in the launch log. Then
|
||||
the launcher reads the instance's credential status from the broker
|
||||
(metadata only), builds the bundle in the run directory, spawns the runner
|
||||
under `unshare`, binds the outer process's pid and start time, adds the
|
||||
in its bwrap sandbox (`packages/seat/README.md`, "The sandbox"), binds the
|
||||
outer bwrap process's pid and start time, adds the
|
||||
session to `sessions.json`, and writes the capability as one line on the
|
||||
runner's stdin. The capability is never on disk, in argv or in the
|
||||
environment. When the runner exits, the launcher sends `endLaunch` with the
|
||||
end reason from its exit code (`packages/harness/README.md`), which writes
|
||||
`session.ended`, releases the role and kills the run's capabilities.
|
||||
|
||||
A Claude Code session logs in with `CLAUDE_CODE_OAUTH_TOKEN`,
|
||||
`ANTHROPIC_API_KEY`, or `ANTHROPIC_AUTH_TOKEN` with `ANTHROPIC_BASE_URL`
|
||||
(a proxy's key), from the host's environment, which the allowlist passes
|
||||
on. The bus unit loads it ("Unit" below). Nothing from the host's
|
||||
`~/.claude` reaches a session, whose `HOME` is its own. With none of them
|
||||
set, a Claude Code launch refuses `no-session-credential` before anything
|
||||
starts. A Pi session gets the same variables through the allowlist. A
|
||||
shell the harness opens, Claude Code's `Bash` or Pi's `bash`, can read
|
||||
them.
|
||||
|
||||
A Pi session logs in only through the variables `ENV_ALLOW` passes. Its
|
||||
`HOME` is the run's own, so Pi never reads the operator's `~/.pi/agent`
|
||||
(`auth.json`, `models.json`, `settings.json`). A Pi launch has no
|
||||
credential check: a Pi role on a provider whose login lives only in those
|
||||
files, or whose key isn't an `ENV_ALLOW` variable, launches and then fails
|
||||
at its first turn.
|
||||
|
||||
The manifest records the Pi version from the repository's pin (the
|
||||
`@earendil-works/pi-coding-agent` dependency in the root `package.json`)
|
||||
and, for Claude Code, `--version` from the binary sessions run:
|
||||
@@ -257,7 +277,7 @@ A host that starts reads the `sessions.json` the last host left. An entry
|
||||
for this business means that host died without ending the launch, and its
|
||||
run would keep holding the role. Before the launch socket opens, the
|
||||
launcher SIGKILLs each such session that still runs (same start time,
|
||||
`unshare` on its command line), binds the run again with the recorded
|
||||
bwrap on its command line), binds the run again with the recorded
|
||||
identity and ends it as `host-lost`, which releases the role. A
|
||||
`sessions.json` that can't be read refuses the host start with exit 3; move
|
||||
it aside after checking which sessions still run.
|
||||
@@ -308,6 +328,7 @@ The unit settings:
|
||||
first when it isn't one of those
|
||||
- `Environment=MOSAIC_CLAUDE_BIN=`: the Claude Code binary that managed
|
||||
sessions and the manifest's version run
|
||||
- `EnvironmentFile=`: the Claude Code session credential (below)
|
||||
|
||||
`MOSAIC_CLAUDE_BIN` comes from `--claude PATH`, or else `command -v claude`
|
||||
in the installing shell. The path is kept as given, so the native install's
|
||||
@@ -316,9 +337,21 @@ following its updates; reinstall to change it. It must be absolute, an
|
||||
executable file, and only `A-Z a-z 0-9 / . _ + -`, because systemd expands
|
||||
`%` and splits `Environment=` on spaces; anything else refuses with exit 1
|
||||
and writes nothing. With no claude found, the unit gets a comment line
|
||||
instead and sessions run `claude` from the unit's `PATH`. A running host
|
||||
instead and sessions run `claude` from the unit's `PATH`, which in the
|
||||
sandbox finds only a `claude` under `/usr`. A running host
|
||||
keeps the environment it started with until it restarts.
|
||||
|
||||
The session credential comes from
|
||||
`EnvironmentFile=-%h/.config/mosaic-dev/secrets/claude-session.env`, the
|
||||
one place the path is set. The file holds one line, for example
|
||||
`CLAUDE_CODE_OAUTH_TOKEN=<token>`, mode 0600. The operator writes it;
|
||||
nothing in the repository creates or reads it, and no session sees the
|
||||
file itself, only the variable. The leading `-` lets the unit start
|
||||
without it, so Pi sessions still launch and a Claude Code launch refuses
|
||||
`no-session-credential`. To change the credential, edit or swap the file
|
||||
(or this line and this paragraph), then restart the unit; a running host
|
||||
keeps the environment it started with.
|
||||
|
||||
The unit has no `network-online.target` ordering, since a user unit cannot
|
||||
order on that system target. The notifier needs no network at start: a
|
||||
send that fails is journaled and retried.
|
||||
|
||||
Reference in New Issue
Block a user