@evoclock/pi-agentic-driver 0.9.1 → 0.9.3

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/.agentic-driver/router.defaults.json +64 -0
  2. package/LICENSE +11 -8
  3. package/README.md +116 -32
  4. package/extensions/attended-authority-guard.ts +1 -1
  5. package/extensions/code-phage.js +1 -1
  6. package/extensions/herdr-communication.ts +1 -1
  7. package/extensions/herdr-dispatch.ts +1 -1
  8. package/extensions/herdr-lifecycle.ts +1 -1
  9. package/extensions/linux-microvm.ts +1 -1
  10. package/extensions/pulse.ts +33 -2
  11. package/extensions/task-board.ts +1 -1
  12. package/janus/README.draft.md +210 -0
  13. package/janus/README.md +196 -0
  14. package/janus/janus/audit.ts +213 -0
  15. package/janus/janus/budget.ts +101 -0
  16. package/janus/janus/budget_breaker.ts +105 -0
  17. package/janus/janus/config.ts +332 -0
  18. package/janus/janus/errors.ts +90 -0
  19. package/janus/janus/evaluate.ts +230 -0
  20. package/janus/janus/keychain.ts +119 -0
  21. package/janus/janus/providers.ts +544 -0
  22. package/janus/janus/schema.ts +344 -0
  23. package/janus/janus/server.ts +267 -0
  24. package/janus/janus/single_flight.ts +99 -0
  25. package/janus/janus/verdict.ts +192 -0
  26. package/janus/package-lock.json +616 -0
  27. package/janus/package.json +29 -0
  28. package/janus/tests/accounting.test.ts +229 -0
  29. package/janus/tests/allowlist.test.ts +97 -0
  30. package/janus/tests/config.test.ts +151 -0
  31. package/janus/tests/errors.test.ts +147 -0
  32. package/janus/tests/evaluate_pipeline.test.ts +358 -0
  33. package/janus/tests/helpers.ts +217 -0
  34. package/janus/tests/providers.test.ts +292 -0
  35. package/janus/tests/redaction_budget.test.ts +227 -0
  36. package/janus/tests/schema.test.ts +356 -0
  37. package/janus/tests/server.test.ts +288 -0
  38. package/janus/tests/single_flight.test.ts +84 -0
  39. package/janus/tsconfig.json +19 -0
  40. package/janus/user.janus.plist +51 -0
  41. package/lib/adapters/diff-scope.mjs +1 -1
  42. package/lib/adapters/evidence.mjs +1 -1
  43. package/lib/adapters/narrative.mjs +1 -1
  44. package/lib/adapters/review-feedback.mjs +1 -1
  45. package/lib/adapters/visualization.mjs +1 -1
  46. package/lib/code-phage-core.mjs +1 -1
  47. package/lib/python_ast_metrics.py +1 -1
  48. package/lib/typescript_ast_metrics.mjs +1 -1
  49. package/package.json +14 -3
  50. package/scripts/aidr_writing_review.js +1 -1
  51. package/scripts/enforcement/attended_authority_guard.js +1 -1
  52. package/scripts/enforcement/guest_containment_taxonomy.v1.json +1 -1
  53. package/scripts/enforcement/herdr_async_dispatch_pi.js +1 -1
  54. package/scripts/enforcement/herdr_communication_pi.js +13 -8
  55. package/scripts/enforcement/herdr_lifecycle_pi.js +3 -3
  56. package/scripts/enforcement/linux_microvm_cutover_pi.js +1 -1
  57. package/scripts/enforcement/native_tui_context.js +1 -1
  58. package/scripts/enforcement/pulse_core_pi.js +38 -14
  59. package/scripts/enforcement/pulse_scheduler_pi.js +188 -25
  60. package/scripts/enforcement/router_engine_pi.js +483 -0
  61. package/scripts/enforcement/router_ranking_pi.js +146 -0
  62. package/scripts/enforcement/router_replacement_pi.js +98 -0
  63. package/scripts/enforcement/router_runtime_pi.js +299 -0
  64. package/scripts/enforcement/router_schemas_pi.js +654 -0
  65. package/scripts/enforcement/router_store_pi.js +438 -0
  66. package/scripts/enforcement/task_board_core_pi.js +255 -44
  67. package/skills/pulse/SKILL.md +1 -1
@@ -0,0 +1,64 @@
1
+ {
2
+ "schema": "agentic-driver.router-config.v1",
3
+ "revision": 1,
4
+ "endpoints": {
5
+ "local-default": {
6
+ "url": "http://127.0.0.1:8080",
7
+ "kind": "llama-server"
8
+ }
9
+ },
10
+ "seats": [
11
+ {
12
+ "schema": "agentic-driver.seat.v1",
13
+ "seatId": "local-default",
14
+ "kind": "local",
15
+ "provider": "llama-server",
16
+ "accountId": null,
17
+ "endpointRef": "local-default",
18
+ "model": "replace-with-installed-model",
19
+ "capabilities": ["implement", "review"],
20
+ "containmentTier": "none",
21
+ "maxConcurrency": 1,
22
+ "costClass": "free",
23
+ "quotaCollector": null,
24
+ "clusterMembership": null,
25
+ "enabled": false,
26
+ "deprecated": null
27
+ }
28
+ ],
29
+ "models": {
30
+ "replace-with-installed-model": {
31
+ "aliases": [],
32
+ "contextWindow": 32768,
33
+ "capabilities": ["implement", "review"]
34
+ }
35
+ },
36
+ "collectors": {},
37
+ "eligibility": {
38
+ "rules": [],
39
+ "reserve": {
40
+ "floorPercent": 40,
41
+ "scope": "account-window",
42
+ "coldStartFraction": 0.25,
43
+ "estimateSamples": 20,
44
+ "estimateMinSamples": 5,
45
+ "estimateOutlierSigma": 3,
46
+ "ownerInteractiveOverride": false
47
+ }
48
+ },
49
+ "preferences": {
50
+ "order": [{ "seatId": "local-default" }],
51
+ "costPolicy": "free-first"
52
+ },
53
+ "ranking": {
54
+ "enabled": false,
55
+ "janusUrl": "http://127.0.0.1:7431",
56
+ "maxCandidates": 8,
57
+ "fallback": "preference-order"
58
+ },
59
+ "localHealth": {
60
+ "probeSeconds": 60,
61
+ "warmStateTracking": true
62
+ },
63
+ "secrets": {}
64
+ }
package/LICENSE CHANGED
@@ -723,14 +723,17 @@ on the Software.
723
723
  COMMERCIAL LICENCE OPTION
724
724
  ================================================================================
725
725
 
726
- A commercial licence that waives the obligations of the GNU Affero
727
- General Public License (including the network-use source-disclosure
728
- obligation of Section 13 and the additional terms of Section 7(b)
729
- above) is available for users who cannot or do not wish to comply
730
- with the terms of the GNU Affero General Public License.
731
-
732
- The commercial licence is intended for for-profit entities and any
733
- use in a paid product or service. Contact the author for details.
726
+ Commercial use, modification, and distribution are permitted under
727
+ the GNU Affero General Public License when all AGPL obligations and
728
+ the additional terms of Section 7(b) above are followed. This includes
729
+ the network-use source-disclosure obligation of Section 13 and the
730
+ required preservation of attribution.
731
+
732
+ A separate commercial licence is available for organisations that
733
+ require proprietary modifications, do not wish to disclose covered
734
+ source as required by the AGPL, need alternative attribution terms, or
735
+ otherwise cannot or do not wish to comply with the AGPL. Contact the
736
+ author for details.
734
737
 
735
738
  When published, the commercial licence template will be available at
736
739
  COMMERCIAL.md in this repository.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # pi-agentic-driver v0.9.0
1
+ # pi-agentic-driver
2
2
 
3
3
  <p align="center">
4
4
  <img src="assets/Yamagane-origami.png" alt="pi-agentic-driver, Yamagane origami mark" width="140"/>
@@ -7,7 +7,6 @@
7
7
  <p align="center">
8
8
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL%20v3-blue?style=flat" alt="License: AGPL v3"/></a>
9
9
  <a href="https://www.npmjs.com/package/@evoclock/pi-agentic-driver"><img src="https://img.shields.io/npm/v/@evoclock/pi-agentic-driver?style=flat" alt="npm version"/></a>
10
- <img src="https://img.shields.io/badge/version-0.9.0-blue?style=flat" alt="Version 0.9.0"/>
11
10
  <img src="https://img.shields.io/badge/status-active%20development%20%26%20testing-orange?style=flat" alt="Status"/>
12
11
  <img src="https://img.shields.io/badge/JavaScript-F7DF1E?style=flat&logo=javascript&logoColor=black" alt="JavaScript"/>
13
12
  <img src="https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white" alt="TypeScript"/>
@@ -53,6 +52,9 @@ proofs for agentic workflows.
53
52
  | `agentic_kanban_board_write` | Adds cards to the board through the trusted writer, which records who authorized the work. | shipped |
54
53
  | `agentic_kanban_board_update` | Moves, closes, flags, edits, or removes cards on the board, always recording who authorized the change. | shipped |
55
54
  | `agentic_kanban_board_dispatch` | Claims an eligible board card for automated contained work and creates its assignment envelope. | shipped |
55
+ | `agentic_kanban_pulse` | Checks for ready work and available capacity, then starts assignments under the board policy. | shipped |
56
+ | Router | Capacity/policy routing for Board Pulse: seats with scope statements, tunable policy values, and a binding dispatch gate. | shipped |
57
+ | Janus (bundled service) | Local Jev evaluation service on loopback: semantic ranking, the dispatch-gate judgment, redaction before every external call. See `janus/README.md`. | shipped |
56
58
 
57
59
  **Status: active development and testing.** Each extension ships only after
58
60
  it passes fixture-based acceptance, native tests, live-session checks, and
@@ -66,7 +68,7 @@ components are listed here for transparency and are not packaged.
66
68
  <details>
67
69
  <summary><strong>code-phage, advisory code review</strong> <em>(released, 0.1.1)</em></summary>
68
70
 
69
- `code_phage` reviews a proposed change against a stated goal before the agent
71
+ The `code_phage` tool reviews a proposed change against a stated goal before the agent
70
72
  writes or commits code. Give it a goal, candidate files, accepted
71
73
  requirements, and test paths. It will:
72
74
 
@@ -102,6 +104,82 @@ only, not a runtime dependency).
102
104
  - **inventory refresh.** Git-aware codebase inventory regeneration with
103
105
  verification receipts, so prior-art matching stays honest.
104
106
 
107
+ <details>
108
+ <summary><strong>router, capacity/policy routing with a binding dispatch gate</strong> <em>(released, 0.9.2)</em></summary>
109
+
110
+ Board Pulse selects a route seat through the capacity/policy router. The
111
+ router reads a closed configuration file and a local-first SQLite file for
112
+ operational state (reservations, observations, route-decision audit).
113
+
114
+ Two router features call **Janus** — a separate local service, bundled under
115
+ `janus/`, that is the only component allowed to talk to the TypeSafe Jev
116
+ models:
117
+
118
+ - **Semantic ranking** (optional): Janus ranks up to eight eligible seats;
119
+ on any Janus failure the router falls back to preference order.
120
+ - **The binding dispatch gate**: before a task is claimed, Janus judges
121
+ whether the task fits the selected seat. No verdict, no dispatch — the
122
+ gate fails closed when Janus is unreachable.
123
+
124
+ Janus binds loopback only (`127.0.0.1:8787`), reads its API key from the
125
+ macOS Keychain at runtime, redacts state before every external call, and
126
+ coalesces identical requests. See `janus/README.md` for the endpoint
127
+ contract, Keychain provisioning, and configuration.
128
+
129
+ - **Repository defaults** ship at `.agentic-driver/router.defaults.json`. The
130
+ template contains no accounts and one disabled placeholder seat, so it loads
131
+ out of the box and schedules nothing until you enable a real seat.
132
+ - **User profile (precedence over defaults):**
133
+ `~/.config/agentic-driver/router/profile.json`, or the path in the
134
+ `AGENTIC_DRIVER_ROUTER_PROFILE` environment variable. A profile section
135
+ replaces the matching defaults section wholesale; the merged config must
136
+ validate or the router refuses to produce decisions (fails closed).
137
+ - **Operational state:** `~/.local/share/agentic-driver/router/state.db`, or
138
+ `AGENTIC_DRIVER_ROUTER_DB`. This store is operational state only. The
139
+ authenticated claims file and the board writer remain the only dispatch
140
+ authority; the system repairs SQLite from them, never the reverse.
141
+ - **Runtime requirement:** the operational store uses the built-in
142
+ `node:sqlite` module. You need Node.js 22.5 or newer.
143
+
144
+ <details>
145
+ <summary><strong>Tunable configuration values</strong> <em>(all keys required, defaults are conservative starting values)</em></summary>
146
+
147
+ Every numeric policy in the router is a configuration key — never a
148
+ constant in code. All keys are **required**. The repository defaults below
149
+ are conservative starting values. Your profile overrides them. A missing key
150
+ is a configuration validation failure: the router fails closed rather than
151
+ guess. Changing a value participates in the configuration digest, so cached
152
+ routing decisions computed under the old values no longer apply.
153
+
154
+ | Section | Key | Default | What it controls |
155
+ |---|---|---|---|
156
+ | `eligibility.reserve` | `floorPercent` | 40 | Share of every quota window kept in reserve (per account, per window). Headroom for an unexpected burst and your next interactive session, without idling the account. |
157
+ | `eligibility.reserve` | `scope` | `account-window` | How widely the reserve floor applies (per account per window at launch). |
158
+ | `eligibility.reserve` | `coldStartFraction` | 0.25 | Consumption assumed for a seat with too little history: deliberately generous, so a mis-estimate fails eligibility instead of draining the account. |
159
+ | `eligibility.reserve` | `estimateSamples` | 20 | How many recent dispatches the consumption estimate averages: enough to smooth outliers, quick enough to track a model change. |
160
+ | `eligibility.reserve` | `estimateMinSamples` | 5 | Below this many observations, the estimator treats the average as noise and uses the cold-start value instead. |
161
+ | `eligibility.reserve` | `estimateOutlierSigma` | 3 | Outlier cut for the estimate: one unusually slow dispatch cannot inflate the average, real tail latency is kept. |
162
+ | `eligibility.reserve` | `ownerInteractiveOverride` | false | Whether your own interactive dispatches bypass the reserve rule (they do not, at launch). |
163
+ | `ranking` | `janusUrl` | `http://127.0.0.1:8787` | The local Janus evaluation service endpoint used for semantic ranking. |
164
+ | `ranking` | `maxCandidates` | 8 | The router ranks at most eight eligible seats per request; preference order trims the list first. |
165
+ | `ranking` | `fallback` | `preference-order` | What happens when ranking is unavailable: dispatch falls back to your preference order and never blocks. |
166
+ | `gate` | `threshold` | 0.9 | The dispatch gate's confidence threshold. A task is only dispatched when the evaluation clears it. Lower it only with evidence from your own audit log. |
167
+ | `gate` | `timeoutMs` | 8000 | How long the gate waits for an evaluation. On timeout there is no verdict, and the gate blocks dispatch (fail closed). |
168
+ | `gate` | `requestBudgetTokens` | 30000 | Maximum size of a gate evaluation request. Normal evaluations use under a thousand tokens. |
169
+ | seat record | `maxConcurrency` | 4 | How many workers one seat may run at once. The default suits a typical single machine or hosted seat. Tune it per seat: a cluster takes more, a constrained subscription endpoint may take less. |
170
+ | automation policy | `maxConcurrent` | 4 | How many workers may run at once across all seats. The default is a conservative start. Tune it to the capacity you actually have, and raise it when you add seats or hardware. |
171
+
172
+ Seats are named for infrastructure, not models: `dgx-spark`,
173
+ `dgx-spark-cluster`, `mac-studio`, `mac-studio-cluster`, `strix-halo`,
174
+ `strix-halo-cluster`, `merge-gateway-worker`. The model is a mutable field on
175
+ the seat, so changing models never renumbers your configuration. Each seat
176
+ also carries `scopeStatements`: plain-language descriptions of what the seat
177
+ is for. The dispatch gate reads them when judging whether a task fits.
178
+
179
+ </details>
180
+
181
+ </details>
182
+
105
183
  ## Multi-agent communication
106
184
 
107
185
  *Extensions for controlled coordination between agents.*
@@ -109,8 +187,10 @@ only, not a runtime dependency).
109
187
  <details>
110
188
  <summary><strong>herdr-communication, controlled role communication</strong> <em>(released, 0.2.1)</em></summary>
111
189
 
112
- `agentic_herdr_communication` exchanges controlled, marked reports with
113
- configured Pi worker roles running under [Herdr](https://herdr.dev/) 0.8.2.
190
+ The `agentic_herdr_communication` tool exchanges controlled, marked reports with
191
+ configured Pi worker roles running under [Herdr](https://herdr.dev/) (validated
192
+ against Herdr 0.9.1; the trust seam accepts the Homebrew-managed herdr binary
193
+ across versions rather than pinning one).
114
194
 
115
195
  - **List and observe.** Worker roles are filtered to trusted repositories: a
116
196
  checked-in registry plus canonical-path validation. Unlisted or
@@ -133,7 +213,7 @@ configured Pi worker roles running under [Herdr](https://herdr.dev/) 0.8.2.
133
213
  <details>
134
214
  <summary><strong>herdr-lifecycle, role-labelled worker dispatch</strong> <em>(released, 0.2.1)</em></summary>
135
215
 
136
- `agentic_herdr_spawn_worker` turns one natural-language request into Herdr's
216
+ The `agentic_herdr_spawn_worker` tool turns one natural-language request into Herdr's
137
217
  native lifecycle. Choose a placement (`right`, `below`, or `tab`), a safe role
138
218
  label, a model from the active Pi model roster, and a trusted repository. The
139
219
  extension:
@@ -165,7 +245,7 @@ for the wider task and model-routing workflow.
165
245
  <details>
166
246
  <summary><strong>herdr-dispatch, continuous worker journeys</strong> <em>(released, 0.5.0)</em></summary>
167
247
 
168
- `agentic_worker_dispatch` runs controlled worker journeys and observes worker
248
+ The `agentic_worker_dispatch` tool runs controlled worker journeys and observes worker
169
249
  liveness. Two actions:
170
250
 
171
251
  - **pulse** reports whether a worker role is alive, its current state, and
@@ -192,7 +272,7 @@ silently, and return results as untrusted evidence.
192
272
 
193
273
 
194
274
  <details>
195
- <summary><strong>task board, planned and automated work you can see</strong> <em>(released, 0.9.0)</em></summary>
275
+ <summary><strong>task board and Pulse, planned and automated work you can see</strong> <em>(released, 0.9.1)</em></summary>
196
276
 
197
277
  Keep planned work in canonical `TASKS.md`. You can read it directly or use
198
278
  the generated `board.md` view in Obsidian. Agents use the same canonical
@@ -216,10 +296,10 @@ For automated work, you set an explicit policy for the board. It defines:
216
296
 
217
297
  Without a valid policy, automated dispatch is refused.
218
298
 
219
- `agentic_kanban_board_dispatch` claims the highest-priority eligible card and
220
- creates a single-attempt assignment envelope. The envelope binds the card,
221
- repository, starting revision, assigned branch, allowed paths, stopping
222
- point, expiry, and interaction profile.
299
+ The `agentic_kanban_board_dispatch` tool claims the highest-priority eligible card. It
300
+ creates a single-attempt assignment envelope that binds the card, repository,
301
+ starting revision, assigned branch, allowed paths, stopping point, expiry, and
302
+ interaction profile.
223
303
 
224
304
  Blocked, altered, expired, reused, or unauthorized assignments fail closed.
225
305
  The journey validates the authenticated envelope before each consequential
@@ -232,10 +312,12 @@ Claims and envelopes are kept in authenticated state beside `TASKS.md`. The
232
312
  derived `board.md` view shows active claims but never becomes an authority
233
313
  source.
234
314
 
235
- `agentic_kanban_pulse` checks the board for ready work and available capacity.
236
- A check only reports which work is ready to run. It does not claim cards. Interactive host
237
- work needs explicit policy and one native confirmation for the batch. Automated
238
- work requires containment and never falls back to the host.
315
+ The `agentic_kanban_pulse` tool checks the board for ready work and available capacity.
316
+ A check only reports which work is ready to run. It does not claim cards.
317
+
318
+ Interactive host work needs explicit policy and one native confirmation for
319
+ the batch. Automated work requires containment and never falls back to the
320
+ host.
239
321
 
240
322
  Coming next:
241
323
 
@@ -264,7 +346,7 @@ writer's voice.*
264
346
  <details>
265
347
  <summary><strong>AI;DR, writing review</strong> <em>(released, 0.4.2)</em></summary>
266
348
 
267
- `agentic_aidr` reviews the last assistant response, supplied prose, or a
349
+ The `agentic_aidr` tool reviews the last assistant response, supplied prose, or a
268
350
  Markdown file. It checks four principles:
269
351
 
270
352
  - **Clarity.** Each sentence carries one useful idea.
@@ -304,8 +386,8 @@ QEMU/KVM virtual machine on a Linux host. The job cannot reach the host, the
304
386
  network, or anything else outside the machine. The system deletes the machine
305
387
  after the job ends.
306
388
 
307
- Isolation is off at the start of every session. You turn it on with
308
- `/agentic-isolation-enable` in the Pi TUI, and confirm. The model cannot run
389
+ Isolation is off at the start of every session. Use the `/agentic-isolation-enable`
390
+ command in the Pi TUI, and confirm. The model cannot run
309
391
  that command. Every cutover run asks for its own confirmation too. Nothing is
310
392
  saved to settings, and headless sessions stay blocked.
311
393
 
@@ -532,10 +614,9 @@ acceptance with all prohibited effects absent.
532
614
 
533
615
  ### Portable repository contract
534
616
 
535
- `templates/AGENTS.md` is a starting contract you can copy into any repository
536
- that uses Pi agents. It asks for controlled tasks and realistic tests, and
537
- treats agent reports as untrusted evidence. It never overwrites an existing
538
- `AGENTS.md`; add your project rules below it.
617
+ The `templates/AGENTS.md` file is a starting contract you can copy into any
618
+ repository that uses Pi agents. It asks for controlled tasks and realistic tests, and
619
+ treats agent reports as untrusted evidence. It never overwrites an existing `AGENTS.md`. Add your project rules below it.
539
620
 
540
621
  ## Install
541
622
 
@@ -545,24 +626,24 @@ From npm:
545
626
  pi install npm:@evoclock/pi-agentic-driver
546
627
  ```
547
628
 
548
- Or from Git at a pinned tag:
629
+ Or from Git at the latest release tag (see the repo's tags for the current one; the npm badge above shows the live version):
549
630
 
550
631
  ```sh
551
- pi install git:github.com/evoclock/pi-agentic-driver@v0.9.0
632
+ pi install git:github.com/evoclock/pi-agentic-driver@<latest-tag>
552
633
  ```
553
634
 
554
- Released extensions load standalone; neither requires the other.
635
+ Released extensions load independently.
555
636
 
556
637
  ### Choose which extensions load
557
638
 
558
- A normal install loads all six extensions by default. You can load only the
639
+ A normal install loads all packaged extensions by default. You can load only the
559
640
  extensions you want with the object form in your Pi settings:
560
641
 
561
642
  ```json
562
643
  {
563
644
  "packages": [
564
645
  {
565
- "source": "npm:@evoclock/pi-agentic-driver@0.9.0",
646
+ "source": "npm:@evoclock/pi-agentic-driver",
566
647
  "extensions": [
567
648
  "extensions/aidr.ts",
568
649
  "extensions/code-phage.js"
@@ -572,12 +653,15 @@ extensions you want with the object form in your Pi settings:
572
653
  }
573
654
  ```
574
655
 
575
- The package also includes `herdr-communication.ts`, `herdr-lifecycle.ts`,
576
- `herdr-dispatch.ts`, and `linux-microvm.ts`. Use `pi config` to enable or
656
+ The package includes the `herdr-communication.ts`, `herdr-lifecycle.ts`, and
657
+ `herdr-dispatch.ts` extensions. It also includes `linux-microvm.ts`. Use `pi config` to enable or
577
658
  disable individual resources from an installed package. You are not required
578
659
  to use every extension.
579
660
 
580
661
  ## License
581
662
 
582
- AGPL-3.0-or-later with author-attribution additional terms (Section 7(b));
583
- see [LICENSE](LICENSE). A commercial licence is available on request.
663
+ AGPL-3.0-only with author-attribution additional terms (Section 7(b));
664
+ see [LICENSE](LICENSE). Commercial use, forks, and substantial modifications
665
+ are permitted when all AGPL and attribution obligations are followed. A
666
+ separate commercial licence is available for organisations requiring
667
+ proprietary modifications or other terms incompatible with the AGPL.
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
4
  export default async function attendedAuthorityGuard(pi) {
5
5
  const module = await import(new URL("../scripts/enforcement/attended_authority_guard.js", import.meta.url).href);
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
4
  import {
5
5
  analyzeCodePhage,
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
4
  import { statSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
4
  export default async function herdrDispatchPi(pi) {
5
5
  const dispatch = await import(new URL("../scripts/enforcement/herdr_async_dispatch_pi.js", import.meta.url).href);
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
4
  import { registerHerdrLifecycleInterface } from "../scripts/enforcement/herdr_lifecycle_pi.js";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
4
  import { registerLinuxMicroVMCutoverInterface, registerIsolationSwitchCommands, createIsolationSwitch } from "../scripts/enforcement/linux_microvm_cutover_pi.js";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
4
  // Board Pulse provider extension. Registers the deterministic
5
5
  // agentic_kanban_pulse tool and owns the optional fixed-interval timer. The
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { executeHerdrSpawnWorker } from "../scripts/enforcement/herdr_lifecycle_pi.js";
18
18
  import { resolveBoardPath, registerPulseTools, createPulseTimer, pulseTick, pulseWorkerSpawnSeam } from "../scripts/enforcement/pulse_scheduler_pi.js";
19
+ import { prepareRouterRuntimeWithHealth, closeCachedRouterStores } from "../scripts/enforcement/router_runtime_pi.js";
19
20
 
20
21
  export default function pulsePi(pi) {
21
22
 
@@ -26,9 +27,31 @@ export default function pulsePi(pi) {
26
27
  // worker is ever started. No other creation path exists.
27
28
  const spawnWorker = pulseWorkerSpawnSeam({ executeHerdrSpawnWorker });
28
29
 
30
+ // The runtime is rebuilt per tool call and per automated tick (W3): a
31
+ // snapshot frozen at session setup would leave every subscription seat
32
+ // stale after the first observation expiry and freeze the prior-reservation
33
+ // term. The localHealth probe adapter (W2) is attached here, producing
34
+ // endpoint-keyed fresh health observations for local seats. `check`
35
+ // assembles the runtime read-only (W4): no store writes, no store creation.
36
+ const routerRuntimeFor = async (ctx, boardPath, { readOnly = false } = {}) => {
37
+ const runtime = await prepareRouterRuntimeWithHealth({
38
+ repoRoot: ctx?.cwd || process.cwd(), boardPath,
39
+ defaultsPath: ctx?.routerDefaultsPath ?? null, profilePath: ctx?.routerProfilePath ?? null,
40
+ dbPath: ctx?.routerDbPath ?? null, cacheRoot: ctx?.routerCacheRoot ?? null,
41
+ consumptionReceipts: ctx?.consumptionReceipts ?? [], readOnly,
42
+ fetchFn: ctx?.routerHealthFetch ?? null,
43
+ });
44
+ const installed = new Set(runtime.routerConfig.seats.map((seat) => seat.model).filter((model) => {
45
+ try { return ctx?.modelRegistry?.get?.(model) != null; } catch { return false; }
46
+ }));
47
+ runtime.routerSnapshot.modelInstalled = installed;
48
+ return { ...runtime, activeSessions: ctx?.activeSessions ?? [], providerLimit: ctx?.providerLimit ?? null };
49
+ };
50
+
29
51
  const result = registerPulseTools(pi, {
30
52
  resolveBoardPath: (ctx) => resolveBoardPath(typeof ctx === "string" ? ctx : ctx?.cwd || process.cwd()),
31
53
  spawnWorker,
54
+ routerRuntimeFor,
32
55
  });
33
56
 
34
57
  // Optional timer (§8): created only after session_start, only when the
@@ -43,6 +66,9 @@ export default function pulsePi(pi) {
43
66
  timer.clear();
44
67
  timer = null;
45
68
  }
69
+ // Release the cached per-dbPath store handles with the session so the
70
+ // runtime never accumulates DatabaseSync handles (W5).
71
+ closeCachedRouterStores();
46
72
  };
47
73
  const setupTimer = async (_event, ctx) => {
48
74
  clearTimer();
@@ -54,10 +80,15 @@ export default function pulsePi(pi) {
54
80
  const check = checkAutomationPolicy(policy, { boardPath });
55
81
  const pulse = check.ok ? check.policy.pulse : null;
56
82
  if (!pulse || pulse.enabled !== true || pulse.mode !== "automated") return;
83
+ // W3: the tick asks for a FRESH runtime every tick instead of reusing a
84
+ // setup-time snapshot. The factory re-reads collector caches, health
85
+ // probes, and the prior-reservation term under the read-time freshness
86
+ // rules; a factory failure fails that tick closed.
57
87
  timer = createPulseTimer({
58
88
  intervalSeconds: pulse.intervalSeconds,
59
89
  fillOnStart: pulse.fillOnStart === true,
60
- tick: () => pulseTick({ boardPath, spawnWorker, context: ctx }),
90
+ tick: () => pulseTick({ boardPath, spawnWorker, context: ctx,
91
+ routerRuntimeFor: () => routerRuntimeFor(ctx, boardPath, { readOnly: false }) }),
61
92
  });
62
93
  timer.start();
63
94
  } catch {
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
4
  // BOARD-1 provider extension. Both tools register at startup; the board is
5
5
  // resolved per tool call from the calling session's working directory. A