| Epiphany | `origin/main` | `ef956865` | was Huginn's leaf pin (no pin since `afc35f9`); local checkout is on `codex/eureka-pipeline-state``4006b14b`. GitHub `isArchived: false` |
- **F5 Epiphany residue on Yggdrasil (12:13 UTC, before the teardown; what the teardown left is follow-up `eureka-body:follow_up:epiphany-body-leftovers`).** Units `epiphany.service`,
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.
| Pipeline wire ids | `epiphany.pipeline.<kind>.v2`, epoch `epiphany.pipeline.epoch.v2`, defined by the leaf, the Huginn crate `crates/eureka-pipeline` (lib `eureka_pipeline`), ids unchanged. | Renamed once to `eureka.pipeline.<kind>.v3` by one `mind.redb` migration, at the next breaking leaf change or before the first hosted mind (ruling `wire-names-rename-before-hosting`). Adding the `run` kind or the `Mind` authority variant is additive (old stored documents decode; no migration), so it does not trigger the rename; a field added with the leaf's `= absent` marker is additive too (leaf-read-anchors r2: old bytes decode, empty fields encode byte-identically, `golden/envelopes.txt` gains lines and loses none); a field added any other way would. | Huginn owns the leaf. The migration is a live-mind upgrade, which is the operator's (`mind-rules-which-forks`). |
| `run` | `<instance>:run:<label>` (cut-run-kind r2). Label `mind-<YYYYMMDD>T<HHMMSS><mmm>Z` from mind-launch's `run_label`; two launches in one millisecond produce the same key with different content, which admission answers `Conflict`, so the name stays injective. Order is admission ordinal (F15), not label. | The heartbeat opens a PersonaTurn run with no claims each time it launches a Persona turn (a `mind-persona` start that finds no such run opens none and runs nothing) (ruling `eureka-substrate:ruling:every-wake-a-run`); the Persona tool opens SelfRun runs with claims; both go through `open_and_launch`, and the unit starts only after the run commits (the run is the grant). The Self in a unit admits under its label and ends it `Recorded { reason }`. A run whose unit is not alive past a 120 s start grace is closed `Recorded { reason: "unit not active" }` by the next `open_and_launch` of the same turn, before it checks anything else. It is Recorded, not Withdrawn, so a unit that keeps dying counts as an empty ended run and trips the breaker. Only a unit whose start failed in the launch itself is Withdrawn. Idle time comes from the newest PersonaTurn run's `admitted_at` (any status), not `started_on` (a date cannot carry seconds); there is no cadence, the heartbeat's initiative timeline decides when a Persona wakes. | Opening: `open_and_launch`, called by the heartbeat for PersonaTurn and by the Persona tool for SelfRun, each with a `Grant` that only `read_effective` constructs. One live run of hers per turn and instance: huginn-mind admission refuses a second in-force run with `operated_by: Mind` of the same turn and instance as `AlreadyLive { run }` (ruling `one-live-self-run`, option `one-live-per-turn`; cut run-live-admission). `open_and_launch` answers `Busy` from that refusal, or earlier, when the holder it has just read is alive. Operator runs never count. `operated_by` is declared by the writer (follow-up `eureka-substrate:follow_up:operator-authority-needs-operator-credential`). Ending: the Self that worked it; a dead one is closed by the next launch of its turn (cut mind-launch r3). No sweep exists elsewhere. Forbidden: Hands, Soul, Imagination, Life. |
| Repetition breaker | No state of its own (rulings `self-waker`, `merge-gate-own-soul`). Trip condition derived from one selection: the documents citing the item, in any role. Counted runs are the item's ended SelfRun runs of hers (`operated_by: Mind`, not withdrawn) admitted after the newest question `raised_in` the item. Trip when the last 3 counted runs are empty, or 10 of the last 50. A run is empty when no citing document other than a run has an ordinal in its window (its admission up to the next run's, or now). Documents that cite something else never count, wherever they were admitted (F21). | Evaluated in `open_and_launch` when a Self run claims the item, not by the heartbeat. When tripped, it admits one `question` labelled `breaker-<run label>` under the item's campaign, `raised_in` the item, and launches nothing. The run label is the label the refused launch would have used, so every trip has its own key. The existing blocked-spec recipe then removes the item from the queue. Answered by a ruling or withdrawn: either reopens the item, and that question is the boundary, so a reopened item starts a fresh count. | `open_and_launch` admits the question; any Self may answer it (substrate-internal, `mind-rules-which-forks`). |
| Brake | One document per instance in a root-owned CultCache store `/etc/gamecult/minds/<instance>/control.cc`, type `eureka.control.brake.v1 { released: bool, set_at, set_by }`. | Replaced whole by the operator's CLI; never appended. Absent or undecodable reads as held. `mind-control brake hold` writes it held and then, in the same command, stops her running units: `systemctl stop` (blocking) over `mind-self@<i>:*` and `mind-persona@<i>:*`, through `SystemdLauncher::stop_all` (cut brake-stop; ruling `self-run-actuator`; F18-F20). | Operator only (ruling `brake-dial-root-store-now`); her units read it, and the file is not writable by any user she runs as. The hold is the one stopper of her units. `read_effective` is the one reader. Its `Released` answer carries a `Grant` (cadence and run cap, private fields, not `Clone`), which `open_and_launch` takes by value, so no run opens without a released reading (cut mind-launch r3). |
| 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`; 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 |
| Mind web builds (`cut-minds-web.h4`; gamecult-ops `6564bd4`, `0c0ee90`) | `/srv/minds-web/<mind>/<build>/`, served as `https://minds.gamecult.org/<mind>/<build>/`. Live 2026-11-02: root `root:root 0755`, eight folders (aqua, eureka, heimdall, kiko, libby, nibu, void, weksa) each `mind-<m>-self:mind-<m>-self 0755`, all empty; vhost `/etc/nginx/sites-enabled/minds.gamecult.org.conf` present; `/eureka/` answers 404 (no build yet). | A Mind's Self run writes a build folder; nginx serves it static: no proxy, no cookie, no CSP, no autoindex, `disable_symlinks if_not_owner`, dotfiles 404, one location for every Mind (a new Mind needs a folder, not an nginx edit). | The Mind's own Self unit alone (`ReadWritePaths=-/srv/minds-web/<m>` in `mind-self@<m>.service.d/endpoint.conf`, seen for aqua, eureka, heimdall, kiko; no Persona drop-in names minds-web). The installer owns the folders; no Mind writes another's folder; root-owned `/srv/cinny` stays refused (a chat-origin preview slot was refused: a Mind's script would share the chat client's origin). Void's beta is `/srv/minds-web/void/`; the `/srv/site` grant was dropped (`b970917`). | **Shared origin recorded:** all Minds share the one origin `minds.gamecult.org`, same-site with `chat.gamecult.org`, so a script one Mind publishes can read another's storage, and no credential or cookie may ever be put on these pages. Per-Mind origins need a separate registrable domain, waiting on the operator's domain choice (gamecult-ops `docs/research/2026-11-02-artifact-host-safety.md`). A running Self unit takes a new grant at its next start. |
| 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>.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). |
| 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` (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`. |
| `mind-control` | `/opt/gamecult/huginn/releases/<sha>/mind-control`, root:root 0700, reached by the `current` symlink; `/usr/local/sbin/mind-control` links to `/opt/gamecult/huginn/current/mind-control`. | Built and installed with each Huginn release by the Release step of `runbooks/huginn-yggdrasil.md`. Replaced when `current` flips. The previous release is kept until the new one verifies. | Install, replace: root via the Release step. Run: root only (mode 0700), which covers the operator's verbs and the gate, which systemd runs as root through the `+` prefix. No user of hers can execute it. |
| `mind-heartbeat` | `/opt/gamecult/huginn/releases/<sha>/mind-heartbeat`, root:root 0755. | Owner of initiative: reads the swarm envelope, the mind (busy, idle, run ends), the mouth (typed sender, `m.mentions`, room) and the connector (classifier urgency, spend); writes `heartbeat.cc` (Initiative per instance, poll traces) and launches Persona turns through `open_and_launch`. The operator tagging or naming a Persona fills its initiative once per message; a member never does. | `mind-heartbeat timeline` prints the latest poll. |
| The gate | `ExecCondition=+/opt/gamecult/huginn/current/mind-control --instance %i check` in both spending templates. The verb is cut mind-unit-gate. | Runs at every start of a Persona or Self unit. It exits 0 only when `read_effective` is `Released`; held, absent, undecodable or out of bounds exit 2 (systemd skips on 1-254). It holds no state and writes nothing. | Owner: `read_effective`, the one derivation. Change: a Huginn release (the verb) or the script (the line). It closes the window in which a launch that read Released before a hold starts its unit after the hold's stop (follow-up `mind-units-brake-gate`). |
| Polkit rule | `/etc/polkit-1/rules.d/60-mind-units.rules`, root 0644, source `polkit/60-mind-units.rules` in gamecult-ops. Needs package `polkitd` (absent today, F22; question `mind-unit-start-grant`). | Installed and replaced by the script. Instance-generic: one file serves every mind. | Root changes it. It grants exactly one thing: action `org.freedesktop.systemd1.manage-units`, verb `start`, unit `mind-persona@<i>.service` or `mind-self@<i>.service`, to subject `mind-<i>`. Every other request falls through to systemd's default, `auth_admin`, which an unattended caller cannot satisfy. |
| Control store | `/etc/gamecult/minds/<i>/control.cc`, root:root 0644 in a 0755 dir (cut mind-control-store). | The install script writes `brake hold` once, if no store exists, so a fresh install reads `held` with a name and time rather than `absent`. Afterwards only the operator's verbs change it. | Operator only (`brake-dial-root-store-now`). |
| A unit on a brake hold | The instance unit above. | `brake hold` writes the brake held, then stops both units of the instance and waits; the unit ends inactive, its run stays in force. A start after the hold is skipped by the gate and ends inactive (dead), not failed. The next `open_and_launch` of that turn after a release closes the run Recorded "unit not active" (mind-launch r3 step 0). Nothing restarts a stopped unit: `Restart=no`, the waker does not launch without a Grant, and Idunn does not know the unit. | Stopper: the hold only. Restarter: none. |
| Idunn's view | No binding, no target. | Idunn observes and restarts only workloads it launched itself, and every Idunn workload is a resident service that continuity restarts. It has no one-shot, timer or template kind (F22). So today it never touches these units. The deletion line of the hand install (ruling `mind-units-templated-interim`: Idunn targets before the first hosted member) needs an Idunn job workload that is never continuity-restarted. That is follow-up `idunn-job-workload`. | Idunn: none until that follow-up lands. Its owner then may install and change the templates and users, and must never start or restart a spending unit. |
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
| 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".) | |
Rulings: `tell-is-just-the-message`, `tell-delta-three-defaults`, `what-reaches-each-turn-ruled`. Owner: `persona.rs`. A session is one process and nothing compacts it, so what she was told stays in her context; a line carries only what is new.
- **First line of a process** (`PersonaInbox::start_line` -> `whole_line` -> `render_input`): `TURN_INSTRUCTION` (the Perfect Machine, stewardship, inversion of control, originator vision as evidence, her tools, work that changes a repo goes to a Self run), a refused batch's note if an earlier process left one, her latest runs, responsibilities, recall, plan windows and tempo, the rooms and people she may name, the room tail (`render_tail`: the mouth's `tail` on `chat_pending.v3`, field 2, a defaulted `Option` so an older two-field mouth still decodes and is told the tail is unavailable; per speakable room the newest 20 and 60 in all, at most 24 KiB as rendered, framed as untrusted data with a truncation marker and a note naming rooms with no line; a delivery's event id drops its tail line; a DM session reads its own room alone; nothing is accumulated in the organ; ruling `wake-reads-room-tail`; deploy order: Huginn must deploy before Bifrost #51, because a Huginn without the `tail` field fails to decode the mouth's three-field reply), `MESSAGES_HEADER`, the messages, and the tail: an opener when there are no messages (`WAKE_ASK` on a day she has started no Self run of her own, else "No new messages."), then, when there are messages, `ANSWER_MESSAGES`, then `WORK_QUESTION` on every first line, with messages or without. Later lines end on `WORK_TAIL` (`tell_line`: plain, refused and stale retells alike). A wake is always a first line: a session starts with an empty batch and gets the whole machine, so a wake never arrives as a bare later line. A restart (cache TTL, cap rotation, ceiling flush, failed result) is a new process and tells the whole machine again.
- **Later line** (`tell_line`): the organ's notice lines first, a refused batch's note before the messages, then `earlier`/`message` lines, then `WORK_TAIL`. No instruction, no machine; `tell_line` reads nothing from her mind.
- **Notice lines**: `notice role=organ at=... text="Your Self run <label> ended: <outcome>"`. They are not messages and answer to no delivery. Source: one read of her Self run slot (`holders` for SelfRun) at most once per `SLOT_READ_EVERY_S`=15 s (the chat poll is 5 s; the slot read cost 1.4-2 s of daemon CPU on Eureka's 14.7k-document mind, so 5 s was 28-41% of a core); `slot_seen` holds the runs last seen holding it, whoever started them, and her own start joins it unread. A run seen to leave gets its closing read once. A failed read keeps `slot_seen` and delays the notice. Only a live session fills `ended`, so a notice never starts a session; with notices and no deliveries `poll` reports pending and `tell` writes a line of notices alone. A run she did not start that opens and ends between two reads is unseen (about 10 of 157 Self runs at 15 s); the next first line shows it.
- **Flush** (`flush_text`): `TURN_REMINDER` (the stewardship sentence) plus the ask, never the first-line instruction. It records what it shows (`flush-shown`).
- **The `earlier` lines**: printed once per session, in a thread or a room alike (`told: BTreeSet<LineKey>` for the process; `earlier_shown`), her own lines since `started_ms` excluded; one line's `earlier` text for a room is bounded at `EARLIER_MAX_BYTES_PER_ROOM`=16 KiB rendered (a line too big is skipped, never drops older ones). One line's rendered messages are bounded at `LINE_MESSAGES_MAX_BYTES`=32 KiB: the oldest delivery whatever its size, then the longest prefix that fits (`one_line_of`); the rest waits unacked for the next line. A prefix, never a skip, so the ack and `told_through` pass only what was told.
`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` (everyone and every open item the lines the CLI echoed this process showed: the only people a reply may name and open items it may settle or take on; a line in flight bounds nothing yet, `bce5023`; a later line adds the people on it, no open items), `told`, `slot_seen`, `slot_read_due_ms`, `ended`. 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), a later line reading her mind, a notice that opens a session. Shared paths: the mouth, and the mind (read only: the slot's holders and one closing per ended run). Cut line: what the session already holds is never told again; what changes is told by the organ as a notice or a message; anything else she asks her mind for. Verification layer: `persona/tests.rs` timeline tests (echo-driven clock), Soul s1-s3 on the earlier cuts and the tell-delta verdicts in `research/2026-11-02-soul-queue-and-one-shot-trace.md`. Brief for a future cut here: read the three rulings first; a new thing she must know either rides the first line (whole machine) or is a notice with its own source and bound, never a field on every line.
Stale text corrected: the earlier update here said the instruction "opens every line, including a flush alone and a retell" and that `earlier` lines appear "once per batch"; both held at ffd4136 and are false at 3177b33. The sequence doc `research/2026-10-31-live-mind-sessions-sequence.md` carries its own closure notes.
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=b22762aac5e9b4766a640abbb1ed8fa49b9ff13f` in the installer (gamecult-ops `0c0ee90`, cut `cut-minds-web.s2`), which matches the live seeds' `.git/HEAD` (read 2026-11-02 ~22:15 UTC: aqua, eureka, kiko and void at b22762a; `MIND_PERSONA_DOCTRINE` names each Mind's own `/etc/gamecult/minds/<m>/claude-seed/skills/eureka/doctrine/` files). 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).
-`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`: built as the Mind web builds row above (static only, a folder per Mind, one shared origin). No Mind has published a build yet: map the surface live and its content empty.
| Source re-index: upsert before delete (`eureka-body-embed-outage`; `huginn-daemon/src/sources/mod.rs`, `index/qdrant.rs`) | The sources indexer in the daemon. Every chunk it writes is stamped with the head commit; a chunk id is its content. | A repo's changed paths (or the whole repo), the embedder pool | New chunks upserted first. Only then `delete_by(repo, paths, keeping=head)` removes chunks in scope whose `commit` is not the head (Qdrant filter `must` repo/paths, `must_not` commit = head). A retired repo still deletes all (`keeping: None`). | None stored beyond the collection. A failed embed now leaves what the index held (before: delete first, so an embedder outage emptied the paths it was re-indexing). | Deleting before the new chunks are written; a delete that cannot name the head it keeps | Qdrant collection, shared with every Mind's recall | The delete's filter is the cut: stale means "another commit", never "everything at these paths" | `index/tests.rs`, `sources/tests.rs` (the filter pinned `0af7fd2`; the slow-reserve stub `d9a4c27`). Live behavior unprobed here. |
| Embedder pool bounds (`index/ollama.rs`) | The daemon's one embedder client | Primary (GPU) endpoints and the reserve (CPU) endpoints | `EmbedBounds::DEFAULT` for primaries (short, under about 25 s a text); `EmbedBounds::RESERVE` for the reserve: connect 3 s, floor 60 s, 30 s per text, a batch bounded only by the caller's budget | None | Giving a CPU reserve a GPU's bound: it takes 16-27 s a text idle and 52 s loaded and would be given up on | Pool tiers | The tier decides the bound (`pool_bounded`) | `the_reserve_is_given_what_a_cpu_takes_and_a_gpu_is_not`, `a_slow_reserve_answers_under_its_own_bound_where_a_gpus_would_have_failed` |
| Tempo fallback controller (`mind-body/src/tempo.rs`, `tempo/sim.rs`; rulings `tempo-default-is-usage-fitter`, `tempo-fallback-pid-tuned-by-sim`, `tempo-controller-knows-the-ceiling`) | `tempo.rs`: `fallback_beat(...)` is a PID on the gap between the seven-day usage trajectory to the target permille and what was used, acting on the turn rate; gains `GAINS` {kp 0.02, ki 0.01, kd 0} were chosen by the committed sim (`docs/research/2026-11-01-tempo-pid-sim.md`), never by a run-time search. **Merged, not wired**: nothing outside `tempo.rs` and its tests calls `fallback_beat`; the heartbeat still takes `tempo::tally` of votes (heartbeat.rs:687). | A `Reading` (used permille, observed_at, resets_at), the previous `Fallback`, whether a vote decides, and the caller's one `saturated` report (last beat was the floor, or launched nothing because every eligible Mind was busy) | A beat in `FALLBACK_MIN_S`=60 .. `FALLBACK_MAX_S`=3600 s and the next `Fallback` | `Fallback {integral, last_used_permille, last_observed_at_ms, last_now_ms, carry_per_h, window_resets_at_ms, rate_per_h}`. The heartbeat is to own persisting it; it is never published as truth. One call integrates at most `MAX_STEP_MS` (the longest beat); a reading older than `STALE_AFTER_MS` (2x the longest beat) or from an expired window is no evidence; a new window clears the integral; while a vote decides nothing integrates, and the carry fades over `CARRY_FADE_H`=0.25 h. | The sim is test-only and the controller must stay the same function the sim sweeps; the controller must not search at run time; tuned gains are not hand-edited without a rerun | None yet | Controller is pure; the sim is its plant model (8 Minds, busy-time lognormal fit to 1284 turns over 29 h, Self run every 25th turn at 5x cost) | `tempo/tests.rs`; `cargo test -p mind-body tempo::sim::sweep -- --ignored --nocapture` reruns the sweep. The 7 new lines in `public_surface.txt` are the public API. Cut to wire: the heartbeat calls `fallback_beat` when `tally` returns none, owns `Fallback`, and passes `saturated`; not done. |
### 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.
5.`self-run-environment` (gamecult-ops): the `mind-self@` lines A2 leaves to ops (`StateDirectory`, `WorkingDirectory`, `RuntimeDirectory`,
`MIND_SELF_RUN_CAP_S` (= `RuntimeMaxSec`), optional `MIND_SELF_CONFIG_SEED`, `CLAUDE_LEASE_SOCKET`, `HUGINN_ENDPOINT`), Claude Code and the `huginn` CLI on Yggdrasil (follow-up
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)