@animalabs/connectome-host 0.7.4 → 0.8.1

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 (67) 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 +320 -0
  7. package/CONTRIBUTING.md +47 -19
  8. package/README.md +27 -0
  9. package/bun.lock +26 -32
  10. package/changelog.d/README.md +28 -0
  11. package/package.json +6 -6
  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 +221 -32
  20. package/src/framework-agent-config.ts +3 -0
  21. package/src/framework-strategy.ts +42 -0
  22. package/src/gate-telemetry.ts +134 -0
  23. package/src/headless.ts +10 -0
  24. package/src/index.ts +194 -55
  25. package/src/mcpl-config.ts +99 -1
  26. package/src/modules/identity-module.ts +310 -2
  27. package/src/modules/instructions-module.ts +265 -0
  28. package/src/modules/mcpl-admin-module.ts +58 -11
  29. package/src/modules/subagent-module.ts +18 -0
  30. package/src/modules/web-ui-module.ts +32 -4
  31. package/src/recipe.ts +821 -25
  32. package/src/web/panel-data.ts +44 -1
  33. package/src/workspace-mounts.ts +73 -0
  34. package/test/audit-module-optins.test.ts +10 -3
  35. package/test/cache-keepalive-log.test.ts +83 -0
  36. package/test/commands-qa-family.test.ts +239 -0
  37. package/test/conversations-recipe.test.ts +142 -0
  38. package/test/count-tokens-model.test.ts +31 -0
  39. package/test/framework-fkm-composition.test.ts +35 -3
  40. package/test/framework-strategy-defaults.test.ts +60 -0
  41. package/test/gate-telemetry-adapter.test.ts +84 -0
  42. package/test/gate-telemetry.test.ts +124 -0
  43. package/test/identity-and-surfaces.test.ts +212 -1
  44. package/test/instructions-module.test.ts +258 -0
  45. package/test/mcpl-admin-module.test.ts +41 -0
  46. package/test/mcpl-agent-overlay.test.ts +51 -3
  47. package/test/mcpl-child-env.test.ts +64 -0
  48. package/test/nudge-command.test.ts +47 -0
  49. package/test/recipe-cache-keepalive.test.ts +59 -0
  50. package/test/recipe-compression-fallback.test.ts +19 -0
  51. package/test/recipe-hybrid-prose-routing.test.ts +12 -0
  52. package/test/recipe-instructions.test.ts +176 -0
  53. package/test/recipe-kv-unified.test.ts +87 -0
  54. package/test/recipe-mcp-source.test.ts +54 -0
  55. package/test/recipe-openai-compatible.test.ts +54 -0
  56. package/test/recipe-path-resolution.test.ts +19 -8
  57. package/test/recipe-provider.test.ts +14 -0
  58. package/test/recipe-save-unresolved.test.ts +244 -0
  59. package/test/recipe-source-only.test.ts +38 -0
  60. package/test/release-changelog.test.ts +202 -0
  61. package/test/subagent-prose-routing.test.ts +109 -0
  62. package/test/subconscious-recipe.test.ts +86 -0
  63. package/test/tool-wrapper-prose-guard-recipe.test.ts +37 -0
  64. package/test/web-ui-module.test.ts +41 -0
  65. package/test/workspace-mounts.test.ts +68 -0
  66. package/web/src/App.tsx +10 -0
  67. 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,327 @@
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.1 — 2026-09-10
10
+
11
+ ### Added
12
+
13
+ - Recipe `subconscious` block (tune-out, agent-framework#77): `enabled`,
14
+ `systemPrompt` (required — the subconscious's mode block), optional `name`,
15
+ `model`, `allowChannelSpeech`, `reAnchorFraction`. Validated at recipe load
16
+ (unknown fields refused by name) and passed through verbatim to
17
+ `FrameworkConfig.subconscious`; the framework owns the defaults. Requires
18
+ agent-framework with tune-out (#115).
19
+
20
+ - Add validated recipe plumbing for Agent Framework's default-off `agent.toolWrapperProseGuard` containment boundary.
21
+
22
+ ### Changed
23
+
24
+ - Gate telemetry reads `x-gate-channel` from the turn's routing locus or,
25
+ for gate-batched wakes that set none, from the framework's telemetry-only
26
+ `wakeChannelId` (agent-framework ≥0.14). On residents with a subconscious
27
+ the stamp now follows the primary (the exactly-one-agent guard had
28
+ silenced it), and — because one provider adapter serves every agent — the
29
+ origin trio is stamped only while the primary is the only agent with a
30
+ turn in flight; overlap withholds rather than misattributes. Compression
31
+ debt is read from the primary as well.
32
+
33
+ ### Fixed
34
+
35
+ - Recipes' `agent.strategy.mergeMaxSourceSpanMessages` now reaches the Context
36
+ Manager (it was accepted but never passed through, so the CM default applied
37
+ regardless of the recipe). Also plumbs and validates the Context Manager's
38
+ `compressionSplitFallback`, `compressionSplitPlaceholder`,
39
+ `compressionSplitMaxCallsPerChunk` and `compressionSplitMaxCallsPer10Min`
40
+ keys (all default off / CM defaults).
41
+
42
+ - Gate telemetry classifies EventGate-batched wakes (`gate:debounce`, source
43
+ `gate`) as origin `event` instead of passing the raw reason through.
44
+
45
+ - Name-taking commands (`/checkpoint`, `/restore`, `/checkout`,
46
+ `/session switch`, `/session delete`) parse the rest of the line instead of
47
+ only the first token, so multi-word names round-trip with
48
+ `/session rename` instead of silently truncating (`/checkpoint my test
49
+ point` used to save a checkpoint named `my`).
50
+ - `/session delete` requires `--confirm`: the bare command echoes exactly
51
+ which session matched (name, id, message count) before anything
52
+ irreversible happens. `/help` documents that switch/delete accept ids.
53
+ - Head-moving commands (`/undo`, `/redo`, `/checkout`, `/restore`,
54
+ `/branchto`, `/newtopic`) are refused while a turn is in flight — moving
55
+ the head mid-stream committed the streaming reply onto the wrong branch,
56
+ detached from its request (orphaned Chronicle nodes), including when the
57
+ move came from a second client on the same session.
58
+ - `/mcp add` on an existing server preserves its env vars and `toolPrefix`
59
+ (and reports the kept env keys); previously a command update silently
60
+ wiped the server's env, which only surfaced when the server next started
61
+ without its tokens.
62
+ - Checkpoints are visible: `/branches` lists them alongside branches, and
63
+ bare `/checkpoint` lists existing checkpoints (matching bare `/restore`).
64
+ - `/budget` displays small values exactly instead of flooring to `0k`
65
+ (`/budget 50` used to report "set to 0k" while rejecting `/budget 0`).
66
+ - `/clear` clears the WebUI transcript view (client-side, like the TUI's
67
+ scrollback wipe) instead of appending a "(cleared)" line while clearing
68
+ nothing; `/help` and the headless reply now say what `/clear` actually
69
+ does — display only, history and context kept.
70
+
71
+ - The WebUI HTTP surface answers honestly: unknown `/debug/*` paths (typos,
72
+ casing, trailing slashes) return a JSON 404 instead of the SPA shell with
73
+ a 200; missing `/assets/*` files return 404 instead of HTML (which
74
+ produced a blank page with a MIME error on stale bundle hashes); non-GET
75
+ methods get 405 with an `Allow` header. SPA client-side routes still fall
76
+ back to the shell.
77
+ - The context-makeup panel's exact token count calls `count_tokens` with the
78
+ model the agent actually runs (provider/Bedrock prefixes normalized away)
79
+ instead of a hardcoded id that 404'd on every install and silently nulled
80
+ `exactTotalTokens`. `COUNT_TOKENS_MODEL` remains as an explicit override;
81
+ non-Anthropic models report `count_tokens_unsupported_model` instead of
82
+ counting against the wrong tokenizer.
83
+
84
+ ## 0.8.0 — 2026-09-05
85
+
86
+ ### Added
87
+
88
+ - Recipes accept `agent.proseRouting: "disabled"` for tool-only external publication when paired with a supporting Agent Framework release.
89
+ - **`modules.instructions`** — a shared living-instructions file (a CLAUDE.md
90
+ analogue kept in a workspace mount) injected into every agent's context on
91
+ every turn, ephemeral subagents included. `true` for defaults
92
+ (`instructions/AGENTS.md`, 32 KB cap, `system` position) or
93
+ `{ path, header, maxBytes, position }`. Reads resolve through the workspace
94
+ mount (scoping + traversal guard apply), are bounded to `maxBytes`, reject
95
+ symlinks leading outside the mount (realpath containment), are cached by
96
+ `(realpath, mtime, size)`, and fail open — a missing file never blocks
97
+ inference. Recipe validation cross-checks the path's mount prefix against
98
+ the effective workspace mounts (explicit and implicit alike) at load time,
99
+ requires `autoMaterialize: true` on a read-write instructions mount so
100
+ agent curation edits actually reach the disk-side injection, and rejects
101
+ the host-managed `_config` mount (it materializes only on branch-changing
102
+ commands). Validation and the runtime share one mount builder
103
+ (`src/workspace-mounts.ts`), so the two cannot drift.
104
+
105
+ - Recipes accept the default-off `agent.strategy.compressionSourceOnly` flag and pass it through to Context Manager's residence-scoped L1 compression request builder (#103).
106
+
107
+ - Gate telemetry stamps why the turn fired: `x-gate-origin` (heartbeat |
108
+ event | mail | operator | raw reason), `x-gate-channel` and
109
+ `x-gate-counterparty` (adapter-namespaced ids, never content or display
110
+ names) ride the stream lane under the same `GATE_TELEMETRY=1` + base-URL
111
+ gate as the debt stamp; background calls on the complete lane carry debt
112
+ only (#113).
113
+
114
+ - Gate-bound Anthropic calls carry an `x-gate-debt-chunks` header with the
115
+ live compression-debt pending-chunk count (membrane `dynamicHeaders`,
116
+ antra-tess/membrane#65) — the gateway records it per ledger row and strips
117
+ it before the vendor. Double-gated on `GATE_TELEMETRY=1` AND a configured
118
+ `ANTHROPIC_BASE_URL`, so the stamp can never reach a vendor endpoint;
119
+ unreadable state sends no header rather than a guess (#109).
120
+
121
+ - Health tab renders the per-agent compression-debt reduction (state, pending
122
+ chunks, oldest age, merge queue) and says "not reported by this stack" when
123
+ absent — the queue is now distinct from context composition, and the top
124
+ line reads "inference queued" (#110).
125
+
126
+ - 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.
127
+
128
+ - **`agent.provider: 'openai-compatible'`** — run an agent against any
129
+ OpenAI chat-completions endpoint (Ollama, vLLM, Together, Groq, NanoGPT,
130
+ ...) via membrane's existing `OpenAICompatibleAdapter`, which no host ever
131
+ wired. The recipe names the endpoint (`agent.baseUrl`, validated as an
132
+ absolute http(s) URL at load) and the model (required — no default for an
133
+ arbitrary endpoint); the key comes from `OPENAI_COMPATIBLE_API_KEY`
134
+ only (no `OPENAI_API_KEY` fallback — `baseUrl` is recipe-controlled, so a
135
+ fallback would silently send a real OpenAI credential to an arbitrary
136
+ endpoint) and may be absent for local servers.
137
+ `agent.baseUrl` with any other provider is rejected at load time.
138
+
139
+ - 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.
140
+
141
+ ### Added
142
+
143
+ - **`agent.cacheKeepalive` — hold an idle agent's 1h prompt cache warm.** With
144
+ `cacheTtl: "1h"`, an idle agent's cached prefix expires after an hour and its
145
+ next wake pays a **2x cache write** over the entire context. Reading an entry
146
+ restarts its clock, so membrane now replays the last request with
147
+ `max_tokens: 0` (prefill only) to refresh it at cache-**read** price (0.1x).
148
+ On by default for the anthropic provider; `{ "enabled": false }` opts out.
149
+ - **Cost is proportional to actual idleness, not to `maxIdleHours`.** A poke
150
+ fires only when the entry is genuinely near expiry, so a busy agent never
151
+ fires one — its own traffic already refreshed the TTL. Measured on mythos
152
+ llm-calls over 36h: 563 of 637 gaps were under 5 minutes, only 5 exceeded
153
+ 1h.
154
+ - Knobs: `maxIdleHours` (default 24, measured from the last **real** request
155
+ so pokes cannot extend their own mandate) and `refreshAfterMinutes`
156
+ (default 45). Recipe validation **rejects `refreshAfterMinutes >= the cache
157
+ TTL`** — such a keepalive always fires after the entry has already expired,
158
+ paying a full cache write on every poke while still looking like a healthy
159
+ successful call.
160
+ - Events land in `service-stderr.log`, warn-level for `ineffective` and
161
+ `disabled`, so a background spender is legible without opening a billing
162
+ dashboard.
163
+ - Sizing, from fable-cm's 11-day log (~500k-token prefix): 49.7M tokens of
164
+ `cache_creation` landed on turns following a >1h idle gap — ~$944 of write
165
+ premium at fable-5 rates that this converts to ~$308 of reads.
166
+
167
+ - **`provider: "mock"` — run the whole host with zero provider spend and no
168
+ credentials.** Wires membrane's existing `MockAdapter` (previously
169
+ unreachable from any recipe) as a first-class provider: echoes the last
170
+ user message by default, or returns `agent.mock.defaultResponse` with
171
+ `agent.mock.echoMode: false` for deterministic scripted output. No API
172
+ key is required or read. Mock calls still ride the generic logging
173
+ decorator, so `llm-calls.*.jsonl` receipts work exactly as they do for
174
+ real providers. `recipes/mock-test.json` is a ready-made offline smoke
175
+ recipe (loopback webui, everything else off).
176
+
177
+ ### Added
178
+
179
+ - **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.
180
+
181
+ - **`conversations` recipe block — per-channel conversation forks.** Maps to
182
+ agent-framework's `ConversationRouter`: the recipe's agent becomes a dormant
183
+ trunk template, and qualifying incoming channel messages spawn per-channel
184
+ fork agents seeded from the trunk's current context. Recipe surface: `bind` /
185
+ `trigger` rules per channel kind (`dm`/`groupDm`/`channel`), `idleTtlMs`
186
+ (default 12h), `closurePrompt`, and `agentPrefix`. The host fills
187
+ `templateAgent` from the recipe and creates a fresh stateful strategy instance
188
+ for each fork. Absent block means no routing and no behavior change.
189
+
190
+ - **Protective reaction-suppression baseline for Discord adapters.** Stdio
191
+ MCPL children now receive `DISCORD_SUPPRESSED_REACTIONS_BASELINE` — the
192
+ agent-framework's exported refusal-annotation set (`REFUSAL_REACTION_BASELINE`,
193
+ comma-joined) — so a never-configured Discord adapter defaults to
194
+ suppressing exactly the markers this host's framework stamps, instead of
195
+ defaulting to nothing. An operator-set value on the server entry
196
+ supersedes the house baseline, and the adapter's own precedence (filters-file
197
+ key including explicit `[]` → legacy operator env → baseline) governs
198
+ enforcement; lost configuration stays stale rather than re-defaulting.
199
+ Requires an agent-framework release carrying the `REFUSAL_REACTION_BASELINE`
200
+ export.
201
+
202
+ - **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.
203
+
204
+ - **`mcpl_list` reports manifest freshness.** Each loaded server now shows the
205
+ last validated manifest revision plus fetch and grant-negotiation timestamps.
206
+ Older Agent Framework versions remain legible as `manifest=unknown`, and the
207
+ server-authored revision is quoted and bounded before reaching model-facing
208
+ text.
209
+
210
+ - **`BEDROCK_BASE_URL` env hook** for the bedrock provider — mirrors
211
+ `ANTHROPIC_BASE_URL`, routing bedrock-runtime calls through an inference
212
+ gateway (gate.animalabs.ai/bedrock/<credSet>). The gate reads the agent
213
+ token from the SigV4 Credential (`AWS_ACCESS_KEY_ID` slot), discards the
214
+ client signature, and re-signs with real AWS creds. First user: Princess,
215
+ moved off the first-party Anthropic API (classifier "bio" false-positive
216
+ streak) onto Bedrock Sonnet 4.5 via gate apse1 — needs membrane ≥1dcd4e3
217
+ for `global.` inference-profile id pass-through.
218
+
219
+ ### Changed
220
+
221
+ - **Dependency floor: agent-framework `^0.10.0`, chronicle `^0.3.0`,
222
+ membrane `^0.5.78`.** af 0.10.0 brings `ConversationRouter` (the
223
+ per-channel conversation-fork machinery this release’s `conversations`
224
+ recipe surface targets, and includes the current `hybrid` prose router) and exports `nudgeAgent`, which `/nudge` has
225
+ called since it landed — on every published af before 0.9.0 that call
226
+ was a guaranteed `TypeError`, so the floor also makes `/nudge` actually
227
+ work. Chronicle `^0.3.0` aligns the whole tree on one chronicle copy
228
+ (previously context-manager `0.6.3` nested its own `0.3.0` next to the
229
+ host's `0.2.x`). Operators: run a clean `npm ci` — a stale
230
+ `node_modules` predating the lock is the known failure mode here.
231
+
232
+ - **The public triumvirate recipes boot from a fresh clone.**
233
+ `knowledge-miner.json` no longer ships a `syncntn` (Notion) block pointing at
234
+ an org-internal adapter that isn't publicly available — with `NOTION_*` env
235
+ vars unset the block failed recipe load, and with them set it died at spawn
236
+ on the dangling `../syncntn` path. The `scribe` block is dropped for the
237
+ same reason: it hard-required `GEMINI_API_KEY` and a `../scribe-mcp`
238
+ sibling checkout, neither mentioned anywhere in the setup guides — a
239
+ guide-following fresh install always got a crashed miner. Notion and
240
+ Scribe are now add-a-block opt-ins, documented in SETUP.md and
241
+ TRIUMVIRATE-SETUP.md (the miner prompt's tool-name contracts are
242
+ unchanged). `triumvirate.json` declares
243
+ webui Basic-Auth defaulting to `admin`/`admin` (override via
244
+ `WEBUI_USERNAME` / `WEBUI_PASSWORD` in `.env`) instead of bare
245
+ `"webui": true`, which the non-loopback bind guard refuses to start.
246
+
247
+ - **agent-framework `^0.11.0`** (was `^0.10.0`). Activates `proseRouting:
248
+ "disabled"` for recipes that set it (#100 accepted the key; the runtime now
249
+ implements it — generated prose is never published externally, only explicit
250
+ tools speak), plus AF 0.11's Windows workspace-mount fix and the
251
+ org-acceleration 429 cooldown. Clears the last two standing cross-package
252
+ `tsc` errors — the typecheck is fully clean at this lock.
253
+
254
+ - Changelog entries now land as per-change fragment files in `changelog.d/`
255
+ (`<slug>.<breaking|added|changed|fixed>.md`), folded into the version
256
+ section at release time — concurrent PRs no longer conflict in
257
+ `CHANGELOG.md`. Editing `## Unreleased` directly still works and is merged
258
+ at the same point.
259
+
260
+ - **membrane `^0.5.80`** (was `^0.5.78`, lockfile-resolved 0.5.79). Two
261
+ latent cache behaviors the host already configures become ACTIVE with this
262
+ relock: the prompt-cache keepalive (`agent.cacheKeepalive`, on by default —
263
+ previously passed to an adapter version with no such field and silently
264
+ ignored, so idle gaps over the 1h TTL repaid a full cache write on wake)
265
+ and the floating cache marker (incremental prompt caching inside the native
266
+ tool loop, membrane's default-on). Both reduce cost; neither changes
267
+ visible agent behavior. Also clears two of the four standing cross-package
268
+ `tsc` errors (the membrane-typing pair).
269
+
270
+ - Depend on `@animalabs/agent-framework` ^0.12.0 and `@animalabs/membrane` ^0.5.82 —
271
+ the published versions that implement the active-turn trigger and the
272
+ lane-aware `dynamicHeaders` the wake-cause stamp (#113) relies on; the
273
+ compatibility cast and optional lookup are gone, and an adapter-level test
274
+ proves a stream call carries the origin trio while a complete call carries
275
+ debt only.
276
+
277
+ ### Fixed
278
+
279
+ - **Prompt-cache keepalive events all go to stderr**, so every one of them lands
280
+ in `service-stderr.log` beside `[inference-refusal]` instead of being split by
281
+ severity across two sinks. Routine `refreshed` events previously went to
282
+ stdout — which the host unit leaves on the journal — so the log an operator
283
+ actually greps showed nothing. Observed on fable-cm 2026-08-23: the keepalive
284
+ refreshed a 523,102-token prefix three times, correctly and with zero cache
285
+ writes, while a monitor tailing `service-stderr.log` reported no activity for
286
+ three hours. A background spender that can't be found in the operator's log is
287
+ indistinguishable from one that never ran.
288
+
289
+ - Plumb `agent.strategy.compressionRecallBudgetTokens` through recipe validation and Framework strategy construction, with positive-integer validation instead of silently accepting an inert key.
290
+
291
+ - **Saved recipe snapshots no longer contain resolved secrets.** `loadRecipe`
292
+ substitutes every `${VAR}` — API tokens included — and the host then wrote
293
+ that fully resolved recipe to `$DATA_DIR/.recipe.json` at default file mode:
294
+ plaintext credentials in the exact directory deployments bind-mount and back
295
+ up (found by an external recipe review that verified live tokens in a backed
296
+ up `data/` directory on a production VM). The snapshot now keeps the
297
+ pre-substitution form — `${VAR}` references literal, a URL `systemPrompt`
298
+ kept as the URL — and a resumed session re-runs substitution, validation,
299
+ and the prompt fetch against the *current* environment, so secret rotation
300
+ and remote prompt updates take effect on restart without re-cooking. The
301
+ file is written 0600 and re-chmod'd 0600 on every save. Legacy resolved
302
+ snapshots (no `$unresolved` marker) still load verbatim, with no
303
+ substitution, so a literal `${...}` surviving in prose cannot fail them;
304
+ resuming an unresolved snapshot whose required env var has since disappeared
305
+ fails loudly naming the variable instead of silently starting the default
306
+ recipe.
307
+
308
+ - **Ephemeral subagents inherit the caller's `proseRouting` mode.** They
309
+ previously always ran AF's `'locus'` default regardless of the recipe, so a
310
+ resident running `proseRouting: "disabled"` still spawned subagents whose
311
+ between-tool-calls prose published live into its open channel as parent
312
+ speech (field-confirmed on a deployed resident, 2026-08-26 — including
313
+ after the recipe adopted `"disabled"`, which reached only the resident).
314
+
315
+ ### Fixed
316
+
317
+ - **`mcpServers.<id>.source` accepts cook's npm registry form.**
318
+ `validateRecipe` demanded `source.url`, but connectome-cook's source grammar
319
+ also has `{ "npm": "pkg@version" }` — which the shipped knowledge-miner
320
+ recipe uses for its gitlab server, so that recipe failed to load
321
+ (`mcpServers.gitlab.source.url must be a non-empty string`). Exactly one of
322
+ `url` / `npm` is now required; the field remains build-tooling metadata,
323
+ ignored at runtime.
324
+
5
325
  ## 0.7.4 — 2026-08-03
6
326
 
7
327
  ### 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 |