@zerwiz/ymir 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/.agents/agents/huginn-researcher.md +1 -1
  2. package/.agents/agents/kvasir-scout.md +1 -1
  3. package/.agents/agents/sindri-developer.md +1 -1
  4. package/.agents/backend/README.md +74 -0
  5. package/.agents/backend/model-bridge.py +74 -4
  6. package/.agents/config/cron.yaml +2 -0
  7. package/.agents/config/eindri-dispatch.json +6 -6
  8. package/.agents/migrations/0003-private-data-separation.sh +11 -1
  9. package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
  10. package/.agents/skills/README.md +1 -1
  11. package/.agents/skills/galdr-ymirsystem/SKILL.md +2 -1
  12. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +73 -10
  13. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +44 -1
  14. package/.agents/skills/galdr-ymirsystem/assets/installation.md +215 -23
  15. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +3 -1
  16. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +39 -1
  17. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +4 -0
  18. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +7 -2
  19. package/.agents/skills/galdr-ymirsystem/assets/registry.md +1 -1
  20. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +2 -2
  21. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +16 -11
  22. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +147 -4
  23. package/.agents/skills/groa-update/SKILL.md +20 -0
  24. package/.agents/skills/hamr-adapters/SKILL.md +2 -2
  25. package/.agents/skills/lifecycle/smoke_test.sh +7 -2
  26. package/.agents/skills/ymir-host/SKILL.md +2 -1
  27. package/.agents/skills/ymir-host/assets/install.md +69 -0
  28. package/AGENTS.md +78 -34
  29. package/CHANGELOG.md +1033 -1
  30. package/README.md +1 -1
  31. package/RULES/02-agents.md +2 -2
  32. package/RULES/04-hoard.md +32 -0
  33. package/RULES/06-append-only.md +50 -0
  34. package/RULES/07-config.md +56 -0
  35. package/RULES/09-electron.md +58 -0
  36. package/RULES/README.md +2 -0
  37. package/bin/a2a-mcp.sh +42 -7
  38. package/bin/agents-config.sh +30 -7
  39. package/bin/apodex-smoke-test.sh +14 -1
  40. package/bin/bifrost-bridge.sh +25 -6
  41. package/bin/bootstrap-macos.sh +6 -1
  42. package/bin/brokk-send.sh +14 -1
  43. package/bin/changelog-assemble.sh +154 -0
  44. package/bin/changelog-guard.sh +39 -7
  45. package/bin/crash-sense.sh +14 -1
  46. package/bin/design-check.sh +80 -0
  47. package/bin/design-icon.sh +153 -0
  48. package/bin/docs-guard.sh +2 -2
  49. package/bin/editor-place.sh +33 -15
  50. package/bin/eindri-acclaim.sh +14 -1
  51. package/bin/eindri-seat.sh +83 -0
  52. package/bin/eindri-seen.sh +14 -1
  53. package/bin/eindri-start.sh +14 -1
  54. package/bin/eir-doctor.sh +47 -1
  55. package/bin/feature-inventory.sh +73 -0
  56. package/bin/fleet-apply.sh +15 -2
  57. package/bin/github-deploy.sh +14 -1
  58. package/bin/gjallarhorn-expose.sh +43 -9
  59. package/bin/gjallarhorn-notify.sh +14 -1
  60. package/bin/gjallarhorn-purge.sh +14 -1
  61. package/bin/gjallarhorn-tunnel.sh +16 -3
  62. package/bin/groa-update.sh +9 -1
  63. package/bin/hall-snapshot.sh +21 -2
  64. package/bin/herdr-run.sh +23 -2
  65. package/bin/hlidskjalf-agents.sh +172 -0
  66. package/bin/hlidskjalf-usage.sh +196 -0
  67. package/bin/hoard-guard.sh +135 -0
  68. package/bin/hoard-lib.sh +91 -11
  69. package/bin/hodd.sh +28 -4
  70. package/bin/huginn-research-worker.sh +14 -1
  71. package/bin/local-model-lock.sh +22 -3
  72. package/bin/mimir-bridge.py +33 -0
  73. package/bin/mimir-bridge.sh +14 -1
  74. package/bin/mimir-reflect.sh +27 -0
  75. package/bin/mjollnir-webhook.sh +14 -1
  76. package/bin/mjollnir.sh +22 -3
  77. package/bin/model-hardware.sh +17 -3
  78. package/bin/model-resolve.sh +14 -1
  79. package/bin/nornir-job-hall-snapshot.sh +28 -0
  80. package/bin/nornir-job-nsr-compliance.sh +54 -0
  81. package/bin/npm-publish.sh +136 -0
  82. package/bin/omarchy-install.sh +18 -5
  83. package/bin/omarchy-plugins.sh +15 -2
  84. package/bin/omarchy-sense.sh +14 -1
  85. package/bin/pi-model-agnostic.sh +71 -0
  86. package/bin/private-guard.sh +20 -0
  87. package/bin/project-git.sh +18 -4
  88. package/bin/public-guard.sh +3 -2
  89. package/bin/sessrumnir-ensure.sh +1 -1
  90. package/bin/sessrumnir.sh +16 -3
  91. package/bin/smidja-bootstrap.sh +5 -1
  92. package/bin/syn-asset-pretool-check.sh +14 -1
  93. package/bin/syn-spawn-pretool-check.sh +58 -0
  94. package/bin/tailscale-sync.sh +15 -2
  95. package/bin/telegram-bot.sh +14 -1
  96. package/bin/ui-truth-check.sh +75 -0
  97. package/bin/valknut-load.sh +42 -4
  98. package/bin/wedge-notify.sh +14 -1
  99. package/bin/workspace-rag.sh +14 -1
  100. package/bin/ymir-install.sh +219 -54
  101. package/bin/ymir-plan.sh +356 -0
  102. package/bin/ymir-say.sh +14 -1
  103. package/bin/ymir-setup-auth.sh +12 -1
  104. package/bin/ymir-validate.sh +28 -8
  105. package/docs/lore.md +5 -1
  106. package/docs/runbooks/agents.md +1 -1
  107. package/package.json +13 -3
  108. package/scripts/electron.sh +74 -15
  109. package/scripts/lower.sh +34 -0
  110. package/scripts/raise.sh +42 -0
  111. package/scripts/start.sh +52 -2
  112. package/.agents/config/agents.yaml +0 -50
  113. package/.agents/config/tailscale-sync.yaml +0 -27
  114. package/.agents/memory/kaia.engram +0 -0
  115. package/.agents/memory/kaia.engram-shm +0 -0
  116. package/.agents/memory/kaia.engram-wal +0 -0
  117. package/.agents/memory/well/episodes.jsonl +0 -367
  118. package/.agents/memory/well/workspace.jsonl +0 -27
@@ -28,6 +28,9 @@ Quick rules:
28
28
  ## Location & stack
29
29
 
30
30
  - App: `apps/hlidskjalf` — **React 19 + Vite + TypeScript**, state via **Zustand**.
31
+ - The app lives in **its own repo** (`zerwiz/hlidskjalf`, registered in the home
32
+ registry) — the monorepo never tracks it; `bin/ymir-install.sh`'s `apps` step
33
+ clones it into `apps/hlidskjalf` at install, or fast-forwards a present clone.
31
34
  - Gate API: `apps/hlidskjalf/server/index.ts` — **Bun + `bun:sqlite`**, read-only,
32
35
  bound to the runtime and the smithy trace; Vite proxies `/api` → `:3889`.
33
36
  - Raise everything with the scripts (do not hand-start each process):
@@ -95,6 +98,13 @@ Rules that hold it honest:
95
98
 
96
99
  - `.shell` grid: `236px rail | 1fr`; rows `56px topbar · stage · stream`
97
100
  (the stream height is draggable, persisted in `streamHeight`).
101
+ - The **Fleet graph** (`gates/Fleet.tsx`) lays the roster on a 100×100 canvas:
102
+ Brokk as the hub, everyone else in a square-ish grid fan (`cols =
103
+ ceil(sqrt(n))`, pitch ≥ one ring + label) so 20 agents never collide; rings
104
+ carry the house glyph from `DOMAINS[agent.domain]` with a `ymirlabs`
105
+ fallback, and the live cards come from `bin/hlidskjalf-agents.sh` — which
106
+ reads each figure's `domain:` frontmatter (never a hardcoded `ymirlabs` for
107
+ everyone).
98
108
  - `.rail` (brand · gates · tenants · status), `.topbar` (realm chip · search ·
99
109
  accent · density · trace index · account), `main.stage` (the gate), `.stream`
100
110
  (Ratatoskr + Runes, pausable).
@@ -156,6 +166,18 @@ router `:8080`, Bifrost `:4603`), never only the first — a dead engine falls
156
166
  through to a live one. Recall reads `.agents/memory/well/episodes.jsonl`
157
167
  directly, so restoring that file revives the chat's memory without a restart.
158
168
 
169
+ ### The EmberBackground hearth (the shared fire)
170
+
171
+ The animated glow behind the shell, the login, and Sessrúmnir's chat comes from
172
+ **one** hearth, not four copies: `midgard/design-system/ember.js` (`startEmbers`),
173
+ with Sessrúmnir's `ember-background.tsx` as its React port. Both draw the same
174
+ embers + haze, respect reduced-motion, and — since the Sep 17 mend — re-seed
175
+ via **ResizeObserver on the container**, not just `window.resize`: a split pane
176
+ or toggled sidebar changes the chat column's size without a window resize, and a
177
+ canvas that sized itself once at a stale dimension never shows the fire. The
178
+ shared module watches the host; the port watches its parent; re-seeding fires
179
+ only when the measured size actually changed (never per-frame).
180
+
159
181
  ### The gate API reads cheaply (added 2026-09-16)
160
182
 
161
183
  The gate reads append-only ledgers (Runes, the well's `episodes.jsonl`, masterplan
@@ -201,7 +223,10 @@ API on the same port (`scripts/start.sh` raises it). Details: `assets/smidja.md`
201
223
  ## Agents, skills & mythological naming
202
224
 
203
225
  - The Forge gate (`src/gates/Forge.tsx`) creates/edits **Eindri** and **skills**,
204
- and reads/edits the smithy's prompts.
226
+ and reads/edits the smithy's prompts. The skill list shows each skill's own
227
+ text: the name + aett rune on the head row, and the description line
228
+ underneath (`forge-item-desc`, dimmed until hover) — the description comes
229
+ from the live `/api/skills` index, truncated to 240 chars by the gate API.
205
230
  - Naming law: `src/data/mythology.ts` maps a craft/capability → the Norse figure
206
231
  whose myth matches it (smith→Sindri, skald→Bragi, sage→Huginn, judge→Tyr,
207
232
  forger→Brokk …). Skills take an **aett** prefix.
@@ -253,6 +278,17 @@ npm run build # must be green
253
278
  - When the shell, tokens, gates, or the gate API change, update this asset and
254
279
  `docs/design.md` together.
255
280
 
281
+ ### The agent path the Fleet gate reads (2026-09-17)
282
+
283
+ `server/index.ts` decides whether a figure is *registered* by testing for its
284
+ profile in the OpenCode agent directory. That directory is **`.opencode/agents/`
285
+ — PLURAL**, which is what OpenCode actually loads. The singular
286
+ `.opencode/agent/` was never read by the harness, so twenty correct symlinks sat
287
+ in a directory no loader opened and the gate reported figures unregistered while
288
+ the tree held them all. Any code that resolves an agent profile must use the
289
+ plural path; the loader (`bin/valknut-load.sh`) migrates a legacy singular dir
290
+ forward and binds the plural one.
291
+
256
292
  ## Domains, not houses (Rule 01/03)
257
293
 
258
294
  The eight Labs are **domains (Greinar)** — the knowledge axes — not houses. A
@@ -292,6 +328,13 @@ the password comes from `HLIDSKJALF_AUTH` in `.env.local`, never inline. A
292
328
  hardcoded name here is both a leak into the public tree and wrong for any other
293
329
  operator — `bin/public-guard.sh` exists to catch exactly that class of mistake.
294
330
 
331
+ **The gate must actually receive it.** `scripts/start.sh` loads `.env.local`
332
+ (mode `0600`, gitignored) into the environment before raising the gate, because
333
+ the bun process reads `process.env.HLIDSKJALF_AUTH` and nothing else was loading
334
+ it — so a credential set by `bin/ymir-setup-auth.sh` never took effect, and an
335
+ empty `GATE_AUTH` makes the gate treat every request as authenticated. After
336
+ setting a credential, restart the gate: `scripts/stop.sh; scripts/start.sh`.
337
+
295
338
  ### One login, and it is the one with the lore (2026-09-12)
296
339
 
297
340
  There used to be **two** ways in, and the second was a mistake:
@@ -5,12 +5,13 @@
5
5
  > page is the map.
6
6
 
7
7
  One command sets up the whole system for the user; it self-heals what it can and
8
- reports what it cannot. It **asks for consent first** (accept the plan, or pass
9
- `--yes` for non-interactive use), and it **validates at the end** that what it
8
+ reports what it cannot. It **asks for consent first** with a plan it computes on
9
+ this host, not a recited paragraph — and it **validates at the end** that what it
10
10
  claims is actually running.
11
11
 
12
12
  ```
13
13
  bin/ymir-install.sh # the first setup (idempotent; asks to proceed)
14
+ bin/ymir-install.sh --plan # the plan, probed — changes nothing (--json too)
14
15
  bin/ymir-install.sh --check # report only, no writes, no prompt
15
16
  bin/ymir-install.sh --yes # non-interactive (accept the plan)
16
17
  bin/ymir-install.sh --skip-engines --skip-services
@@ -18,25 +19,198 @@ bin/ymir-install.sh --no-desktop # don't open the desktop apps at the end
18
19
  bin/ymir-install.sh --status # alias of --check
19
20
  ```
20
21
 
22
+ ## The plan comes first — `bin/ymir-plan.sh`
23
+
24
+ The consent a real install asks for is a **computed plan**, one row per step, each
25
+ carrying its state and the reason for it. A hardcoded paragraph cannot know the
26
+ host: the old one named an Omarchy version on a Mac, promised a workspace tree that
27
+ already stood, and never mentioned that no application had been installed at all.
28
+
29
+ ```
30
+ bin/ymir-plan.sh # the plan (TOON)
31
+ bin/ymir-plan.sh --json # the same, for automation
32
+ bin/ymir-plan.sh --phase 5 # one phase
33
+ bin/ymir-plan.sh --blocked # only what cannot proceed, and why
34
+ ```
35
+
36
+ ```
37
+ plan_states[5]{state,means}:
38
+ "DO","a change will be made"
39
+ "SKIP","already satisfied — nothing to do"
40
+ "INFO","a fact about this host, discovered; no change implied"
41
+ "BLOCKED","cannot run — the reason names what is missing"
42
+ "CONSENT","needs the operator's word (a credential, an invite, the shells)"
43
+ ```
44
+
45
+ ```
46
+ plan_phases[9]{n,name,gate}:
47
+ "0","resolve","this host, the container engine, the code root, and the home"
48
+ "1","code","the tree is intact — and holds nothing of the operator's"
49
+ "2","home","the operator's world, outside the tree"
50
+ "3","runtimes","git/python3 · bun/uv/mcp<2> · pi · hermes · the terminal backend"
51
+ "4","engines","treehouse · no-mistakes · sandcastle · the Utgard image"
52
+ "5","apps","hlidskjalf · odrerir · sessrumnir · smidja — web build required, shell gated"
53
+ "6","wire","the way in (auth · invite) and the launcher entries"
54
+ "7","raise","every port listening, then the desktop apps"
55
+ "8","verify","what stands, honestly"
56
+ ```
57
+
58
+ `ymir-install.sh` prints the plan at the consent prompt; `--plan`/`--dry-run` prints
59
+ it and exits without writing. The plan is recomputed on every run, so it cannot
60
+ drift behind the code the way a paragraph did. `bin/ymir-plan.sh` is the ward for
61
+ the law below: its phase-1 `purity` row names any of the operator's things found in
62
+ the code tree.
63
+
64
+ ## Where things live — the package is code, the home is the operator's
65
+
66
+ **Law (Rule 04):** the package carries the **core systems** — everything needed to
67
+ run the programs. Everything the **operator** owns goes to `$YMIR_HOME`: their
68
+ info, their records, their documents, their settings, their state, their
69
+ credentials. A packaged install (npm) treats its tree as read-only; the next
70
+ upgrade replaces it, so anything of theirs kept there is kept at its peril.
71
+
72
+ ```
73
+ roots[5]{root,resolves_from,holds}:
74
+ "ymir_home_root","$YMIR_HOME → the choice recorded at installation → one documented default","everything private the operator owns"
75
+ "hoard_root","$YMIR_HOARD → <home>/hodd","docs · secrets · identity · tenants · memory"
76
+ "hoard_data_dir","$YMIR_DATA_DIR → <hoard>/data","this machine's records: operator, fleet, machines, the host profile"
77
+ "hoard_state_dir","$YMIR_STATE_DIR → <home>/state","runtime state: pids, logs, locks, caches"
78
+ "hoard_settings_dir","$YMIR_SETTINGS_DIR → <home>/config","settings: agents.yaml, cron.yaml, tailscale-sync, the wedge channel"
79
+ ```
80
+
81
+ Plus `hoard_local_env` → `$YMIR_HOME/.env.local`: the operator's **credentials**
82
+ (`HLIDSKJALF_AUTH`, OAuth keys, tokens), never in the tree.
83
+
84
+ **The home is chosen, not assumed.** A real interactive install asks once
85
+ (`step_home`), records the answer as machine state under `~/.config/ymir/home`
86
+ (the same place `engram-python` and `accounts.json` live), and every later script
87
+ resolves it through `bin/hoard-lib.sh`. `--check` never writes; `--yes` takes what
88
+ is recorded, else the documented default.
89
+
90
+ ```bash
91
+ bin/hoard-lib.sh # the lib (source-safe; functions only)
92
+ ymir_home_root HOME # → the home
93
+ hoard_settings_dir SETTINGS # → <home>/config
94
+ hoard_local_env ENVFILE # → <home>/.env.local
95
+ ```
96
+
97
+ **Rule for new code:** never write `$ROOT/data`, `$ROOT/state`, `$ROOT/config`,
98
+ `$ROOT/.env.local` or `$ROOT/workspace`. Resolve the root through the lib. A script
99
+ that points an operator's thing into the tree is the bug the `purity` row exists to
100
+ catch.
101
+
102
+ ### Carrying an existing tree home — migration 0005
103
+
104
+ Every *writer* resolves the home now, but an installation made before this change
105
+ still holds the operator's things in the tree. `.agents/migrations/0005-roots-out-of-tree.sh`
106
+ carries them:
107
+
108
+ ```
109
+ what moves from to
110
+ ------------------ --------------------- ----------------------------
111
+ this machine's data/ <hoard>/data
112
+ records
113
+ runtime state state/ <home>/state
114
+ (pids · logs · (including the (running services keep their open
115
+ locks · the applied-migrations fds across the move — `mv` keeps
116
+ applied-marker) marker) the inode)
117
+ credentials the local env file $YMIR_HOME/.env.local (0600)
118
+ settings .agents/config/* <home>/config
119
+ that git does NOT track
120
+ ```
121
+
122
+ One name, two files — the migration decides by evidence, never by assumption:
123
+
124
+ ```
125
+ collision[3]{case,what_happens,nothing_lost}:
126
+ "identical content","the tree's copy is removed","the content is provably at home already"
127
+ "different content","the home's keeps the name; the tree's is carried beside it as <name>.stale-<UTC>","both are real, so both are kept — never merged, never discarded"
128
+ "not a plain file","left in place and reported","a directory is not silently swallowed"
129
+ ```
130
+
131
+ Templates and defaults never move: `*.example` (and `*.example.*`), `.gitkeep`, and
132
+ any settings file **git tracks** — that is the distro's shipped default, not the
133
+ operator's. The plan's `purity` row applies the same rule, asking git about the
134
+ real path (the tree's `config` is a symlink into `.agents/config`, and git tracks
135
+ what the index holds, not the link). Idempotent: a second run changes nothing.
136
+
137
+ ```bash
138
+ bin/ymir-migrate.sh status # pending / applied
139
+ bin/ymir-migrate.sh apply --dry-run # name the migration, touch nothing
140
+ bin/ymir-migrate.sh apply # carry it
141
+ ```
142
+
143
+ Stop the runtime first if you want no stale pid files; nothing is lost either way.
144
+
145
+ ## The app packages — how the four surfaces arrive
146
+
147
+ The distro depends on the surfaces as their own npm packages, so one
148
+ `npm install -g @zerwiz/ymir` fetches them into the tree:
149
+
150
+ ```
151
+ app_packages[4]{package,repo,what}:
152
+ "@zerwiz/hlidskjalf","zerwiz/hlidskjalf","the control plane — dist/ ships built, served on :3888"
153
+ "@zerwiz/odrerir","zerwiz/odrerir","the live hall — dist/ ships built"
154
+ "@zerwiz/sessrumnir","zerwiz/sessrumnir","the seat-hall desktop — out/ ships built (a fork of pi-desktop)"
155
+ "@zerwiz/smidja-factory","zerwiz/smidja","the smithy and its visualizer — the factory plus the UI's source, built at install"
156
+ ```
157
+
158
+ They are **optionalDependencies**, deliberately: a broken app package must never
159
+ stop the CORE from installing, and a package not yet on the registry is skipped by
160
+ npm and starts arriving the moment it is published. The plan's phase-5 rows report
161
+ each surface separately — installed, declared-but-not-fetched, or no package at all
162
+ — so a silent skip cannot hide.
163
+
164
+ A global install nests them under the distro's own `node_modules`
165
+ (`<prefix>/lib/node_modules/@zerwiz/ymir/node_modules/@zerwiz/<app>`); a local one
166
+ hoists them to `node_modules/@zerwiz/<app>`. The plan checks both shapes.
167
+
168
+ ```
169
+ bin/npm-publish.sh # the platform only
170
+ bin/npm-publish.sh --all # the platform + every app package
171
+ bin/npm-publish.sh --dry-run --all # what would go out, and from where
172
+ bin/npm-publish.sh --unpublish @zerwiz/ymir@0.1.5 # take ONE version back
173
+ ```
174
+
175
+ **A version can be taken back, and the window is short.** npm permits unpublishing
176
+ **one version for 72 hours** after it was published; past that it is npm support's
177
+ door. So `--unpublish` demands a spec that names the version — never a bare name —
178
+ and the row says where the CDN may still serve the tarball for a while afterwards.
179
+ Publishing a newer version is the other half of the repair: it moves the `latest`
180
+ tag off the bad build at once, even before the removal propagates.
181
+
182
+ **A `files[]` entry that names a directory overrides `.gitignore`.** This is how
183
+ `@zerwiz/ymir@0.1.5` shipped the memory well: `files: [".agents/"]` packed that
184
+ subtree *including* the gitignored stores, and the repo stayed clean while the
185
+ artefact did not. Name what ships, exclude what must not, and prove it with
186
+ `npm pack --dry-run` — the repo's cleanliness says nothing about the tarball.
187
+
188
+ The app repos are cloned into `apps/` by the install's `apps` step (from the
189
+ registry's `repo: apps/<path>` blocks), which is where `--all` reads their
190
+ manifests. The token comes from the hoard, never from `~/.npmrc`.
191
+
21
192
  ## The steps
22
193
 
23
194
  ```
24
- install[20]{step,what,self-heals}:
195
+ install[23]{step,what,self-heals}:
25
196
  "panes","the run shown in a herdr pane","bin/herdr-run.sh sits a pane beside the caller when inside herdr; inline otherwise — a pane that cannot be raised never loses the work"
26
197
  "prereqs","git python3 bun docker|podman gh · mcp<2","bin/prereq-ensure.sh installs bun+uv+mcp in user space; engram is an honest optional SKIP"
27
198
  "memory-well","the engram engine (Mimirsbrunn)","optional; reported with the exact next command, never a fake fix"
28
- "tree","workspace/{work,personal}/<domains>, companies/, workspaces.yaml, projects.yaml, and the hoard OUTSIDE the repo (secrets/ · docs/ · identity/ · tenants/ at $YMIR_HOARD, else $YMIR_HOME)","creates if missing; hoard_root resolves through bin/hoard-lib.sh so no script can point the hoard inside the checkout (Rule 04), and an empty secrets/platform.env (0600) is seeded so bin/hodd.sh emit resolves"
199
+ "home","the home the operator CHOOSES, recorded under ~/.config/ymir/home","asks once, records the answer; --check never writes, --yes takes what is recorded, else the documented default"
200
+ "tree","workspace/{work,personal}/<domains>, companies/, workspaces.yaml, projects.yaml, and the hoard OUTSIDE the repo (secrets/ · docs/ · identity/ · tenants/ at the hoard root under the chosen home)","creates if missing; hoard_root resolves through bin/hoard-lib.sh so no script can point the hoard inside the checkout (Rule 04), and an empty secrets/platform.env (0600) is seeded so bin/hodd.sh emit resolves"
201
+ "apps","the app repos (the app split) — hlidskjalf · hlidskjalf-mobile · odrerir · sessrumnir · smidja","reads $HOARD/identity/projects.yaml (never guesses a remote); clones a missing apps/<path> from its registered git{} block, fast-forwards a present one, and stamps the smithy engine (apps/smidja) from the cloned factory's templates"
29
202
  "engines","treehouse · sandcastle · no-mistakes","installs treehouse + no-mistakes from their installers"
30
203
  "hermes","the Nous Research agent runtime","installs via bin/hermes-ensure.sh when absent"
31
- "sessrumnir","the Sessrúmnir desktop GUI (vendored pi-desktop at apps/sessrumnir)","bin/sessrumnir-ensure.sh installs deps + builds on first run (deps are never committed); launch via bin/sessrumnir.sh"
204
+ "sessrumnir","the Sessrúmnir desktop GUI (its own repo; lands via the `apps` step at apps/sessrumnir)","bin/sessrumnir-ensure.sh installs deps + builds on first run (deps are never committed); launch via bin/sessrumnir.sh"
32
205
  "backend","Þjazi — herdr (protocol 14+) or tmux","bin/herdr-ensure.sh detects/tests version, installs via the pinned installer or falls back to tmux"
33
- "host","this machine — learnt on EVERY host","bin/omarchy-sense.sh learns the setup, bin/desktop-place.sh places the apps, the Omarchy post-update hook (when Omarchy), the wedge-alarm channel, and (on Omarchy) an OFFER of the suggested shell plugins — listed, never installed unbidden; seeds the private config/agents.yaml from its example"
206
+ "host","this machine — sensed on EVERY host","bin/host-sense.sh senses the setup on ANY host (Rule 05); the Omarchy layer then RECORDS it (bin/omarchy-sense.sh observe), places the apps (bin/desktop-place.sh), installs the post-update hook and the wedge-alarm channel, and (on Omarchy) offers the suggested shell plugins — listed, never installed unbidden; seeds the private config/agents.yaml from its example"
34
207
  "sandbox","utgard-runner:latest image","builds via bin/utgard.sh build on Docker or rootless Podman; distinguishes an unreachable engine from a build failure"
35
208
  "memory","engram store + harness MCP registrations","raises the bridge; reports MCP coverage"
36
209
  "smidja","smidja/smidja_data/smidja.db","bin/smidja-bootstrap.sh creates it from the tracer schema + a bootstrap session"
37
210
  "visualizer","the Smíðja visualizer UI (Vue, served on :8437)","builds ./dist with bun when absent — the API serves the UI from dist, and without it the API answers but shows no interface"
38
211
  "loaders","agents/skills into the harnesses","runs bin/valknut-load.sh"
39
212
  "gates","the git delivery gates — secret-guard (pre-commit), branch-guard + changelog-guard (pre-push)","bin/secret-guard.sh --install and bin/changelog-guard.sh --install seat the versioned guards into .git/hooks, so the gate is live from the first commit of a fresh clone; idempotent"
213
+ "marks","each app's rune icon + .desktop entry into the operator's own desktop, and the Ymir contract into pi's agent home","bin/design-icon.sh mint --all + install writes to $HOME/.local/share (never a session sandbox), so every app is dockable and pinnable; the contract symlink means every pi session, in ANY folder, loads Brokk"
40
214
  "invite","the way in for anyone else — an invite code","bin/ymir-invite.sh ensure mints one only when nothing is live, so the step is idempotent; the code is printed at the end of the run and again in workspace/INSTALL.md"
41
215
  "register","workspace/INSTALL.md","writes the record"
42
216
  "services","gate API, SPA, Nornir, bridges, visualizer","raises via scripts/start.sh (which builds the visualizer UI when ./dist is absent)"
@@ -44,7 +218,7 @@ install[20]{step,what,self-heals}:
44
218
  "validate","the running system","bin/ymir-validate.sh — live port/store/process checks"
45
219
  ```
46
220
 
47
- **22** `step_*` functions are defined. A step is not a row: one step may emit
221
+ **25** `step_*` functions are defined (`home` asks, `tree` builds). A step is not a row: one step may emit
48
222
  several. `prereqs` also emits `memory-well`, `host` also emits `agents-config`,
49
223
  `smidja` also emits `visualizer`, and `spa` also emits `hlidskjalf`. `--check`
50
224
  skips the runtime-only steps (`services`, `desktop`, `validate`), which have
@@ -94,8 +268,10 @@ now build it when absent:
94
268
  (cd .agents/skills/smidja-factory/apps/visualizer && bun run build) # vue-tsc + vite
95
269
  ```
96
270
 
97
- `bin/ymir-validate.sh` reports `visualizer` FAIL when `./dist` is missing, so the
98
- gap cannot silently return.
271
+ `bin/ymir-validate.sh` reports `visualizer` FAIL when `./dist` is missing **and**
272
+ when the build exists but nothing is listening on `:8437`. A PASS means the UI is
273
+ built *and* the API is up — so a built-but-dead visualizer (a bad `CMD_DB`, a
274
+ crashed API) can no longer read as green.
99
275
 
100
276
  ## Desktop placement (Omarchy desktops, not monitors)
101
277
 
@@ -163,13 +339,20 @@ non-interactive callers; without it a non-interactive `add` refuses with exit 3)
163
339
  The installer only *offers*.
164
340
 
165
341
  ## Consent
166
- A real install prints its plan and waits for `[y/N]`. Declining changes nothing
167
- (exit 3). `--check` never prompts. A non-interactive caller without `--yes` is
168
- refused rather than silently proceeding.
342
+ A real install prints **the plan it computed** (`bin/ymir-plan.sh`) and waits for
343
+ `[y/N]`. Declining changes nothing (exit 3). `--check` and `--plan` never prompt.
344
+ A non-interactive caller without `--yes` is refused rather than silently
345
+ proceeding.
346
+
347
+ The plan names every step with its state and the reason for it — including the
348
+ home the operator is asked to choose, the four app surfaces and whether each can
349
+ be installed at all, the terminal backend, and what will be skipped and why. The
350
+ state vocabulary is `DO · SKIP · INFO · BLOCKED · CONSENT`.
169
351
 
170
- The plan names every change, including the terminal backend (herdr/tmux), the
171
- host learning, and the desktop placement so the operator accepts what is
172
- actually done, not a shorter list that drifted behind the code.
352
+ `--check` writes nothing and that includes the migrations. `bin/ymir-migrate.sh
353
+ apply` **moves private data**, so the step chain runs it only on a real run;
354
+ a preview leaves the home exactly as it found it. (It used to apply them even
355
+ under `--check`, which moved a home during a "report only" pass.)
173
356
 
174
357
  ## The operator's way in (auth) — `bin/ymir-setup-auth.sh`
175
358
 
@@ -225,7 +408,7 @@ part is off. `--json` for machine consumption.
225
408
  | **sandcastle** (`mattpocock/sandcastle`) | Utgard | Docker/Podman/Vercel sandboxes |
226
409
  | **no-mistakes** (`kunchenguid/no-mistakes`) | Mjollnir · Glitnir | clean-PR validation gate |
227
410
  | **Hermes** (`NousResearch/hermes-agent`, MIT) | — (product name) | worker agent runtime: own brain, memory, skills, subagents, sandbox backends |
228
- | **pi-desktop** (`FaqFirebase/pi-desktop`, Apache-2.0) | **Sessrúmnir** (vendored at `apps/sessrumnir`) | desktop GUI for the Pi/OMP coding agents — the seat-hall, themed with the Ymir way-of palette |
411
+ | **pi-desktop** (`FaqFirebase/pi-desktop`, Apache-2.0) | **Sessrúmnir** (cloned from its own repo to `apps/sessrumnir` at install) | desktop GUI for the Pi/OMP coding agents — the seat-hall, themed with the Ymir way-of palette |
229
412
 
230
413
  ## Missing dependencies
231
414
 
@@ -298,14 +481,16 @@ company is the operator's to name; Ymir ships no default.
298
481
  bin/ymir-install.sh --check # all steps OK/WARN
299
482
  bin/ymir-validate.sh # the running system actually works
300
483
  bin/herdr-ensure.sh status # the Þjazi backend and its protocol floor
301
- bin/omarchy-sense.sh status # what Ymir has learnt about this host
484
+ bin/host-sense.sh # sense THIS machine (any host)
485
+ bin/omarchy-sense.sh status # the Omarchy recording (Omarchy hosts)
302
486
  bin/saga-session-start.sh # the session digest
303
487
  bash .agents/skills/galdr-ymirsystem/scripts/compliance-check.sh
304
488
  ```
305
489
 
306
- On an **Omarchy** host the installer also learns the machine
307
- (`bin/omarchy-sense.sh`) and installs a `post-update` hook so Ymir re-learns it
308
- every time Omarchy updates. On a non-Omarchy host that step is a clean SKIP.
490
+ On **every** host the installer senses the machine with `bin/host-sense.sh`. On
491
+ an **Omarchy** host the layer also *records* it (`bin/omarchy-sense.sh observe`)
492
+ and installs a `post-update` hook so Ymir re-learns it every time Omarchy
493
+ updates. On a non-Omarchy host that layer step is a clean SKIP.
309
494
 
310
495
  Rule: the installer is **idempotent** — running it again changes nothing but
311
496
  fills gaps. It never overwrites real user data.
@@ -470,7 +655,7 @@ machines may run the runtime.
470
655
  | Layer | Runs on | What it owns |
471
656
  |---|---|---|
472
657
  | **Portable core** | Linux, macOS, Windows (WSL2; MSYS best-effort) | the runtime — session digest, lock, watch, cron, skills — the installer's user-space prerequisites, the capability shim, generated machine config |
473
- | **Omarchy layer** | Omarchy only | learning the host (`omarchy-sense`), placing each app on its own numbered Hyprland desktop (`desktop-place`), the launcher entries (`.desktop`), the suggested shell plugins, the post-update hook |
658
+ | **Omarchy layer** | Omarchy only | recording the host (`omarchy-sense observe`), placing each app on its own numbered Hyprland desktop (`desktop-place`), the launcher entries (`.desktop`), the suggested shell plugins, the post-update hook |
474
659
 
475
660
  The core never grows a Hyprland branch, and the Omarchy layer is never stretched
476
661
  into pretending it is portable. An Omarchy-specific step is gated on the host and
@@ -480,8 +665,15 @@ reports a clean skip anywhere else:
480
665
  [ -d /usr/share/omarchy ] && ... || add host OK "... (not an Omarchy host)"
481
666
  ```
482
667
 
483
- That is why `step_host` and `step_desktop` are the only steps that mention
484
- Omarchy: everywhere else, the same code path runs on any host.
668
+ That is why the only Omarchy branches left in the core installer are the ones
669
+ that call the layer (`step_omarchy`) and desktop placement (`step_desktop`):
670
+ `step_host` senses the host portably with `host-sense`, and everywhere else the
671
+ same code path runs on any host.
672
+
673
+ The **consent preamble** the installer prints before it acts says the same thing —
674
+ *"learn this machine (OS, desktop, packages, configs, monitors, scale — Omarchy
675
+ hosts recorded first-class)"* — so the operator is told what will happen in the
676
+ portable terms the code now uses, not the Omarchy-only terms it used before.
485
677
 
486
678
  ### What the Omarchy layer installs
487
679
 
@@ -154,7 +154,9 @@ hardcoded timeline is gone.
154
154
  - **First `observe` slow (~20–30 s)** → the embedding model is warming; later
155
155
  writes are instant.
156
156
  - **Bridge down / `COLD`** → `bin/mimir-bridge.sh --start`; check
157
- `state/mimir-bridge.log`.
157
+ `$YMIR_STATE_DIR/mimir-bridge.log` — runtime state lives in the home the
158
+ operator chose (`hoard_state_dir` via `bin/hoard-lib.sh`), never in the code
159
+ tree: a packaged install replaces its tree on upgrade.
158
160
 
159
161
  **Portability.** The bridge signals processes through `ymir_kill_matching` from
160
162
  `bin/ymir-platform.sh` rather than calling `pkill` directly, because `pkill` is
@@ -62,7 +62,7 @@ same day. That is deliberate: cron is idempotent-by-date, not retrying-by-failur
62
62
  |---|---|---|
63
63
  | `BROKK_ROOT_OVERRIDE` | script's parent | Root when `BROKK_HOME` unset. |
64
64
  | `BROKK_HOME` | `$ROOT` | Home owning the jobs and state. |
65
- | `BROKK_STATE_OVERRIDE` | `$BROKK_HOME/state` | Scheduler state directory. |
65
+ | `BROKK_STATE_OVERRIDE` | `$YMIR_STATE_DIR` (`<home>/state`, via `bin/hoard-lib.sh`) | Scheduler state directory — in the home the operator chose, never in the code tree. |
66
66
  | `BROKK_CONFIG_OVERRIDE` | `$BROKK_HOME/config` | Where `cron.yaml` lives. |
67
67
  | `BROKK_CRON_LOG_MAX_BYTES` | `1048576` | Rotation threshold. |
68
68
  | `BROKK_REALM` | `""` | Realm exported to jobs (jobs also fall back to `data/realm.md`). |
@@ -153,6 +153,44 @@ removed and the failure is reported plainly.
153
153
  | `BROKK_MEMORY_PRUNE` | `0` (disabled) |
154
154
  | `BROKK_MEMORY_PRUNE_DAYS` | `7` |
155
155
 
156
+ ### 3.5 Óðrerir — Live Hall snapshot (`bin/nornir-job-hall-snapshot.sh`, 08:00)
157
+
158
+ The Live Hall is a glass: it reads `apps/odrerir/public/livehall.json`
159
+ (same-origin, `cache: no-store`). This job writes that snapshot from real state
160
+ via `bin/hall-snapshot.sh` — runes, the project registry, the cron gauge, the
161
+ wake queue, standing smiths, armed `when-` sources, and the landed errands —
162
+ then carves Rune `odrerir / hall.snapshot`. Scheduled at 08:00 so it follows
163
+ the 06:00 observer and the 07:00 briefing: the morning board carries the day's
164
+ fresh runes.
165
+
166
+ | Reads | Writes |
167
+ |---|---|
168
+ | runes ledger · `hodd/identity/projects.yaml` · `config/cron.yaml` · `state/.wake-queue` · herdr agent list · armed when-sources · `state/eindri-reports/archive/` | `apps/odrerir/public/livehall.json` (gitignored runtime) · Rune `odrerir / hall.snapshot` |
169
+
170
+ - The snapshot is **runtime, never repo**: `apps/odrerir/public/livehall.json`
171
+ is gitignored, so the job never dirties a branch.
172
+ - Absent `livehall.json` on the Hall is *not* a build failure — the page paints
173
+ the saga's own count and says so. This job is what makes the board true.
174
+ - Idempotent by nature: each run rewrites the same snapshot from the same
175
+ inputs; the cron date-guard suppresses repeat dispatch within a day.
176
+
177
+ ### 3.6 Tyr — NSR compliance round (`bin/nornir-job-nsr-compliance.sh`, 02:30)
178
+
179
+ The NorthStar deterministic gate, run nightly in the quiet hours so sunrise
180
+ finds the doors mended or the Rune already says which broke. Runs every
181
+ `.compliance/gates/check_*.sh` (danger · env · paths · platform · wiring)
182
+ and carves one Rune with the verdict — `nornir / nsr.compliance` on a clean
183
+ round, `nornir / nsr.compliance.failed` (exit 1) naming the failed gates.
184
+
185
+ | Reads | Writes |
186
+ |---|---|
187
+ | `.compliance/gates/check_*.sh` (deterministic, no network) | Rune `nornir / nsr.compliance[.failed]` · `state/last` line |
188
+
189
+ - The morning briefing reads the ledger, so a FAIL is seen at 07:00, not
190
+ found by accident.
191
+ - Idempotent: gates are pure checks; the cron date-guard suppresses repeat
192
+ dispatch within a day; safe to invoke by hand (`bash bin/nornir-job-nsr-compliance.sh`).
193
+
156
194
  ### 3.4 Yggdrasil — git sync (`bin/nornir-job-git-sync.sh`, 00:00)
157
195
 
158
196
  The world-tree kept in order. Two modes, both non-destructive:
@@ -110,6 +110,10 @@ snapshot[1]{command,out}:
110
110
  - **Ports are sacred:** `:4322` is Óðrerir's alone. Do not move it into a
111
111
  Hlidskjalf route or a launcher call — its door is a plain anchor in a new tab
112
112
  (`.hall-btn`, rune **ᛟ**, "To the Hall") and its own window.
113
+ - **`.astro/dev.json` is runtime state, never tracked.** Astro rewrites it on
114
+ every start with the live `pid` and the host's own addresses (LAN + tailnet),
115
+ so committing it leaks a private IP per run. It is gitignored; the generated
116
+ types beside it (`content.d.ts`, `types.d.ts`) stay tracked.
113
117
 
114
118
  ## 5. Files you will touch
115
119
 
@@ -1,4 +1,9 @@
1
- # PI Profile — Default Brokk Harness Profile for Ymir
1
+ # PI Profile — the SHAPE of the Brokk harness profile, not a machine's values.
2
+ #
3
+ # Rule 07A: a model id is the USER's, not the repo's. Every operator's differ, so
4
+ # this template carries a placeholder; the live values come from the user's own
5
+ # roster at $YMIR_HOME/hodd/config/agents.yaml, resolved through
6
+ # bin/agents-config.sh. Never commit a real model id here.
2
7
 
3
8
  profile:
4
9
  name: "pi"
@@ -6,7 +11,7 @@ profile:
6
11
  version: "1.0.0"
7
12
 
8
13
  agent:
9
- model: "lmstudio/qwen3.6-35b-a3b"
14
+ model: "<your-model-id>" # resolved from $YMIR_HOME/hodd/config/agents.yaml
10
15
  temperature: 0.7
11
16
  max_tokens: 8192
12
17
  system_prompt: "BROKK — SYSTEM OPERATING MANUAL (see .opencode/agents/brokk.md)"
@@ -292,7 +292,7 @@ The house law lives in `RULES/`; Galdr must know it (Rule 01/02/03):
292
292
 
293
293
  - **Eindri are the specialists** (marketer, builder, researcher, planner,
294
294
  reviewer, documenter, scout, …); each names its **domain** and its **craft**.
295
- - **Agents live only in `.agents/agents/*.md`.** `.opencode/agent` and
295
+ - **Agents live only in `.agents/agents/*.md`.** `.opencode/agents` and
296
296
  `.pi/agents` are **symlinks** to the canonical profiles — never edit them;
297
297
  edit `.agents/agents/<profile>.md` and run `bin/valknut-load.sh --all`.
298
298
  - **No mock agents.** Ids, names, domains, models, status are real and sourced;
@@ -32,7 +32,7 @@ siblings of this file (`eindri-orchestration.md`, `nornir-jobs.md`). The runtime
32
32
  | 11 | Secrets never committed | secret scan + ignore audit | No secret literal; ignore rules cover env files. |
33
33
  | 12 | Governed assets current | `compliance-check.sh` (`assets` check) | A governed path changed in the working tree has its owning asset changed too. |
34
34
  | 13 | **Governed paths resolve** | `compliance-check.sh` (`governed` check) | Every path in `AGENTS.md`'s `governed[]` table exists (or its glob matches something). |
35
- | 14 | **Harness surfaces resolve** | `compliance-check.sh` (`harnesses` check) | Every link in `.claude/agents`, `.codex/agents`, `.cursor/agents`, `.pi/agents`, `.opencode/agent` lands (one hop) in `.agents/agents/`, and no nested `SKILL.md` carries frontmatter — a phantom skill. |
35
+ | 14 | **Harness surfaces resolve** | `compliance-check.sh` (`harnesses` check) | Every link in `.claude/agents`, `.codex/agents`, `.cursor/agents`, `.pi/agents`, `.opencode/agents` lands (one hop) in `.agents/agents/`, and no nested `SKILL.md` carries frontmatter — a phantom skill. |
36
36
  | 15 | **Skill index true** | `compliance-check.sh` (`skillindex` check) | Every real skill dir is named in `.agents/skills/README.md`, and every `.agents/skills/<name>` path the assets cite exists. |
37
37
 
38
38
  ### Governed paths — load the asset before you edit
@@ -88,7 +88,7 @@ Two failures hide in the harness layer, and neither is visible from the code:
88
88
 
89
89
  1. **A symlink that no longer resolves.** Rule 02 says `.agents/agents` is
90
90
  canonical and every harness dir (`.claude/agents`, `.codex/agents`,
91
- `.cursor/agents`, `.pi/agents`, `.opencode/agent`) is a link into it. The
91
+ `.cursor/agents`, `.pi/agents`, `.opencode/agents`) is a link into it. The
92
92
  `galdr-cli` → `galdr-ymirsystem` rename left `.agents/agents/galdr.md`
93
93
  dangling, so Galdr's agent surface did not exist in *any* harness — and
94
94
  nothing failed loudly; the harness simply had no such agent.
@@ -18,7 +18,10 @@ Ymir itself the instance lives at `apps/smidja/`** — it moved out of the repo
18
18
  so the smithy sits with the other apps, and its runtime data
19
19
  (`apps/smidja/smidja_data/`, gitignored) may also live at `$YMIR_HOME/smidja/`,
20
20
  which the runtime prefers when it exists. Every path in *this* repo points at
21
- `apps/smidja/`.
21
+ `apps/smidja/`. The monorepo tracks neither the factory nor the engine: the
22
+ factory is cloned from `zerwiz/smidja` into `apps/smidja-factory` by the
23
+ installer's `apps` step, and the engine is stamped into `apps/smidja` from that
24
+ clone's `templates/smidja` — exactly as `install.py` does for a target repo.
22
25
 
23
26
  ## What it is
24
27
 
@@ -84,10 +87,10 @@ installed on this machine) — do not reintroduce `sqlite3`.
84
87
 
85
88
  ## The trace
86
89
 
87
- - DB: **`apps/smidja/smidja_data/smidja.db`** (WAL; read-only readers never block a run).
88
- The runtime prefers `$YMIR_HOME/smidja/smidja.db` when it exists (the data
89
- belongs outside the repo); the app path is the fallback.
90
- Created at the first run.
90
+ - DB: **`$YMIR_HOME/smidja/smidja.db`** (WAL; read-only readers never block a run).
91
+ `0003-private-data-separation` moved it out of the checkout; an in-repo
92
+ `apps/smidja/smidja_data/smidja.db` is the fallback. Created at the first run
93
+ (`bin/smidja-bootstrap.sh`); `scripts/start.sh` resolves the same pair.
91
94
  - Tables: `sessions` (`smidja_id, smidja_name, request, status, engineer,
92
95
  started_at, ended_at, total_tokens, total_cost, archived`), `phases`
93
96
  (`phase_id, smidja_id, seq, name, kind, owner, description, status, attempt,
@@ -111,8 +114,8 @@ installed on this machine) — do not reintroduce `sqlite3`.
111
114
  saved `neutral` from an older build resolves to fensalir. The categorical
112
115
  palettes (event dots, agent lanes in `src/lib/events.ts`) are data, not chrome,
113
116
  and were re-cut onto the cloth while staying mutually distinct.
114
- - `scripts/start.sh` raises it (`CMD_DB=<repo>/apps/smidja/smidja_data/smidja.db`,
115
- `PORT=8437`); `scripts/stop.sh` lowers it. Port overrides:
117
+ - `scripts/start.sh` raises it (`CMD_DB=$YMIR_HOME/smidja/smidja.db`, else the
118
+ in-repo fallback; `PORT=8437`); `scripts/stop.sh` lowers it. Port overrides:
116
119
  `SMIDJA_VIZ_API_PORT`.
117
120
  - Served under Hlidskjalf's **Sessions** gate via **Open visualizer**
118
121
  (`VITE_VISUALIZER_URL`, default `http://127.0.0.1:8437`).
@@ -142,7 +145,7 @@ every 5s, so a finished run appears without a reload.
142
145
 
143
146
  `bin/nornir-job-observer.sh` runs on the Nornir schedule (06:00). It reads **only
144
147
  Ymir's own runtime** — `docs/masterplan.md`, `.agents/agents`, `.agents/memory/well`,
145
- `workspace/memory/runes_audit.md`, **`apps/smidja/smidja_data/smidja.db`**, and the
148
+ `workspace/memory/runes_audit.md`, **`$YMIR_HOME/smidja/smidja.db`**, and the
146
149
  read-only external worktree root — and writes only `state/observer.log` + Runes.
147
150
  There is **no `~/command` connection** anywhere in Ymir. Rune sources:
148
151
  `ymir.orders · ymir.agents · ymir.well · ymir.runes · smidja.runs ·
@@ -167,9 +170,11 @@ Expected: `2/2 phases passed`, a `smidja_id`, and the DB written. Then
167
170
  `llama-cpp/<id>`; LM Studio `:1234` may be down. Confirm with `pi --list-models`.
168
171
  3. **The visualizer's local `node_modules` may be a partial bun install** — the API
169
172
  still serves the prebuilt `dist/`, so open `:8437`, not `:8438`.
170
- 4. **Never edit `smidja/smidja_data/sessions/`** — it is the run record.
171
- 5. **Protected files** (`smidja/smidja_modules/`, `smidja/smidja_*.py`, the config)
173
+ 4. **Never edit `apps/smidja/smidja_data/sessions/`** — it is the run record.
174
+ 5. **Protected files** (`apps/smidja/smidja_modules/`, `apps/smidja/smidja_*.py`, the config)
172
175
  are enforced by `smidja_modules/permissions.py`; agents roll back unauthorized
173
- changes.
176
+ changes. The smithy moved from the repo root `smidja/` to `apps/smidja/`
177
+ (Amendment C); `bin/smidja-bootstrap.sh` searches both so a stale layout cannot
178
+ break the DB seed again.
174
179
  6. New skills synthesized for the smithy are validated in Utgard (Gungnir, W0007)
175
180
  before production.