@zerwiz/ymir 0.1.27 → 0.1.28

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 (143) hide show
  1. package/.agents/skills/groa-update/SKILL.md +30 -0
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +47 -8
  4. package/bin/fixes-guard.sh +112 -0
  5. package/bin/fixes.sh +157 -0
  6. package/bin/ymir.js +35 -0
  7. package/docs/fixes/agents/unversioned-2026-09-11-chat-uses-the-root-pi-model-catalog.md +15 -0
  8. package/docs/fixes/agents/unversioned-2026-09-11-shared-well-for-every-harness-no-mock-clickable-memories.md +26 -0
  9. package/docs/fixes/agents/unversioned-2026-09-13-gleipnir-sees-when-an-agent-is-turned-off.md +26 -0
  10. package/docs/fixes/agents/unversioned-2026-09-14-the-eindri-brokk-wake-bridge-they-can-talk-back-now.md +23 -0
  11. package/docs/fixes/agents/unversioned-2026-09-16-a-sandboxed-worker-cannot-think-auto-stops-killing-agent.md +18 -0
  12. package/docs/fixes/agents/unversioned-2026-09-17-agents-md-states-the-one-law-no-private-data-in-the-publ.md +25 -0
  13. package/docs/fixes/agents/unversioned-2026-09-17-every-figure-loads-the-agent-directory-was-never-read.md +25 -0
  14. package/docs/fixes/agents/unversioned-2026-09-17-pi-gets-its-agents-and-a-dead-extension-comes-back.md +26 -0
  15. package/docs/fixes/agents/unversioned-2026-09-17-the-well-as-tools-ymir-s-first-custom-pi-extension.md +20 -0
  16. package/docs/fixes/gate/0.1.28-fixes-guard-replaces-the-changelog-gate.md +7 -0
  17. package/docs/fixes/gate/0.1.28-the-changelog-system-is-replaced-by-fix-notes.md +8 -0
  18. package/docs/fixes/gate/0.1.28-the-frozen-changelog-becomes-per-component-notes.md +9 -0
  19. package/docs/fixes/hlidskjalf/unversioned-2026-09-11-automate-the-install-consent-gate-self-healing-prereqs-v.md +21 -0
  20. package/docs/fixes/hlidskjalf/unversioned-2026-09-11-single-tenant-workspaces-shipped.md +15 -0
  21. package/docs/fixes/hlidskjalf/unversioned-2026-09-13-the-cloth-reaches-all-three-halls-hlidskjalf-sm-ja-the-s.md +25 -0
  22. package/docs/fixes/hlidskjalf/unversioned-2026-09-16-one-login-for-every-app-on-the-web-the-gate-fronts-them-.md +24 -0
  23. package/docs/fixes/hlidskjalf/unversioned-2026-09-16-the-credential-reaches-the-gate-and-the-hall-stops-leaki.md +23 -0
  24. package/docs/fixes/hlidskjalf/unversioned-2026-09-16-the-delivery-gates-install-themselves-rename-drift-mende.md +22 -0
  25. package/docs/fixes/hlidskjalf/unversioned-2026-09-16-the-panels-that-crashed-the-gate-that-lied-and-the-well-.md +25 -0
  26. package/docs/fixes/hlidskjalf/unversioned-2026-09-17-0-1-14-the-panels-reach-the-gate-and-the-fourth-window-s.md +9 -0
  27. package/docs/fixes/hlidskjalf/unversioned-2026-09-17-models-providers-and-paths-are-the-user-s-and-the-gate-s.md +26 -0
  28. package/docs/fixes/hlidskjalf/unversioned-2026-09-17-the-northstar-gate-runs-nightly-the-platform-door-it-cau.md +16 -0
  29. package/docs/fixes/hoard/unversioned-2026-09-16-every-app-wears-its-own-rune.md +27 -0
  30. package/docs/fixes/hoard/unversioned-2026-09-16-hodd-lives-outside-the-repo-and-the-installer-makes-it-s.md +24 -0
  31. package/docs/fixes/hoard/unversioned-2026-09-16-the-icons-are-real-runes-now-eight-of-them-were-drawings.md +33 -0
  32. package/docs/fixes/hoard/unversioned-2026-09-17-0-1-26-published-and-the-vault-door-named.md +8 -0
  33. package/docs/fixes/hoard/unversioned-2026-09-17-one-well-in-the-hoard-every-reader-of-kaia-s-memory-reso.md +23 -0
  34. package/docs/fixes/hoard/unversioned-2026-09-17-skrymir-opens-the-hoard-not-the-checkout.md +18 -0
  35. package/docs/fixes/hoard/unversioned-2026-09-17-the-hoard-encrypts-its-secrets-at-rest.md +20 -0
  36. package/docs/fixes/hoard/unversioned-2026-09-17-the-hoard-gets-a-guard-and-rule-04-stops-contradicting-t.md +19 -0
  37. package/docs/fixes/hoard/unversioned-2026-09-17-the-home-s-re-laid-layout-taught-to-the-system.md +17 -0
  38. package/docs/fixes/hoard/unversioned-2026-09-17-the-private-home-gets-the-ward-it-never-had.md +25 -0
  39. package/docs/fixes/hoard/unversioned-2026-09-17-the-schedule-is-the-user-s-cron-leaves-the-tree-lives-in.md +27 -0
  40. package/docs/fixes/install/0.1.28-a-stale-install-can-no-longer-hide.md +8 -0
  41. package/docs/fixes/install/unversioned-2026-09-11-desktop-shell-tunnel-and-temporary-auth.md +14 -0
  42. package/docs/fixes/install/unversioned-2026-09-11-smooth-install-provider-choice-no-forced-key-real-prereq.md +22 -0
  43. package/docs/fixes/install/unversioned-2026-09-16-apodex-joins-the-weave-research-planning-provider.md +27 -0
  44. package/docs/fixes/install/unversioned-2026-09-16-icons-the-operator-can-pin-and-one-pair-of-hands-for-the.md +29 -0
  45. package/docs/fixes/install/unversioned-2026-09-16-the-desktop-never-dials-out-cloudflare-stays-outside-ele.md +25 -0
  46. package/docs/fixes/install/unversioned-2026-09-17-0-1-12-the-icons-reach-the-live-tree-and-a-missing-surfa.md +10 -0
  47. package/docs/fixes/install/unversioned-2026-09-17-0-1-8-the-day-s-work-reaches-npm.md +25 -0
  48. package/docs/fixes/install/unversioned-2026-09-17-a-stale-install-cannot-hide-and-the-one-liner-cannot-be-.md +9 -0
  49. package/docs/fixes/install/unversioned-2026-09-17-both-shapes-a-clone-and-an-npm-install-and-the-patience-.md +26 -0
  50. package/docs/fixes/install/unversioned-2026-09-17-gr-a-knows-the-npm-half-how-to-renew-the-published-packa.md +9 -0
  51. package/docs/fixes/install/unversioned-2026-09-17-npm-only-the-hall-answers-on-its-own-port-and-the-marks-.md +27 -0
  52. package/docs/fixes/install/unversioned-2026-09-17-the-apps-leave-the-monorepo-installation-pulls-them-from.md +23 -0
  53. package/docs/fixes/install/unversioned-2026-09-17-the-doors-the-cloth-and-the-icons-that-were-not-there.md +27 -0
  54. package/docs/fixes/install/unversioned-2026-09-17-the-editor-stops-being-banished-to-its-own-desktop.md +18 -0
  55. package/docs/fixes/install/unversioned-2026-09-17-the-four-surfaces-arrive-by-npm-and-an-old-tree-gives-up.md +24 -0
  56. package/docs/fixes/install/unversioned-2026-09-17-the-house-gains-an-unpublish-door.md +19 -0
  57. package/docs/fixes/install/unversioned-2026-09-17-the-icons-reach-the-live-tree-and-a-missing-surface-is-a.md +26 -0
  58. package/docs/fixes/install/unversioned-2026-09-17-the-install-asks-with-a-plan-and-the-operator-s-things-l.md +24 -0
  59. package/docs/fixes/install/unversioned-2026-09-17-the-installer-actually-installs-the-whole-platform-then-.md +9 -0
  60. package/docs/fixes/install/unversioned-2026-09-17-the-installer-has-a-design-of-its-own.md +9 -0
  61. package/docs/fixes/install/unversioned-2026-09-17-the-npm-door-stands-the-surfaces.md +13 -0
  62. package/docs/fixes/install/unversioned-2026-09-17-the-one-liner-the-readme-promised-and-it-sets-the-path.md +9 -0
  63. package/docs/fixes/install/unversioned-2026-09-17-ymir-command-not-found-is-a-path-fault-and-here-is-the-c.md +8 -0
  64. package/docs/fixes/install/unversioned-2026-09-18-the-readme-leads-with-the-doors-that-work.md +13 -0
  65. package/docs/fixes/odrerir/unversioned-2026-09-14-rerir-forged-eindri-odrerir-pane-w3-p7.md +12 -0
  66. package/docs/fixes/odrerir/unversioned-2026-09-17-the-live-hall-s-board-becomes-true-the-snapshot-lands-on.md +18 -0
  67. package/docs/fixes/runtime/unversioned-2026-09-11-gleipnir-lock-pid-binding-fix.md +17 -0
  68. package/docs/fixes/runtime/unversioned-2026-09-11-hardcoded-per-page-feeds-header-stream.md +19 -0
  69. package/docs/fixes/runtime/unversioned-2026-09-11-per-page-rolling-40-message-feeds.md +13 -0
  70. package/docs/fixes/runtime/unversioned-2026-09-11-smi-ja-views-are-repo-local.md +17 -0
  71. package/docs/fixes/runtime/unversioned-2026-09-11-stats-1-1-kaia-chat-forge-prompts.md +23 -0
  72. package/docs/fixes/runtime/unversioned-2026-09-11-the-well-is-real-mimirsbrunn-engram.md +21 -0
  73. package/docs/fixes/runtime/unversioned-2026-09-12-fleet-preferences.md +11 -0
  74. package/docs/fixes/runtime/unversioned-2026-09-12-house-law-rules-and-the-grein-naming.md +11 -0
  75. package/docs/fixes/runtime/unversioned-2026-09-12-r-preference-moved-out-of-the-tracked-tree.md +10 -0
  76. package/docs/fixes/runtime/unversioned-2026-09-12-the-dashboards-stop-crash-looping-on-the-igpu.md +20 -0
  77. package/docs/fixes/runtime/unversioned-2026-09-12-the-well-actually-installs-it-never-did.md +23 -0
  78. package/docs/fixes/runtime/unversioned-2026-09-12-updater-forged-credential-moved-to-env.md +12 -0
  79. package/docs/fixes/runtime/unversioned-2026-09-14-a-vacant-helm-is-entered-not-punted-watcher-wake-repair.md +24 -0
  80. package/docs/fixes/runtime/unversioned-2026-09-14-the-echo-guard-no-wake-flood-through-gn-s-door.md +18 -0
  81. package/docs/fixes/runtime/unversioned-2026-09-16-app-packaging-prepared-under-the-ymir-scope-amendment-c-.md +23 -0
  82. package/docs/fixes/runtime/unversioned-2026-09-16-four-readers-that-still-pointed-at-the-pre-split-world.md +26 -0
  83. package/docs/fixes/runtime/unversioned-2026-09-16-no-blue-the-forge-language-replaces-the-old-blue.md +26 -0
  84. package/docs/fixes/runtime/unversioned-2026-09-16-omarchy-first-host-aware-windows-and-macos-get-a-door.md +25 -0
  85. package/docs/fixes/runtime/unversioned-2026-09-16-smi-ja-moves-into-the-app-tree.md +17 -0
  86. package/docs/fixes/runtime/unversioned-2026-09-16-the-changelog-guard-no-push-ships-untold.md +19 -0
  87. package/docs/fixes/runtime/unversioned-2026-09-16-the-core-senses-the-real-host-not-omarchy-s-shadow.md +23 -0
  88. package/docs/fixes/runtime/unversioned-2026-09-16-the-hearth-spreads-one-fire-three-more-surfaces.md +25 -0
  89. package/docs/fixes/runtime/unversioned-2026-09-16-the-loader-stops-deleting-merge-never-overwrite.md +27 -0
  90. package/docs/fixes/runtime/unversioned-2026-09-16-the-mesh-reaches-teams-and-anchor-and-show-stops-guessin.md +24 -0
  91. package/docs/fixes/runtime/unversioned-2026-09-17-0-1-10-the-hall-answers-on-its-port-and-the-marks-land.md +15 -0
  92. package/docs/fixes/runtime/unversioned-2026-09-17-0-1-11-four-surfaces-their-marks-and-a-door-that-mends.md +18 -0
  93. package/docs/fixes/runtime/unversioned-2026-09-17-glitnir-stops-hanging-and-one-process-is-one-row.md +15 -0
  94. package/docs/fixes/runtime/unversioned-2026-09-17-main-is-repaired-and-the-report-wears-the-cloth.md +25 -0
  95. package/docs/fixes/runtime/unversioned-2026-09-17-not-again-and-four-surfaces-in-one-raise.md +26 -0
  96. package/docs/fixes/runtime/unversioned-2026-09-17-smoke-test-all-ui-two-checks-that-lied-about-their-own-s.md +20 -0
  97. package/docs/fixes/runtime/unversioned-2026-09-17-the-changelog-stops-conflicting-fragments-folded-by-a-sc.md +25 -0
  98. package/docs/fixes/runtime/unversioned-2026-09-17-the-doors-written-down-where-a-user-will-find-them.md +20 -0
  99. package/docs/fixes/runtime/unversioned-2026-09-17-the-duplicate-extensions-removed-from-the-project-tree.md +17 -0
  100. package/docs/fixes/runtime/unversioned-2026-09-17-the-extensions-deployed-without-their-modules.md +23 -0
  101. package/docs/fixes/runtime/unversioned-2026-09-17-the-feed-follows-brokk-s-thinking-past-the-composer.md +17 -0
  102. package/docs/fixes/runtime/unversioned-2026-09-17-the-fleet-stops-colliding-a-grid-fan-and-each-smith-s-ow.md +21 -0
  103. package/docs/fixes/runtime/unversioned-2026-09-17-the-fork-stands-the-machine.md +12 -0
  104. package/docs/fixes/runtime/unversioned-2026-09-17-the-fragment-convention-is-enforced-where-github-runs-it.md +26 -0
  105. package/docs/fixes/runtime/unversioned-2026-09-17-the-layout-swept-every-reader-taught-the-new-doors.md +24 -0
  106. package/docs/fixes/runtime/unversioned-2026-09-17-the-model-that-raised-itself-and-the-workhorse-that-repl.md +26 -0
  107. package/docs/fixes/runtime/unversioned-2026-09-17-the-package-shipped-the-memory-well-one-version-now-excl.md +26 -0
  108. package/docs/fixes/runtime/unversioned-2026-09-17-the-phantom-optional-pins-cut.md +9 -0
  109. package/docs/fixes/runtime/unversioned-2026-09-17-the-sweep-and-the-door-that-mends-itself.md +26 -0
  110. package/docs/fixes/sessrumnir/unversioned-2026-09-12-the-editor-keeps-its-own-seat.md +16 -0
  111. package/docs/fixes/sessrumnir/unversioned-2026-09-13-sessr-mnir-the-seat-hall.md +28 -0
  112. package/docs/fixes/sessrumnir/unversioned-2026-09-13-the-carved-cloth-reaches-the-seat-hall-sessr-mnir.md +27 -0
  113. package/docs/fixes/sessrumnir/unversioned-2026-09-13-the-new-tree-the-mesh-the-seat-hall.md +25 -0
  114. package/docs/fixes/sessrumnir/unversioned-2026-09-13-the-realm-seat-carved-wayof.md +24 -0
  115. package/docs/fixes/sessrumnir/unversioned-2026-09-16-apodex-seat-verified-the-weave-mended.md +27 -0
  116. package/docs/fixes/sessrumnir/unversioned-2026-09-16-the-visualizer-finds-its-db-and-the-seat-hall-actually-b.md +22 -0
  117. package/docs/fixes/sessrumnir/unversioned-2026-09-17-0-1-13-the-re-voiced-seat-arrives-and-the-packages-are-l.md +8 -0
  118. package/docs/fixes/sessrumnir/unversioned-2026-09-17-sessr-mnir-stops-wearing-pi-s-clothes.md +23 -0
  119. package/docs/fixes/sessrumnir/unversioned-2026-09-17-the-hearth-re-seeds-on-its-container-sessr-mnir-s-chat-f.md +23 -0
  120. package/docs/fixes/sessrumnir/unversioned-2026-09-17-the-packaged-seat-carries-the-watch.md +11 -0
  121. package/docs/fixes/sessrumnir/unversioned-2026-09-17-the-readme-lists-the-packages-and-the-pin-moves-to-the-r.md +16 -0
  122. package/docs/fixes/sessrumnir/unversioned-2026-09-17-the-seat-s-name-and-the-visualizer-s-home.md +12 -0
  123. package/docs/fixes/skills/unversioned-2026-09-11-galdr-tyr-assets-symlink-not-copies.md +11 -0
  124. package/docs/fixes/skills/unversioned-2026-09-11-load-the-owning-asset-before-editing-a-governed-path-5-l.md +21 -0
  125. package/docs/fixes/skills/unversioned-2026-09-11-omarchy-first-host-skills-machine-learning-native-fixes.md +27 -0
  126. package/docs/fixes/skills/unversioned-2026-09-13-the-imported-names-leave-the-halls-norse-naming-purge.md +26 -0
  127. package/docs/fixes/skills/unversioned-2026-09-16-gunnl-joins-the-hall-skillopt-integration.md +17 -0
  128. package/docs/fixes/skills/unversioned-2026-09-16-one-skills-tree-every-harness-the-config-stomp-mended.md +25 -0
  129. package/docs/fixes/skills/unversioned-2026-09-16-the-skill-loading-audit-wrong-skills-unloaded-the-index-.md +26 -0
  130. package/docs/fixes/skills/unversioned-2026-09-17-0-1-9-both-shapes-reach-the-registry.md +17 -0
  131. package/docs/fixes/skills/unversioned-2026-09-17-midgard-holds-public-assets-only-the-company-wiki-is-gon.md +23 -0
  132. package/docs/fixes/skills/unversioned-2026-09-17-the-forge-speaks-each-skill-s-own-text-in-the-index.md +16 -0
  133. package/docs/fixes/skills/unversioned-2026-09-17-the-local-git-round-joins-the-loom-the-stack-s-doors-are.md +19 -0
  134. package/docs/fixes/skills/unversioned-2026-09-17-the-registry-says-where-a-repo-lives-and-what-it-is-for.md +20 -0
  135. package/docs/fixes/smidja/unversioned-2026-09-16-the-pane-bridge-keeps-its-quoting-phase-3-verified.md +22 -0
  136. package/docs/fixes/smidja/unversioned-2026-09-16-the-roster-can-finally-run-an-agent-on-pi.md +23 -0
  137. package/docs/fixes/smidja/unversioned-2026-09-16-the-roster-s-effect-lands-and-the-backend-pin-stops-bein.md +19 -0
  138. package/docs/fixes/smidja/unversioned-2026-09-16-the-validator-stops-calling-a-dead-visualizer-healthy.md +20 -0
  139. package/docs/fixes/smidja/unversioned-2026-09-17-the-fleet-was-a-rack-of-terminals-now-it-is-the-roster.md +18 -0
  140. package/docs/fixes/smidja/unversioned-2026-09-17-the-plan-learns-the-smithy-s-package-name-and-the-instal.md +25 -0
  141. package/docs/fixes/smidja/unversioned-2026-09-17-the-smithy-can-see-the-well.md +20 -0
  142. package/docs/fixes/smidja/unversioned-2026-09-17-the-smithy-s-dependency-names-the-package-npm-will-actua.md +20 -0
  143. package/package.json +1 -1
@@ -91,6 +91,36 @@ This touches only the Brokk repo and its own worktrees, never anything under `pr
91
91
  that is drift the update should have carried out — name it to the Allfather.
92
92
  Owning asset: `.agents/skills/galdr-ymirsystem/assets/installation.md`.
93
93
 
94
+ ## Updating Ymir on npm — the other half of renewing
95
+
96
+ Gróa fast-forwards the **tree**; the published **packages** need the same care, and
97
+ the traps here are quiet ones. In order:
98
+
99
+ ```
100
+ npm_renewal[6]{step,how,why}:
101
+ "1 choose the number","fetch the registry's latest FIRST (curl …/@zerwiz%2fymir, not memory) and bump one above it","the registry can be AHEAD of main when a publish went out from a branch — main said 0.1.18 while npm served 0.1.25"
102
+ "2 land the bump by PR","branch → commit → gh pr create → merge; never push main (branch-guard refuses)","the delivery gate: a change leaves by PR, and the Allfather's approval is the merge"
103
+ "3 publish","bin/npm-publish.sh — it opens the vault's door for the token","the token is in hodd/secrets/platform.env.age, never in ~/.npmrc (that one has been stale before)"
104
+ "4 if it says the token is absent","the DOOR is broken, not the key: with `age` present, age -d -i hodd/secrets/age.key hodd/secrets/platform.env.age | sed -n 's/^NPM_TOKEN=//p'","bin/hodd.sh emit currently returns empty for both spellings; the vault is fine, the emit path is not — mend it"
105
+ "5 verify","fetch the VERSION document (…/@zerwiz/ymir/<version>) — it answers before the packument does","the registry lags for minutes; the packument can 404 while the version and tarball already resolve"
106
+ "6 the four apps ride along","their pins move in the same release (hlidskjalf · odrerir · sessrumnir · smidja-factory)","the distro depends on them; a pin left behind keeps shipping the old app to every user"
107
+ ```
108
+
109
+ **The traps, named — each one cost a night:**
110
+
111
+ - **A stale `latest` makes an install a silent no-op.** npm resolved a cached
112
+ `latest`, saw the same version, printed *"changed 200 packages"* and left the old
113
+ build in place. Publish and reinstall with **`@latest --prefer-online`**, and give
114
+ a stuck machine **`npm cache clean --force`**. The CLI now *says* when it is out
115
+ of date (once a day), so a stale install cannot hide.
116
+ - **npm gates install scripts.** Electron's postinstall never runs, so a fresh
117
+ install has web surfaces and no windows; each shell needs `npm rebuild electron`
118
+ (the installer's `step_desktop` does it, and `bin/electron-lib.sh` verifies).
119
+ - **A package that ships less than it needs is a fault in its own repo.** Declare
120
+ `name`, `files` and the absence of `private` where the package lives — never
121
+ rewrite a manifest at publish time (that is how `vite.config.ts` went missing and
122
+ every panel died on `Unexpected token '<'`).
123
+
94
124
  ## Safety
95
125
 
96
126
  - **Fast-forward only.** Dirty, diverged, offline, or non-default-branch targets are skipped and reported, never forced or stashed. Nothing with unlanded work is ever discarded.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ > **Frozen 2026-09-18.** This file is the historical record and is no longer
2
+ > written to (Rule 06: a record is never rewritten). New work is recorded as fix
3
+ > notes — one file per fix, per component — under `docs/fixes/`, written with
4
+ > `bin/fixes.sh record` and guarded by `bin/fixes-guard.sh`.
5
+
6
+ ## 2026-09-18 — the README leads with the doors that work
7
+
8
+ - **The npm page and GitHub both render `README.md`**, so a user meets it first — and
9
+ it buried the door that always works (`npx @zerwiz/ymir`) and the PATH cure inside
10
+ a later section. It now opens with the one-liner (install into a prefix you own, PATH
11
+ written, setup run, door proven), names `npx` second, and answers *"ymir: command not
12
+ found"* and the stale-install trap where they happen.
13
+ - The three doors and their honest reasons: `npx` needs no PATH at all; the one-liner
14
+ sets it once; `npm install -g` works wherever npm's global bin is on PATH.
15
+
16
+ ## 2026-09-17 — Gróa knows the npm half: how to renew the published packages
17
+
18
+ - **Her skill gains the whole procedure** — choose the number from the registry (never from memory: it can be ahead of main), land the bump by PR, publish through the vault, verify by the *version document* (the packument lags), and move the four apps pins in the same release.
19
+ - **And the traps, each of which cost a night:** a stale cached `latest` makes `npm i -g` a silent no-op that still prints success; npm gates Electron s postinstall so windows need `npm rebuild electron`; and a package must declare its own `name`/`files`/privacy where it lives rather than having a manifest rewritten at publish time.
20
+ - The token lives in the encrypted vault; when a tool reports it absent, the DOOR is broken, not the key — and `age -d -i hodd/secrets/age.key hodd/secrets/platform.env.age` opens it while `bin/hodd.sh emit` does not.
21
+
22
+ ## 2026-09-17 — a stale install cannot hide, and the one-liner cannot be stranded
23
+
24
+ - **The fault the Allfather hit:** `npm install -g @zerwiz/ymir` reported *"changed 200 packages"* and changed nothing — npm resolved a **cached `latest`**, saw the same version, and left the old build in place. The installed copy stayed 0.1.14 while the registry served 0.1.26, and nothing told the user. With 750+ downloads, that is not a detail.
25
+ - **The CLI now says when it is out of date** — once a day, on a terminal, silenced by the same `ymir config notice version off` as every other notice: *"a newer Ymir is on npm 0.1.14 → 0.1.26 · update: npm i -g @zerwiz/ymir"*.
26
+ - **The one-liner is cache-proof**: it installs `@latest` with `--prefer-online`, so a stale packument cannot strand a fresh machine.
27
+
1
28
 
2
29
  ## 2026-09-17 — the installer actually installs (the whole platform, then proves it)
3
30
 
package/README.md CHANGED
@@ -637,15 +637,54 @@ resolved from `$YMIR_HOME/secrets/platform.env` at runtime.
637
637
 
638
638
  ---
639
639
 
640
- ## Install — the four doors
640
+ ## Install
641
+
642
+ **One command. It installs into a prefix you own, writes the PATH into your shell,
643
+ runs the first setup, and then proves it.**
641
644
 
642
645
  ```bash
643
646
  curl -fsSL https://raw.githubusercontent.com/zerwiz/ymir/main/install.sh | bash
644
- npx @zerwiz/ymir install # no global install
645
- git clone https://github.com/zerwiz/ymir.git ~/Ymir && cd ~/Ymir && bin/ymir-install.sh
646
- npm install -g @zerwiz/ymir # the CLI, global
647
647
  ```
648
648
 
649
+ **No PATH, no prefix, nothing to configure — run it without installing:**
650
+
651
+ ```bash
652
+ npx @zerwiz/ymir
653
+ ```
654
+
655
+ **Already manage Node and npm yourself:**
656
+
657
+ ```bash
658
+ npm install -g @zerwiz/ymir # then: ymir
659
+ ```
660
+
661
+ ### If `ymir` is "command not found"
662
+
663
+ The command **is** installed — npm put it in its global bin directory, and your
664
+ shell does not look there. This is npm's behaviour, not Ymir's, and it is the most
665
+ common *"it does not work"* in the whole Node ecosystem.
666
+
667
+ ```bash
668
+ npm prefix -g # where npm puts global commands
669
+ ls "$(npm prefix -g)/bin/ymir" # the command is there
670
+ export PATH="$(npm prefix -g)/bin:$PATH" # this shell
671
+ echo 'export PATH="$(npm prefix -g)/bin:$PATH"' >> ~/.bashrc # every shell after
672
+ ```
673
+
674
+ **Or skip the question entirely** — `npx @zerwiz/ymir` works with no PATH at all,
675
+ and the one-liner above sets it once.
676
+
677
+ ### If the version never changes
678
+
679
+ An install can *succeed* and change nothing: npm may resolve a cached `latest` and
680
+ report success while the old build stays. Clear it once:
681
+
682
+ ```bash
683
+ npm cache clean --force && npm i -g @zerwiz/ymir@latest --prefer-online
684
+ ```
685
+
686
+ The CLI itself now says when it is out of date, once a day.
687
+
649
688
  **Who can install this today.** The Ymir distro is private while it is young —
650
689
  so `curl`, `npx` and `git clone` work for the author and invited users, not for
651
690
  the public (an unknown clone returns 404). If you would like access, ask:
@@ -736,11 +775,11 @@ alone if you want only that hall.
736
775
 
737
776
  ```
738
777
  packages[5]{package,what,live}:
739
- "@zerwiz/ymir","the distro — the runtime, the doors, the installer, the plan","0.1.12"
740
- "@zerwiz/hlidskjalf","the high seat — the control plane, the gate and the landing","0.1.0"
741
- "@zerwiz/odrerir","the live hall — the board on :4322","0.1.0"
778
+ "@zerwiz/ymir","the distro — the runtime, the doors, the installer, the plan","0.1.28"
779
+ "@zerwiz/hlidskjalf","the high seat — the control plane, the gate and the landing","0.1.1"
780
+ "@zerwiz/odrerir","the live hall — the board on :4322","0.1.1"
742
781
  "@zerwiz/smidja-factory","the smithy — the agent factory, its skills and the visualizer on :8437","0.1.1"
743
- "@zerwiz/sessrumnir","the seat-hall — the desktop where you speak with Brokk","0.1.9"
782
+ "@zerwiz/sessrumnir","the seat-hall — the desktop where you speak with Brokk","0.1.10"
744
783
  ```
745
784
 
746
785
  ```bash
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # fixes-guard.sh — the delivery gate reads, and it checks the RECORD.
3
+ #
4
+ # What it replaces, and why: `changelog-guard` passed if a push range merely
5
+ # *touched* CHANGELOG.md — a stray line satisfied it, so it never verified that
6
+ # anything was recorded. And the hook in front of it FOLDED fragments into the
7
+ # monolith, mutating the tree mid-push and then demanding a commit for the edit it
8
+ # had just made.
9
+ #
10
+ # This gate only READS. A push must carry at least one new fix note
11
+ # (`docs/fixes/<component>/<version>-<slug>.md`), and it says which of the push's
12
+ # touched paths that note's component covers — a mismatch is reported, never
13
+ # silently accepted.
14
+ #
15
+ # fixes-guard.sh --install # seat it as the pre-push hook (replacing the old one)
16
+ # fixes-guard.sh --check # judge the current branch against its base, by hand
17
+ # git push # the hook runs it with the refs on stdin
18
+ #
19
+ # Override, loudly: YMIR_SKIP_FIXES_GUARD=1
20
+ #
21
+ # Exit: 0 the record is there, 1 it is not, 2 usage.
22
+ set -u
23
+
24
+ VERSION="1.0.0"
25
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
27
+ cd "$ROOT" || exit 1
28
+
29
+ [ "${YMIR_SKIP_FIXES_GUARD:-0}" = 1 ] && { printf 'fixes-guard[1]{gate,result}:\n "push","skipped (YMIR_SKIP_FIXES_GUARD=1)"\n'; exit 0; }
30
+
31
+ case "${1-}" in
32
+ -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
33
+ -h|--help) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
34
+ --install)
35
+ H="$(git rev-parse --git-path hooks)/pre-push"
36
+ mkdir -p "$(dirname "$H")"
37
+ cat >"$H" <<EOF
38
+ #!/usr/bin/env bash
39
+ # pre-push — the delivery gate. Installed by bin/fixes-guard.sh --install.
40
+ # 1. branch-guard — never push a protected branch
41
+ # 2. fixes-guard — every push carries a fix note for what it changed (read-only)
42
+ set -u
43
+ refs="\$(mktemp)"; trap 'rm -f "\$refs"' EXIT
44
+ cat >"\$refs"
45
+ "$ROOT/bin/branch-guard.sh" <"\$refs" || exit 1
46
+ "$ROOT/bin/fixes-guard.sh" <"\$refs" || exit 1
47
+ EOF
48
+ chmod +x "$H"
49
+ printf 'fixes-guard[1]{gate,result}:\n "hook","installed at %s"\n' "${H#"$ROOT"/}"
50
+ exit 0 ;;
51
+ --check) ;;
52
+ "") ;;
53
+ *) printf 'error: unknown flag %s\nhelp: bin/fixes-guard.sh [--install|--check]\n' "$1" >&2; exit 2 ;;
54
+ esac
55
+
56
+ # ── the judgement ────────────────────────────────────────────────────────────
57
+ # The range: from stdin when a hook calls us, else the branch against its base.
58
+ refs="$(cat 2>/dev/null || true)"
59
+ head_sha=""; remote_sha=""
60
+ if [ -n "$refs" ]; then
61
+ while read -r _local local_sha _remote remote_sha2; do
62
+ [ -n "${local_sha:-}" ] || continue
63
+ case "$local_sha" in 000000*) continue ;; esac
64
+ head_sha="$local_sha"; remote_sha="${remote_sha2:-}"
65
+ done <<<"$refs"
66
+ fi
67
+ [ -n "$head_sha" ] || head_sha="$(git rev-parse HEAD)"
68
+
69
+ base=""
70
+ if [ -n "$remote_sha" ] && [ "$remote_sha" != 0000000000000000000000000000000000000000 ]; then
71
+ base="$remote_sha"
72
+ else
73
+ for cand in origin/main main master; do
74
+ git rev-parse --verify -q "$cand" >/dev/null 2>&1 || continue
75
+ base="$(git merge-base "$head_sha" "$cand" 2>/dev/null || true)"
76
+ [ -n "$base" ] && break
77
+ done
78
+ fi
79
+ [ -n "$base" ] || { printf 'fixes-guard[1]{gate,result}:\n "push","no base to compare — first push of a branch is judged on its own"\n'; exit 0; }
80
+
81
+ range="${base}..${head_sha}"
82
+ notes="$(git diff --name-only --diff-filter=A "$range" 2>/dev/null | grep -E '^docs/fixes/[a-z]+/[0-9][^/]*\.md$' || true)"
83
+ touched="$(git diff --name-only "$range" 2>/dev/null | grep -v '^docs/fixes/' | grep -v '^CHANGELOG' || true)"
84
+
85
+ if [ -z "$notes" ]; then
86
+ printf 'fixes-guard[1]{gate,result}:\n "push","refused — no fix note in this range"\n' >&2
87
+ printf 'help: bin/fixes.sh record --component=<%s> --version=<v> --title="what changed"\n' \
88
+ "install|runtime|skills|agents|hlidskjalf|odrerir|sessrumnir|smidja|hoard|gate" >&2
89
+ printf 'note: a note is a file under docs/fixes/<component>/ — write one, commit it, push again.\n' >&2
90
+ exit 1
91
+ fi
92
+
93
+ # The record is there: now say whether it speaks to what the push changed.
94
+ covered=0
95
+ for n in $notes; do
96
+ c="$(printf '%s' "${n#docs/fixes/}" | cut -d/ -f1)"
97
+ case "$touched" in
98
+ *"$c"*) covered=$((covered+1)) ;;
99
+ *bin/fixes*|*install*) [ "$c" = install ] && covered=$((covered+1)) ;;
100
+ *bin/ymir*|*bin/sessrumnir*|*bin/smidja*|*bin/npm-publish*|*.agents/skills/*) covered=$((covered+1)) ;;
101
+ esac
102
+ done
103
+
104
+ count="$(printf '%s\n' "$notes" | grep -c . || true)"
105
+ if [ "$covered" -gt 0 ]; then
106
+ printf 'fixes-guard[1]{gate,result}:\n "push","ok — %s note(s), and the record speaks to the change"\n' "$count"
107
+ else
108
+ printf 'fixes-guard[1]{gate,result}:\n "push","ok — %s note(s); none names a path this range touched"\n' "$count"
109
+ printf 'note: a note for another component is still a record — but say what THIS push changed.\n' >&2
110
+ printf ' components touched: %s\n' "$(printf '%s' "$touched" | head -4 | tr '\n' ' ')" >&2
111
+ fi
112
+ exit 0
package/bin/fixes.sh ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env bash
2
+ # fixes.sh — the fix notes. ONE FILE PER FIX, and nothing to fold.
3
+ #
4
+ # The two systems this replaces both failed us, and for the same reason: they kept
5
+ # a MONOLITH (CHANGELOG.md) and fed it from fragments (CHANGELOG.d). The pre-push
6
+ # hook folded the fragments into the monolith, then demanded a commit for the edit
7
+ # it had just made — a push that mutates the tree it is pushing, and a gate that
8
+ # passed if a range merely *touched* the file.
9
+ #
10
+ # So there is no monolith and no fold here. A note IS a file:
11
+ #
12
+ # docs/fixes/<component>/<version>-<slug>.md
13
+ #
14
+ # Two branches never write the same path, so the collision is impossible. The
15
+ # history of a component is its directory, sorted. Nothing is ever assembled at
16
+ # push time, and the gate that guards it only READS (bin/fixes-guard.sh).
17
+ #
18
+ # bin/fixes.sh record --component=install --version=0.1.28 --title="…" \
19
+ # [--why="…"] [--files="a,b"] [--date=YYYY-MM-DD]
20
+ # bin/fixes.sh list [<component>] # the components, or one's notes
21
+ # bin/fixes.sh show <file|component> # a note, or the newest of a component
22
+ # bin/fixes.sh diff --component=X --from=V --to=V
23
+ # bin/fixes.sh validate # every note's header, and its order
24
+ # bin/fixes.sh components # just the names
25
+ #
26
+ # Exit: 0 ok, 1 error, 2 usage.
27
+ set -u
28
+
29
+ VERSION="1.0.0"
30
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
32
+ FIXES="$ROOT/docs/fixes"
33
+
34
+ # The components are the machine's own parts, not an invented list.
35
+ COMPONENTS="install runtime skills agents hlidskjalf odrerir sessrumnir smidja hoard gate"
36
+
37
+ usage() { sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//'; }
38
+ die() { printf 'error: %s\n' "$1" >&2; [ -n "${2:-}" ] && printf 'help: %s\n' "$2" >&2; exit 1; }
39
+ known() { case " $COMPONENTS " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
40
+ slugify() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/--*/-/g; s/^-//; s/-$//' | cut -c1-56; }
41
+
42
+ action="${1-}"; shift || true
43
+ case "$action" in
44
+ -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
45
+ -h|--help|"") usage; exit 0 ;;
46
+ esac
47
+
48
+ component=""; version=""; title=""; why=""; files=""; date=""; from=""; to=""
49
+ for a in "$@"; do
50
+ case "$a" in
51
+ --component=*) component="${a#*=}" ;;
52
+ --version=*) version="${a#*=}" ;;
53
+ --title=*) title="${a#*=}" ;;
54
+ --why=*) why="${a#*=}" ;;
55
+ --files=*) files="${a#*=}" ;;
56
+ --date=*) date="${a#*=}" ;;
57
+ --from=*) from="${a#*=}" ;;
58
+ --to=*) to="${a#*=}" ;;
59
+ -*) die "unknown argument $a" "bin/fixes.sh --help" ;;
60
+ *) pos="${a}" ;;
61
+ esac
62
+ done
63
+
64
+ notes_of() { # <component> — its notes, newest first (the version decides the order)
65
+ local c="$1"
66
+ [ -d "$FIXES/$c" ] || return 0
67
+ ls -1 "$FIXES/$c"/*.md 2>/dev/null | sort -V -r
68
+ }
69
+
70
+ case "$action" in
71
+ components)
72
+ printf 'components[%d]{name}:\n' "$(printf '%s\n' $COMPONENTS | wc -l | tr -d ' ')"
73
+ for c in $COMPONENTS; do printf ' "%s"\n' "$c"; done
74
+ ;;
75
+
76
+ list)
77
+ c="${pos:-${component:-}}"
78
+ if [ -n "$c" ]; then
79
+ known "$c" || die "unknown component $c" "one of: $COMPONENTS"
80
+ n=0; while IFS= read -r f; do [ -n "$f" ] || continue; n=$((n+1)); printf ' "%s","%s","%s"\n' "$c" "$(basename "$f")" "$(sed -n '1s/^## [^·]*· \([^ ]*\) ·.*/\1/p' "$f")"; done < <(notes_of "$c")
81
+ [ "$n" -gt 0 ] || printf 'fixes: %s has no notes yet\n' "$c"
82
+ else
83
+ printf 'fixes[%d]{component,notes}:\n' "$(printf '%s\n' $COMPONENTS | wc -l | tr -d ' ')"
84
+ for c in $COMPONENTS; do
85
+ cnt=$(notes_of "$c" | grep -c . || true)
86
+ printf ' "%s",%s\n' "$c" "$cnt"
87
+ done
88
+ fi
89
+ ;;
90
+
91
+ show)
92
+ what="${pos:-${component:-}}"; [ -n "$what" ] || die "what shall I show?" "bin/fixes.sh show <component|file>"
93
+ if [ -f "$what" ]; then f="$what"
94
+ elif [ -f "$FIXES/$what" ]; then f="$FIXES/$what"
95
+ else
96
+ known "$what" || die "unknown component $what" "one of: $COMPONENTS"
97
+ f="$(notes_of "$what" | head -1)"
98
+ [ -n "$f" ] || { printf 'fixes: %s has no notes yet\n' "$what"; exit 0; }
99
+ fi
100
+ cat "$f"
101
+ ;;
102
+
103
+ record)
104
+ [ -n "$component" ] && [ -n "$version" ] && [ -n "$title" ] \
105
+ || die "record needs --component, --version and --title" \
106
+ "bin/fixes.sh record --component=install --version=0.1.28 --title=\"…\""
107
+ known "$component" || die "unknown component $component" "one of: $COMPONENTS"
108
+ date="${date:-$(date -u +%Y-%m-%d)}"
109
+ dir="$FIXES/$component"; mkdir -p "$dir"
110
+ f="$dir/${version}-$(slugify "$title").md"
111
+ [ -e "$f" ] && die "that note already exists: ${f#"$ROOT"/}" "bin/fixes.sh show ${f#"$ROOT"/}"
112
+ {
113
+ printf '## %s · %s · %s — %s\n\n' "$component" "$version" "$date" "$title"
114
+ [ -n "$why" ] && printf '### Why\n%s\n\n' "$why"
115
+ printf '### Files\n'
116
+ if [ -n "$files" ]; then printf '%s' "$files" | tr ',' '\n' | sed 's/^ *//; s/^/- `/; s/$/`/'
117
+ else printf -- '- *(none named)*\n'; fi
118
+ } >"$f"
119
+ printf 'fixes[1]{component,version,note}:\n "%s","%s","%s"\n' "$component" "$version" "${f#"$ROOT"/}"
120
+ ;;
121
+
122
+ diff)
123
+ [ -n "$component" ] || die "diff needs --component" "bin/fixes.sh diff --component=install --from=0.1.27 --to=0.1.28"
124
+ known "$component" || die "unknown component $component"
125
+ n=0
126
+ while IFS= read -r f; do
127
+ [ -n "$f" ] || continue
128
+ b="$(basename "$f")"
129
+ case "$b" in
130
+ "${from}"-*|"${to}"-*) n=$((n+1)); printf '\n'; cat "$f" ;;
131
+ esac
132
+ done < <(notes_of "$component")
133
+ [ "$n" -gt 0 ] || printf 'fixes: nothing between %s and %s for %s\n' "$from" "$to" "$component"
134
+ ;;
135
+
136
+ validate)
137
+ fails=0; checked=0
138
+ [ -d "$FIXES" ] || { printf 'fixes: docs/fixes/ does not exist yet\n'; exit 0; }
139
+ for d in "$FIXES"/*/; do
140
+ [ -d "$d" ] || continue
141
+ c="$(basename "$d")"
142
+ known "$c" || { printf ' "%s","unknown component directory"\n' "$c"; fails=$((fails+1)); continue; }
143
+ for f in "$d"*.md; do
144
+ [ -e "$f" ] || continue
145
+ checked=$((checked+1))
146
+ hdr="$(sed -n '1p' "$f")"
147
+ printf '%s' "$hdr" | grep -qE "^## ${c} · ([0-9]+\.[0-9]+\.[0-9]+[^ ]*|unversioned) · [0-9]{4}-[0-9]{2}-[0-9]{2} — .+" \
148
+ || { printf ' "%s","malformed header: %s"\n' "${f#"$ROOT"/}" "$hdr"; fails=$((fails+1)); }
149
+ grep -q '^### Files$' "$f" || { printf ' "%s","no Files section"\n' "${f#"$ROOT"/}"; fails=$((fails+1)); }
150
+ done
151
+ done
152
+ if [ "$fails" = 0 ]; then printf 'fixes[1]{state,checked}:\n "valid",%s\n' "$checked"
153
+ else printf 'fixes[1]{state,failed}:\n "invalid",%s\n' "$fails"; exit 1; fi
154
+ ;;
155
+
156
+ *) die "unknown action ${action:-}" "bin/fixes.sh [record|list|show|diff|validate|components]" ;;
157
+ esac
package/bin/ymir.js CHANGED
@@ -82,6 +82,40 @@ function versionNotice() {
82
82
  } catch { /* a version notice must never break a door */ }
83
83
  }
84
84
 
85
+ // ── is this copy current? ────────────────────────────────────────────────────
86
+ // An install can succeed and change NOTHING: npm resolves a cached `latest`, sees
87
+ // the same version, and says "changed 200 packages" while the old build stays. A
88
+ // user cannot tell — so the tool says it, once a day, where a human is watching.
89
+ function checkForUpdate() {
90
+ if (!process.stderr.isTTY || process.env.YMIR_NO_UPDATE_CHECK === '1') return;
91
+ if (noticeState('version') === 'off') return;
92
+ try {
93
+ const os = require('node:os');
94
+ const stamp = path.join(STATE_DIR, 'update-check');
95
+ const today = new Date().toISOString().slice(0, 10);
96
+ if (fs.existsSync(stamp) && fs.readFileSync(stamp, 'utf8').trim() === today) return;
97
+ fs.mkdirSync(STATE_DIR, { recursive: true });
98
+ fs.writeFileSync(stamp, today + '\n');
99
+ const https = require('node:https');
100
+ const req = https.get('https://registry.npmjs.org/@zerwiz/ymir/latest', { timeout: 1500 }, (res) => {
101
+ let body = '';
102
+ res.on('data', (d) => { body += d; });
103
+ res.on('end', () => {
104
+ try {
105
+ const latest = JSON.parse(body).version;
106
+ if (latest && latest !== pkg.version) {
107
+ process.stderr.write('\n' + bronze(' a newer Ymir is on npm ') + faint(`${pkg.version} → `) + bronze(latest) + '\n' +
108
+ faint(' update: npm i -g @zerwiz/ymir (no PATH? npx @zerwiz/ymir)') + '\n' +
109
+ (noticeState('hints') === 'off' ? '' : faint(' (not again: ymir config notice version off)') + '\n'));
110
+ }
111
+ } catch { /* a notice must never break a door */ }
112
+ });
113
+ });
114
+ req.on('error', () => {});
115
+ req.on('timeout', () => req.destroy());
116
+ } catch { /* never break a door */ }
117
+ }
118
+
85
119
  // ── the doors ────────────────────────────────────────────────────────────────
86
120
  // verb → { script, args } — args are prepended to whatever the operator passes,
87
121
  // so a verb can be a doorway to a sub-verb of a script that has several.
@@ -143,6 +177,7 @@ const argv = process.argv.slice(2);
143
177
  const first = argv[0];
144
178
 
145
179
  versionNotice();
180
+ checkForUpdate();
146
181
  if (!first) run(DOORS.install.script, []);
147
182
  if (first === '--version' || first === '-v' || first === '-V') {
148
183
  process.stdout.write(`${pkg.version}\n`);
@@ -0,0 +1,15 @@
1
+ ## agents · unversioned · 2026-09-11 — Chat uses the root Pi model catalog
2
+
3
+ ### Why
4
+ - **Fix:** the Kaia chat guessed a llama.cpp model id by regex and fell through
5
+ to the Bifrost bridge (`:4603` → 401) when the guess failed.
6
+ - **Now:** the gate API reads the operator's root Pi catalog
7
+ (`~/.pi/agent/models.json`) for the exact provider → base URL, model id, and
8
+ key (llama.cpp router `:8080`, `sk-not-key-required`). A chosen local model
9
+ is tried only on its own base; the default is the operator's Pi default
10
+ (`qwen3.6-35b-a3b@iq3_s`). `/api/chat/models` lists the real connected set.
11
+ - **Verified:** `gemma-4-12b@q3_k_s`, `qwen3.6-35b-a3b@iq3_s` (default), and
12
+ `qwen3.5-9b@q4_k_s` all answered live; no 401.
13
+
14
+ ### Files
15
+ - *(carried from the frozen CHANGELOG.md)*
@@ -0,0 +1,26 @@
1
+ ## agents · unversioned · 2026-09-11 — Shared well for every harness; no mock; clickable memories
2
+
3
+ ### Why
4
+ - **Harnesses:** the engram MCP server is now registered for all four OpenCode
5
+ accounts (`opencode`, `opencode-rd`, `opencode-work`, `opencode-oczer`), Pi
6
+ (`~/.pi/agent/settings.json` + `mcp.json` + `.pi/mcp.json`), Claude, Cursor,
7
+ and Codex. Registered **unscoped** so every harness reads the one shared well
8
+ (per-call `agent_id` still attributes writes).
9
+ - **No mock:** purged every test/mock episode (`smoke`/`Test observation`) from
10
+ both the engram store and `episodes.jsonl` → 367 true episodes. Test writes
11
+ never enter the well.
12
+ - **UI:** Well memories are now **clickable** — `GET /api/well/episode?id=` and
13
+ a bridge `/episode` endpoint feed a modal with the full memory text.
14
+ - **Bridge fix:** `/recent` and `/recall` restored; added `/episode`.
15
+ - **Lifecycle fix:** `bifrost-bridge.sh` / `mimir-bridge.sh` now stop by process
16
+ match when the PID file is missing, and record the PID when the port is
17
+ already up — so `scripts/stop.sh` truly lowers the whole system and
18
+ `scripts/start.sh` truly raises it.
19
+ - **Galdr:** new asset `.agents/skills/galdr-ymirsystem/assets/memory-well.md` (store,
20
+ bridge, MCP, harness matrix, laws, verify), routed in `SKILL.md`; registry +
21
+ harness README updated; mirrored to tyr.
22
+ - **Verified:** MCP recall 1.0 / stats 367, full stop→start cycle (3888/3889/
23
+ 4602/4603/8437), compliance 8/8, smoke 8/8.
24
+
25
+ ### Files
26
+ - *(carried from the frozen CHANGELOG.md)*
@@ -0,0 +1,26 @@
1
+ ## agents · unversioned · 2026-09-13 — Gleipnir sees when an agent is turned off
2
+
3
+ ### Why
4
+ - **Problem:** the machine lock stuck “held by another Brokk session” forever.
5
+ Nothing released it ( `gleipnir_lock_release` had zero callers; Pi's exit
6
+ hook only stopped the arm child), and liveness was `kill(0)` alone — which
7
+ reports a zombie (dead but unreaped) and a recycled pid as alive, so an
8
+ agent you turned off kept the helm until its pid happened to look gone.
9
+ Worse, the Sessrúmnir desktop RPC session holds the live lock yet never
10
+ arms supervision (no turn ever calls `gna_watch_arm`), stranding every
11
+ other session read-only.
12
+ - **Fix:** `bin/gleipnir-lock-lib.sh` now records the owner's starttime in a
13
+ `brokk.lock.starttime` sidecar and reaps a holder whose `/proc/<pid>/stat`
14
+ state is `Z`/`X` or whose starttime no longer matches (pid reuse) — dead,
15
+ zombie, and recycled holders are cleared at session start/acquire. Gná
16
+ (`gna-pi-watch.ts`) mirrors that liveness, reclaims a stale lock directly in
17
+ `gna_watch_arm` (`missing` no longer punts to saga-session-start.sh), and
18
+ drops its own lock on real process exit (`releaseLockIfOwned`); the turn-end
19
+ guard (`syn-turnend-guard.ts`) uses the same zombie-aware check.
20
+ - **Impact:** turning an agent off — cleanly, by kill (zombie), or after pid
21
+ reuse — frees the machine lock for the next session. The live desktop seat
22
+ still holds it until that app is closed or its own session arms; that is the
23
+ law (on
24
+
25
+ ### Files
26
+ - *(carried from the frozen CHANGELOG.md)*
@@ -0,0 +1,23 @@
1
+ ## agents · unversioned · 2026-09-14 — The Eindri→Brokk wake bridge (they can talk back now)
2
+
3
+ ### Why
4
+ - **Ask:** the seated smiths' reports lived in their panes only; the Allfather
5
+ wanted a feature that wakes Brokk when an Eindri reports, so the fleet's
6
+ doings reach the primary's session — "we are in control, Brokk".
7
+ - **Built:** `bin/eindri-seen.sh` (condition — a report file landed or herdr
8
+ shows the smith left `working`), `bin/eindri-acclaim.sh` (action — files the
9
+ report durably under state/eindri-reports/, marks done under
10
+ state/eindri-done/, appends the wake to state/.wake-queue for Sága's drain,
11
+ sounds the desktop note), and `bin/eindri-watch.sh` (the control door —
12
+ `arm | retire | list | reconcile`, one when-source per smith on the Norns'
13
+ loom via fm-procevent-when.sh, action hash-bound, fires once on a stable
14
+ true, terminal, re-armable).
15
+ - **Armed live:** when-odrerir (already fired — his report was filed) and
16
+ when-sessrumnir-cloth (still working). `fm-procement.sh reconcile` started=2.
17
+ - **First report delivered through the wire:** odrerir's Óðrerir saga — deck
18
+ green at :4322, 52 Chromium checks, commit 38a326b on yggdrasil/odrerir, main
19
+ untouched, plus the hall.ymir.zerwiz.org server story (setup-hall.sh) and
20
+ three asks awaiting the Allfather (go live, landing mobile wart, PLAN log).
21
+
22
+ ### Files
23
+ - *(carried from the frozen CHANGELOG.md)*
@@ -0,0 +1,18 @@
1
+ ## agents · unversioned · 2026-09-16 — a sandboxed worker cannot think: `auto` stops killing agents
2
+
3
+ ### Why
4
+ Spawning an Eindri with the default `--isolation auto` produced an **empty pane
5
+ and no status line**. The cause: `auto` chose Utgard whenever the image existed,
6
+ and Utgard runs with `--network none` — so the worker could reach neither its
7
+ cloud model (OpenCode Go) nor a local one (llama.cpp) and died at launch, silently.
8
+
9
+ - **`auto` now keeps the worker in its worktree.** A spawned Eindri is a
10
+ model-driven worker: it must reach a model endpoint to think. Utgard is for
11
+ untrusted *code*, not for the agent's own brain, so the automatic choice is
12
+ `off`, and it says why.
13
+ - **`--isolation on` warns loudly.** It stays available for a sandbox that can
14
+ actually reach a model, but it now prints that Utgard has no network and the
15
+ agent will die silently without one.
16
+
17
+ ### Files
18
+ - *(carried from the frozen CHANGELOG.md)*
@@ -0,0 +1,25 @@
1
+ ## agents · unversioned · 2026-09-17 — AGENTS.md states the one law: no private data in the public repo
2
+
3
+ ### Why
4
+ - **The law is now first.** A `first_law` block sits directly under the mandate
5
+ in `AGENTS.md`: never store personal or private data in this repo — not a
6
+ secret, a key, a name, a plan, a schedule, a client, a credential, or a note.
7
+ The repo is public; private data lives at `$YMIR_HOME`, under `hodd/`.
8
+ - **The private-data section was rebuilt against the disk.** It had duplicated
9
+ `secrets/` and `identity/` lines, no `hodd/` level, and a layout that no longer
10
+ matched the home. It now shows the real tree, names `$YMIR_HOME/hodd/` as *the*
11
+ private data path, and records that `bin/hoard-lib.sh` is the one source of
12
+ truth for it.
13
+ - **Every `$YMIR_HOME/...` path in the file was corrected** to `$YMIR_HOME/hodd/...`
14
+ — the directory rules table, the registry reference, the secrets reference, and
15
+ the append-only ledger path all pointed at the old flat layout.
16
+ - **Two wards are now documented**: `bin/secret-guard.sh` (a secret entering the
17
+ repo) and Eir's `hoard` surface (private data drifting outside the hoard, or a
18
+ `.ymir-layout.yaml` naming a path that does not exist).
19
+ - **Staging discipline is written down.** Stage named files in the home, never
20
+ `git add -A` — a scratch file written seconds earlier gets swept into a commit
21
+ and pushed. This happened this day and cost a history rewrite.
22
+ - **`hodd/AGENTS.example.md`** carried the same stale flat paths and a wrong
23
+
24
+ ### Files
25
+ - *(carried from the frozen CHANGELOG.md)*
@@ -0,0 +1,25 @@
1
+ ## agents · unversioned · 2026-09-17 — every figure loads: the agent directory was never read
2
+
3
+ ### Why
4
+ - **The bug, stated plainly.** Twenty agent profiles existed, twenty symlinks
5
+ pointed at them, and every gate was green — yet OpenCode loaded **three**
6
+ agents. The symlinks lived in `.opencode/agent/` (SINGULAR). OpenCode reads
7
+ `.opencode/agents/` (PLURAL). Twenty correct links sat in a directory no loader
8
+ opened, so only `brokk` and `hnoss` — the two declared by hand in
9
+ `opencode.json` — ever appeared.
10
+ - **The directory is fixed and migrated.** `.opencode/agent/` → `.opencode/agents/`
11
+ (20 links, all resolving). `bin/valknut-load.sh` now writes the plural path and
12
+ **migrates a legacy singular dir forward**, so an old home heals instead of
13
+ silently keeping its agents invisible.
14
+ - **The roster now declares what it names.** `bin/agents-config.sh apply` only
15
+ touched an agent already present in `opencode.json` (`if a in ablock`), so a
16
+ figure the roster knew but the config had never seen stayed undeclared. The
17
+ roster is the source of truth; a name in it now reaches the harness config.
18
+ - **A new gate catches the class.** `compliance-check.sh` gains **`roster`**: the
19
+ roster (`config/agents.yaml.example`) and the canonical tree
20
+ (`.agents/agents/*.md`) must name the same figures. A figure in the tree but not
21
+ the roster falls to `default_model` and is never declared; a figure in the
22
+ roster with no profile is a phantom. This is the check that would have caught
23
+
24
+ ### Files
25
+ - *(carried from the frozen CHANGELOG.md)*