@evoclock/pi-agentic-driver 0.8.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -27
- package/extensions/task-board.ts +10 -3
- package/package.json +1 -1
- package/scripts/enforcement/herdr_async_dispatch_pi.js +90 -18
- package/scripts/enforcement/task_board_core_pi.js +1446 -51
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pi-agentic-driver v0.
|
|
1
|
+
# pi-agentic-driver v0.9.0
|
|
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,7 @@
|
|
|
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.
|
|
10
|
+
<img src="https://img.shields.io/badge/version-0.9.0-blue?style=flat" alt="Version 0.9.0"/>
|
|
11
11
|
<img src="https://img.shields.io/badge/status-active%20development%20%26%20testing-orange?style=flat" alt="Status"/>
|
|
12
12
|
<img src="https://img.shields.io/badge/JavaScript-F7DF1E?style=flat&logo=javascript&logoColor=black" alt="JavaScript"/>
|
|
13
13
|
<img src="https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white" alt="TypeScript"/>
|
|
@@ -51,6 +51,8 @@ proofs for agentic workflows.
|
|
|
51
51
|
| `agentic_worker_dispatch` | Runs controlled worker journeys and observes worker liveness. | shipped |
|
|
52
52
|
| `agentic_kanban_board` | Shows the workspace task board: lanes, flags, priorities, dependencies, and which cards can run. | shipped |
|
|
53
53
|
| `agentic_kanban_board_write` | Adds cards to the board through the trusted writer, which records who authorized the work. | shipped |
|
|
54
|
+
| `agentic_kanban_board_update` | Moves, closes, flags, edits, or removes cards on the board, always recording who authorized the change. | shipped |
|
|
55
|
+
| `agentic_kanban_board_dispatch` | Claims an eligible board card for automated contained work and creates its assignment envelope. | shipped |
|
|
54
56
|
|
|
55
57
|
**Status: active development and testing.** Each extension ships only after
|
|
56
58
|
it passes fixture-based acceptance, native tests, live-session checks, and
|
|
@@ -190,30 +192,51 @@ silently, and return results as untrusted evidence.
|
|
|
190
192
|
|
|
191
193
|
|
|
192
194
|
<details>
|
|
193
|
-
<summary><strong>task board, planned work you can see</strong> <em>(released, 0.
|
|
195
|
+
<summary><strong>task board, planned and automated work you can see</strong> <em>(released, 0.9.0)</em></summary>
|
|
194
196
|
|
|
195
|
-
Keep planned work
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
Keep planned work in canonical `TASKS.md`. You can read it directly or use
|
|
198
|
+
the generated `board.md` view in Obsidian. Agents use the same canonical
|
|
199
|
+
record through natural-language board tools.
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
You can tell an agent to add, edit, move, block, propose, close, or remove a
|
|
202
|
+
card. The trusted writer assigns its ID, checks its fields, records your
|
|
203
|
+
instruction, and protects its dispatch provenance.
|
|
201
204
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
instruction, or your approval of an agent's proposal. A card without that
|
|
206
|
-
record cannot be dispatched. A hand-edited card cannot fake the record, and
|
|
207
|
-
a tampered card refuses to run.
|
|
205
|
+
Agents cannot invent additional work or complete a card on their own. An
|
|
206
|
+
agent report is evidence for your review. Only your clear instruction or
|
|
207
|
+
direct board action completes the card.
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
agent to close it. An agent report that says the work is finished is
|
|
211
|
-
evidence for your review. It is never the completion itself.
|
|
209
|
+
For automated work, you set an explicit policy for the board. It defines:
|
|
212
210
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
- allowed roles and repositories;
|
|
212
|
+
- contained placement;
|
|
213
|
+
- the maximum concurrent work;
|
|
214
|
+
- the policy expiry; and
|
|
215
|
+
- the risk ceiling.
|
|
216
|
+
|
|
217
|
+
Without a valid policy, automated dispatch is refused.
|
|
218
|
+
|
|
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.
|
|
223
|
+
|
|
224
|
+
Blocked, altered, expired, reused, or unauthorized assignments fail closed.
|
|
225
|
+
The journey validates the authenticated envelope before each consequential
|
|
226
|
+
step. It runs on the assigned branch and consumes the envelope when the
|
|
227
|
+
attempt ends.
|
|
228
|
+
|
|
229
|
+
Consuming an envelope releases the claim. It does not mark the card done.
|
|
230
|
+
|
|
231
|
+
Claims and envelopes are kept in authenticated state beside `TASKS.md`. The
|
|
232
|
+
derived `board.md` view shows active claims but never becomes an authority
|
|
233
|
+
source.
|
|
234
|
+
|
|
235
|
+
Coming next:
|
|
236
|
+
|
|
237
|
+
- an optional watcher that checks for dispatchable work;
|
|
238
|
+
- Vogelkop support for the shared card fields; and
|
|
239
|
+
- our own Obsidian plugin for canonical `TASKS.md`.
|
|
217
240
|
|
|
218
241
|
</details>
|
|
219
242
|
|
|
@@ -388,10 +411,10 @@ end" or "don't wait for me" and the journey runs in autonomous mode. Say
|
|
|
388
411
|
"do these two things" and it runs in the normal mode, pausing for you
|
|
389
412
|
between steps. The mode is recorded in the report either way.
|
|
390
413
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
414
|
+
Autonomous journeys can run work you dispatch directly or work claimed from
|
|
415
|
+
a task board. Board-managed journeys use the card's authenticated assignment
|
|
416
|
+
envelope and the board's automation policy. Direct journeys continue to work
|
|
417
|
+
without a board.
|
|
395
418
|
|
|
396
419
|
### The cast
|
|
397
420
|
|
|
@@ -520,7 +543,7 @@ pi install npm:@evoclock/pi-agentic-driver
|
|
|
520
543
|
Or from Git at a pinned tag:
|
|
521
544
|
|
|
522
545
|
```sh
|
|
523
|
-
pi install git:github.com/evoclock/pi-agentic-driver@v0.
|
|
546
|
+
pi install git:github.com/evoclock/pi-agentic-driver@v0.9.0
|
|
524
547
|
```
|
|
525
548
|
|
|
526
549
|
Released extensions load standalone; neither requires the other.
|
|
@@ -534,7 +557,7 @@ extensions you want with the object form in your Pi settings:
|
|
|
534
557
|
{
|
|
535
558
|
"packages": [
|
|
536
559
|
{
|
|
537
|
-
"source": "npm:@evoclock/pi-agentic-driver@0.
|
|
560
|
+
"source": "npm:@evoclock/pi-agentic-driver@0.9.0",
|
|
538
561
|
"extensions": [
|
|
539
562
|
"extensions/aidr.ts",
|
|
540
563
|
"extensions/code-phage.js"
|
package/extensions/task-board.ts
CHANGED
|
@@ -9,18 +9,25 @@
|
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// The canonical file comes first: the projection (board.md) is a derived
|
|
13
|
+
// view and must never be the board the tools operate on.
|
|
14
|
+
const BOARD_FILENAMES = ["TASKS.md", "board.md"];
|
|
13
15
|
|
|
16
|
+
// Returns the board path for the workspace: an existing board file if one is
|
|
17
|
+
// present, otherwise the canonical TASKS.md candidate (the write tool
|
|
18
|
+
// bootstraps a fresh board there). Null only when the workspace is unknown.
|
|
14
19
|
export function resolveBoardPath(cwd) {
|
|
15
20
|
if (typeof cwd !== "string" || cwd === "") return null;
|
|
16
21
|
for (const name of BOARD_FILENAMES) {
|
|
17
22
|
const candidate = join(cwd, name);
|
|
18
23
|
if (existsSync(candidate)) return candidate;
|
|
19
24
|
}
|
|
20
|
-
return
|
|
25
|
+
return join(cwd, "TASKS.md");
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export default async function taskBoardPi(pi) {
|
|
24
29
|
const module = await import(new URL("../scripts/enforcement/task_board_core_pi.js", import.meta.url).href);
|
|
25
|
-
return module.registerKanbanBoardTools(pi, {
|
|
30
|
+
return module.registerKanbanBoardTools(pi, {
|
|
31
|
+
resolveBoardPath: (ctx) => resolveBoardPath(typeof ctx === "string" ? ctx : ctx?.cwd || process.cwd()),
|
|
32
|
+
});
|
|
26
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evoclock/pi-agentic-driver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Guardrail extensions for Agentic Driver: advisory review, bounded Herdr communication, and guarded worker lifecycle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
HERDR_COMMUNICATION_ACTIONS,
|
|
17
17
|
} from "./herdr_communication_pi.js";
|
|
18
18
|
import { isNativeTuiContext } from "./native_tui_context.js";
|
|
19
|
+
import { prepareEnvelopeForExecution, validateEnvelopeForExecution, consumeEnvelope } from "./task_board_core_pi.js";
|
|
19
20
|
|
|
20
21
|
export const WORKER_DISPATCH_TOOL = "agentic_worker_dispatch";
|
|
21
22
|
export const WORKER_DISPATCH_SCHEMA = "agentic-driver.worker-dispatch.v1";
|
|
@@ -246,24 +247,53 @@ export async function runWorkerJourney(params, context, options = {}, signal) {
|
|
|
246
247
|
const communicationOptions = options.communication ?? options;
|
|
247
248
|
const replacementRole = options.replacementRole ?? role;
|
|
248
249
|
const replacementModel = options.replacementModel ?? options.model ?? params.model;
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
250
|
+
const board = options.board && typeof options.board === "object"
|
|
251
|
+
&& typeof options.board.boardPath === "string" && options.board.envelope
|
|
252
|
+
? options.board
|
|
253
|
+
: null;
|
|
254
|
+
let boardAttemptStarted = false;
|
|
255
|
+
let envelopeConsumed = false;
|
|
256
|
+
const finish = async (requestedStatus) => {
|
|
257
|
+
let status = requestedStatus;
|
|
258
|
+
if (board && boardAttemptStarted && !envelopeConsumed) {
|
|
259
|
+
const reason = ["completed", "exhausted", "cancelled", "failed", "waiting-approval", "role-blocked", "worker-unresponsive"].includes(status)
|
|
260
|
+
? status
|
|
261
|
+
: status.startsWith("worker-unresponsive") ? "worker-unresponsive" : "failed";
|
|
262
|
+
let consumed;
|
|
263
|
+
try {
|
|
264
|
+
consumed = consumeEnvelope({ boardPath: board.boardPath, envelopeId: board.envelope.envelopeId, reason });
|
|
265
|
+
} catch (error) {
|
|
266
|
+
consumed = { ok: false, code: error?.code, reason: String(error?.message || error) };
|
|
267
|
+
}
|
|
268
|
+
if (consumed?.ok !== true) {
|
|
269
|
+
status = "failed";
|
|
270
|
+
journey.status = "failed";
|
|
271
|
+
journey.code = consumed?.code || "envelope-consume-failed";
|
|
272
|
+
journey.steps.push({ step: journey.steps.length + 1, taskId: null, status: "failed", error: consumed?.reason || "envelope consumption failed closed" });
|
|
273
|
+
} else {
|
|
274
|
+
envelopeConsumed = true;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
journey.status = status;
|
|
278
|
+
return {
|
|
279
|
+
schema: WORKER_DISPATCH_SCHEMA,
|
|
280
|
+
ok: status === "completed" || status === "exhausted" || status === "waiting-approval",
|
|
281
|
+
action: "dispatch",
|
|
282
|
+
mode,
|
|
283
|
+
autonomy,
|
|
284
|
+
...(journey.cast ? { cast: journey.cast } : {}),
|
|
285
|
+
role,
|
|
286
|
+
status,
|
|
287
|
+
steps: journey.steps,
|
|
288
|
+
stepCount: journey.steps.filter((step) => step.status === "done").length,
|
|
289
|
+
code: journey.code,
|
|
290
|
+
report: journeyReceipt(journey),
|
|
291
|
+
reportMarkers: { open: "[WORKER_JOURNEY_REPORT_BEGIN]", close: "[WORKER_JOURNEY_REPORT_END]" },
|
|
292
|
+
handoff: journey.handoff,
|
|
293
|
+
nonAuthorizing: true,
|
|
294
|
+
persisted: false,
|
|
295
|
+
};
|
|
296
|
+
};
|
|
267
297
|
|
|
268
298
|
// Explicit unstuck path: with native confirmation, spin up a replacement
|
|
269
299
|
// worker for the same trusted repository/role through the existing
|
|
@@ -375,6 +405,35 @@ export async function runWorkerJourney(params, context, options = {}, signal) {
|
|
|
375
405
|
}
|
|
376
406
|
if (signal?.aborted) return finish("cancelled");
|
|
377
407
|
|
|
408
|
+
// Board-planned journey: authenticate the persisted envelope, prepare its
|
|
409
|
+
// assigned branch exactly once, then validate again. Every consequential
|
|
410
|
+
// prompt below repeats validation so mid-journey drift fails closed.
|
|
411
|
+
const checkBoardEnvelope = () => board
|
|
412
|
+
? validateEnvelopeForExecution({ boardPath: board.boardPath, envelope: board.envelope })
|
|
413
|
+
: { ok: true };
|
|
414
|
+
if (board) {
|
|
415
|
+
let guard;
|
|
416
|
+
try {
|
|
417
|
+
const prepared = prepareEnvelopeForExecution({ boardPath: board.boardPath, envelope: board.envelope });
|
|
418
|
+
if (!prepared.ok) guard = prepared;
|
|
419
|
+
else {
|
|
420
|
+
boardAttemptStarted = true;
|
|
421
|
+
guard = checkBoardEnvelope();
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
journey.status = "failed";
|
|
425
|
+
journey.code = error?.code || "envelope-invalid";
|
|
426
|
+
journey.steps.push({ step: 0, taskId: null, status: "failed", error: String(error?.message || error).slice(0, 256) });
|
|
427
|
+
return finish("failed");
|
|
428
|
+
}
|
|
429
|
+
if (!guard.ok) {
|
|
430
|
+
journey.status = "failed";
|
|
431
|
+
journey.code = guard.code || "envelope-invalid";
|
|
432
|
+
journey.steps.push({ step: 0, taskId: null, status: "failed", error: guard.reason });
|
|
433
|
+
return finish("failed");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
378
437
|
for (let stepIndex = 1; stepIndex <= maxSteps; stepIndex += 1) {
|
|
379
438
|
if (signal?.aborted) { journey.status = "cancelled"; return finish("cancelled"); }
|
|
380
439
|
|
|
@@ -452,6 +511,19 @@ export async function runWorkerJourney(params, context, options = {}, signal) {
|
|
|
452
511
|
}
|
|
453
512
|
}
|
|
454
513
|
|
|
514
|
+
// Revalidate immediately before the consequential prompt. This catches
|
|
515
|
+
// expiry, card/claim changes, and repository/branch/revision drift that
|
|
516
|
+
// occurred during pulse, queue observation, or confirmation.
|
|
517
|
+
if (board) {
|
|
518
|
+
const guard = checkBoardEnvelope();
|
|
519
|
+
if (!guard.ok) {
|
|
520
|
+
journey.status = "failed";
|
|
521
|
+
journey.code = guard.code || "envelope-invalid";
|
|
522
|
+
journey.steps.push({ step: stepIndex, taskId: task.id, status: "failed", error: guard.reason });
|
|
523
|
+
return finish("failed");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
455
527
|
// One prompt exchange. Any failure is terminal for the journey; there is
|
|
456
528
|
// no invisible retry or resend.
|
|
457
529
|
const exchange = await executeHerdrCommunication(
|