@zerwiz/ymir 0.1.13 → 0.1.15

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 (78) hide show
  1. package/.agents/agents/brokk.md +10 -9
  2. package/.agents/agents/forseti-reviewer.md +2 -2
  3. package/.agents/agents/mimir-planner.md +2 -2
  4. package/.agents/assets/agents/registry.md +22 -0
  5. package/.agents/config/agents.yaml +53 -0
  6. package/.agents/config/{cron.yaml → cron.yaml.example} +5 -3
  7. package/.agents/config/tailscale-sync.yaml +9 -0
  8. package/.agents/config/wedge-alarm +3 -0
  9. package/.agents/migrations/0003-private-data-separation.sh +4 -4
  10. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +6 -1
  11. package/.agents/skills/galdr-ymirsystem/assets/installation.md +5 -4
  12. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +5 -5
  13. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +59 -4
  14. package/.agents/skills/galdr-ymirsystem/assets/registry.md +74 -338
  15. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +1 -1
  16. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +5 -3
  17. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +1 -1
  18. package/.agents/skills/ratatoskr-a2a/SKILL.md +1 -1
  19. package/.agents/skills/tyr-check/SKILL.md +5 -5
  20. package/.agents/skills/ymir-host/SKILL.md +1 -1
  21. package/.agents/state/.wake-queue +0 -0
  22. package/.agents/state/.wake-queue.seq +1 -0
  23. package/.agents/state/.watcher-down +1 -0
  24. package/.agents/state/procevent-inbox/when-hall-button.1.adapter +1 -0
  25. package/.agents/state/procevent-inbox/when-hall-button.1.handled +0 -0
  26. package/.agents/state/procevent-inbox/when-hall-button.1.result +8 -0
  27. package/.agents/state/procevent-inbox/when-hall-button.2.adapter +1 -0
  28. package/.agents/state/procevent-inbox/when-hall-button.2.handled +0 -0
  29. package/.agents/state/procevent-inbox/when-hall-button.2.result +5 -0
  30. package/.agents/state/procevent-inbox/when-odrerir.1.adapter +1 -0
  31. package/.agents/state/procevent-inbox/when-odrerir.1.handled +0 -0
  32. package/.agents/state/procevent-inbox/when-odrerir.1.result +8 -0
  33. package/.agents/state/procevent-inbox/when-odrerir.2.adapter +1 -0
  34. package/.agents/state/procevent-inbox/when-odrerir.2.handled +0 -0
  35. package/.agents/state/procevent-inbox/when-odrerir.2.result +5 -0
  36. package/.agents/state/procevent-inbox/when-odrerir.3.adapter +1 -0
  37. package/.agents/state/procevent-inbox/when-odrerir.3.handled +0 -0
  38. package/.agents/state/procevent-inbox/when-odrerir.3.result +5 -0
  39. package/.agents/state/procevent-inbox/when-odrerir.4.adapter +1 -0
  40. package/.agents/state/procevent-inbox/when-odrerir.4.handled +0 -0
  41. package/.agents/state/procevent-inbox/when-odrerir.4.result +8 -0
  42. package/.agents/state/procevent-inbox/when-odrerir.5.adapter +1 -0
  43. package/.agents/state/procevent-inbox/when-odrerir.5.handled +0 -0
  44. package/.agents/state/procevent-inbox/when-odrerir.5.result +8 -0
  45. package/.agents/state/procevent-inbox/when-sessrumnir-cloth.1.adapter +1 -0
  46. package/.agents/state/procevent-inbox/when-sessrumnir-cloth.1.handled +0 -0
  47. package/.agents/state/procevent-inbox/when-sessrumnir-cloth.1.result +8 -0
  48. package/AGENTS.md +17 -10
  49. package/CHANGELOG.md +129 -0
  50. package/bin/a2a-mcp.sh +9 -2
  51. package/bin/app-lib.sh +8 -1
  52. package/bin/docs-guard.sh +1 -1
  53. package/bin/erindi-brief.sh +1 -1
  54. package/bin/hamr-harness.sh +1 -1
  55. package/bin/hoard-lib.sh +10 -0
  56. package/bin/mimir-bridge.py +8 -1
  57. package/bin/mimir-bridge.sh +7 -1
  58. package/bin/mimir-reflect.sh +8 -1
  59. package/bin/nornir-cron-start.sh +21 -1
  60. package/bin/nornir-job-bragi-scrape.sh +116 -0
  61. package/bin/nornir-job-daily-briefing.sh +7 -7
  62. package/bin/nornir-job-forgejo-git.sh +87 -0
  63. package/bin/nornir-job-observer.sh +5 -4
  64. package/bin/rodd-operational-input.sh +1 -1
  65. package/bin/saga-session-start.sh +4 -3
  66. package/bin/sessrumnir.sh +11 -0
  67. package/bin/syn-arm-pretool-check.sh +1 -1
  68. package/bin/syn-cd-pretool-check.sh +1 -1
  69. package/bin/syn-watch-arm.sh +1 -1
  70. package/bin/vor-crew-state.sh +1 -1
  71. package/bin/ymir-install.sh +47 -2
  72. package/bin/ymir-marketing-stack.sh +323 -0
  73. package/docs/runbooks/models.md +1 -1
  74. package/docs/workspaces.md +4 -0
  75. package/package.json +10 -4
  76. package/scripts/electron.sh +2 -2
  77. package/.agents/bus/messages.json +0 -1
  78. package/.agents/config/agents.machine.example.yaml +0 -16
@@ -0,0 +1,8 @@
1
+ when: when-odrerir
2
+ status: fired
3
+ detail: the condition held and the action exited 0
4
+ condition_polls: 2
5
+ action_exit: 0
6
+ output:
7
+ eindri-acclaim[1]{agent,detail}:
8
+ "odrerir","report filed at state/eindri-reports/odrerir.md"
@@ -0,0 +1,5 @@
1
+ when: when-odrerir
2
+ status: ambiguous
3
+ detail: the action was already claimed but its outcome was never captured; verify its effect manually before retiring
4
+ condition_polls: 0
5
+ output:
@@ -0,0 +1,5 @@
1
+ when: when-odrerir
2
+ status: ambiguous
3
+ detail: the action was already claimed but its outcome was never captured; verify its effect manually before retiring
4
+ condition_polls: 0
5
+ output:
@@ -0,0 +1,8 @@
1
+ when: when-odrerir
2
+ status: fired
3
+ detail: the condition held and the action exited 0
4
+ condition_polls: 59
5
+ action_exit: 0
6
+ output:
7
+ eindri-acclaim[1]{agent,detail}:
8
+ "odrerir","left working (herdr state flipped; read the pane: herdr agent read odrerir)"
@@ -0,0 +1,8 @@
1
+ when: when-odrerir
2
+ status: fired
3
+ detail: the condition held and the action exited 0
4
+ condition_polls: 110
5
+ action_exit: 0
6
+ output:
7
+ eindri-acclaim[1]{agent,detail}:
8
+ "odrerir","left working (herdr state flipped; read the pane: herdr agent read odrerir)"
@@ -0,0 +1,8 @@
1
+ when: when-sessrumnir-cloth
2
+ status: fired
3
+ detail: the condition held and the action exited 0
4
+ condition_polls: 105
5
+ action_exit: 0
6
+ output:
7
+ eindri-acclaim[1]{agent,detail}:
8
+ "sessrumnir-cloth","left working (herdr state flipped; read the pane: herdr agent read sessrumnir-cloth)"
package/AGENTS.md CHANGED
@@ -89,7 +89,7 @@ governed[7]{path,load_first}:
89
89
  "apps/hlidskjalf/**",".agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md"
90
90
  "apps/odrerir/**",".agents/skills/galdr-ymirsystem/assets/odrerir-hall.md"
91
91
  "bin/mimir*.sh | bin/mimir-bridge.py",".agents/skills/galdr-ymirsystem/assets/memory-well.md"
92
- "bin/nornir-* | config/cron.yaml",".agents/skills/galdr-ymirsystem/assets/nornir-jobs.md"
92
+ "bin/nornir-* | config/cron.yaml*",".agents/skills/galdr-ymirsystem/assets/nornir-jobs.md"
93
93
  "bin/valknut-load.sh | .pi/** | .opencode/**",".agents/skills/galdr-ymirsystem/assets/harness-integration/README.md"
94
94
  "bin/smidja* | .agents/skills/smidja-factory/**",".agents/skills/galdr-ymirsystem/assets/smidja.md"
95
95
  ```
@@ -167,8 +167,8 @@ them may ever be written inside this repo** (see "Private data — YMIR_HOME").
167
167
  outputs[7]{kind,path}:
168
168
  "Business / strategy","$YMIR_HOME/hodd/identity/companies/"
169
169
  "Marketing / social","$YMIR_HOME/hodd/workspaces/marketing/"
170
- "Software specs","$YMIR_HOME/hodd/workspaces/development/"
171
- "Personal / schedules","$YMIR_HOME/hodd/workspaces/life/"
170
+ "Software specs","$YMIR_HOME/hodd/workspaces/work/"
171
+ "Personal / schedules","$YMIR_HOME/hodd/workspaces/personal/"
172
172
  "Daily logs","$YMIR_HOME/hodd/memory/daily/YYYY-MM-DD.md"
173
173
  "Shared PUBLIC assets","midgard/"
174
174
  "Global audit entries","$YMIR_HOME/hodd/memory/runes_audit.md"
@@ -207,7 +207,7 @@ isolation[8]{id,rule}:
207
207
  - Redis pub/sub is the queue *under* the A2A task model (A2A = semantics, Redis =
208
208
  throughput). Messages follow `.agents/bus/protocol.ts`.
209
209
  - Every A2A message is observed into **Mimirsbrunn** and logged to **Runes**.
210
- - **Status:** the native backbone is **planned, not built** — the plan is private at `hodd/docs/ratatoskr.md`. Today an external `a2abridge` daemon (A2A + MCP over Tailscale) is what runs; `.agents/bus/` is a stub. Way of Teams (`~/CodeP/wayofteams`) is the sold control plane; Ymir's daemon must also work standalone.
210
+ - **Status:** the native backbone is **planned, not built** — the plan is private at `memory/plans/mesh/25-ratatoskr-a2a.md` (in `$YMIR_HOME`). Today an external `a2abridge` daemon (A2A + MCP over Tailscale) is what runs; `.agents/bus/` is a stub. Way of Teams (`~/CodeP/wayofteams`) is the sold control plane; Ymir's daemon must also work standalone.
211
211
  - Kaia orchestrates: dispatch Eindri as A2A tasks, recall memory before dispatch,
212
212
  honour the anti-hallucination gate; specialists reach tools via MCP.
213
213
 
@@ -389,21 +389,28 @@ env-driven, **never** in this repo. The real layout:
389
389
 
390
390
  ```
391
391
  YMIR_HOME/ ← private git repo (pushed to the user's private GitHub)
392
+ ├── README.md / AGENTS.md # home map + the private home contract
392
393
  ├── hodd/ ← THE HOARD: all private data lives under here
393
- │ ├── identity/ # projects.yaml, workspaces.yaml, companies/, realms
394
394
  │ ├── data/ # operator, fleet, machines, inventories
395
- │ ├── docs/ # masterplan, plans/, runbooks, incidents
395
+ │ ├── docs/ # masterplan.md, business/, daily/, server-knowledge/
396
+ │ ├── identity/ # companies/, realms, entity cards
397
+ │ ├── memory/ # well, daily logs, runes audit ledger
398
+ │ ├── plans/ # live working plans
396
399
  │ ├── secrets/ # platform.env.age + age.key (the vault)
397
- │ ├── tenants/ # per-tenant private trees
398
- │ └── memory/ # well, daily logs, audit ledger
400
+ │ ├── state/ # runtime state; stale-* backups
401
+ │ └── workspaces/ # marketing/ · personal/ · work/
402
+ ├── memory/plans/<domain>/ # plan archive, domain-sorted (tracked)
399
403
  ├── config/ # agents.yaml and per-machine overlays
400
- ├── memory/ # the live engram (kaia.engram)
401
404
  ├── state/ # runtime state (ephemeral)
402
405
  ├── smidja/ # factory databases (ephemeral)
403
- ├── workspaces/ # work/, personal/, companies/
404
406
  └── svartalfaheim/ # per-realm scoped material
405
407
  ```
406
408
 
409
+ > 2026-09-17: the home was re-laid out — plans moved from `hodd/docs/plans/`
410
+ > to `memory/plans/<domain>/`, workspaces moved under `hodd/workspaces/`, and
411
+ > dropped-from-git material rests in `hodd/state/stale-*`. The hodd example
412
+ > (`hodd/AGENTS.example.md`) teaches the shape.
413
+
407
414
  `$YMIR_HOME/hodd/` **is** the private data path — there is no second, flat copy.
408
415
  `bin/hoard-lib.sh` resolves it (`hoard_root`), and it is the single source of
409
416
  truth: a script that needs the hoard calls it, never a hardcoded path.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,132 @@
1
1
 
2
+
3
+ ## 2026-09-17 — the npm door stands the surfaces
4
+
5
+ - **`@zerwiz/ymir` now depends on its surfaces**: `@zerwiz/{hlidskjalf,
6
+ odrerir, sessrumnir, smidja-factory}` are declared dependencies (bump
7
+ `0.1.14` → `0.1.15`), so `npm i -g @zerwiz/ymir` (or a packaged install)
8
+ delivers the app shells under `node_modules/@zerwiz/*`, which `app-lib`
9
+ resolves. The audit's one gap — the platform carried no apps — is closed.
10
+ - **`@zerwiz/smidja-factory` `0.1.1` → `0.1.2`**: the visualizer's nanoid fix
11
+ and the eye's-home SKILL line reach npm.
12
+
13
+ ## 2026-09-17 — the seat's name and the visualizer's home
14
+
15
+ - **omarchy, not omarchy-1** — the machine is `omarchy`; the fleet config docs
16
+ and the tailscale-sync map say so.
17
+ - **The Smíðja visualizer's home is the smidja repo clone** —
18
+ `apps/smidja-factory/apps/visualizer`; the skill tree carries no copy (the
19
+ stale build was moved aside). `smidja.md` (the governed asset), the smithy
20
+ SKILL, and the install asset all point at the clone.
21
+
22
+ ## 2026-09-17 — the layout swept: every reader taught the new doors
23
+
24
+ - **bin/nornir-job-observer.sh** — masterplan, runes ledger, and the well now
25
+ resolve to the hoard homes (`hodd/docs/masterplan.md`,
26
+ `hodd/memory/runes_audit.md`, `hodd/memory/well`), overridable via
27
+ `BROKK_MASTERPLAN`/`BROKK_WELL_DIR`.
28
+ - **bin/saga-session-start.sh** — the Sága digest's open-orders read follows
29
+ the home (`hodd/docs/masterplan.md`).
30
+ - **bin/nornir-job-daily-briefing.sh** — message/comment paths updated to the
31
+ same doors.
32
+ - **bin/docs-guard.sh** — block message names both new homes (hodd/docs,
33
+ memory/plans).
34
+ - **.agents/agents/{brokk,forseti-reviewer,mimir-planner}.md** — the plan
35
+ shelf, masterplan, and append-only log pointers moved to their hoard
36
+ homes (`memory/plans/<domain>/`, `hodd/docs/*`).
37
+ - **.agents/skills/{tyr-check,ymir-host,ratatoskr-a2a}/SKILL.md** — the same
38
+ pointers, plus the Ratatoskr spec → `memory/plans/mesh/25-ratatoskr-a2a.md`
39
+ and `hodd/docs/a2a-runs.md`.
40
+ - **docs/workspaces.md** — status note: workspaces settled under
41
+ `hodd/workspaces/` (2026-09-17).
42
+
43
+ ## 2026-09-17 — the home's re-laid layout taught to the system
44
+
45
+ - **The layout changed (2026-09-17):** plans filed by domain under
46
+ `$YMIR_HOME/memory/plans/<domain>/`, live plans in `hodd/plans/`,
47
+ workspaces under `hodd/workspaces/`, dropped-from-git material in
48
+ `hodd/state/stale-*`. The hodd example (`hodd/AGENTS.example.md`) now
49
+ teaches the new shape.
50
+ - **AGENTS.md** — the private-home tree block, the outputs table
51
+ (`hodd/workspaces/work/`, `hodd/workspaces/personal/`), and the Ratatoskr
52
+ plan pointer updated to the new homes.
53
+ - **bin/nornir-job-daily-briefing.sh** — the morning brief's masterplan and
54
+ plan-status reads follow the new layout (`hodd/docs/masterplan.md`,
55
+ `memory/plans`) with `BROKK_MASTERPLAN`/`BROKK_PLANS_DIR` overrides.
56
+
57
+ ## 2026-09-17 — the installer has a design of its own
58
+
59
+ - **The result is rendered, not dumped.** The plan got the cloth and the install did not: twenty-nine raw TOON rows and a poem, which the Allfather rightly called zero design. Now the same rows are rendered for the eye on stderr — a rule, a verdict (*"it stands — 22 steps done · 7 warned · 1 skipped"*), every warning with its reason, and the skips in one line. The TOON stays the data on stdout; the design is for the person.
60
+ - **The consent shows the plan again.** `--colour` renders on stderr and I had sent *both* streams to `/dev/null`, so the plan was invisible at the very moment it was being accepted.
61
+ - **The seat can mend its own runtime**, and says why when it cannot — npm gates Electron postinstall, so a fresh install otherwise ends with a window that cannot open.
62
+
63
+
64
+ ## 2026-09-17 — one well, in the hoard: every reader of Kaia's memory resolves the hoard store
65
+
66
+ - **The memory is ONE store and it lives in the hoard** —
67
+ `$YMIR_HOME/hodd/memory/kaia.engram` — never in the tree, never in a
68
+ migrated copy. `bin/hoard-lib.sh` gains `hoard_memory_store` (the one
69
+ resolver); every reader honors it or an explicit `ENGRAM_DB`:
70
+ `mimir-bridge.sh`/`mimir-bridge.py`, `mimir-reflect.sh`, `a2a-mcp.sh`,
71
+ `ymir-install.sh` step_memory, and the rendered `.pi/mcp.json.example`
72
+ (`__YMIR_HOME__/hodd/memory/kaia.engram`).
73
+ - **The duplicate is dead.** The old store (`$YMIR_HOME/memory/kaia.engram`,
74
+ plus the migration seed copy) is struck from this machine and the vault;
75
+ its 8 episodes were seeded into the hoard store first. The public ymir
76
+ repo's `.agents/memory/kaia.engram*` leftovers are deleted (already guarded
77
+ by `.gitignore`) and the 0003 migration now copies INTO the hoard.
78
+ - The Allfather's decision, accepted and recorded everywhere: the well must
79
+ travel between his computers, so it rides the private vault as one store.
80
+
81
+ galdr-reread: `.agents/skills/galdr-ymirsystem/assets/memory-well.md` — the
82
+ Store row, MCP command, law #4, and the Python recipe now name the hoard path.
83
+
84
+ ## 2026-09-17 — the local git round joins the loom; the stack's doors are in the registry
85
+
86
+ - **`bin/nornir-job-forgejo-git.sh` (02:45)** — reads the local forge's open
87
+ issues each night (`$YMIR_HOME/config/forge.env` names the door; token
88
+ optional), writes a daily digest to the hoard, carves Rune
89
+ `forgejo / git.issues` — or `git.door-down` (exit 1) when the tunnel is
90
+ closed, so a dead door is seen at sunrise. Unarmed without a URL, honestly.
91
+ - **The marketing stack's doors are now in the registry** —
92
+ `marketing_doors[6]` (Mautic · Postiz · Activepieces · Forgejo · SearXNG ·
93
+ Grafana) with reach notes, mirrored to the Galdr asset. Agents (Bragi ·
94
+ Sindri) provision the stack for any user via
95
+ `bin/ymir-marketing-stack.sh`, or reach the Allfather's live server stack.
96
+
97
+ galdr-reread: `.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md` — §3.7
98
+ (Forgejo round) and §3.8 (marketing stack provisioner); registry mirrored.
99
+
100
+ ## 2026-09-17 — the schedule is the user's: cron leaves the tree, lives in the home
101
+
102
+ - **The tracked tree no longer carries the live cron schedule.** The Nornir
103
+ scheduler now resolves the schedule in this order: `BROKK_CONFIG_OVERRIDE`
104
+ (explicit) → **the home's own `$YMIR_HOME/config/cron.yaml`** (users set
105
+ their jobs there) → the repo's `config/cron.yaml.example` (a template, never
106
+ the live list). `bin/nornir-cron-start.sh` resolves the home through
107
+ `bin/hoard-lib.sh`, the one answer the whole runtime shares.
108
+ - `config/cron.yaml` → `config/cron.yaml.example` (the tracked template stays;
109
+ the live schedule lives in the operator's home).
110
+ - **The marketing stack can be provisioned on any computer** —
111
+ `bin/ymir-marketing-stack.sh up|status|down|doors` stands Mautic + Postiz +
112
+ Activepieces (+ optionally Forgejo) from the same OSS engines the server
113
+ runs, env-driven, ports virtualized, secrets generated once into the home
114
+ (never inline); agents (Bragi · Sindri) provision it for any user.
115
+ - **Bragi's scrape round, honest to Firecrawl** — `bin/nornir-job-bragi-scrape.sh`
116
+ reads the operator's `config/scrape-sources.yaml` and scrapes with the real
117
+ Firecrawl SDK (scrape/search + markdown, BYOK key from the home's secrets),
118
+ landing clean markdown in the marketing workspace and carving a Rune.
119
+
120
+ galdr-reread: `.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md` — §2
121
+ rewritten (the schedule is the user's; example template; the four-inputs
122
+ contract unchanged).
123
+
124
+ ## 2026-09-17 — 0.1.14: the panels reach the gate, and the fourth window ships
125
+
126
+ - **`@zerwiz/hlidskjalf` 0.1.1** — the package now declares its own `name`, `files` and the absence of `private`, and ships **`vite.config.ts`**. Without that config a packaged SPA is served by `vite preview` with **no `/api` proxy**, so every panel answered `index.html` and died on `Unexpected token '<'<`. That was the whole panel fault.
127
+ - **`@zerwiz/odrerir` 0.1.1** — ships its **`electron/`** half, so the fourth hall can have a window; its name is `@zerwiz/odrerir` and it is no longer marked private (npm refuses those, EPRIVATE).
128
+ - Both apps declared what ships **in their own repos**, instead of a manifest rewritten at publish time — which is how the wrong things shipped in the first place.
129
+
2
130
  ## 2026-09-17 — 0.1.13: the re-voiced seat arrives, and the packages are listed
3
131
 
4
132
  - **`@zerwiz/sessrumnir` 0.1.9** — the seat that says Sessrúmnir and "Brokk is working", with the fork`s name gone from its source, its bundles, its installer and its package identity. Pinned so a fresh install receives it.
@@ -17,6 +145,7 @@
17
145
  old voice through the `0.1.8-alpha` pin.
18
146
  - **`@zerwiz/ymir@0.1.13`** is the packet that carries them.
19
147
 
148
+
20
149
  ## 2026-09-17 — 0.1.12: the icons reach the live tree, and a missing surface is a failure
21
150
 
22
151
  - **The launcher entries name the door** (`Exec=ymir hlidskjalf`, …) instead of an absolute path into whichever tree minted them — the fault that made every icon do nothing.
package/bin/a2a-mcp.sh CHANGED
@@ -36,8 +36,15 @@ if [ "$PROJECT" = 1 ]; then PI_MCP="$ROOT/.pi/mcp.json"; else PI_MCP="$HOME/.pi/
36
36
 
37
37
  WOTES="$(command -v wayofteams-mcp 2>/dev/null || true)"
38
38
  ENGRAM_BIN="${ENGRAM_BIN:-$HOME/.local/bin/engram-mcp}"
39
- YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
40
- ENGRAM_DB="${ENGRAM_DB:-$YMIR_HOME/memory/kaia.engram}"
39
+ # The well is ONE memory and it lives in the hoard — always. An explicit
40
+ # ENGRAM_DB is the operator's escape hatch; without it the hoard decides.
41
+ if [ -n "${ENGRAM_DB:-}" ]; then
42
+ ENGRAM_DB_ABS="$ENGRAM_DB"
43
+ else
44
+ . "$SCRIPT_DIR/hoard-lib.sh" 2>/dev/null || true
45
+ hoard_memory_store ENGRAM_DB_ABS
46
+ fi
47
+ ENGRAM_DB="$ENGRAM_DB_ABS"
41
48
 
42
49
  # The Teams plane (WayOfTeams) and Anchor memory are REMOTE MCP servers, named by
43
50
  # URL. The URL is credential-ish, so it comes from the environment — never the
package/bin/app-lib.sh CHANGED
@@ -49,7 +49,14 @@ app_dir() { # <surface> <result-var> — a clone's apps/<x>, else the package
49
49
  "$_apd_root/node_modules/@zerwiz/$_apd_pkg" \
50
50
  "$(dirname "$_apd_root")/$_apd_pkg"
51
51
  do
52
- [ -d "$_apd_c" ] && { printf -v "$_apd_rv" '%s' "$_apd_c"; return 0; }
52
+ # A hollow directory is not an app. An install step used to `mkdir` — or a
53
+ # clone used to fail — leaving `apps/<name>` empty, and that empty shell
54
+ # shadowed the real package beside it until the SPA could not be served and
55
+ # nobody knew why. An app declares itself: a manifest, a build, or sources.
56
+ if [ -d "$_apd_c" ] \
57
+ && { [ -f "$_apd_c/package.json" ] || [ -d "$_apd_c/dist" ] || [ -d "$_apd_c/src" ]; }; then
58
+ printf -v "$_apd_rv" '%s' "$_apd_c"; return 0
59
+ fi
53
60
  done
54
61
  printf -v "$_apd_rv" '%s' ""
55
62
  return 1
package/bin/docs-guard.sh CHANGED
@@ -35,5 +35,5 @@ if [ "${1:-}" = "--all" ]; then
35
35
  scan_list < <(git -C "$ROOT" ls-files)
36
36
  else
37
37
  scan_list < <(git -C "$ROOT" diff --cached --name-only --diff-filter=ACM)
38
- fi || { printf 'docs-guard: blocked — docs/ is public; move it to $YMIR_HOME/hodd/docs/ (Rule 04)\n' >&2; exit 1; }
38
+ fi || { printf 'docs-guard: blocked — docs/ is public; move it to $YMIR_HOME/hodd/docs/ (or $YMIR_HOME/memory/plans/ for plans) (Rule 04)\n' >&2; exit 1; }
39
39
  exit 0
@@ -6,7 +6,7 @@
6
6
  # readable "Delivery contract: mode=<mode>" line, the worktree-isolation
7
7
  # assertion, the status protocol, and the steering-inbox receive/ack section.
8
8
  # Ported from the upstream distro brief scaffold for plan 29
9
- # (docs/plans/29-brokk-distro-runtime.md).
9
+ # (memory/plans/core/29-brokk-distro-runtime.md).
10
10
  #
11
11
  # Usage:
12
12
  # erindi-brief.sh <task-id> <repo-name> --mode <no-mistakes|direct-PR|local-only>
@@ -5,7 +5,7 @@
5
5
  # runtime the process tree is wearing so Brokk can choose the correct adapter,
6
6
  # launch flags, and dispatch profile. Ported from the upstream agent-distro
7
7
  # reference (bin/hamr-harness.sh) and retargeted to the Brokk runtime
8
- # (docs/plans/29-brokk-distro-runtime.md).
8
+ # (memory/plans/core/29-brokk-distro-runtime.md).
9
9
  #
10
10
  # Usage:
11
11
  # hamr-harness.sh print own harness:
package/bin/hoard-lib.sh CHANGED
@@ -103,3 +103,13 @@ hoard_local_env() { # <result-var> — the operator's env file (secrets, ports)
103
103
  ymir_home_root home
104
104
  printf -v "$result_var" '%s' "${YMIR_ENV_FILE:-$home/.env.local}"
105
105
  }
106
+
107
+ hoard_memory_store() { # <result-var> — Kaia's engram store, ALWAYS in the hoard
108
+ # The well is one memory and it lives in the hoard (hodd/memory) — never in
109
+ # the tree, never in a migrated copy. Everything that reads the store resolves
110
+ # through here so no script can point the well at a duplicate (Rule 04/07).
111
+ local result_var=${1-} home
112
+ [ -n "$result_var" ] || return 2
113
+ ymir_home_root home
114
+ printf -v "$result_var" '%s' "$home/hodd/memory/kaia.engram"
115
+ }
@@ -29,7 +29,14 @@ from pathlib import Path
29
29
  from urllib.parse import parse_qs, urlparse
30
30
 
31
31
  ROOT = Path(__file__).resolve().parent.parent
32
- STORE = Path(os.environ.get("ENGRAM_DB", str(ROOT / ".agents/memory/kaia.engram")))
32
+ # The well is ONE memory and it lives in the hoard — never in the tree and
33
+ # never in a migrated copy. The shell bridge passes ENGRAM_DB; this default
34
+ # (hoard | legacy-local) keeps a hand-started bridge honest either way.
35
+ _home = Path(os.environ.get("YMIR_HOME", str(Path.home() / "Documents/Ymir")))
36
+ STORE = Path(os.environ.get(
37
+ "ENGRAM_DB",
38
+ str(_home / "hodd/memory/kaia.engram")
39
+ )).expanduser()
33
40
  HOST = os.environ.get("MIMIRSBRUNN_HOST", "127.0.0.1")
34
41
  PORT = int(os.environ.get("MIMIRSBRUNN_PORT", os.environ.get("ENGRAM_PORT", "4602")))
35
42
  DEFAULT_AGENT = os.environ.get("ENGRAM_AGENT_ID", "kaia")
@@ -33,7 +33,13 @@ hoard_state_dir YMIR_STATE_DIR
33
33
  hoard_data_dir YMIR_DATA_DIR
34
34
  YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
35
35
  BRIDGE="$ROOT/bin/mimir-bridge.py"
36
- DB="${ENGRAM_DB:-$YMIR_HOME/memory/kaia.engram}"
36
+ # The well is ONE memory and it lives in the hoard — always. An explicit
37
+ # ENGRAM_DB is the operator's escape hatch; without it the hoard decides.
38
+ if [ -n "${ENGRAM_DB:-}" ]; then
39
+ DB="$ENGRAM_DB"
40
+ else
41
+ hoard_memory_store DB
42
+ fi
37
43
  PORT="${MIMIRSBRUNN_PORT:-4602}"
38
44
  PID_FILE="${YMIR_STATE_DIR:-$YMIR_HOME/state}/mimir-bridge.pid"
39
45
  LOG_FILE="${YMIR_STATE_DIR:-$YMIR_HOME/state}/mimir-bridge.log"
@@ -11,7 +11,14 @@
11
11
  # YMIR_ROUTER_URL=http://127.0.0.1:8080/v1 YMIR_REFLECT_MODEL=qwen3.6-35b-a3b bin/mimir-reflect.sh
12
12
  set -euo pipefail
13
13
 
14
- WELL="${YMIR_WELL:-${YMIR_HOME:-$HOME/Documents/Ymir}/memory/kaia.engram}"
14
+ # The well is ONE memory and it lives in the hoard — always (Rule 04/07).
15
+ # An explicit YMIR_WELL/ENGRAM_DB is the operator's escape hatch.
16
+ if [ -n "${YMIR_WELL:-}" ] || [ -n "${ENGRAM_DB:-}" ]; then
17
+ WELL="${YMIR_WELL:-${ENGRAM_DB:-}}"
18
+ else
19
+ . "$SCRIPT_DIR/hoard-lib.sh" 2>/dev/null || true
20
+ hoard_memory_store WELL
21
+ fi
15
22
  BASE="${YMIR_ROUTER_URL:-http://127.0.0.1:1234/v1}"
16
23
  MODEL="${YMIR_REFLECT_MODEL:-apodex-1.0-mini}"
17
24
 
@@ -34,8 +34,28 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
34
34
  ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
35
35
  BROKK_HOME="${BROKK_HOME:-$ROOT}"
36
36
  STATE="${BROKK_STATE_OVERRIDE:-$BROKK_HOME/state}"
37
- CONFIG="${BROKK_CONFIG_OVERRIDE:-$BROKK_HOME/config}"
37
+ # The schedule is the USER'S, not the distro's: an explicit BROKK_CONFIG_OVERRIDE
38
+ # wins, then the home's own config/cron.yaml, then the repo's example (which
39
+ # ships what a fresh hour suggests but never claims to be the live schedule).
40
+ if [ -n "${BROKK_CONFIG_OVERRIDE:-}" ]; then
41
+ CONFIG="$BROKK_CONFIG_OVERRIDE"
42
+ else
43
+ . "$SCRIPT_DIR/hoard-lib.sh" # ymir_home_root — one answer, never drift
44
+ ymir_home_root _h
45
+ if [ -r "$_h/config/cron.yaml" ]; then
46
+ CONFIG="$_h/config"
47
+ elif [ -r "$BROKK_HOME/config/cron.yaml" ] && [ "$BROKK_HOME" != "$_h" ]; then
48
+ CONFIG="$BROKK_HOME/config"
49
+ else
50
+ CONFIG="$BROKK_HOME/config"
51
+ fi
52
+ fi
38
53
  CRON_CONFIG="$CONFIG/cron.yaml"
54
+ # Fall back to the shipped example ONLY when no user schedule exists yet — the
55
+ # example is a template, never the live job list.
56
+ if [ ! -r "$CRON_CONFIG" ] && [ -r "$BROKK_HOME/config/cron.yaml.example" ]; then
57
+ CRON_CONFIG="$BROKK_HOME/config/cron.yaml.example"
58
+ fi
39
59
  PID_FILE="$STATE/cron.pid"
40
60
  LOG_FILE="$STATE/cron.log"
41
61
  STAMP_DIR="$STATE/.cron-fired"
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env bash
2
+ # nornir-job-bragi-scrape.sh — Bragi, on the loom: the scheduled scrape round.
3
+ #
4
+ # Reads a list of sources from the USER'S home (never the tree) and scrapes
5
+ # each with Firecrawl into clean markdown, landed in the marketing workspace.
6
+ # The source list is the operator's: $YMIR_HOME/config/scrape-sources.yaml
7
+ #
8
+ # format:
9
+ # sources:
10
+ # - name: monster-stack
11
+ # url: https://example.com/docs
12
+ # kind: scrape|search # default scrape
13
+ # note: why this source matters
14
+ #
15
+ # Firecrawl is BYOK: FIRECRAWL_API_KEY comes from the home's platform.env
16
+ # (bin/hodd.sh emit), never inline. A missing key is reported, not faked — the
17
+ # job is honest about what it could not do.
18
+ #
19
+ # Stateless: read sources → scrape → write markdown under the marketing
20
+ # workspace → carve one Rune → exit.
21
+ set -u
22
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
24
+
25
+ # The home the operator chose — one answer, never drift (Rule 04/07).
26
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
27
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
29
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
30
+ done
31
+ unset _yr _yc
32
+ fi
33
+ hoard_root _HOME
34
+ hoard_data_dir _DATA
35
+
36
+ SOURCES="${BROKK_SCRAPE_SOURCES:-$_HOME/config/scrape-sources.yaml}"
37
+ OUT_DIR="${BROKK_SCRAPE_OUT:-$_HOME/workspaces/marketing/scraped}"
38
+ mkdir -p "$OUT_DIR"
39
+
40
+ # Firecrawl key: from the home's secrets, by path — never inline.
41
+ FIRECRAWL_API_KEY="${FIRECRAWL_API_KEY:-}"
42
+ if [ -z "$FIRECRAWL_API_KEY" ] && [ -x "$ROOT/bin/hodd.sh" ]; then
43
+ FIRECRAWL_API_KEY="$("$ROOT/bin/hodd.sh" emit secrets/platform.env 2>/dev/null | grep -m1 '^FIRECRAWL_API_KEY=' | cut -d= -f2-)"
44
+ fi
45
+
46
+ [ -r "$SOURCES" ] || {
47
+ printf 'bragi-scrape: no sources at %s — nothing scheduled to scrape (exit 0)\n' "$SOURCES"
48
+ exit 0
49
+ }
50
+
51
+ command -v python3 >/dev/null 2>&1 || { printf 'error: python3 required for Firecrawl SDK\n'; exit 1; }
52
+
53
+ # Parse the YAML sources (name/url/kind) and scrape each with the SDK.
54
+ TODAY="$(date -u +%Y-%m-%d)"
55
+ ok=0; failed=0; skipped=0
56
+ rows="$(python3 - "$SOURCES" <<'PY'
57
+ import sys, re
58
+ txt = open(sys.argv[1]).read()
59
+ items = []
60
+ for m in re.finditer(r'^\s*-\s+name:\s*(\S+)\s*$', txt, re.M):
61
+ start = m.end()
62
+ name = m.group(1)
63
+ url = kind = ""
64
+ for line in txt[start:].splitlines():
65
+ line = line.strip()
66
+ if line.startswith("- name:") : break
67
+ if line.startswith("url:") and not url: url = line[4:].strip()
68
+ if line.startswith("kind:") and not kind: kind = line[5:].strip()
69
+ items.append((name, url, kind or "scrape"))
70
+ for i in items: print("|".join(i))
71
+ PY
72
+ )"
73
+
74
+ if [ -z "$FIRECRAWL_API_KEY" ]; then
75
+ failed=$failed; skipped=1
76
+ printf 'bragi-scrape: FIRECRAWL_API_KEY absent — sources listed, nothing scraped\n'
77
+ # still record the rune honestly
78
+ else
79
+ while IFS='|' read -r name url kind; do
80
+ [ -n "$name" ] || continue
81
+ out="$OUT_DIR/$TODAY-$name.md"
82
+ if python3 - "$url" "$kind" "$out" "$FIRECRAWL_API_KEY" <<'PY'
83
+ import os, sys
84
+ url, kind, out, key = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
85
+ try:
86
+ from firecrawl import FirecrawlApp
87
+ except ImportError:
88
+ sys.exit(2) # engine not installed
89
+ app = FirecrawlApp(api_key=key)
90
+ try:
91
+ doc = app.scrape_url(url, params={"formats": ["markdown"]}) if kind == "scrape" \
92
+ else app.search(url, params={"formats": ["markdown"]})
93
+ text = doc.get("markdown", "") if isinstance(doc, dict) else str(doc)
94
+ with open(out, "w") as f:
95
+ f.write("<!-- source: %s ; kind: %s ; fetched: %s -->\n\n%s\n" % (url, kind, __import__("datetime").datetime.utcnow().isoformat() + "Z", text))
96
+ sys.exit(0)
97
+ except Exception as exc:
98
+ print("scrape error: %s" % exc, file=sys.stderr)
99
+ sys.exit(1)
100
+ PY
101
+ then ok=$((ok+1))
102
+ elif [ $? -eq 2 ]; then skipped=$((skipped+1))
103
+ else failed=$((failed+1)); fi
104
+ done <<< "$rows"
105
+ fi
106
+
107
+ printf 'bragi-scrape: sources=%s ok=%s failed=%s skipped=%s out=%s\n' \
108
+ "$(printf '%s\n' "$rows" | grep -c .)" "$ok" "$failed" "$skipped" "$OUT_DIR"
109
+
110
+ # Carve the Rune — the ledger lives in the hoard.
111
+ # shellcheck source=bin/runes-append.sh
112
+ . "$ROOT/bin/runes-append.sh"
113
+ runes_append "bragi" "scrape.round" --realm "${BROKK_REALM:-}" \
114
+ --message "scrape round: ok=$ok failed=$failed skipped=$skipped -> $OUT_DIR" >/dev/null \
115
+ || printf 'bragi-scrape: rune append failed (non-fatal)\n'
116
+ [ "$failed" = 0 ] && exit 0 || exit 1
@@ -5,7 +5,7 @@
5
5
  # briefing — no model call — from four grounded inputs:
6
6
  # 1. open forge orders parsed from docs/masterplan.md (append-only source)
7
7
  # 2. live fleet/state under state/ (cron, metas, status, session lock)
8
- # 3. active plan status from docs/plans/*.md
8
+ # 3. active plan status from memory/plans/*.md
9
9
  # 4. today's Runes tail from workspace/memory/runes_audit.md
10
10
  #
11
11
  # Output: svartalfaheim/<realm>/workspace/memory/daily/YYYY-MM-DD.md
@@ -40,8 +40,8 @@ BRIEF_DIR="${BROKK_BRIEF_DIR:-${YMIR_HOME:-$HOME/Documents/Ymir}/svartalfaheim/$
40
40
  BRIEF_FILE="$BRIEF_DIR/$DATE.md"
41
41
  MAX_ORDERS="${BROKK_BRIEF_MAX_ORDERS:-15}"
42
42
  MAX_RUNES="${BROKK_BRIEF_MAX_RUNES:-12}"
43
- MASTERPLAN="$ROOT/docs/masterplan.md"
44
- PLANS_DIR="$ROOT/docs/plans"
43
+ MASTERPLAN="${BROKK_MASTERPLAN:-${YMIR_HOME:-$HOME/Documents/Ymir}/hodd/docs/masterplan.md}"
44
+ PLANS_DIR="${BROKK_PLANS_DIR:-${YMIR_HOME:-$HOME/Documents/Ymir}/memory/plans}"
45
45
 
46
46
  mkdir -p "$BRIEF_DIR" || { printf 'error: cannot create %s\n' "$BRIEF_DIR"; exit 1; }
47
47
  TMP="$BRIEF_DIR/.$DATE.md.tmp.$$"
@@ -90,11 +90,11 @@ if [ -r "$MASTERPLAN" ]; then
90
90
  done
91
91
  if [ "${total:-0}" -gt "$MAX_ORDERS" ]; then
92
92
  emit ""
93
- emit "_(truncated — ${total} open orders total; see docs/masterplan.md §3)_"
93
+ emit "_(truncated — ${total} open orders total; see hodd/docs/masterplan.md §3)_"
94
94
  fi
95
95
  rm -f "$orders_tmp"
96
96
  else
97
- emit "ABSENT: docs/masterplan.md not readable"
97
+ emit "ABSENT: hodd/docs/masterplan.md not readable"
98
98
  fi
99
99
  emit ""
100
100
 
@@ -105,7 +105,7 @@ if [ -r "$MASTERPLAN" ]; then
105
105
  awk '/^## 2\. Active Queue/ {on=1; next} /^## 3\./ {on=0} on && /^[0-9]+\. / {print}' "$MASTERPLAN" \
106
106
  | while IFS= read -r q; do emit "- ${q#* }"; done
107
107
  else
108
- emit "ABSENT: docs/masterplan.md not readable"
108
+ emit "ABSENT: hodd/docs/masterplan.md not readable"
109
109
  fi
110
110
  emit ""
111
111
 
@@ -156,7 +156,7 @@ if [ -d "$PLANS_DIR" ]; then
156
156
  emit "- \`$pname\` — ${pstatus:-unknown}"
157
157
  done
158
158
  else
159
- emit "ABSENT: docs/plans/ not readable"
159
+ emit "ABSENT: memory/plans/ not readable"
160
160
  fi
161
161
  emit ""
162
162