@evoclock/pi-agentic-driver 0.9.0 → 0.9.2

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 (38) hide show
  1. package/.agentic-driver/router.defaults.json +64 -0
  2. package/LICENSE +11 -8
  3. package/README.md +94 -29
  4. package/extensions/attended-authority-guard.ts +1 -1
  5. package/extensions/code-phage.js +1 -1
  6. package/extensions/herdr-communication.ts +9 -2
  7. package/extensions/herdr-dispatch.ts +1 -1
  8. package/extensions/herdr-lifecycle.ts +5 -4
  9. package/extensions/linux-microvm.ts +1 -1
  10. package/extensions/pulse.ts +105 -0
  11. package/extensions/task-board.ts +1 -1
  12. package/lib/adapters/diff-scope.mjs +1 -1
  13. package/lib/adapters/evidence.mjs +1 -1
  14. package/lib/adapters/narrative.mjs +1 -1
  15. package/lib/adapters/review-feedback.mjs +1 -1
  16. package/lib/adapters/visualization.mjs +1 -1
  17. package/lib/code-phage-core.mjs +1 -1
  18. package/lib/python_ast_metrics.py +1 -1
  19. package/lib/typescript_ast_metrics.mjs +1 -1
  20. package/package.json +17 -3
  21. package/scripts/aidr_writing_review.js +1 -1
  22. package/scripts/enforcement/attended_authority_guard.js +1 -1
  23. package/scripts/enforcement/guest_containment_taxonomy.v1.json +1 -1
  24. package/scripts/enforcement/herdr_async_dispatch_pi.js +66 -4
  25. package/scripts/enforcement/herdr_communication_pi.js +55 -19
  26. package/scripts/enforcement/herdr_lifecycle_pi.js +3 -3
  27. package/scripts/enforcement/linux_microvm_cutover_pi.js +1 -1
  28. package/scripts/enforcement/native_tui_context.js +1 -1
  29. package/scripts/enforcement/pulse_core_pi.js +292 -0
  30. package/scripts/enforcement/pulse_scheduler_pi.js +1083 -0
  31. package/scripts/enforcement/router_engine_pi.js +483 -0
  32. package/scripts/enforcement/router_ranking_pi.js +146 -0
  33. package/scripts/enforcement/router_replacement_pi.js +98 -0
  34. package/scripts/enforcement/router_runtime_pi.js +299 -0
  35. package/scripts/enforcement/router_schemas_pi.js +604 -0
  36. package/scripts/enforcement/router_store_pi.js +438 -0
  37. package/scripts/enforcement/task_board_core_pi.js +597 -48
  38. package/skills/pulse/SKILL.md +39 -0
@@ -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,7 @@ 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
56
 
57
57
  **Status: active development and testing.** Each extension ships only after
58
58
  it passes fixture-based acceptance, native tests, live-session checks, and
@@ -66,7 +66,7 @@ components are listed here for transparency and are not packaged.
66
66
  <details>
67
67
  <summary><strong>code-phage, advisory code review</strong> <em>(released, 0.1.1)</em></summary>
68
68
 
69
- `code_phage` reviews a proposed change against a stated goal before the agent
69
+ The `code_phage` tool reviews a proposed change against a stated goal before the agent
70
70
  writes or commits code. Give it a goal, candidate files, accepted
71
71
  requirements, and test paths. It will:
72
72
 
@@ -109,8 +109,10 @@ only, not a runtime dependency).
109
109
  <details>
110
110
  <summary><strong>herdr-communication, controlled role communication</strong> <em>(released, 0.2.1)</em></summary>
111
111
 
112
- `agentic_herdr_communication` exchanges controlled, marked reports with
113
- configured Pi worker roles running under [Herdr](https://herdr.dev/) 0.8.2.
112
+ The `agentic_herdr_communication` tool exchanges controlled, marked reports with
113
+ configured Pi worker roles running under [Herdr](https://herdr.dev/) (validated
114
+ against Herdr 0.9.1; the trust seam accepts the Homebrew-managed herdr binary
115
+ across versions rather than pinning one).
114
116
 
115
117
  - **List and observe.** Worker roles are filtered to trusted repositories: a
116
118
  checked-in registry plus canonical-path validation. Unlisted or
@@ -133,7 +135,7 @@ configured Pi worker roles running under [Herdr](https://herdr.dev/) 0.8.2.
133
135
  <details>
134
136
  <summary><strong>herdr-lifecycle, role-labelled worker dispatch</strong> <em>(released, 0.2.1)</em></summary>
135
137
 
136
- `agentic_herdr_spawn_worker` turns one natural-language request into Herdr's
138
+ The `agentic_herdr_spawn_worker` tool turns one natural-language request into Herdr's
137
139
  native lifecycle. Choose a placement (`right`, `below`, or `tab`), a safe role
138
140
  label, a model from the active Pi model roster, and a trusted repository. The
139
141
  extension:
@@ -165,7 +167,7 @@ for the wider task and model-routing workflow.
165
167
  <details>
166
168
  <summary><strong>herdr-dispatch, continuous worker journeys</strong> <em>(released, 0.5.0)</em></summary>
167
169
 
168
- `agentic_worker_dispatch` runs controlled worker journeys and observes worker
170
+ The `agentic_worker_dispatch` tool runs controlled worker journeys and observes worker
169
171
  liveness. Two actions:
170
172
 
171
173
  - **pulse** reports whether a worker role is alive, its current state, and
@@ -192,7 +194,7 @@ silently, and return results as untrusted evidence.
192
194
 
193
195
 
194
196
  <details>
195
- <summary><strong>task board, planned and automated work you can see</strong> <em>(released, 0.9.0)</em></summary>
197
+ <summary><strong>task board and Pulse, planned and automated work you can see</strong> <em>(released, 0.9.1)</em></summary>
196
198
 
197
199
  Keep planned work in canonical `TASKS.md`. You can read it directly or use
198
200
  the generated `board.md` view in Obsidian. Agents use the same canonical
@@ -216,10 +218,10 @@ For automated work, you set an explicit policy for the board. It defines:
216
218
 
217
219
  Without a valid policy, automated dispatch is refused.
218
220
 
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.
221
+ The `agentic_kanban_board_dispatch` tool claims the highest-priority eligible card. It
222
+ creates a single-attempt assignment envelope that binds the card, repository,
223
+ starting revision, assigned branch, allowed paths, stopping point, expiry, and
224
+ interaction profile.
223
225
 
224
226
  Blocked, altered, expired, reused, or unauthorized assignments fail closed.
225
227
  The journey validates the authenticated envelope before each consequential
@@ -232,9 +234,16 @@ Claims and envelopes are kept in authenticated state beside `TASKS.md`. The
232
234
  derived `board.md` view shows active claims but never becomes an authority
233
235
  source.
234
236
 
237
+ The `agentic_kanban_pulse` tool checks the board for ready work and available capacity.
238
+ A check only reports which work is ready to run. It does not claim cards.
239
+
240
+ Interactive host work needs explicit policy and one native confirmation for
241
+ the batch. Automated work requires containment and never falls back to the
242
+ host.
243
+
235
244
  Coming next:
236
245
 
237
- - an optional watcher that checks for dispatchable work;
246
+ - contained Pulse execution through Testudo;
238
247
  - Vogelkop support for the shared card fields; and
239
248
  - our own Obsidian plugin for canonical `TASKS.md`.
240
249
 
@@ -259,7 +268,7 @@ writer's voice.*
259
268
  <details>
260
269
  <summary><strong>AI;DR, writing review</strong> <em>(released, 0.4.2)</em></summary>
261
270
 
262
- `agentic_aidr` reviews the last assistant response, supplied prose, or a
271
+ The `agentic_aidr` tool reviews the last assistant response, supplied prose, or a
263
272
  Markdown file. It checks four principles:
264
273
 
265
274
  - **Clarity.** Each sentence carries one useful idea.
@@ -299,8 +308,8 @@ QEMU/KVM virtual machine on a Linux host. The job cannot reach the host, the
299
308
  network, or anything else outside the machine. The system deletes the machine
300
309
  after the job ends.
301
310
 
302
- Isolation is off at the start of every session. You turn it on with
303
- `/agentic-isolation-enable` in the Pi TUI, and confirm. The model cannot run
311
+ Isolation is off at the start of every session. Use the `/agentic-isolation-enable`
312
+ command in the Pi TUI, and confirm. The model cannot run
304
313
  that command. Every cutover run asks for its own confirmation too. Nothing is
305
314
  saved to settings, and headless sessions stay blocked.
306
315
 
@@ -527,10 +536,9 @@ acceptance with all prohibited effects absent.
527
536
 
528
537
  ### Portable repository contract
529
538
 
530
- `templates/AGENTS.md` is a starting contract you can copy into any repository
531
- that uses Pi agents. It asks for controlled tasks and realistic tests, and
532
- treats agent reports as untrusted evidence. It never overwrites an existing
533
- `AGENTS.md`; add your project rules below it.
539
+ The `templates/AGENTS.md` file is a starting contract you can copy into any
540
+ repository that uses Pi agents. It asks for controlled tasks and realistic tests, and
541
+ treats agent reports as untrusted evidence. It never overwrites an existing `AGENTS.md`. Add your project rules below it.
534
542
 
535
543
  ## Install
536
544
 
@@ -540,24 +548,24 @@ From npm:
540
548
  pi install npm:@evoclock/pi-agentic-driver
541
549
  ```
542
550
 
543
- Or from Git at a pinned tag:
551
+ 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):
544
552
 
545
553
  ```sh
546
- pi install git:github.com/evoclock/pi-agentic-driver@v0.9.0
554
+ pi install git:github.com/evoclock/pi-agentic-driver@<latest-tag>
547
555
  ```
548
556
 
549
- Released extensions load standalone; neither requires the other.
557
+ Released extensions load independently.
550
558
 
551
559
  ### Choose which extensions load
552
560
 
553
- A normal install loads all six extensions by default. You can load only the
561
+ A normal install loads all packaged extensions by default. You can load only the
554
562
  extensions you want with the object form in your Pi settings:
555
563
 
556
564
  ```json
557
565
  {
558
566
  "packages": [
559
567
  {
560
- "source": "npm:@evoclock/pi-agentic-driver@0.9.0",
568
+ "source": "npm:@evoclock/pi-agentic-driver",
561
569
  "extensions": [
562
570
  "extensions/aidr.ts",
563
571
  "extensions/code-phage.js"
@@ -567,12 +575,69 @@ extensions you want with the object form in your Pi settings:
567
575
  }
568
576
  ```
569
577
 
570
- The package also includes `herdr-communication.ts`, `herdr-lifecycle.ts`,
571
- `herdr-dispatch.ts`, and `linux-microvm.ts`. Use `pi config` to enable or
578
+ The package includes the `herdr-communication.ts`, `herdr-lifecycle.ts`, and
579
+ `herdr-dispatch.ts` extensions. It also includes `linux-microvm.ts`. Use `pi config` to enable or
572
580
  disable individual resources from an installed package. You are not required
573
581
  to use every extension.
574
582
 
583
+ ## Router configuration
584
+
585
+ Board Pulse selects a route seat through the capacity/policy router. The
586
+ router reads a closed configuration file and a local-first SQLite file for
587
+ operational state (reservations, observations, route-decision audit).
588
+
589
+ - **Repository defaults** ship at `.agentic-driver/router.defaults.json`. The
590
+ template contains no accounts and one disabled placeholder seat, so it loads
591
+ out of the box and schedules nothing until you enable a real seat.
592
+ - **User profile (precedence over defaults):**
593
+ `~/.config/agentic-driver/router/profile.json`, or the path in the
594
+ `AGENTIC_DRIVER_ROUTER_PROFILE` environment variable. A profile section
595
+ replaces the matching defaults section wholesale; the merged config must
596
+ validate or the router refuses to produce decisions (fails closed).
597
+ - **Operational state:** `~/.local/share/agentic-driver/router/state.db`, or
598
+ `AGENTIC_DRIVER_ROUTER_DB`. This store is operational state only. The
599
+ authenticated claims file and the board writer remain the only dispatch
600
+ authority, and SQLite is repaired from them, never the reverse.
601
+ - **Runtime requirement:** the operational store uses the built-in
602
+ `node:sqlite` module. Node.js 22.5 or newer is required.
603
+
604
+ ### Tunable configuration values
605
+
606
+ Every numeric policy in the router is a configuration key — never a
607
+ constant in code. All keys are **required**. The repository defaults below
608
+ are conservative starting values. Your profile overrides them. A missing key
609
+ is a configuration validation failure: the router fails closed rather than
610
+ guess. Changing a value participates in the configuration digest, so cached
611
+ routing decisions computed under the old values no longer apply.
612
+
613
+ | Section | Key | Default | What it controls |
614
+ |---|---|---|---|
615
+ | `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. |
616
+ | `eligibility.reserve` | `scope` | `account-window` | How widely the reserve floor applies (per account per window at launch). |
617
+ | `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. |
618
+ | `eligibility.reserve` | `estimateSamples` | 20 | How many recent dispatches the consumption estimate averages: enough to smooth outliers, quick enough to track a model change. |
619
+ | `eligibility.reserve` | `estimateMinSamples` | 5 | Below this many observations the average is treated as noise and the cold-start value is used instead. |
620
+ | `eligibility.reserve` | `estimateOutlierSigma` | 3 | Outlier cut for the estimate: one unusually slow dispatch cannot inflate the average, real tail latency is kept. |
621
+ | `eligibility.reserve` | `ownerInteractiveOverride` | false | Whether your own interactive dispatches bypass the reserve rule (they do not, at launch). |
622
+ | `ranking` | `janusUrl` | `http://127.0.0.1:8787` | The local janus evaluation service endpoint used for semantic ranking. |
623
+ | `ranking` | `maxCandidates` | 8 | At most eight eligible seats are ranked per request; preference order trims first. |
624
+ | `ranking` | `fallback` | `preference-order` | What happens when ranking is unavailable: dispatch falls back to your preference order and never blocks. |
625
+ | `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. |
626
+ | `gate` | `timeoutMs` | 8000 | How long the gate waits for an evaluation. On timeout there is no verdict and dispatch is blocked (fail closed). |
627
+ | `gate` | `requestBudgetTokens` | 30000 | Maximum size of a gate evaluation request. Normal evaluations use under a thousand tokens. |
628
+ | 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. |
629
+ | 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. |
630
+
631
+ Seats are named for infrastructure, not models: `dgx-spark-cluster`,
632
+ `mac-studio`, `merge-gateway-worker`. The model is a mutable field on the
633
+ seat, so changing models never renumbers your configuration. Each seat also
634
+ carries `scopeStatements`: plain-language descriptions of what the seat is
635
+ for. The dispatch gate reads them when judging whether a task fits.
636
+
575
637
  ## License
576
638
 
577
- AGPL-3.0-or-later with author-attribution additional terms (Section 7(b));
578
- see [LICENSE](LICENSE). A commercial licence is available on request.
639
+ AGPL-3.0-only with author-attribution additional terms (Section 7(b));
640
+ see [LICENSE](LICENSE). Commercial use, forks, and substantial modifications
641
+ are permitted when all AGPL and attribution obligations are followed. A
642
+ separate commercial licence is available for organisations requiring
643
+ 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,7 +1,14 @@
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
+
4
+ import { statSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
3
6
 
4
7
  export default async function herdrCommunicationPi(pi) {
5
- const module = await import(new URL("../scripts/enforcement/herdr_communication_pi.js", import.meta.url).href);
8
+ const moduleUrl = new URL("../scripts/enforcement/herdr_communication_pi.js", import.meta.url);
9
+ // Native ESM retains transitive modules across Pi's Jiti extension reload.
10
+ // Bind this import to the current file revision so /reload cannot reuse it.
11
+ moduleUrl.searchParams.set("mtime", String(statSync(fileURLToPath(moduleUrl)).mtimeMs));
12
+ const module = await import(moduleUrl.href);
6
13
  return module.registerHerdrCommunicationInterface(pi);
7
14
  }
@@ -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,7 +1,8 @@
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
- export default async function herdrLifecyclePi(pi) {
5
- const module = await import(new URL("../scripts/enforcement/herdr_lifecycle_pi.js", import.meta.url).href);
6
- return module.registerHerdrLifecycleInterface(pi);
4
+ import { registerHerdrLifecycleInterface } from "../scripts/enforcement/herdr_lifecycle_pi.js";
5
+
6
+ export default function herdrLifecyclePi(pi) {
7
+ return registerHerdrLifecycleInterface(pi);
7
8
  }
@@ -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
 
@@ -0,0 +1,105 @@
1
+ // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ // Board Pulse provider extension. Registers the deterministic
5
+ // agentic_kanban_pulse tool and owns the optional fixed-interval timer. The
6
+ // board is resolved per tool call from the calling session's working
7
+ // directory; a workspace with no board file gets a structured
8
+ // board-unavailable result. Board Pulse is capacity scheduling; it is
9
+ // distinct from Herdr worker liveness pulse.
10
+ //
11
+ // No resources start in the extension factory: the timer is created and
12
+ // started only on session_start and cleared idempotently on session_shutdown
13
+ // (PULSE_DESIGN_v3 §8). The worker spawn seam is the existing guarded
14
+ // herdr-lifecycle boundary — native confirmation, trusted repository,
15
+ // installed model roll, fixed argv, shell:false — never raw pane management.
16
+
17
+ import { executeHerdrSpawnWorker } from "../scripts/enforcement/herdr_lifecycle_pi.js";
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";
20
+
21
+ export default function pulsePi(pi) {
22
+
23
+ // Guarded worker-creation seam (§2.5): every Pulse spawn goes through
24
+ // pulseWorkerSpawnSeam, which is placement-aware and fails closed — host
25
+ // placements route to executeHerdrSpawnWorker (native confirmation,
26
+ // verification); container/microvm placements are denied before any host
27
+ // worker is ever started. No other creation path exists.
28
+ const spawnWorker = pulseWorkerSpawnSeam({ executeHerdrSpawnWorker });
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
+
51
+ const result = registerPulseTools(pi, {
52
+ resolveBoardPath: (ctx) => resolveBoardPath(typeof ctx === "string" ? ctx : ctx?.cwd || process.cwd()),
53
+ spawnWorker,
54
+ routerRuntimeFor,
55
+ });
56
+
57
+ // Optional timer (§8): created only after session_start, only when the
58
+ // resolved workspace policy enables Pulse in automated mode with a valid
59
+ // interval. fillOnStart queues one tick after startup; it is not catch-up.
60
+ // Ticks never overlap and missed ticks are never replayed. Reload/new/
61
+ // resume/fork emit session_shutdown first, so exactly one fresh runtime
62
+ // exists at a time and no in-memory single-flight state is inherited.
63
+ let timer = null;
64
+ const clearTimer = () => {
65
+ if (timer) {
66
+ timer.clear();
67
+ timer = null;
68
+ }
69
+ // Release the cached per-dbPath store handles with the session so the
70
+ // runtime never accumulates DatabaseSync handles (W5).
71
+ closeCachedRouterStores();
72
+ };
73
+ const setupTimer = async (_event, ctx) => {
74
+ clearTimer();
75
+ try {
76
+ const boardPath = resolveBoardPath(ctx?.cwd || process.cwd());
77
+ if (!boardPath) return;
78
+ const { readAutomationPolicy, checkAutomationPolicy } = await import(new URL("../scripts/enforcement/task_board_core_pi.js", import.meta.url).href);
79
+ const policy = readAutomationPolicy(boardPath);
80
+ const check = checkAutomationPolicy(policy, { boardPath });
81
+ const pulse = check.ok ? check.policy.pulse : null;
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.
87
+ timer = createPulseTimer({
88
+ intervalSeconds: pulse.intervalSeconds,
89
+ fillOnStart: pulse.fillOnStart === true,
90
+ tick: () => pulseTick({ boardPath, spawnWorker, context: ctx,
91
+ routerRuntimeFor: () => routerRuntimeFor(ctx, boardPath, { readOnly: false }) }),
92
+ });
93
+ timer.start();
94
+ } catch {
95
+ // No-board/no-policy/disabled policy produce zero scheduling effects.
96
+ clearTimer();
97
+ }
98
+ };
99
+ if (typeof pi?.on === "function") {
100
+ pi.on("session_start", setupTimer);
101
+ pi.on("session_shutdown", clearTimer);
102
+ }
103
+
104
+ return result;
105
+ }
@@ -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
@@ -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
  // diff-scope adapter — changed-range extraction for code-phage.
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
  // evidence adapter — content-bound source anchors for code-phage.
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
  // narrative adapter — structured, concern-grouped review narrative.
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
  // review-feedback adapter — two-intent review items for code-phage.
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
  // visualization adapter — optional bounded visual payloads for code-phage.
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 { createHash } from "node:crypto";
5
5
  import { lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
@@ -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
  #!/usr/bin/env python3
5
5
  """AST-backed complexity metrics for code-phage.
@@ -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 ts from "typescript";
5
5
 
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@evoclock/pi-agentic-driver",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Guardrail extensions for Agentic Driver: advisory review, bounded Herdr communication, and guarded worker lifecycle.",
5
5
  "type": "module",
6
- "license": "AGPL-3.0-or-later",
6
+ "license": "AGPL-3.0-only",
7
+ "engines": {
8
+ "node": ">=22.5.0"
9
+ },
7
10
  "keywords": [
8
11
  "pi-package",
9
12
  "pi-extension",
@@ -40,6 +43,7 @@
40
43
  "extensions/herdr-communication.ts",
41
44
  "extensions/herdr-dispatch.ts",
42
45
  "extensions/task-board.ts",
46
+ "extensions/pulse.ts",
43
47
  "lib/adapters/diff-scope.mjs",
44
48
  "lib/adapters/evidence.mjs",
45
49
  "lib/adapters/narrative.mjs",
@@ -66,7 +70,17 @@
66
70
  "extensions/aidr.ts",
67
71
  "scripts/aidr_writing_review.js",
68
72
  "templates/AGENTS.md",
69
- "scripts/enforcement/task_board_core_pi.js"
73
+ "scripts/enforcement/task_board_core_pi.js",
74
+ "scripts/enforcement/pulse_core_pi.js",
75
+ "scripts/enforcement/pulse_scheduler_pi.js",
76
+ "scripts/enforcement/router_engine_pi.js",
77
+ "scripts/enforcement/router_schemas_pi.js",
78
+ "scripts/enforcement/router_ranking_pi.js",
79
+ "scripts/enforcement/router_store_pi.js",
80
+ "scripts/enforcement/router_replacement_pi.js",
81
+ "scripts/enforcement/router_runtime_pi.js",
82
+ ".agentic-driver/router.defaults.json",
83
+ "skills/pulse/SKILL.md"
70
84
  ],
71
85
  "pi": {
72
86
  "extensions": [
@@ -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 { createHash, randomUUID } from "node:crypto";
5
5
  import fs, { realpathSync } from "node:fs";
@@ -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
  // Attended-authority destructive-command guard for Pi.
5
5
  //