@animalabs/connectome-host 0.7.4 → 0.8.0

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 (60) hide show
  1. package/.env.example +12 -5
  2. package/.github/PULL_REQUEST_TEMPLATE.md +3 -2
  3. package/.github/workflows/changelog.yml +9 -4
  4. package/.github/workflows/ci.yml +5 -3
  5. package/.github/workflows/publish.yml +12 -6
  6. package/CHANGELOG.md +245 -0
  7. package/CONTRIBUTING.md +47 -19
  8. package/README.md +27 -0
  9. package/bun.lock +27 -31
  10. package/changelog.d/README.md +28 -0
  11. package/package.json +5 -5
  12. package/recipes/SETUP.md +11 -5
  13. package/recipes/TRIUMVIRATE-SETUP.md +68 -14
  14. package/recipes/knowledge-miner.json +0 -30
  15. package/recipes/mock-test.json +19 -0
  16. package/recipes/triumvirate.json +6 -1
  17. package/scripts/release-changelog.ts +210 -21
  18. package/src/cache-keepalive-log.ts +41 -0
  19. package/src/commands.ts +96 -0
  20. package/src/framework-strategy.ts +37 -0
  21. package/src/gate-telemetry.ts +106 -0
  22. package/src/headless.ts +10 -0
  23. package/src/index.ts +167 -55
  24. package/src/mcpl-config.ts +99 -1
  25. package/src/modules/identity-module.ts +310 -2
  26. package/src/modules/instructions-module.ts +265 -0
  27. package/src/modules/mcpl-admin-module.ts +58 -11
  28. package/src/modules/subagent-module.ts +18 -0
  29. package/src/recipe.ts +732 -25
  30. package/src/web/panel-data.ts +19 -0
  31. package/src/workspace-mounts.ts +73 -0
  32. package/test/audit-module-optins.test.ts +10 -3
  33. package/test/cache-keepalive-log.test.ts +83 -0
  34. package/test/conversations-recipe.test.ts +142 -0
  35. package/test/framework-fkm-composition.test.ts +35 -3
  36. package/test/framework-strategy-defaults.test.ts +19 -0
  37. package/test/gate-telemetry-adapter.test.ts +84 -0
  38. package/test/gate-telemetry.test.ts +91 -0
  39. package/test/identity-and-surfaces.test.ts +212 -1
  40. package/test/instructions-module.test.ts +258 -0
  41. package/test/mcpl-admin-module.test.ts +41 -0
  42. package/test/mcpl-agent-overlay.test.ts +51 -3
  43. package/test/mcpl-child-env.test.ts +64 -0
  44. package/test/nudge-command.test.ts +47 -0
  45. package/test/recipe-cache-keepalive.test.ts +59 -0
  46. package/test/recipe-compression-fallback.test.ts +19 -0
  47. package/test/recipe-hybrid-prose-routing.test.ts +12 -0
  48. package/test/recipe-instructions.test.ts +176 -0
  49. package/test/recipe-kv-unified.test.ts +87 -0
  50. package/test/recipe-mcp-source.test.ts +54 -0
  51. package/test/recipe-openai-compatible.test.ts +54 -0
  52. package/test/recipe-path-resolution.test.ts +19 -8
  53. package/test/recipe-provider.test.ts +14 -0
  54. package/test/recipe-save-unresolved.test.ts +244 -0
  55. package/test/recipe-source-only.test.ts +38 -0
  56. package/test/release-changelog.test.ts +202 -0
  57. package/test/subagent-prose-routing.test.ts +109 -0
  58. package/test/workspace-mounts.test.ts +68 -0
  59. package/web/src/App.tsx +1 -0
  60. package/web/src/Health.tsx +61 -1
package/.env.example CHANGED
@@ -23,11 +23,18 @@ ANTHROPIC_API_KEY=sk-ant-...
23
23
  # GITLAB_TOKEN=glpat-...
24
24
  # GITLAB_API_URL=https://gitlab.example.com/api/v4
25
25
 
26
- # Notion (knowledge-miner.json: syncntn)
26
+ # Notion only if you add a Notion MCP server block back to
27
+ # knowledge-miner.json (none ships by default; see TRIUMVIRATE-SETUP.md Step 6)
27
28
  # NOTION_STORAGE_URL=http://localhost:8000
28
29
  # NOTION_WORKSPACE_ID=...
29
30
 
30
- # Scribe audio/video transcription (knowledge-miner.json: scribe)
31
- # GEMINI_API_KEY=... # REQUIRED if you keep the scribe server (recipe uses bare ${GEMINI_API_KEY}); powers transcription
32
- # NOTION_API_KEY=... # optional (recipe uses ${NOTION_API_KEY:-}); only scribe--scribe_notion_page needs it
33
- # SCRIBE_GLOSSARY_URL=... # optional (recipe uses ${SCRIBE_GLOSSARY_URL:-}); unset = transcribe without a glossary
31
+ # Web UI credentials (triumvirate.json: webui). Defaults to admin:admin —
32
+ # CHANGE THESE for anything reachable beyond your own machine.
33
+ # WEBUI_USERNAME=admin
34
+ # WEBUI_PASSWORD=admin
35
+
36
+ # Scribe — audio/video transcription. Only if you add a scribe block back to
37
+ # knowledge-miner.json (none ships by default; see TRIUMVIRATE-SETUP.md Step 6)
38
+ # GEMINI_API_KEY=... # required by the scribe block (bare ${GEMINI_API_KEY}); powers transcription
39
+ # NOTION_API_KEY=... # optional (block uses ${NOTION_API_KEY:-}); only scribe--scribe_notion_page needs it
40
+ # SCRIBE_GLOSSARY_URL=... # optional (block uses ${SCRIBE_GLOSSARY_URL:-}); unset = transcribe without a glossary
@@ -19,8 +19,9 @@
19
19
 
20
20
  ---
21
21
 
22
- - [ ] `CHANGELOG.md` updated under `## Unreleased` — or this change is
23
- internal-only / test-only / docs-only (apply the `no-changelog` label).
22
+ - [ ] Changelog fragment added `changelog.d/<slug>.<breaking|added|changed|fixed>.md`
23
+ (see `changelog.d/README.md`) or this change is internal-only /
24
+ test-only / docs-only (apply the `no-changelog` label).
24
25
 
25
26
  <!-- AI-assisted contributions are welcome and normal here — see
26
27
  CONTRIBUTING.md for the attribution convention (footer + Co-Authored-By). -->
@@ -14,19 +14,24 @@ jobs:
14
14
  if: "!contains(github.event.pull_request.labels.*.name, 'no-changelog')"
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v6
17
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
18
18
  with:
19
19
  fetch-depth: 0
20
20
 
21
- - name: Require CHANGELOG.md update when src/ changes
21
+ - name: Require changelog fragment when src/ changes
22
22
  run: |
23
23
  base="${{ github.event.pull_request.base.sha }}"
24
24
  head="${{ github.event.pull_request.head.sha }}"
25
25
  changed=$(git diff --name-only "$base...$head")
26
+ # Fragments must be *added* — a deleted or renamed fragment also
27
+ # appears in --name-only and must not satisfy the check.
28
+ added=$(git diff --name-only --diff-filter=A "$base...$head")
26
29
  echo "Changed files:"
27
30
  echo "$changed"
28
- if echo "$changed" | grep -q '^src/' && ! echo "$changed" | grep -qx 'CHANGELOG.md'; then
29
- echo "::error::This PR touches src/ but not CHANGELOG.md. Add an entry under 'Unreleased' (see CONTRIBUTING.md), or apply the 'no-changelog' label if the change is internal-only."
31
+ if echo "$changed" | grep -q '^src/' \
32
+ && ! echo "$added" | grep -Eq '^changelog\.d/[^/]+\.(breaking|added|changed|fixed)\.md$' \
33
+ && ! echo "$changed" | grep -qx 'CHANGELOG.md'; then
34
+ echo "::error::This PR touches src/ but carries no changelog entry. Add a fragment changelog.d/<slug>.<breaking|added|changed|fixed>.md (see CONTRIBUTING.md), or apply the 'no-changelog' label if the change is internal-only."
30
35
  exit 1
31
36
  fi
32
37
  echo "OK"
@@ -25,15 +25,17 @@ jobs:
25
25
  os: [ubuntu-latest, macos-latest]
26
26
 
27
27
  steps:
28
- - uses: actions/checkout@v6
28
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
29
+ with:
30
+ persist-credentials: false
29
31
 
30
32
  - name: Setup Node.js
31
- uses: actions/setup-node@v6
33
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
32
34
  with:
33
35
  node-version: 24
34
36
 
35
37
  - name: Setup Bun
36
- uses: oven-sh/setup-bun@v2
38
+ uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
37
39
  with:
38
40
  bun-version: 1.3.14
39
41
 
@@ -15,15 +15,17 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
 
17
17
  steps:
18
- - uses: actions/checkout@v6
18
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
19
+ with:
20
+ persist-credentials: false
19
21
 
20
22
  - name: Setup Node.js
21
- uses: actions/setup-node@v6
23
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
22
24
  with:
23
25
  node-version: 24
24
26
 
25
27
  - name: Setup Bun
26
- uses: oven-sh/setup-bun@v2
28
+ uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
27
29
  with:
28
30
  bun-version: 1.3.14
29
31
 
@@ -51,7 +53,9 @@ jobs:
51
53
  id-token: write
52
54
 
53
55
  steps:
54
- - uses: actions/checkout@v6
56
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
57
+ with:
58
+ persist-credentials: false
55
59
 
56
60
  - name: Require changelog section for this release
57
61
  run: |
@@ -63,7 +67,7 @@ jobs:
63
67
  fi
64
68
 
65
69
  - name: Setup Node.js
66
- uses: actions/setup-node@v6
70
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
67
71
  with:
68
72
  # node 24 ships npm >= 11.5.1, required for OIDC publishing.
69
73
  node-version: 24
@@ -91,7 +95,9 @@ jobs:
91
95
  contents: write
92
96
 
93
97
  steps:
94
- - uses: actions/checkout@v6
98
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
99
+ with:
100
+ persist-credentials: false
95
101
 
96
102
  - name: Mirror changelog section into release notes
97
103
  env:
package/CHANGELOG.md CHANGED
@@ -1,7 +1,252 @@
1
1
  # Changelog
2
2
 
3
+ Entries land with the change that causes them, as fragment files in
4
+ [`changelog.d/`](changelog.d/) that are folded into a version section at
5
+ release time — see [CONTRIBUTING.md](CONTRIBUTING.md#changelog).
6
+
3
7
  ## Unreleased
4
8
 
9
+ ## 0.8.0 — 2026-09-05
10
+
11
+ ### Added
12
+
13
+ - Recipes accept `agent.proseRouting: "disabled"` for tool-only external publication when paired with a supporting Agent Framework release.
14
+ - **`modules.instructions`** — a shared living-instructions file (a CLAUDE.md
15
+ analogue kept in a workspace mount) injected into every agent's context on
16
+ every turn, ephemeral subagents included. `true` for defaults
17
+ (`instructions/AGENTS.md`, 32 KB cap, `system` position) or
18
+ `{ path, header, maxBytes, position }`. Reads resolve through the workspace
19
+ mount (scoping + traversal guard apply), are bounded to `maxBytes`, reject
20
+ symlinks leading outside the mount (realpath containment), are cached by
21
+ `(realpath, mtime, size)`, and fail open — a missing file never blocks
22
+ inference. Recipe validation cross-checks the path's mount prefix against
23
+ the effective workspace mounts (explicit and implicit alike) at load time,
24
+ requires `autoMaterialize: true` on a read-write instructions mount so
25
+ agent curation edits actually reach the disk-side injection, and rejects
26
+ the host-managed `_config` mount (it materializes only on branch-changing
27
+ commands). Validation and the runtime share one mount builder
28
+ (`src/workspace-mounts.ts`), so the two cannot drift.
29
+
30
+ - Recipes accept the default-off `agent.strategy.compressionSourceOnly` flag and pass it through to Context Manager's residence-scoped L1 compression request builder (#103).
31
+
32
+ - Gate telemetry stamps why the turn fired: `x-gate-origin` (heartbeat |
33
+ event | mail | operator | raw reason), `x-gate-channel` and
34
+ `x-gate-counterparty` (adapter-namespaced ids, never content or display
35
+ names) ride the stream lane under the same `GATE_TELEMETRY=1` + base-URL
36
+ gate as the debt stamp; background calls on the complete lane carry debt
37
+ only (#113).
38
+
39
+ - Gate-bound Anthropic calls carry an `x-gate-debt-chunks` header with the
40
+ live compression-debt pending-chunk count (membrane `dynamicHeaders`,
41
+ antra-tess/membrane#65) — the gateway records it per ledger row and strips
42
+ it before the vendor. Double-gated on `GATE_TELEMETRY=1` AND a configured
43
+ `ANTHROPIC_BASE_URL`, so the stamp can never reach a vendor endpoint;
44
+ unreadable state sends no header rather than a guess (#109).
45
+
46
+ - Health tab renders the per-agent compression-debt reduction (state, pending
47
+ chunks, oldest age, merge queue) and says "not reported by this stack" when
48
+ absent — the queue is now distinct from context composition, and the top
49
+ line reads "inference queued" (#110).
50
+
51
+ - Add complete fail-closed recipe validation and strategy passthrough for `foldingStrategy: "kv-unified"`; partial policies, invalid occupancy bands, unsafe approximation grids, and implicit treeification are rejected at load time.
52
+
53
+ - **`agent.provider: 'openai-compatible'`** — run an agent against any
54
+ OpenAI chat-completions endpoint (Ollama, vLLM, Together, Groq, NanoGPT,
55
+ ...) via membrane's existing `OpenAICompatibleAdapter`, which no host ever
56
+ wired. The recipe names the endpoint (`agent.baseUrl`, validated as an
57
+ absolute http(s) URL at load) and the model (required — no default for an
58
+ arbitrary endpoint); the key comes from `OPENAI_COMPATIBLE_API_KEY`
59
+ only (no `OPENAI_API_KEY` fallback — `baseUrl` is recipe-controlled, so a
60
+ fallback would silently send a real OpenAI credential to an arbitrary
61
+ endpoint) and may be absent for local servers.
62
+ `agent.baseUrl` with any other provider is rejected at load time.
63
+
64
+ - Recipes can pass the Context Manager source-only compression controls through Host/FKM, including the new default-off L1 and merge final-fallback modes, with boolean validation and cross-agent isolation.
65
+
66
+ ### Added
67
+
68
+ - **`agent.cacheKeepalive` — hold an idle agent's 1h prompt cache warm.** With
69
+ `cacheTtl: "1h"`, an idle agent's cached prefix expires after an hour and its
70
+ next wake pays a **2x cache write** over the entire context. Reading an entry
71
+ restarts its clock, so membrane now replays the last request with
72
+ `max_tokens: 0` (prefill only) to refresh it at cache-**read** price (0.1x).
73
+ On by default for the anthropic provider; `{ "enabled": false }` opts out.
74
+ - **Cost is proportional to actual idleness, not to `maxIdleHours`.** A poke
75
+ fires only when the entry is genuinely near expiry, so a busy agent never
76
+ fires one — its own traffic already refreshed the TTL. Measured on mythos
77
+ llm-calls over 36h: 563 of 637 gaps were under 5 minutes, only 5 exceeded
78
+ 1h.
79
+ - Knobs: `maxIdleHours` (default 24, measured from the last **real** request
80
+ so pokes cannot extend their own mandate) and `refreshAfterMinutes`
81
+ (default 45). Recipe validation **rejects `refreshAfterMinutes >= the cache
82
+ TTL`** — such a keepalive always fires after the entry has already expired,
83
+ paying a full cache write on every poke while still looking like a healthy
84
+ successful call.
85
+ - Events land in `service-stderr.log`, warn-level for `ineffective` and
86
+ `disabled`, so a background spender is legible without opening a billing
87
+ dashboard.
88
+ - Sizing, from fable-cm's 11-day log (~500k-token prefix): 49.7M tokens of
89
+ `cache_creation` landed on turns following a >1h idle gap — ~$944 of write
90
+ premium at fable-5 rates that this converts to ~$308 of reads.
91
+
92
+ - **`provider: "mock"` — run the whole host with zero provider spend and no
93
+ credentials.** Wires membrane's existing `MockAdapter` (previously
94
+ unreachable from any recipe) as a first-class provider: echoes the last
95
+ user message by default, or returns `agent.mock.defaultResponse` with
96
+ `agent.mock.echoMode: false` for deterministic scripted output. No API
97
+ key is required or read. Mock calls still ride the generic logging
98
+ decorator, so `llm-calls.*.jsonl` receipts work exactly as they do for
99
+ real providers. `recipes/mock-test.json` is a ready-made offline smoke
100
+ recipe (loopback webui, everything else off).
101
+
102
+ ### Added
103
+
104
+ - **Hybrid prose routing.** Recipes may set `agent.proseRouting: "hybrid"`: unprefixed text keeps ordinary frozen-locus delivery, while a leading `>>>destination` publication envelope routes through Agent Framework’s existing authorized cross-surface resolver. Source text retains the envelope; recipients see only the body; success/failure returns to resident context.
105
+
106
+ - **`conversations` recipe block — per-channel conversation forks.** Maps to
107
+ agent-framework's `ConversationRouter`: the recipe's agent becomes a dormant
108
+ trunk template, and qualifying incoming channel messages spawn per-channel
109
+ fork agents seeded from the trunk's current context. Recipe surface: `bind` /
110
+ `trigger` rules per channel kind (`dm`/`groupDm`/`channel`), `idleTtlMs`
111
+ (default 12h), `closurePrompt`, and `agentPrefix`. The host fills
112
+ `templateAgent` from the recipe and creates a fresh stateful strategy instance
113
+ for each fork. Absent block means no routing and no behavior change.
114
+
115
+ - **Protective reaction-suppression baseline for Discord adapters.** Stdio
116
+ MCPL children now receive `DISCORD_SUPPRESSED_REACTIONS_BASELINE` — the
117
+ agent-framework's exported refusal-annotation set (`REFUSAL_REACTION_BASELINE`,
118
+ comma-joined) — so a never-configured Discord adapter defaults to
119
+ suppressing exactly the markers this host's framework stamps, instead of
120
+ defaulting to nothing. An operator-set value on the server entry
121
+ supersedes the house baseline, and the adapter's own precedence (filters-file
122
+ key including explicit `[]` → legacy operator env → baseline) governs
123
+ enforcement; lost configuration stays stale rather than re-defaulting.
124
+ Requires an agent-framework release carrying the `REFUSAL_REACTION_BASELINE`
125
+ export.
126
+
127
+ - **Standing autobiographical production target.** Recipes may set `agent.strategy.productionBudgetTokens` to keep the summary forest deep enough for a later live context-budget descent without a fold storm. This is a context-token target passed through to Context Manager, not a provider-spend ceiling; omission preserves Context Manager defaults.
128
+
129
+ - **`mcpl_list` reports manifest freshness.** Each loaded server now shows the
130
+ last validated manifest revision plus fetch and grant-negotiation timestamps.
131
+ Older Agent Framework versions remain legible as `manifest=unknown`, and the
132
+ server-authored revision is quoted and bounded before reaching model-facing
133
+ text.
134
+
135
+ - **`BEDROCK_BASE_URL` env hook** for the bedrock provider — mirrors
136
+ `ANTHROPIC_BASE_URL`, routing bedrock-runtime calls through an inference
137
+ gateway (gate.animalabs.ai/bedrock/<credSet>). The gate reads the agent
138
+ token from the SigV4 Credential (`AWS_ACCESS_KEY_ID` slot), discards the
139
+ client signature, and re-signs with real AWS creds. First user: Princess,
140
+ moved off the first-party Anthropic API (classifier "bio" false-positive
141
+ streak) onto Bedrock Sonnet 4.5 via gate apse1 — needs membrane ≥1dcd4e3
142
+ for `global.` inference-profile id pass-through.
143
+
144
+ ### Changed
145
+
146
+ - **Dependency floor: agent-framework `^0.10.0`, chronicle `^0.3.0`,
147
+ membrane `^0.5.78`.** af 0.10.0 brings `ConversationRouter` (the
148
+ per-channel conversation-fork machinery this release’s `conversations`
149
+ recipe surface targets, and includes the current `hybrid` prose router) and exports `nudgeAgent`, which `/nudge` has
150
+ called since it landed — on every published af before 0.9.0 that call
151
+ was a guaranteed `TypeError`, so the floor also makes `/nudge` actually
152
+ work. Chronicle `^0.3.0` aligns the whole tree on one chronicle copy
153
+ (previously context-manager `0.6.3` nested its own `0.3.0` next to the
154
+ host's `0.2.x`). Operators: run a clean `npm ci` — a stale
155
+ `node_modules` predating the lock is the known failure mode here.
156
+
157
+ - **The public triumvirate recipes boot from a fresh clone.**
158
+ `knowledge-miner.json` no longer ships a `syncntn` (Notion) block pointing at
159
+ an org-internal adapter that isn't publicly available — with `NOTION_*` env
160
+ vars unset the block failed recipe load, and with them set it died at spawn
161
+ on the dangling `../syncntn` path. The `scribe` block is dropped for the
162
+ same reason: it hard-required `GEMINI_API_KEY` and a `../scribe-mcp`
163
+ sibling checkout, neither mentioned anywhere in the setup guides — a
164
+ guide-following fresh install always got a crashed miner. Notion and
165
+ Scribe are now add-a-block opt-ins, documented in SETUP.md and
166
+ TRIUMVIRATE-SETUP.md (the miner prompt's tool-name contracts are
167
+ unchanged). `triumvirate.json` declares
168
+ webui Basic-Auth defaulting to `admin`/`admin` (override via
169
+ `WEBUI_USERNAME` / `WEBUI_PASSWORD` in `.env`) instead of bare
170
+ `"webui": true`, which the non-loopback bind guard refuses to start.
171
+
172
+ - **agent-framework `^0.11.0`** (was `^0.10.0`). Activates `proseRouting:
173
+ "disabled"` for recipes that set it (#100 accepted the key; the runtime now
174
+ implements it — generated prose is never published externally, only explicit
175
+ tools speak), plus AF 0.11's Windows workspace-mount fix and the
176
+ org-acceleration 429 cooldown. Clears the last two standing cross-package
177
+ `tsc` errors — the typecheck is fully clean at this lock.
178
+
179
+ - Changelog entries now land as per-change fragment files in `changelog.d/`
180
+ (`<slug>.<breaking|added|changed|fixed>.md`), folded into the version
181
+ section at release time — concurrent PRs no longer conflict in
182
+ `CHANGELOG.md`. Editing `## Unreleased` directly still works and is merged
183
+ at the same point.
184
+
185
+ - **membrane `^0.5.80`** (was `^0.5.78`, lockfile-resolved 0.5.79). Two
186
+ latent cache behaviors the host already configures become ACTIVE with this
187
+ relock: the prompt-cache keepalive (`agent.cacheKeepalive`, on by default —
188
+ previously passed to an adapter version with no such field and silently
189
+ ignored, so idle gaps over the 1h TTL repaid a full cache write on wake)
190
+ and the floating cache marker (incremental prompt caching inside the native
191
+ tool loop, membrane's default-on). Both reduce cost; neither changes
192
+ visible agent behavior. Also clears two of the four standing cross-package
193
+ `tsc` errors (the membrane-typing pair).
194
+
195
+ - Depend on `@animalabs/agent-framework` ^0.12.0 and `@animalabs/membrane` ^0.5.82 —
196
+ the published versions that implement the active-turn trigger and the
197
+ lane-aware `dynamicHeaders` the wake-cause stamp (#113) relies on; the
198
+ compatibility cast and optional lookup are gone, and an adapter-level test
199
+ proves a stream call carries the origin trio while a complete call carries
200
+ debt only.
201
+
202
+ ### Fixed
203
+
204
+ - **Prompt-cache keepalive events all go to stderr**, so every one of them lands
205
+ in `service-stderr.log` beside `[inference-refusal]` instead of being split by
206
+ severity across two sinks. Routine `refreshed` events previously went to
207
+ stdout — which the host unit leaves on the journal — so the log an operator
208
+ actually greps showed nothing. Observed on fable-cm 2026-08-23: the keepalive
209
+ refreshed a 523,102-token prefix three times, correctly and with zero cache
210
+ writes, while a monitor tailing `service-stderr.log` reported no activity for
211
+ three hours. A background spender that can't be found in the operator's log is
212
+ indistinguishable from one that never ran.
213
+
214
+ - Plumb `agent.strategy.compressionRecallBudgetTokens` through recipe validation and Framework strategy construction, with positive-integer validation instead of silently accepting an inert key.
215
+
216
+ - **Saved recipe snapshots no longer contain resolved secrets.** `loadRecipe`
217
+ substitutes every `${VAR}` — API tokens included — and the host then wrote
218
+ that fully resolved recipe to `$DATA_DIR/.recipe.json` at default file mode:
219
+ plaintext credentials in the exact directory deployments bind-mount and back
220
+ up (found by an external recipe review that verified live tokens in a backed
221
+ up `data/` directory on a production VM). The snapshot now keeps the
222
+ pre-substitution form — `${VAR}` references literal, a URL `systemPrompt`
223
+ kept as the URL — and a resumed session re-runs substitution, validation,
224
+ and the prompt fetch against the *current* environment, so secret rotation
225
+ and remote prompt updates take effect on restart without re-cooking. The
226
+ file is written 0600 and re-chmod'd 0600 on every save. Legacy resolved
227
+ snapshots (no `$unresolved` marker) still load verbatim, with no
228
+ substitution, so a literal `${...}` surviving in prose cannot fail them;
229
+ resuming an unresolved snapshot whose required env var has since disappeared
230
+ fails loudly naming the variable instead of silently starting the default
231
+ recipe.
232
+
233
+ - **Ephemeral subagents inherit the caller's `proseRouting` mode.** They
234
+ previously always ran AF's `'locus'` default regardless of the recipe, so a
235
+ resident running `proseRouting: "disabled"` still spawned subagents whose
236
+ between-tool-calls prose published live into its open channel as parent
237
+ speech (field-confirmed on a deployed resident, 2026-08-26 — including
238
+ after the recipe adopted `"disabled"`, which reached only the resident).
239
+
240
+ ### Fixed
241
+
242
+ - **`mcpServers.<id>.source` accepts cook's npm registry form.**
243
+ `validateRecipe` demanded `source.url`, but connectome-cook's source grammar
244
+ also has `{ "npm": "pkg@version" }` — which the shipped knowledge-miner
245
+ recipe uses for its gitlab server, so that recipe failed to load
246
+ (`mcpServers.gitlab.source.url must be a non-empty string`). Exactly one of
247
+ `url` / `npm` is now required; the field remains build-tooling metadata,
248
+ ignored at runtime.
249
+
5
250
  ## 0.7.4 — 2026-08-03
6
251
 
7
252
  ### Changed
package/CONTRIBUTING.md CHANGED
@@ -43,7 +43,7 @@ plus, when applicable, **Not verified**, **Out of scope**, and
43
43
  - **Tests accompany behavior changes.** Review scrutinizes test substance,
44
44
  not mere presence — a test that can't fail on the unfixed code will be
45
45
  called out.
46
- - **Changelog entry** under `## Unreleased` for anything behavior-affecting
46
+ - **Changelog fragment** in `changelog.d/` for anything behavior-affecting
47
47
  (see below).
48
48
 
49
49
  Conventional-commit-style titles (`feat(recipe): …`, `fix(subagent): …`) are
@@ -80,33 +80,61 @@ that don't fail on unfixed code, or with claims the branch itself disproves.
80
80
 
81
81
  ## Changelog
82
82
 
83
- `CHANGELOG.md` keeps a standing `## Unreleased` section with
84
- `### Breaking` / `### Added` / `### Changed` / `### Fixed` subsections
85
- (loosely [Keep a Changelog](https://keepachangelog.com/)).
86
-
87
- - **The entry lands with the change** same commit, or at least the same
83
+ Changelog entries land as **fragment files** in
84
+ [`changelog.d/`](changelog.d/) one file per change and are folded into
85
+ `CHANGELOG.md` (loosely [Keep a Changelog](https://keepachangelog.com/)) at
86
+ release time. One file per change is what keeps concurrent work from
87
+ conflicting: when every PR edited the same `## Unreleased` section, any PR
88
+ that outlived another merge hit a conflict in `CHANGELOG.md`; distinct files
89
+ never do.
90
+
91
+ - **Format:** `changelog.d/<slug>.<breaking|added|changed|fixed>.md`, a flat
92
+ file directly in `changelog.d/`, containing one or more markdown bullets
93
+ (`- …`) written exactly as they should appear in `CHANGELOG.md`:
94
+ continuation lines indent two spaces, nested bullets are fine, headings
95
+ and horizontal rules are refused (even indented — a heading inside a
96
+ fragment would corrupt the section structure). The slug just has to be
97
+ unique among pending fragments and filesystem-safe — the PR number works,
98
+ and so does the branch name with `/` replaced by `-`
99
+ (`100-prose-routing.added.md`, `fix-retry-backoff.fixed.md`). The release
100
+ script scans the directory fail-closed: a subdirectory, an unrecognized
101
+ category suffix, or any other stray file aborts the release rather than
102
+ silently stranding an entry.
103
+
104
+ - **The fragment lands with the change** — same commit, or at least the same
88
105
  PR. This binds direct pushes to `main` just as much as PRs. On PRs, CI
89
- enforces it softly: touching `src/` without touching `CHANGELOG.md` fails
90
- the `changelog` check unless the `no-changelog` label is applied.
106
+ enforces it softly: touching `src/` without adding a fragment (or editing
107
+ `CHANGELOG.md`) fails the `changelog` check unless the `no-changelog`
108
+ label is applied.
91
109
  - **What needs an entry:** anything an operator, recipe author, or module
92
110
  developer would notice — behavior, config/recipe schema, CLI, tool
93
111
  surfaces, defaults. Internal refactors, test-only, and docs-only changes
94
112
  don't.
95
- - **Breaking entries are audience-scoped.** Name the audience in the heading
96
- (`### Breaking (recipe authors only)`) and cover: **who needs to act**,
113
+ - **Breaking entries are audience-scoped.** Open the bullet by naming who
114
+ needs to act (`- **Recipe authors:** …`) and cover: **who needs to act**,
97
115
  **migration**, and **unchanged** (what readers might fear broke but
98
116
  didn't). The fleet recipe-path entry in `CHANGELOG.md` is the canonical
99
117
  example of the format.
118
+ - **Editing `## Unreleased` in `CHANGELOG.md` directly still works** and is
119
+ merged with the fragments at release time — it remains the right place to
120
+ restructure pending entries, and the escape hatch for anything the
121
+ fragment format can't express (e.g. an audience-qualified
122
+ `### Breaking (recipe authors only)` heading, which `breaking` fragments
123
+ will then join). Keep one `## Unreleased` heading — the release script
124
+ refuses more than one, since only the first is ever cut.
100
125
  - **Releases** (maintainers): `npm version <patch|minor|major>` does the
101
- whole cut — the `version` hook retitles `Unreleased` to
102
- `## X.Y.Z — YYYY-MM-DD` (keeping a fresh `Unreleased` above it, and
103
- refusing to release when there are no entries), then npm commits and tags.
104
- `git push --follow-tags` triggers CI, which refuses a tag with no matching
105
- changelog section, publishes `@animalabs/connectome-host` to npm, and
106
- creates the GitHub release with that section as its notes. The two release
107
- jobs are independent: some consumers run github-clone checkouts, so
108
- release notes must exist even when npm publish fails. Version bumps are a
109
- maintainer release-time action, not part of feature PRs.
126
+ whole cut — the `version` hook folds the pending fragments plus any
127
+ entries filed directly under `Unreleased` into `## X.Y.Z — YYYY-MM-DD`
128
+ (subsections emitted in `### Breaking` / `### Added` / `### Changed` /
129
+ `### Fixed` order), deletes the consumed fragments, keeps a fresh empty
130
+ `Unreleased` above, and refuses to release when there is nothing to
131
+ release; npm then commits and tags. `git push --follow-tags` triggers CI,
132
+ which refuses a tag with no matching changelog section, publishes
133
+ `@animalabs/connectome-host` to npm, and creates the GitHub release with
134
+ that section as its notes. The two release jobs are independent: some
135
+ consumers run github-clone checkouts, so release notes must exist even
136
+ when npm publish fails. Version bumps are a maintainer release-time
137
+ action, not part of feature PRs.
110
138
 
111
139
  ## Building and testing
112
140
 
package/README.md CHANGED
@@ -118,6 +118,31 @@ device-code flow if needed. No `OPENAI_API_KEY` is used for this provider. Use
118
118
  warns if the service reports that it fell back to Standard; Fast mode consumes
119
119
  subscription credits at a higher rate when applied.
120
120
 
121
+ ### OpenAI-compatible endpoints (Ollama, vLLM, Together, Groq, NanoGPT, ...)
122
+
123
+ Any server speaking the OpenAI chat-completions API works through the generic
124
+ `openai-compatible` provider — the recipe names the endpoint and the model:
125
+
126
+ ```json
127
+ {
128
+ "agent": {
129
+ "provider": "openai-compatible",
130
+ "baseUrl": "http://localhost:11434/v1",
131
+ "model": "qwen3:32b",
132
+ "systemPrompt": "You are a helpful assistant."
133
+ }
134
+ }
135
+ ```
136
+
137
+ The API key is read from `OPENAI_COMPATIBLE_API_KEY` only — deliberately no
138
+ `OPENAI_API_KEY` fallback: `baseUrl` is recipe-controlled, and a real OpenAI
139
+ credential must never be sent silently to an arbitrary endpoint. Local
140
+ servers usually need none. `agent.model` is required —
141
+ there is no default model for an arbitrary endpoint. Tool calls use the
142
+ standard `tool_calls` format, so the endpoint must support function calling
143
+ for tool-using recipes. Provider-side prompt caching and cache accounting
144
+ depend on what the endpoint reports.
145
+
121
146
  ## What it provides
122
147
 
123
148
  - **Web UI**: browser operator console (`modules.webui`) — live chat with full interiority (thinking, tool calls, streaming), agent/fleet tree, context makeup + compression coverage, call ledger with cache verdicts and billing-grade costs, health/ops alerts, Chronicle branch tree, lessons, MCPL config, workspace files; scoped read-only observer access via device keys
@@ -128,6 +153,7 @@ subscription credits at a higher rate when applied.
128
153
  - **Session management**: Isolated sessions with auto-naming
129
154
  - **MCPL support**: Connect any MCP/MCPL server; wake subscriptions for selective event triggering
130
155
  - **File products**: Write reports and documents, materialize to disk
156
+ - **Shared instructions** (opt-in, `modules.instructions`): a living instructions document (CLAUDE.md analogue) kept in a workspace mount and injected into every agent's context on every turn — the resident agent and all ephemeral subagents. Edits take effect on the next turn; nothing is persisted to history. Defaults: path `instructions/AGENTS.md`, `position: "system"`, 32 KiB cap (reads are bounded to the cap); a missing file is fail-open (no injection, warn once), while a path naming a nonexistent mount fails at recipe load — including on the implicit default workspace (`input` + `products`), whose mount set can never satisfy the default path, so declare an `instructions` mount explicitly. **Who edits, and how it propagates**: the module reads *disk*; agent `workspace--write`/`edit` land in Chronicle and reach disk only on an `autoMaterialize: true` mount — validation therefore requires it on a read-write instructions mount. On a read-only mount the flow reverses: human/deploy edits to disk reach the injection, but not `workspace--read` (which serves Chronicle) — prefer routing human feedback through conversation and letting the agent make the edit. Symlinks that lead outside the mount are rejected (realpath containment), never injected. **Cache note**: at `position: "system"` the block lives in every agent's prompt-cache prefix, so each edit is a fleet-wide cache cold start on the next turn — curate in batches, or use `afterUser` for cache-cheap, lower-salience injection. Compared to **lessons** (`modules.lessons`): lessons are a structured, confidence-scored store with model-driven retrieval; instructions are one free-form curated document, always present verbatim
131
157
 
132
158
  For `openai-responses` and `openai-codex`, an object-valued
133
159
  `modules.retrieval` can set `reasoningEffort` (`none`, `minimal`, `low`,
@@ -156,6 +182,7 @@ npm install
156
182
  |----------|---------|-------------|
157
183
  | `ANTHROPIC_API_KEY` | (required) | Anthropic API key |
158
184
  | `OPENAI_API_KEY` | — | OpenAI Platform key for `openai-responses` recipes |
185
+ | `OPENAI_COMPATIBLE_API_KEY` | — | Key for `openai-compatible` recipes (no `OPENAI_API_KEY` fallback by design); omit for local servers |
159
186
  | `CODEX_BINARY` | `codex` | Codex CLI executable for `openai-codex` subscription auth |
160
187
  | `CODEX_HOME` | `~/.codex` | Codex credential/config directory |
161
188
  | `CODEX_BASE_URL` | ChatGPT Codex backend | Optional subscription transport override |