Coding agents: read CLAUDE.md first, then this file. The architectural rules below override pattern-matching from your training data — OrionII is a bus-routed entity runtime, not a chat function with extra logging.
-
All communication between Mentor, Id, Ego, and local Superego goes through the
EventBustrait insrc-tauri/src/orion/bus/. Direct imports between these participant modules — outsideservice.rs::build()where they are spawned — are a regression. If you find yourself wanting one, that is a signal to add a topic. -
Topics are an enum in
bus/mod.rs::Topic. Do not pass raw strings as topic names. Adding a topic requires an ADR (see docs/ADR-001-entity-event-bus.md). -
Every
Envelopecarriessoul_ref—blake3:<hex>over the bytes ofcharter.md, computed viabus::current_soul_ref(&Charter)reading from theSharedChartercell threaded throughOrionCore. Code that publishes without one is a bug, not an optimization. -
The entity ↔ SAO seam is HTTP. Sanitized events leave only via the
egress.outboundsubscriber inorion/egress.rs. Inbound governance arrives only viagovernance.inbound. Do not add other bridges, and do not callSaoShipperfrom outsideegress.rs. -
Commissioning artefacts are write-once-from-SAO.
charter.mdandbirth_certificate.jsonin%APPDATA%\OrionII\are written by either the commissioning finalize path or thegovernancesubscriber oncharter.update. Do not write them from anywhere else. Private key material (mentor or entity) MUST never appear in OrionII memory or on local disk — the only public-facing crypto material is the public-key fingerprints displayed at the IdentityKey stage. Thecommissioning_clientmodule is the only file that calls/api/orion/commission/*.
OrionII and SAO are two repos with one operator-facing contract. If a change touches a SAO-facing OrionII surface, inspect the sibling SAO repo (normally C:\Repo\SAO) and either update SAO in the same workstream or leave a concrete SAO follow-up plan in the final response.
SAO-facing surfaces include:
src-tauri/src/orion/commissioning_client.rs,birth.rs,sao.rs,bootstrap.rs, anddocs/sao-commissioning-contract.md.- Commissioning and birth wire shapes:
agentId,agentName,orionId,newToken,birthCertificate,charterHash,soulRef. - Token-invalid handling (
401/403), already-commissioned start responses, agent-not-found, unsupported commissioning versions, and repair routing. - SAO bundle and installer distribution:
config.json,deployment.json,Install-OrionII.cmd,Install-OrionII.ps1, MSI inclusion, installer-source registry defaults.
When this checklist is triggered:
- Do not add OrionII EventBus topics for SAO alignment. The seam stays HTTP.
- Do not add SAO endpoints unless the commissioning contract doc is updated.
- Keep OrionII
/api/orion/commission/*calls confined tocommissioning_client.rs. - Add or adjust tests in both repos where the contract moved. OrionII should cover command serialization/routing; SAO should cover route contracts, auth statuses, bundle contents, and installer-source selection.
- In the final response, say whether SAO source was updated, whether only the running SAO registry was changed, or whether a separate SAO change remains.
OrionII is delivered as a SAO-issued agent bundle, not a generic desktop app. There is no auto-updater (yet) and no git pull && cargo run story for end users. After any change to this repo, the operator must round-trip through SAO before the change reaches their installed runtime:
npm run build:installer— builds the release MSI with the bundlednats-serversidecar (scripts/build-installer.ps1). Plaintauri buildwill NOT package the durable broker; do not suggest it.- Publish that MSI to SAO's installer-source registry so SAO can include it in fresh agent bundles. (SAO owns the distribution surface — there is no host-side MSI staging.)
- In SAO, re-issue the agent bundle for the target agent. The ZIP contains
config.json, the entity JWT, andInstall-OrionII.cmd. - The operator re-runs
Install-OrionII.cmd. It writes%APPDATA%\OrionII\config.json, runs the MSI, and launches OrionII. On boot OrionII re-reads the bundle anchor and re-fetchesGET /api/orion/birth, so SAO-side policy/model/personality changes apply automatically.
When you finish a change in this repo, your end-of-turn summary MUST tell the user which of these short-circuits applies, in plain language:
- OrionII source code change (Rust in
src-tauri/, TypeScript insrc/, packaging inscripts/, deps inCargo.toml/package.json) → "Requires a new MSI build, SAO bundle re-issue, andInstall-OrionII.cmdre-run on the operator's machine. The pasted-config short-circuit will not pick up this change." - Bundle/policy/model surface change only (the change lands in SAO's birth response — provider, model, policy rules, personality) → "No OrionII rebuild needed. Re-issue the bundle from SAO; operators can either re-run
Install-OrionII.cmdor paste the newconfig.jsoninto the cockpit's Enrollment notice and click Apply (which callsapply_bundle_configand hot-swaps theOrionCore)." - Local-dev iteration only (
npm run tauri devagainstSAO_BASE_URL+SAO_DEV_BEARER_TOKEN) → "Dev path only. Production operators still need the MSI + bundle round-trip above."
Never imply changes "just work" after a recompile. The bus-routed entity model means soul_ref, identity, JWT, model selection, and bus_transport are all bundle-bound — they do not refresh on a hot file watch. If a change touches any of those, prefer the bundle path explicitly.
Run through this checklist. If any answer is "yes, this is a participant," do not add a command — add a subscriber.
- Is this a thin adapter, or does it contain logic? Tauri command bodies should be ≤ 5 lines: validate input, publish to a topic, return. Logic belongs in subscribers (
orion/{id,ego,superego_local,egress}.rsand friends), not command bodies. - Does this need to wait for an entity-internal response? If yes, do not block in the command. Publish, return a correlation id, and have the UI listen for the response on a Tauri event emitted by a UI-facing subscriber (see
service.rs::spawn_ui_emitterfor the pattern). - What's the
soul_refsource? If your command publishes anEnvelope, it must callcurrent_soul_ref(&charter)against theSharedChartercell onOrionCore(read-locked; the call is fast). - Should this be a topic, not a command? If two participants need to coordinate, that's a topic, not a command. Commands are for human → entity ingress only.
OllamaModelProvider and SaoProxyProvider use async reqwest::Client, and
the bus round-trip integration test in service.rs is active. If you change
the model layer again, check that:
ModelProvidertrait callers (Id and Ego subscribers) move to.awaitcorrectly.- No
block_in_placecalls reappear inid.rs/ego.rs. - The
mentor_input_round_trips_to_ego_actionintegration test still passes.
That file is the one ethical seam between the entity and SAO. Any change should preserve:
- All outbound SAO traffic flows through
sanitize(). sanitize()runs beforeenqueue_sao/ship_sao_egressis called.- No other module calls
SaoShipper::ship_pendingdirectly. (The user-triggered Tauri commandship_sao_egresscallscore.ship_sao_egress()which goes throughpersistence.ship_sao_egress— same path.)
OrionII supports durable and non-durable transports behind the
EventBus trait. The choice lives in config.json → bus_transport
(see ADR-003):
in_memory— tokio broadcast, no durability. Default.nats_jetstream— product durable path. Local nats-server sidecar managed bynats_supervisor; JetStream stores envelopes on disk.external_nats_jetstream— connect to an externally managed NATS node.bundled_iggy— experimental local iggy-server sidecar managed byiggy_supervisor.external_iggy— experimental externally managed Iggy node.
When you change anything bus-related, check that:
- No subscriber call site changed shape —
bus.subscribe(t)/rx.recv().awaitis identical across transports. - Any new failure mode falls back to
InMemoryBuswith a log, not a panic. The entity stays alive even when the broker doesn't. - The relevant supervisor still spawns its child with
kill_on_drop(true)so a crashed OrionII never leaks the broker. - Release packaging still goes through
scripts/build-installer.ps1, which prepares the sidecar and enables TauriexternalBinfor the MSI build. Product Windows packaging usesnats-server.exevia the official release ZIP orORIONII_NATS_SERVER; Iggy packaging remains optional through-BusSidecar iggy. Do not putexternalBinback into the defaulttauri.conf.json; that breaks clean-machinecargo check. -
rotate_iggy_tokenis the only entry point that writes the PAT store. The PAT store is{config_dir}/OrionII/iggy_patwith mode 600 on Unix; do not store it elsewhere.
Async-fn commands hold the tokio::sync::Mutex<OrionCore> guard
across .await. That's deliberate (Send-safe across worker threads),
but it means commands serialize on each other. If you find yourself
adding a command that takes a long time, check whether it should
publish to a topic and let a subscriber handle it instead.
The canonical 8 (do not add without an ADR):
| Variant | String | Direction |
|---|---|---|
Topic::MentorInput |
mentor.input |
UI → entity |
Topic::IdStimulus |
id.stimulus |
external sensors → entity |
Topic::IdReaction |
id.reaction |
Id → Ego |
Topic::EgoDeliberation |
ego.deliberation |
Ego → audit |
Topic::EgoAction |
ego.action |
Ego → UI / Superego / egress |
Topic::SuperegoLocalEvaluation |
superego.local.evaluation |
local Superego → audit |
Topic::EgressOutbound |
egress.outbound |
entity → SAO seam |
Topic::GovernanceInbound |
governance.inbound |
SAO → entity (policy, evaluations) |
GovernanceInbound envelopes carry a kind discriminator on payload.kind:
kind |
Payload shape | Subscriber action |
|---|---|---|
policy.refresh |
{ "policy": <PolicyOverlay> } |
governance.rs calls persistence.apply_sao_refresh(Vec::new(), policy) under the lock |
The apply_sao_policy_refresh Tauri command fetches the policy (or synthesises a local-fallback) and publishes a policy.refresh envelope. The command body MUST NOT call persistence.apply_sao_refresh directly — that bypasses the bus and is the kind of drift that confuses agents working downstream.
Resurrected interrupt and agent-task topics (dropped during the bus consolidation) come back as ADR-002+ when those features re-land — never as raw strings.