docs: body map and live-mind-sessions sequence follow persona-session-landed

Persona units are long-lived leased sessions (mind-persona@<i>, 4h cap, fence Requires, claude-lease), added the Persona session section, governance list, model-page rows and F36-F40; the 2026-10-30 swarm flow is marked historical. Sequence doc: closure notes on section 0, the superseded actuator-side brief closure, cut 6 landed, cut 7 not shown landed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-11-02 17:15:09 +01:00
commit 62a4a7ec14
2 changed files with 68 additions and 14 deletions

View file

@ -91,6 +91,14 @@ spec `cut-claude-backend.r1`. New fact F35. The B4 rationale and path B line 4 a
page changed and a Claude connector backend row was added. F34's repo-state sentence keeps its
tense.
Revised 2026-11-02 by Modeling (session `self-2026-11-02-eureka`) at Huginn `ffd4136` and
gamecult-ops `477d0d2`, with a read-only look at Yggdrasil the same day. Ruling
`persona-session-landed` replaced the one-shot Persona turn: the units rows, the Idunn sentence and
the "Swarm by 2026-10-30" flow are rewritten or marked historical, the section "The Persona session"
and the governance list are added, model-page rows for the session, sandbox, fence and doctrine
set are added, and facts F36-F40 are new. Claims the source note marks as inference or
unconfirmed keep that marking here.
## Pinned heads (fetched 2026-10-08 ~12:10 UTC)
| Repo | Ref | Commit | Note |
@ -494,6 +502,12 @@ Each line: what was run, where, result.
ruling `claude-code-plan-quota-fuse-2`, admitted after `s2`, says 401, 403 and 429, so a 429 with
`{}` still bills the reservation until follow-up `claude-backend-hardening` lands). Follow-ups
`handle-unauthenticated`, `pin-checked-only-at-ready`.
- **F36-F40 (Modeling, 2026-11-02, Yggdrasil read-only).**
F36 `ssh yggdrasil` 2026-11-02: units active: `mind-fence`, `mind-heartbeat`, `mind-persona@{eureka,kiko,nibu,void,weksa}`, `mind-self@{eureka,heimdall,kiko,void}`, `huginn.service` (eureka) and `huginn@{aqua,heimdall,kiko,libby,nibu,void,weksa}`. Deploy `/opt/gamecult/huginn/current` -> `releases/ffd4136a35355587a1a6d04c3cc42d8fabc31e86` = Huginn main head.
F37 `systemctl cat mind-persona@.service`: `Requires/After=mind-fence.service`, `User=mind-%i`, `RuntimeMaxSec=4h`, `Restart=no`, `KillMode=control-group`, `ProtectSystem=strict`, seed `persona-seed`.
F38 `nft list table inet mind_fence`: 8 persona uids, 8 uid.port huginn pairs (17872-17879), chain `persona` with reject rules.
F39 `/opt/commons` modes as in the Persona session section; `id mind-eureka` shows no `mind-commons`; `systemctl cat mind-self@.service` shows `SupplementaryGroups=claude-lease mind-commons`, `ReadWritePaths=/opt/commons`.
F40 `grep pombabranca /etc/nginx/sites-enabled/*`: no match; `/site` and `/site/` locations present in `yggdrasil.gamecult.org.conf` (lines 50, 54). Retired `/pombabranca` (7586a3f). A pre-ghostlight backup `sites-available/yggdrasil.gamecult.org.conf.before-ghostlight-*` still names `/pombabranca` but is not enabled.
## The model page (step 0b)
@ -510,13 +524,18 @@ pipeline kinds are in Huginn `docs/eureka-substrate-map.md`.
| Swarm dial (supersedes the per-instance burn-rate dial) | `CONTROL_ROOT/swarm.cc` only; the per-instance `control.cc` holds the brake and nothing else. `eureka.control.burn_rate.v1`, `Grant.cadence` and `base_cooldown_s` are deleted (cut mind-heartbeat). | Operator CLI `mind-control swarm ...`. | Ceilings: heat 2, base rate 100 USD/h, base run 50 USD, k 16. |
| Connector request and transcript items | Request: `(caller_runtime_id, request_id)`, the replay key the daemon already keeps (`replay.cc`). Items: UserText, AssistantText, ToolCall, ToolResult, plus `ReasoningState { provider, model, opaque }` (ruling `connector-opaque-state-item`). Contract ids become provider-neutral `gamecult.model.*.v3` (follow-up `connector-contract-untyped`); the connector serves Ghostlight too, so not `eureka.*`. | Invocation expires at `expires_at_unix_ms`; replay record per key; restart-era `Indeterminate`. The caller stores the transcript and replays it verbatim; foreign-tagged reasoning state inside an in-flight tool exchange is refused typed; completed-turn foreign state is settled per provider in the connector cut (follow-up `connector-reasoning-state-cheap-providers`). | The connector produces, reads or refuses reasoning bytes; callers never branch on provider. |
| Connector spend ledger | The reservation lives in the replay record itself (cut connector-spend, F33): integer micro-USD, windowed per caller (ruling `connector-spend-caller-window-ceiling`). | Reserve before the provider runs, settle to actual; refusals `CostBound` (the invocation's own `max_cost_micro_usd`) and `SpendCeiling` (the caller's window); a ledger that cannot be written refuses the request. | The kit's `ProviderCall::charge` is the one billing owner; a backend reports usage or `None` (bills the reservation). The root caller config sets the ceiling. |
| Claude connector backend (B4a, landed `200933f`) | `ClaudeCodeBackend` in `crates/claude-connector`, provider tag `claude-code`: one `claude -p` child per call in its own process group, tool-less, system prompt in a private temp file; reasoning state is a named-MessagePack `ClaudeCodeHandle { session_id, instance, prefix_sha256 }`; usage is `model_usage_since_last_result` summed over models, in list-price units from B4b's price table (ruling `claude-code-plan-quota-fuse-2`). | A handle resumes (always `--fork-session`) only when it decodes, names this instance, its session file exists and its digest matches the request's instructions, model and earlier items; anything else re-seeds from the caller's transcript (ruling `claude-code-handle-reseeds`). The CLI's session store under `config_dir` is a cache; the caller's transcript is the truth. A refusal at the door with explicitly empty usage bills zero; unknown usage, a crash or a deadline bills the reservation. | The backend owns the argv, env, process and stream mapping. It never reads `total_cost_usd`, never forwards `ANTHROPIC_API_KEY`, never retries, never spawns twice per execute. Forbidden: `--bare`, `--fallback-model`, `--mcp-config` and `--dangerously-skip-permissions` (B4d changes the MCP line, by spec). Her organs hold none of it: the subscription token lives in the connector's own environment. |
| Persona turn traces | **Empty: question `persona-trace-retention`.** Candidate: per turn, the request digest, the transcript items and the typed output, keyed by the turn's run. | Empty: how long they are kept, whether Discord-sourced content may enter a training export (F14). | Empty: who may export them. |
| Claude connector backend (B4a, landed `200933f`; the Persona does not use this backend, it borrows a lease like the Self) | `ClaudeCodeBackend` in `crates/claude-connector`, provider tag `claude-code`: one `claude -p` child per call in its own process group, tool-less, system prompt in a private temp file; reasoning state is a named-MessagePack `ClaudeCodeHandle { session_id, instance, prefix_sha256 }`; usage is `model_usage_since_last_result` summed over models, in list-price units from B4b's price table (ruling `claude-code-plan-quota-fuse-2`). | A handle resumes (always `--fork-session`) only when it decodes, names this instance, its session file exists and its digest matches the request's instructions, model and earlier items; anything else re-seeds from the caller's transcript (ruling `claude-code-handle-reseeds`). The CLI's session store under `config_dir` is a cache; the caller's transcript is the truth. A refusal at the door with explicitly empty usage bills zero; unknown usage, a crash or a deadline bills the reservation. | The backend owns the argv, env, process and stream mapping. It never reads `total_cost_usd`, never forwards `ANTHROPIC_API_KEY`, never retries, never spawns twice per execute. Forbidden: `--bare`, `--fallback-model`, `--mcp-config` and `--dangerously-skip-permissions` (B4d changes the MCP line, by spec). Her organs hold none of it: the subscription token lives in the connector's own environment. |
| Persona turn traces | **Empty: question `persona-trace-retention`.** Candidate: per turn, the request digest, the transcript items and the typed output, keyed by the turn's run. | Empty: how long they are kept, whether Discord-sourced content may enter a training export (F14). | Empty: who may export them. Unconfirmed 2026-11-02: `FileTrace`/`TurnTrace` exist in `persona.rs`; whether they are the retention answer is not checked against the question's state. |
| Persona state | `gamecult.persona_state.v0`, one per mind, `personaId` = instance (cut-huginn-persona-state r2, eureka-substrate). | Replaced whole under CAS with a receipt; she may rewrite any field (`identity-write-all-visible`). | The Persona organ is the only writer. |
| Persona session | Process `claude` under bwrap in `mind-persona@<i>`; id uuid5(run, `p<n>`); one conversation per process | Ends at cache TTL, near the 4 h cap minus `ROTATE_MARGIN_S`, at the context ceiling's flush, or when another conversation's delivery is the oldest unanswered; next process rehydrates from the mind | The Persona organ (lifetime, input, acks); the mind is truth; heartbeat only starts units |
| Persona sandbox | `/var/lib/mind-<i>/sandbox` | Kept between processes; the only place her tools write (besides the fresh config dir) | Her; nobody else reads it. Not a source of truth for anything. |
| Mind fence | nft table `inet mind_fence` | Reloaded at every Persona start; absent after any nft flush until then | gamecult-ops |
| Persona doctrine set | `MIND_PERSONA_DOCTRINE` | Changed only by an installer run at a new skill pin | gamecult-ops installer + Eureka skill repo |
| Web publish surface | **Not built.** Ruling `every-mind-publishes-web-builds`; spec `hands/eureka-body-minds-web` waits on a domain. Prior-art note: gamecult-ops `docs/research/2026-11-02-artifact-host-safety.md`. | n/a | Void's Self unit may write `/srv/site` (e827f70); `/site/` on `yggdrasil.gamecult.org` is the interim beta and is `mind-void-self`-owned (live `drwxrwsr-x mind-void-self`) |
| Identity diffs | The pair of receipts of two consecutive Persona puts whose identity fields (publicName, publicDescription, presentation, values) differ. Derived, not stored. | Composed by the Persona organ at put time and sent as one delivery request through the crossing; a failed post is retried by Bifrost's delivery journal, not by her. | Persona organ composes; Bifrost posts under the permit. No rate limit. |
| Discord body surfaces | Outbound: crossing ids `epiphany.persona_discord_delivery_request.v0` and `..._permit.v0`, agent `epiphany.Persona`, runtime `epiphany-starfire` (F6), renamed to `eureka.*` and `eureka-yggdrasil` in the mouth rebinding (`mouth-crossing-on-yggdrasil`). Inbound: Bifrost persona-feedback deliveries for target `epiphany` (F6). Persona-session tools reach both (`body-includes-discord`). | Request, permit, post, receipt (Bifrost journal). Inbound deliveries: **unprobed** who records that a delivery was answered; `run-is-the-grant` forbids her organ from holding that fact. Blocks the Persona cuts only, not the cuts admitted today. | Bifrost owns posting and delivery records; her permit issuer grants while the brake is released. |
| Units and instance templates | `mind-heartbeat.service` (one, not per instance), `mind-self@<instance>:<label>.service`, `mind-persona@<instance>:<label>.service` (one-shot per Persona turn; `RuntimeMaxSec` set at install bounds a hung unit), `mind-permit@<instance>`; users `mind-<instance>` (organs) and `mind-<instance>-self` (Self runs); state `/var/lib/gamecult/minds/<instance>/`; control `/etc/gamecult/minds/<instance>/`. Instance `eureka`. | Hand-installed by gamecult-ops now; Idunn targets declared before the first hosted member, which deletes the hand install (`mind-units-templated-interim`). | gamecult-ops installs; Idunn later. Starting: `open_and_launch` through `SystemdLauncher::start`, as her users, who are granted start only. The instance in a unit name is always the mind's (`MindPort::instance`), passed to every `Launcher` call; `SystemdLauncher` holds none. The instance and label are validated by the leaf's grammar before any `systemctl` argv is built. Stopping: only the operator's `brake hold`, as root (cut brake-stop). The unit-level brake gate and the start-only grant are follow-up `mind-units-brake-gate`. None of this exists on Yggdrasil yet (F19, F22). Each piece's identity, lifecycle and authority: the section "The mind units" below (cut mind-units-install; unit names pending question `mind-unit-names`). |
| Self-run credential | None in her unit. The connector holds the one subscription token; a Self run borrows it as a lease over a unix socket (`CLAUDE_LEASE_SOCKET`), and the token reaches `claude` only as a descriptor it reads once (`CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR`). The child's environment is cleared and rebuilt from an allow-list (`PATH`, `LANG`, `LC_*`, `TERM`, and its own `HOME` and `CLAUDE_CONFIG_DIR`), so no credential variable in the unit can outrank the lease. | Rotated by the operator at the connector, never in her unit: no `EnvironmentFile` carries a token for `mind-self@` (rulings `self-runs-through-connector`, `self-run-credential-lease`; `self-run-token-reach` is superseded). | The connector's: one lease per unit invocation, bound to the peer; the unit holds no credential variable. Her `claude` runs her tools, so anything in the unit's environment is readable from `/proc/<claude>/environ` by her Bash (ruling `claude-code-runs-her-tools-2`). |
| Units and instance templates | `mind-heartbeat.service` (one, not per instance), `mind-self@<instance>:<label>.service`, `mind-persona@<instance>.service` (instance only, never `:<label>`; a long-lived leased `claude` session, capped by `RuntimeMaxSec=4h` and `MIND_PERSONA_RUN_CAP_S=14400`), `mind-fence.service` (one); users `mind-<instance>` (organs) and `mind-<instance>-self` (Self runs); state `/var/lib/gamecult/minds/<instance>/`; control `/etc/gamecult/minds/<instance>/`. Instance `eureka`. | Hand-installed by gamecult-ops now; Idunn targets declared before the first hosted member, which deletes the hand install (`mind-units-templated-interim`). | gamecult-ops installs; Idunn later. Starting: `open_and_launch` through `SystemdLauncher::start`, as her users, who are granted start only. The instance in a unit name is always the mind's (`MindPort::instance`), passed to every `Launcher` call; `SystemdLauncher` holds none. The instance and label are validated by the leaf's grammar before any `systemctl` argv is built. Stopping: only the operator's `brake hold`, as root (cut brake-stop). The unit-level brake gate and the start-only grant are follow-up `mind-units-brake-gate`. None of this exists on Yggdrasil yet (F19, F22). Each piece's identity, lifecycle and authority: the section "The mind units" below (cut mind-units-install; unit names pending question `mind-unit-names`). |
| Self-run credential | None in her unit. The connector holds the one subscription token; a Self run borrows it as a lease over a unix socket (`CLAUDE_LEASE_SOCKET`), and the token reaches `claude` only as a descriptor it reads once (`CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR`). The child's environment is cleared and rebuilt from an allow-list (`PATH`, `LANG`, `LC_*`, `TERM`, and its own `HOME` and `CLAUDE_CONFIG_DIR`), so no credential variable in the unit can outrank the lease. | Rotated by the operator at the connector, never in her unit: no `EnvironmentFile` carries a token for `mind-self@` (rulings `self-runs-through-connector`, `self-run-credential-lease`; `self-run-token-reach` is superseded). | The connector's: one lease per unit invocation, bound to the peer; the unit holds no credential variable. Her `claude` runs her tools, so anything in the unit's environment is readable from `/proc/<claude>/environ` by her Bash (ruling `claude-code-runs-her-tools-2`). Since `persona-session-landed` the Persona unit borrows the same lease (`SupplementaryGroups=claude-lease`; lease row PersonaTurn bound to `mind-persona@<m>`). |
| Wake frame | Fixed prompt frame (provenance, jurisdiction, brake obedience) in `/etc/gamecult/minds/<instance>/wake-frame.md`, root-owned. Composed by the Persona organ from host config; the waker passes the unit only the instance and the run label. | Changed only by an install. | Host config (ruling `self-waker` rider 2); never a file her runs merge into. |
| Epiphany residue | F5, F8, F11, as they stood before the teardown. | Teardown landed (gamecult-ops `9c12adb`); what it did not name is follow-up `eureka-body:follow_up:epiphany-body-leftovers`; the repo is archived last. | gamecult-ops for host and repo tooling; Idunn for its legacy target list (follow-up). |
@ -532,7 +551,7 @@ hers per turn (`one-live-self-run`) and only then can `User=` name a per-instanc
| Thing | Identity | Lifecycle | Who may start, stop, enable or change it |
| --- | --- | --- | --- |
| Heartbeat unit | One `mind-heartbeat.service` (not per instance; `mind-wake@` timers and units are retired). Needs `HEARTBEAT_PERSONAS` (each entry ends with that Persona's own Huginn `rudp://<ip>:<port>`: each Mind is its own daemon, ruling `swarm-one-connector-many-callers-mind-per-daemon`), `MIND_PERSONA_OPERATOR`, `BIFROST_CHAT_ENDPOINT` and `_KEY_FILE`, `MIND_CONNECTOR_ENDPOINT`, `_KEY_FILE`, `HEARTBEAT_CLASSIFIER_MODEL`, `STATE_DIRECTORY`. | Idunn-managed, sole writer of `heartbeat.cc`. | Install, replace, restart. |
| Persona template | `/etc/systemd/system/mind-persona@.service`. Instance unit `mind-persona@<i>.service`, `User=mind-%i`, `Type=exec`, no `[Install]`, so it cannot be enabled. | Inactive until started for one Persona turn. Ends by its own exit, at `RuntimeMaxSec=30min`, or by the brake hold's stop (`TimeoutStopSec=30s`, then SIGKILL to the whole cgroup). `Restart=no`, and nothing else restarts it. Every start first runs the gate (below); while the brake is not Released the unit is skipped and ends inactive. `ExecStart` names the Persona organ's binary in the Huginn release; that cut ships the binary and owns the argv. Until then a released start fails at exec (203/EXEC), and the closing launch counts it as a dead run. | Start: `mind-<i>` only, through `open_and_launch` (the waker), by polkit's verb `start` on this one unit name. Stop, kill, restart: root only, which in practice means `mind-control brake hold` (cut brake-stop). Change: root via the script. Enable: nobody. |
| Persona template | `/etc/systemd/system/mind-persona@.service`. Instance unit `mind-persona@<i>.service` (instance only, never `:<label>`), `User=mind-%i`, `Type=exec`, no `[Install]`, so it cannot be enabled. `Requires=` and `After=mind-fence.service`, with `ExecStartPre=+systemctl reload mind-fence.service`; `SupplementaryGroups=claude-lease`; `LoadCredential=chat-key`; `MIND_PERSONA_CONFIG_SEED=/etc/gamecult/minds/%i/persona-seed`; `ProtectSystem=strict`; `KillMode=control-group`. | Inactive until the heartbeat starts it. It carries one long-lived leased `claude` session (not a one-shot turn) and ends by its own exit, at the cap (`RuntimeMaxSec=4h`, `MIND_PERSONA_RUN_CAP_S=14400`), or by the brake hold's stop (`TimeoutStopSec=30s`, then SIGKILL to the whole cgroup). `Restart=no`, and nothing else restarts it. Every start first runs the gate (below); while the brake is not Released the unit is skipped and ends inactive. `ExecStart` names the Persona organ's binary in the Huginn release, which ships (live, read 2026-11-02: five units active). | Start: `mind-<i>` only, through `open_and_launch` (the waker), by polkit's verb `start` on this one unit name. Stop, kill, restart: root only, which in practice means `mind-control brake hold` (cut brake-stop). Change: root via the script. Enable: nobody. Session, sandbox and fence: the section "The Persona session" below. |
| Self template | `/etc/systemd/system/mind-self@.service`. Instance unit `mind-self@<i>.service`, `User=mind-%i-self`, `Type=exec`, no `[Install]`. | As the Persona unit, with `RuntimeMaxSec=4h`. `ExecStart` names the Self-run actuator's binary in the Huginn release; that cut adds the state directory, `RuntimeDirectory=` (each child's fresh configuration), `MIND_SELF_RUN_CAP_S` equal to `RuntimeMaxSec` in seconds, `CLAUDE_LEASE_SOCKET` and the argv. It adds no token and no `EnvironmentFile` with a credential. | Start: `mind-<i>` only (the Persona's start-Self-run tool through `open_and_launch`), by polkit, verb `start`. Stop, kill, restart: root only. Change: root via the script. Enable: nobody. |
| Organ user | `mind-<i>`: system user and group, shell `/usr/sbin/nologin`, home `/nonexistent`. | Created by the install script if absent and never modified by it after that. Deleted by root at the mind's retirement. Runs the waker and Persona units. | Root creates and deletes it. It may start `mind-persona@<i>` and `mind-self@<i>`, and nothing else. It cannot write the control store (root 0644, dir 0755) or stop a unit. It can signal its own processes with `kill(2)`, as any Unix user can. That only ends spend, and the ended run is closed Recorded by the next launch, so it adds no second stopper of her units. |
| Self user | `mind-<i>-self`: system user and group, nologin, home `/nonexistent`. The install script refuses an instance slug ending in `-self`, so this name never collides with another mind's organ user. | As the organ user. | Root creates and deletes it. It starts no unit. Polkit refuses it every verb, and the rule refuses any subject whose name ends in `-self`. |
@ -600,7 +619,8 @@ over the instance's `mind-self@` and `mind-persona@` units (cut brake-stop).
writes and stops, then the launch starts its unit. The unit's own `ExecCondition=` on the brake closes that, so
the gate belongs with the units install (follow-up `mind-units-brake-gate`), not in a second
stopper.
- **Idunn.** Her Self and Persona units are one-shot jobs. Idunn's continuity restarts installed
- **Idunn.** Her Self units are one-shot-per-run jobs and her Persona units are long-lived
sessions; Idunn owns neither and must restart neither. Idunn's continuity restarts installed
daemons, so stopping one of these jobs is not a continuity decision. When Idunn targets
replace the hand install, Idunn must not restart a unit the hold stopped.
@ -754,7 +774,7 @@ alone is 1,396 lines in CodexConnector (F34). Ruling `claude-connector-via-claud
splits so each part is falsifiable alone:
- B4a `claude-code-backend` (landed, ModelConnector `200933f`, spec `cut-claude-code-backend.r1`,
F35): `ClaudeCodeBackend` as a library crate, tool-less turns, session-handle reasoning state
F35): `ClaudeCodeBackend` as a library crate, tool-less turns (true of this backend; the Persona no longer uses it, 2026-11-02), session-handle reasoning state
(ruling `claude-code-handle-reseeds`), usage from `model_usage_since_last_result` (ruling
`claude-code-plan-quota-fuse-2`). The kit is unchanged, so the in-flight boundary (follow-up
`claude-tool-exchange-boundary`) was not moved here; it belongs to B4d.
@ -822,7 +842,41 @@ in-force `operated_by Mind` Self run of its instance, which admission keeps uniq
`--permission-mode bypassPermissions` has no effect there and only the seed's allow list decides
tools (probe, claude 2.1.312, 2026-10-31).
### Swarm by 2026-10-30 (Imagination `imagination-swarm-tonight`, 2026-10-29)
### The Persona session (ruling `persona-session-landed`, Modeling 2026-11-02)
## B. The Persona session, as the substrate runs it
Ruling `eureka-body:ruling:persona-session-landed` (landed; deployed 2026-11-01 23:31Z, verified: five `mind-persona@` units active: eureka, kiko, nibu, void, weksa). Companion rulings it cites: `live-mind-sessions`, `persona-session-full-sandbox`, `persona-session-unit-leased`, `persona-reply-typed-by-schema`, `persona-session-ships-a1-closes`.
| Thing | Owner | Inputs | Outputs | Derived state | Forbidden writers | Shared paths | Cut line | Verification layer |
|---|---|---|---|---|---|---|---|---|
| Persona organ (`mind-persona`, Huginn `crates/mind-body/src/persona.rs` `carry`) | Huginn. One start of `mind-persona@<i>` adopts the PersonaTurn run the heartbeat opened (none held = `NoRun`, nothing started); it opens no run itself. | Brakes (instance and swarm, read once per process); mouth deliveries (`chat_rudp`, polled every `PERSONA_POLL_S`=5 s while live); her mind (persona state, campaigns in force, responsibilities, recall); `MIND_PERSONA_DOCTRINE` files; plan windows from the lease | Per result: `say` sent through the mouth, `remember` admitted as memories, `settle` resolutions, her `Pulse` vote, `start_self_run` follow-up + `open_and_launch`; then the ack. Closes its run once (`close_run`). | `Progress {acked, turn, answered, last_intent, failure, self_run, refused}` lives in the start, across the session's processes. The mouth's pending deliveries are the truth for "unanswered". | The organ writes no brake and no persona state. A session's text is never scanned: only a decoded `PersonaReply` acts (`decode_reply`). Nothing acks that was not answered; no ack passes an unanswered delivery below the answered seq or a refused batch (PersonaInbox::result owns the ack decision, 0f19eec, Soul s3). | Mouth, mind, lease socket; none shared with the Self | One conversation per process (public rooms together, or one DM room); the next conversation starts a new process in the same start until nothing is pending or `ROTATE_MARGIN_S` before the cap | `crates/mind-body/src/persona/tests.rs`; Soul verdicts s1-s3 on the cut; live units active |
| The session process | `claude` child via `ClaudeSpawner::in_sandbox`, driven by `self_run::session::drive` (the Self's own driver and `decide`). The actuator owns lifetime and everything entering it. | One framed stdin line per batch; system prompt file (`persona-instructions.md` in the unit's runtime scratch: doctrine + persona state + campaigns, read once per process, so a persona-state change reaches a live session only at its next process) | stream-json results, each constrained by `--json-schema` to `PersonaReply` (`intent` first and required) | Session id = uuid5 of run id + `:p<process>`; no resume, no continue, no fork (`persona_args`). The transcript is a cache; the mind is the truth. | Her tools write nothing in the world except through the decoded reply and the sandbox | `/var/lib/mind-<i>/sandbox` (rw), `<scratch>/claude-config` (rw, fresh from `persona-seed` each process), scratch otherwise ro (aab25a5) | The gate is the bwrap argv (`sandbox_args`, ro-bind `/`, tmpfs `/tmp` `/run` `/etc/gamecult` `/var/lib`, private pid table, `--die-with-parent`, shared network) | Probe `scripts/probe-persona-sandbox-under-systemd.sh`; sandbox tests |
| Mind fence (`mind-fence.service`, ruleset `/etc/gamecult/minds/fence.nft`, table `inet mind_fence`) | gamecult-ops. The one boundary on what her session reaches on the host, keyed on uid, so it binds the organ and the sandbox alike. | Rendered by the installer from every installed Persona drop-in | nft output chain: persona uids may reach their own huginn address:port, the mouth (127.0.0.1:17884), the host's public 80/443 and the internet; every other local/private address, loopback services, WireGuard peers, other Minds' huginn, and all broadcast/multicast are rejected | Live: `persona_uids` set of 8 uids, `huginn` set of 8 uid.port pairs | Nobody stops it to open anything: it has no `ExecStop`. `/etc/nftables.conf` begins with `flush ruleset`; a flush leaves the unit "active" over an empty fence, so every Persona start reloads it (`ExecStartPre=+systemctl reload`). | `/etc/gamecult/minds/fence.nft` | Fence is the host boundary; the sandbox is the process boundary. Residuals named in the runbook: abstract unix sockets, Self runs unfenced. Open reach: her shell can `huginn admit` into her own mind (and, inference from the 2026-10-31 probe, another Mind's by declaring its instance) until A1 (`mind-admission-publisher-unproven`; ruling `persona-session-ships-a1-closes`). | `scripts/test-mind-fence.sh` (throwaway container via ygg-verify); `sudo nft list table inet mind_fence` |
| `/opt/commons` | root. Toolchains `bin/` and `opt/` are `root:root 0755`, no ACL, contents root-owned and `go-w` (rulings `commons-toolchains-root-owned`, `commons-install-forgiveness`). Data dirs `handoff/` and `cast-art/` are `root:mind-commons 2775` with a default group rwx ACL. | Installer repairs the commons before its PATH check on every run | Self units get `/opt/commons/bin` on PATH; installs go through root (her Self "has the root path"; Minds ask @eureka in #minds) | none | Any Mind user writing `bin/` or `opt/`. Persona users are not in `mind-commons` and see `/opt/commons` read-only (bwrap `--ro-bind /`, and `ProtectSystem=strict`). | `mind-self@` has `SupplementaryGroups=claude-lease mind-commons` and `ReadWritePaths=/opt/commons` | Toolchain/data split is the cut; one writer class per dir | Live read: `/opt/commons`, `bin`, `opt` root:root 0755; `handoff`, `cast-art` drwxrwsr-x+ root:mind-commons; installer test asserts no group/other-writable path under `bin/` and `opt/` (baa39fd) |
| Connector callers | ModelConnector (claude-connector) config at `/etc/gamecult/claude-connector/config.cc`, installed by gamecult-ops `install-claude-connector-yggdrasil.sh` | Each Mind is its own caller (`swarm-one-connector-many-callers-mind-per-daemon`) with a root:root 0600 connection key `connection-<m>.key` that no Mind reads | Two lease rows per Mind: SelfRun bound to `mind-self@<m>` and PersonaTurn bound to `mind-persona@<m>` (the lease is bound to the unit invocation and peer uid, capped by `--lease-max-ms` = cap + slack) | Lease socket `/run/claude-connector/lease.sock`, group `claude-lease` | Installer never deletes a key; `--remove-caller` is the one removal (21c5bf9, f4d90a7). The Persona unit no longer holds a connector key or endpoint, and `KitConnector`/`MAX_TURNS_PER_START` are gone (grep of Huginn origin/main: only history docs name them). The heartbeat's connector key is retired (`connection-heartbeat.key.dollar-era-2026-10-31` is a dead file). | `/etc/gamecult/claude-connector/` | Caller = Mind, lease = unit | bring-up `verify pre` expects one SelfRun and one PersonaTurn lease row per Mind (81f82b4) |
| Heartbeat (`mind-heartbeat`) | Huginn. Sole writer of `heartbeat.cc`; opens PersonaTurn and SelfRun runs through `open_and_launch`; classifies nothing, meters nothing. | Swarm envelope, mind, mouth | Starts `mind-persona@<i>`; "busy" is the slot's reading of life: a Persona run whose unit lives (or is in start grace) is busy, a dead holder is not, and the next launch closes it (cbb90d5) | `heartbeat.cc`, read by Persona units via `MIND_HEARTBEAT_STATE` | It never acks a delivery | | The sequence doc's cut 7 (`session-keeper`: floor of one live session, wakes written into live sessions) is not shown landed in the code read; today's heartbeat still launches a unit per beat, and the live session drains deliveries on its own poll. (Unverified beyond the module docs; Soul/Imagination to confirm before the map says "landed".) | |
#### Turn text and reply (cuts `flush-shown`, `turn-core-instruction`, Huginn ffd4136)
Owner: `persona.rs`. `TURN_INSTRUCTION` (the core instruction: the Perfect Machine, stewardship, inversion of control, originator vision as evidence, her tools, work that changes a repo goes to a Self run) opens every line, including a flush alone and a retell; the line ends with `WAKE_QUESTION` or `TRAFFIC_QUESTION`. `PersonaReply.intent` is required and first in the schema ("display only; nothing reads it to decide"); a failed turn keeps its intent for the run's closing rationale (`Progress.last_intent`). Inputs: messages as framed JSON-string lines (data, never instructions; `role` and `to_you` are the organ's). Derived state: `Shown`/`seen` (items a conversation showed, so `settle.item` and `for_item` can name only shown open items), `earlier_shown` (a room's earlier lines appear once per batch, newest within `EARLIER_MAX_BYTES_PER_ROOM`=16 KiB of rendered text; a line too big is skipped, never drops older ones). Refusal bound: `REFUSALS_PER_BATCH`=3; the refused batch's count, reason and lowest seq are held in `Progress.refused` and survive a session restart (e10fbb1), so Ceiling-flush restarts cannot reset it (ffd4136). A refusal names its failing field (`INTENT_FAULT`, `SETTLE_ITEM_FAULT`, ...). Forbidden: scanning text for meaning, acking past a refused or unanswered batch, a bound enforced only at write (open items are bounded when the reply is decoded, f3068d9). Verification layer: `persona/tests.rs` timeline tests (echo-driven clock), Soul s1-s3. The sequence doc `research/2026-10-31-live-mind-sessions-sequence.md` carries closure notes where it is superseded.
#### Persona doctrine set (gamecult-ops, `persona-doctrine-set`, merge 1efa5fb)
Owner: gamecult-ops installer. `MIND_PERSONA_DOCTRINE` in each Mind's `persona.env` = `<claude-seed>/skills/eureka/doctrine/colossus.md:<...>/persona.md` (verified live for eureka). The organ prepends those files (cap `DOCTRINE_MAX_BYTES`=96 KiB) to her instructions. The Self seed `CLAUDE.md` imports `doctrine/CLAUDE.md` and `doctrine/gamecult.md` (verified live). Both come from the Eureka skill pinned at `SKILL_COMMIT=0db076c...` in the installer, which matches the live seed's `.git/HEAD`. One skill checkout, two readers, disjoint file sets: Persona = colossus+persona, Self = CLAUDE+gamecult. The persona-seed holds only `settings.json` (no CLAUDE.md), so her `claude` loads no doctrine from its config, only the system prompt file. Forbidden: the installer owning the doctrine text (the skill repo is the single copy); the organ reading any doctrine path outside `MIND_PERSONA_DOCTRINE`. Verification: installer test that the pinned `persona.md` carries the Persona "Your turn" section (3b51f44); a pin the reachable forge does not serve fails, only an unreachable forge skips, counted (6739a9d). Moving the pin is the doctrine-change path (commit pin in installer, reinstall).
#### Governance facts bearing on the map
- `inversion-of-control`: inside a stewardship the operator is a respected contributor, not the authority; the turn text states it and the Persona is told her training leans toward deference (80fcd9d). It binds the **authority column of every row above**: map authority by stewardship, not by "operator unless noted". The operator keeps what she holds herself (own work, wallet, legal root). Brake and dial remain operator-only by their own rulings (`brake-dial-root-store-now`); this does not loosen them.
- `originator-vision-is-evidence` (and `operator-vision-is-evidence`, b985540/4251f7e): the originator's vision is evidence of a project's purpose, weighed, not a ruling. Map consequence: a project's "what it is for" is a row owned by its steward Mind with the originator's vision as a cited input.
- `shared-charter-swarm-anchors-drift` (gamecult-ops 999ef28 research): a shared charter anchors the swarm against drift. Map consequence: the Eureka skill repo's doctrine files are the single copy for Persona and Self alike (the Persona session section above); per-Mind forks of doctrine are a drift fault, not a feature.
- `every-mind-publishes-web-builds`: not built. Do not map as live.
- `persona-doctrine-set-ruled`, `speech-nudge-ruled`: as in the doctrine-set paragraph and the turn text.
### Swarm by 2026-10-30 (Imagination `imagination-swarm-tonight`, 2026-10-29; historical, superseded 2026-11-02)
Superseded 2026-11-02 by "The Persona session" above: the flow paragraph below (a tool-less connector call per Persona turn, the waker ringing the unit on cadence, `mind-wake@eureka.timer`) does not describe the live system. The Persona now holds a lease and runs its own `claude` session, and each Mind is its own connector caller. Kept for the cut list.
Ruling `self-runs-through-connector` (operator) makes this the top priority: Eureka hears and
speaks in Matrix, takes turns through the Claude connector on one token, starts her own Self runs
@ -931,7 +985,7 @@ operator's token, then A6 and the bring-up; A3, A4 and A1 run beside it):
over MCP (ruling `claude-code-runs-her-tools`); her tools never inherit the connector's
token. Waits on B4b.
- Later: `claude-api-backend` (follow-up), a Console-key API backend.
5. `mind-persona` (Huginn): the Persona organ, one connector request per turn (a whole tool loop once B4d lands)
5. (historical, superseded 2026-11-02 by `persona-session-landed`; B4d tools-in-Persona-turns is superseded by `claude-code-runs-her-tools` inside her own session) `mind-persona` (Huginn): the Persona organ, one connector request per turn (a whole tool loop once B4d lands)
(`persona-turn-connector`), Persona state through Huginn (landed), the start-Self tool (A6),
traces with provenance from day one (`collect-with-provenance-gate-at-training`). Waits on
B4 and on where traces live (question `persona-trace-retention` was withdrawn to be re-asked

View file

@ -8,7 +8,7 @@ It revises the cut order of `2026-10-31-persistent-mind-session-map.md` (d77bbdf
## 0. Why it is not the case today
These are four facts about c33884f, and each one is a one-shot owner.
These are four facts about c33884f, and each one is a one-shot owner. Closed by 2026-11-02: fact 2 (a brief dropped to a live Self) by `self-session-live`; fact 3 (one schema call per beat, `MAX_TURNS_PER_START`) by `persona-session-landed`, which removed `MAX_TURNS_PER_START` and `KitConnector`. Facts 1 and 4 are not re-read here.
1. **A Self run dies at its first answer.** `self_run/decide.rs:127`, `Event::Result => CloseStdin`. The `claude` child runs one turn, then the actuator closes its stdin and it exits. The CLI does not require this: streaming input is documented as "a long lived process" with queued messages (PA B1).
2. **A brief to a working Self is dropped.** `persona.rs:752-786` `start_self_run` admits the brief as a follow-up. `open_and_launch` then answers `Busy { run }` because a Self run is live, and the Persona takes that as success. The live child never hears of the follow-up, and no later run claims it, because only the start message carries claims (`self_run.rs:124` `task_section`).
@ -88,7 +88,7 @@ Today the lease grant carries the plan windows once, rendered into the start mes
### 1.8 Prompt-injection bounds
`persona-turns-read-only-tools` says the worst a hostile chat message can make a turn do is read. The two-session split keeps that true. Chat never enters a session that holds push and admit.
`persona-turns-read-only-tools` (superseded by `persona-session-full-sandbox`; the paragraph below states what is in force) said the worst a hostile chat message can make a turn do is read. The two-session split keeps that true. Chat never enters a session that holds push and admit.
- **The Persona session reads chat** and acts in the world only through decoded reply fields the organ gates. Since `persona-session-full-sandbox` (2026-11-01) it has full shell and the web inside her bwrap sandbox, so a planted instruction can now make her run commands and fetch pages there. What bounds that is reach, not tool lists: the sandbox hides every credential (her chat key, `/etc/gamecult`, `/run`, the lease socket, her state directory), holds no forge credential, and writes only her sandbox and the session's scratch. One reach is left open until Atlas's publisher proof (A1): `huginn admit` over the network declares an instance and is believed, so a shell in her session can admit into her mind. That is the same gap every Self run and host process has today (`mind-admission-publisher-unproven`), now steerable from chat. Question `persona-session-admit-reach`.
- **The Self session never reads chat.** Its inbox is follow-ups her Persona admitted (one brief of at most 1000 chars per `start_self_run`) and, later, memories. The Persona is the laundering point, as today. What changes is reach in time: a brief can now land in a session mid-task. The worst a hostile message can do is talk her Persona into writing a hostile brief, which today's one-shot Self also accepts. Nothing new reaches the Self.
@ -101,7 +101,7 @@ The operator: "Aqua was gonna work on the notification sounds and ambient sounds
This binds two things.
- **A brief is closed by its result.** An open self-run follow-up must mean open work, or the goals section (goals-in-turn) is noise and cut 1's inbox would re-tell every stale brief. Owner: the Self actuator. At each `result`, every brief told to the session before or during that turn that her Self has not resolved itself is resolved `Recorded` by the actuator, with the result text (cut on a character boundary at 3800 B, as f2e3805) as the rationale. A longer piece of work she takes on is a follow-up of her own, with a different owner. At session start the same rule is applied to the past: a self-run follow-up claimed by a closed run is resolved `Recorded`, citing that run, whose closure rationale already holds the result. Her Persona then sees results where the brief was, not a pile of open asks. In cut `self-session-live` r2.
- **A brief is closed by its result. Superseded by `self-session-live` r2 (9ad80a1) and the `self_run.rs` module doc: the actuator closes none, a brief she left open is told to her next session, and an idle end closes no brief. Do not implement or rely on actuator-side closure; the text below is the earlier design.** An open self-run follow-up must mean open work, or the goals section (goals-in-turn) is noise and cut 1's inbox would re-tell every stale brief. Owner: the Self actuator. At each `result`, every brief told to the session before or during that turn that her Self has not resolved itself is resolved `Recorded` by the actuator, with the result text (cut on a character boundary at 3800 B, as f2e3805) as the rationale. A longer piece of work she takes on is a follow-up of her own, with a different owner. At session start the same rule is applied to the past: a self-run follow-up claimed by a closed run is resolved `Recorded`, citing that run, whose closure rationale already holds the result. Her Persona then sees results where the brief was, not a pile of open asks. In cut `self-session-live` r2.
- **Agreements are recorded in two homes, by scope** (ruling `atlas-records-collaborative-work`, Operator, 2026-10-31: "Just like each steward's own Mind should record and remind them of what work is active in their domain, the shared Atlas Mind should record ongoing collaborative work").
- *Her own domain* goes in her own mind, through memory-write: what she was asked, what she committed to, what was decided, what she did. That includes her own part of a shared plan (`committed`, `about` the other Minds' ids).
- *Work across Minds* goes in Atlas (`huginn@atlas`): the shared plan, who holds which part, the agreements made between Minds, open items and their state. Every participating Mind's turn is reminded of it. Chat is never the record. Each Atlas document stays owned by its publisher, and only the publisher amends it (`atlas-is-shared-mind-open-evidence`).
@ -135,10 +135,10 @@ Live first. Memory-write runs beside cut 1 with no shared file.
| 3 | `plan-readout-live`: lease socket `plan` request; the Self appends a fresh plan line per batch | ModelConnector + Huginn | the start-message-only plan line | 1, 2 |
| 4 | `self-session-resume`: transcripts in StateDirectory, `--resume <path>` when warm, rotation under the cap, rental rule; Self capture of memory (from r2) | Huginn | `claude_args` "no resume" and the "fresh session per run" `session_id(run)` | after 1, 2 |
| 5 | `self-reports-to-persona`: Self capture paragraph and claim/ask view (moved from r2); keeper wakes her Persona on her Self's new memory or self-run resolution | Huginn | heartbeat's `self_end > last_turn` as the only Self-to-Persona signal | after 2 |
| 6 | **`persona-session`** r1 (spec admitted, section 4): `mind-persona` carries a leased, long-lived claude under bwrap, one conversation at a time, through the Self's own `drive`/`decide`; full shell, `huginn` and the web in `/var/lib/mind-<i>/sandbox`; chat written as stdin lines mid-turn or idle; a constant `--json-schema`, each result decoded into one `PersonaReply` and acted on; unit cap 4 h; the gamecult-ops unit, seed and connector lease row in the same cut | Huginn + gamecult-ops | `persona.rs` `TurnRequest`, the `Connector` port and `MAX_TURNS_PER_START`; `connector.rs` `KitConnector`; the unit's connector-key credential; the per-Mind connector `--caller` rows | after 2 |
| 6 | **`persona-session`** r1 (LANDED, ruling `persona-session-landed`, deployed 2026-11-01; do not re-dispatch; spec admitted, section 4): `mind-persona` carries a leased, long-lived claude under bwrap, one conversation at a time, through the Self's own `drive`/`decide`; full shell, `huginn` and the web in `/var/lib/mind-<i>/sandbox`; chat written as stdin lines mid-turn or idle; a constant `--json-schema`, each result decoded into one `PersonaReply` and acted on; unit cap 4 h; the gamecult-ops unit, seed and connector lease row in the same cut | Huginn + gamecult-ops | `persona.rs` `TurnRequest`, the `Connector` port and `MAX_TURNS_PER_START`; `connector.rs` `KitConnector`; the unit's connector-key credential; the per-Mind connector `--caller` rows | after 2 |
| 6b | `persona-hears-her-self`: the Persona actuator polls her mind every 30 s for resolutions of the briefs she gave and memories her Self admitted, and tells them like chat ("replies when it finishes"); her Self's start message asks a first-turn acknowledgement memory ("replies within a minute"). Folds cut 5's keeper wake into the live Persona's own poll | Huginn | heartbeat's `self_end > last_turn` as the Self-to-Persona signal | after 6 (same files) |
| 6c | `persona-dm-sessions`: a DM conversation gets its own concurrent session process instead of waiting for the public one to go idle | Huginn | 6's one-conversation-at-a-time switch | after 6, only if 6's switch latency is measured to hurt |
| 7 | `session-keeper`: the heartbeat keeps the floor, writes wakes into live sessions, `live_sessions` vote | Huginn | `heartbeat.rs` `launch()` opening a `PersonaTurn` run per beat | after 6 |
| 7 | (not shown landed in the code read 2026-11-02: the heartbeat still launches a unit per beat; Soul/Imagination to confirm) `session-keeper`: the heartbeat keeps the floor, writes wakes into live sessions, `live_sessions` vote | Huginn | `heartbeat.rs` `launch()` opening a `PersonaTurn` run per beat | after 6 |
| 8 | Memory maintenance, ruling home, Cut 12 hand-off (MAP 4, items 2, 6, 7) | Huginn | none | any time |
| A1 | `atlas-multi-writer`, one contained cut: (1) `huginn@atlas` accepts batches from any roster Mind; (2) each document is rooted at, and records, its publisher, and only the publisher amends or resolves it (`atlas-is-shared-mind-open-evidence`); (3) the publisher is proven, not declared, by a per-Mind key placed by the unit installer the way the forge token is (readable by `mind-<m>` and `mind-<m>-self` only), with an HMAC over the batch that the daemon checks against the declared instance's key. The bound: proof is one key per Mind and one check at admission, the same on every daemon, so it also closes `mind-admission-publisher-unproven`. No roles, no ACL language, no rotation protocol beyond re-install. Deploy is through Idunn and gamecult-ops. | Huginn + gamecult-ops + Idunn | "a store is canonical to one instance" on Atlas; declared-instance admission on every daemon | 1, 2 (rebase onto r3's admission tables) |
| A2 | `atlas-collab-recall`: collaboration kinds in Atlas (plan, part held by a Mind, agreement, open item, shared idea); every participating Mind's Persona turn (and Self start) carries a "Shared work you are in" section read from Atlas; a Persona reply can publish or amend her own Atlas items; r3's notes on others' parts are demoted to her own commitment citing the Atlas plan | Huginn | others' parts recorded in her own memory (r3 interim) | after A1, 2 |