@evoclock/pi-agentic-driver 1.1.2 → 2.0.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/.agentic-driver/loop-until-done.defaults.json +5 -0
- package/README.md +302 -41
- package/config/merge-policy.v1.example.json +4 -0
- package/config/relay-authority.v1.example.json +7 -0
- package/config/treehouse.v1.json +4 -0
- package/docs/durable-driver-api.md +23 -0
- package/docs/durable-persistence.md +32 -0
- package/docs/merge-policy.md +45 -0
- package/docs/relay-authority.md +9 -0
- package/extensions/aidr.ts +3 -0
- package/extensions/harvest-lessons.ts +8 -0
- package/extensions/herdr-dispatch.ts +32 -3
- package/extensions/loop-until-done.ts +7 -0
- package/extensions/recovery-evidence.ts +3 -3
- package/extensions/treehouse-recycle.ts +47 -0
- package/janus/README.md +2 -4
- package/janus/janus/config.ts +2 -2
- package/janus/janus/providers.ts +2 -4
- package/janus/tests/providers.test.ts +2 -2
- package/{extensions → lib}/task-store-adapter.ts +1 -5
- package/package.json +158 -4
- package/scripts/enforcement/approval_ledger_pi.js +135 -0
- package/scripts/enforcement/attended_authority_guard.js +113 -0
- package/scripts/enforcement/board_claim_bridge_pi.js +10 -10
- package/scripts/enforcement/durable_assignment_pi.js +488 -0
- package/scripts/enforcement/durable_driver_pi.d.ts +51 -0
- package/scripts/enforcement/durable_driver_pi.js +254 -0
- package/scripts/enforcement/herdr_async_dispatch_pi.js +86 -20
- package/scripts/enforcement/herdr_async_seam_pi.js +90 -3
- package/scripts/enforcement/herdr_communication_pi.js +126 -12
- package/scripts/enforcement/herdr_lifecycle_pi.js +730 -81
- package/scripts/enforcement/linux_microvm_cutover_pi.js +3 -3
- package/scripts/enforcement/linux_microvm_remote_fixture.sh +0 -6
- package/scripts/enforcement/loop_until_done_guide.md +13 -0
- package/scripts/enforcement/loop_until_done_pi.js +214 -0
- package/scripts/enforcement/merge_policy.js +181 -0
- package/scripts/enforcement/outward_effect_pi.js +156 -0
- package/scripts/enforcement/recovery_evidence_review_pi.js +31 -3
- package/scripts/enforcement/storage_owner_lock_pi.js +66 -0
- package/scripts/enforcement/task_board_core_pi.js +13 -100
- package/scripts/enforcement/task_board_hash_pi.js +86 -0
- package/scripts/enforcement/treehouse_recycle_pi.js +145 -0
- package/scripts/harvest_lessons.js +219 -0
- package/scripts/relay/record_approval.mjs +10 -0
- package/scripts/relay/send_request.mjs +26 -0
- package/skills/architect-pi/SKILL.md +44 -0
- package/skills/arena-pi/SKILL.md +46 -0
- package/skills/automate-me-pi/SKILL.md +37 -0
- package/skills/benchmark-checklist-pi/SKILL.md +35 -0
- package/skills/blast-radius-pi/SKILL.md +24 -0
- package/skills/bro-pi/SKILL.md +17 -0
- package/skills/correct-pi/SKILL.md +36 -0
- package/skills/create-verification-skill-pi/LICENSE +21 -0
- package/skills/create-verification-skill-pi/SKILL.md +24 -0
- package/skills/decision-to-spec/SKILL.md +5 -5
- package/skills/figure-it-out-pi/SKILL.md +37 -0
- package/skills/harvest-lessons/SKILL.md +56 -0
- package/skills/harvest-lessons/harvest.example.json +8 -0
- package/skills/how/LICENSE +24 -0
- package/skills/how/WORKFLOW_BOUNDARIES.md +19 -0
- package/skills/how-pi/SKILL.md +33 -0
- package/skills/in-your-own-words-pi/SKILL.md +15 -0
- package/skills/interrogate-pi/SKILL.md +35 -0
- package/skills/ketchup-pi/SKILL.md +21 -0
- package/skills/maintain-verification-skill-pi/SKILL.md +37 -0
- package/skills/make-bot-ui-pi/SKILL.md +33 -0
- package/skills/no-comments-pi/SKILL.md +33 -0
- package/skills/poteto-help-pi/SKILL.md +50 -0
- package/skills/poteto-mode-pi/LICENSE +21 -0
- package/skills/poteto-mode-pi/SKILL.md +41 -0
- package/skills/poteto-mode-pi/playbooks.md +95 -0
- package/skills/principle-attack-the-premise-pi/SKILL.md +17 -0
- package/skills/principle-boundary-discipline-pi/SKILL.md +19 -0
- package/skills/principle-build-the-lever-pi/SKILL.md +19 -0
- package/skills/principle-encode-lessons-in-structure-pi/SKILL.md +17 -0
- package/skills/principle-exhaust-the-design-space-pi/SKILL.md +17 -0
- package/skills/principle-experience-first-pi/SKILL.md +17 -0
- package/skills/principle-explain-the-number-pi/SKILL.md +21 -0
- package/skills/principle-fix-root-causes-pi/SKILL.md +21 -0
- package/skills/principle-foundational-thinking-pi/SKILL.md +21 -0
- package/skills/principle-guard-the-context-window-pi/SKILL.md +19 -0
- package/skills/principle-laziness-protocol-pi/SKILL.md +19 -0
- package/skills/principle-make-operations-idempotent-pi/SKILL.md +21 -0
- package/skills/principle-migrate-callers-then-delete-legacy-apis-pi/SKILL.md +19 -0
- package/skills/principle-minimize-reader-load-pi/SKILL.md +19 -0
- package/skills/principle-model-the-domain-pi/SKILL.md +19 -0
- package/skills/principle-never-block-on-the-human-pi/SKILL.md +19 -0
- package/skills/principle-outcome-oriented-execution-pi/SKILL.md +19 -0
- package/skills/principle-prove-it-works-pi/SKILL.md +19 -0
- package/skills/principle-redesign-from-first-principles-pi/SKILL.md +19 -0
- package/skills/principle-separate-before-serializing-shared-state-pi/SKILL.md +19 -0
- package/skills/principle-sequence-verifiable-units-pi/SKILL.md +19 -0
- package/skills/principle-subtract-before-you-add-pi/SKILL.md +19 -0
- package/skills/principle-test-behavior-not-implementation-pi/SKILL.md +21 -0
- package/skills/principle-type-system-discipline-pi/SKILL.md +19 -0
- package/skills/public-pr-wording/SKILL.md +69 -0
- package/skills/public-pr-wording/pr_wording_config.example.json +22 -0
- package/skills/public-pr-wording/pr_wording_lint.py +249 -0
- package/skills/public-pr-wording/pr_wording_rules.json +60 -0
- package/skills/pulse/SKILL.md +6 -0
- package/skills/recall-pi/SKILL.md +29 -0
- package/skills/reflect-pi/SKILL.md +27 -0
- package/skills/setup-pstack-pi/SKILL.md +23 -0
- package/skills/setup-pstack-pi/assets/pstack-model-roles.v1.example.json +7 -0
- package/skills/sharpen-pi/SKILL.md +19 -0
- package/skills/show-me-your-work-pi/SKILL.md +27 -0
- package/skills/swarm-pi/SKILL.md +27 -0
- package/skills/tasks/SKILL.md +8 -2
- package/skills/tdd-pi/SKILL.md +21 -0
- package/skills/teach-pi/SKILL.md +21 -0
- package/skills/technical-writing-pi/SKILL.md +27 -0
- package/skills/typescript-best-practices-pi/SKILL.md +21 -0
- package/skills/unslop-pi/SKILL.md +27 -0
- package/skills/why-pi/SKILL.md +37 -0
|
@@ -15,10 +15,36 @@
|
|
|
15
15
|
// - No bypasses, no model-supplied authority, no silent escalation. The
|
|
16
16
|
// model cannot mark a call safe; classification is structural only.
|
|
17
17
|
|
|
18
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { dirname } from "node:path";
|
|
18
20
|
import { isNativeTuiContext } from "./native_tui_context.js";
|
|
21
|
+
import {
|
|
22
|
+
detectMerge, evaluateMerge, gatherMergeEvidence, loadMergePolicy,
|
|
23
|
+
reviewRecord, reviewRecordPath, sessionIdOf, MERGE_POLICY_SCHEMA,
|
|
24
|
+
} from "./merge_policy.js";
|
|
19
25
|
|
|
20
26
|
export const GUARD_SCHEMA = "pi-agentic-driver.attended-authority.v1";
|
|
21
27
|
|
|
28
|
+
// This records a decision's stated provenance, not the identity of its author.
|
|
29
|
+
// Pi's user-message type alone cannot distinguish typed owner input from a
|
|
30
|
+
// pi.sendUserMessage relay. Never use this record to bypass tool-call guards.
|
|
31
|
+
export function recordDecisionEvidence(input) {
|
|
32
|
+
const kind = input?.kind;
|
|
33
|
+
const quote = typeof input?.quote === "string" ? input.quote : "";
|
|
34
|
+
const where = typeof input?.where === "string" ? input.where.trim() : "";
|
|
35
|
+
const when = typeof input?.when === "string" ? input.when.trim() : "";
|
|
36
|
+
const relayer = typeof input?.relayer === "string" ? input.relayer.trim() : "";
|
|
37
|
+
const ledgerId = typeof input?.ledgerId === "string" ? input.ledgerId.trim() : "";
|
|
38
|
+
const sourced = quote.trim() && where && when && (kind === "direct" || (kind === "relay" && relayer));
|
|
39
|
+
return {
|
|
40
|
+
recordable: Boolean(sourced),
|
|
41
|
+
kind: kind === "direct" || kind === "relay" ? kind : "unknown",
|
|
42
|
+
quote, where, when, relayer: kind === "relay" ? relayer : "", ledgerId,
|
|
43
|
+
identityVerified: false,
|
|
44
|
+
irreversibleAuthority: false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
22
48
|
// Tool names that can mutate the workspace.
|
|
23
49
|
const MUTATING_TOOLS = new Set(["bash", "write", "edit"]);
|
|
24
50
|
|
|
@@ -168,6 +194,16 @@ export function confirmationBody(toolName, input, kind) {
|
|
|
168
194
|
].join("\n");
|
|
169
195
|
}
|
|
170
196
|
|
|
197
|
+
// Structured so a coordinator relaying a headless bot result can pass it up
|
|
198
|
+
// as an owner decision rather than a silent refusal.
|
|
199
|
+
export function needsOwner(tier, missing, repo) {
|
|
200
|
+
return { status: "needs-owner", tier, missing, relayablePhrase: `Approve ${missing.join(", ")} for ${repo}`, nextStep: "Relay the owner's scoped words with their source and record the approval." };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function mergeBlockReason(policy, source, why) {
|
|
204
|
+
return `${MERGE_POLICY_SCHEMA}: needs-owner-approval: pull-request merge refused (policy ${policy}, ${source}): ${why}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
171
207
|
export function denialReason(toolName, kind, context) {
|
|
172
208
|
if (!isNativeTuiContext(context)) {
|
|
173
209
|
return `${GUARD_SCHEMA}: destructive ${toolName} operation (${kind}) refused fail-closed: no native confirmation surface in this headless context`;
|
|
@@ -185,6 +221,31 @@ export async function guardToolCall(event, ctx, options = {}) {
|
|
|
185
221
|
const toolName = String(event?.toolName ?? "").toLowerCase();
|
|
186
222
|
if (!MUTATING_TOOLS.has(toolName)) return undefined;
|
|
187
223
|
const input = event?.input && typeof event.input === "object" ? event.input : {};
|
|
224
|
+
const approvalPath = /(?:^|[\s"'\\/])approvals[\\/]/;
|
|
225
|
+
if ((toolName === "write" || toolName === "edit") && approvalPath.test(String(input.path ?? input.file_path ?? ""))) {
|
|
226
|
+
return { block: true, reason: "Approval ledger writes are reserved for the trusted relay CLI.", ...needsOwner(3, ["ledger edit"], "profile") };
|
|
227
|
+
}
|
|
228
|
+
if (toolName === "bash" && (approvalPath.test(String(input.command ?? "")) || /\brecord_approval\.mjs\b/.test(String(input.command ?? "")))) {
|
|
229
|
+
return { block: true, reason: "Direct approval-ledger access through the agent shell is blocked; use agentic_relay_check_request for read-only checks.", ...needsOwner(3, ["direct ledger access"], "profile") };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Pull-request merges follow the owner's merge policy, never a prompt. The
|
|
233
|
+
// same answer applies in a headless bot session and in direct Pi use.
|
|
234
|
+
if (toolName === "bash") {
|
|
235
|
+
const detection = detectMerge(input.command);
|
|
236
|
+
if (detection.merge) {
|
|
237
|
+
const { policy, source } = (options.loadMergePolicy ?? loadMergePolicy)();
|
|
238
|
+
const gathered = policy === "agents-after-checks-and-review" && detection.ref
|
|
239
|
+
? (options.gatherMergeEvidence ?? gatherMergeEvidence)(detection.ref)
|
|
240
|
+
: undefined;
|
|
241
|
+
const evidence = gathered && !gathered.error
|
|
242
|
+
? { ...gathered, mergerSession: sessionIdOf(options.context ?? ctx) }
|
|
243
|
+
: gathered;
|
|
244
|
+
const decision = evaluateMerge(policy, detection, evidence);
|
|
245
|
+
if (decision.allow) return undefined;
|
|
246
|
+
return { block: true, reason: mergeBlockReason(policy, source, decision.reason) };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
188
249
|
|
|
189
250
|
const bash = toolName === "bash"
|
|
190
251
|
? classifyBashCommand(input.command)
|
|
@@ -217,6 +278,58 @@ export function registerAttendedAuthorityGuard(pi, options = {}) {
|
|
|
217
278
|
if (!pi || REGISTERED.has(pi)) return undefined;
|
|
218
279
|
REGISTERED.add(pi);
|
|
219
280
|
pi.on?.("tool_call", (event, ctx) => guardToolCall(event, ctx, options));
|
|
281
|
+
pi.registerTool?.({
|
|
282
|
+
name: "agentic_decision_evidence",
|
|
283
|
+
label: "Decision evidence",
|
|
284
|
+
description: "Record the stated source of a decision for a private assignment. This does not verify identity or authorize actions.",
|
|
285
|
+
parameters: {
|
|
286
|
+
type: "object",
|
|
287
|
+
properties: {
|
|
288
|
+
kind: { type: "string", enum: ["direct", "relay"] },
|
|
289
|
+
quote: { type: "string" },
|
|
290
|
+
where: { type: "string" },
|
|
291
|
+
when: { type: "string" },
|
|
292
|
+
relayer: { type: "string" },
|
|
293
|
+
ledgerId: { type: "string" },
|
|
294
|
+
},
|
|
295
|
+
required: ["kind", "quote", "where", "when"],
|
|
296
|
+
additionalProperties: false,
|
|
297
|
+
},
|
|
298
|
+
async execute(_toolCallId, params) {
|
|
299
|
+
const record = recordDecisionEvidence(params);
|
|
300
|
+
return {
|
|
301
|
+
content: [{ type: "text", text: record.recordable
|
|
302
|
+
? "Decision source recorded as unverified evidence; no authority created."
|
|
303
|
+
: "Decision source incomplete; quote, where, when and (for relays) relayer are required." }],
|
|
304
|
+
details: { ...record, authorityCreated: false },
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
pi.registerTool?.({
|
|
309
|
+
name: "agentic_merge_review_record",
|
|
310
|
+
label: "Merge review record",
|
|
311
|
+
description: "Record independent Standards and Spec verdicts for an exact PR head. Only read when the owner has set the merge policy to agents-after-checks-and-review.",
|
|
312
|
+
parameters: {
|
|
313
|
+
type: "object",
|
|
314
|
+
properties: {
|
|
315
|
+
headSha: { type: "string" },
|
|
316
|
+
standards: { type: "string", enum: ["PASS", "FAIL"] },
|
|
317
|
+
spec: { type: "string", enum: ["PASS", "FAIL"] },
|
|
318
|
+
standardsReviewer: { type: "string", description: "Identity or session id of the Standards reviewer." },
|
|
319
|
+
specReviewer: { type: "string", description: "Identity or session id of the Spec reviewer." },
|
|
320
|
+
},
|
|
321
|
+
required: ["headSha", "standards", "spec", "standardsReviewer", "specReviewer"],
|
|
322
|
+
additionalProperties: false,
|
|
323
|
+
},
|
|
324
|
+
async execute(_toolCallId, params, _signal, _update, context) {
|
|
325
|
+
// The recording session comes from the host, never from the model.
|
|
326
|
+
const result = reviewRecord({ ...params, recordedBySession: sessionIdOf(context) });
|
|
327
|
+
if (!result.ok) return { content: [{ type: "text", text: result.reason }], details: { recorded: false } };
|
|
328
|
+
const path = reviewRecordPath(result.record.headSha);
|
|
329
|
+
(options.writeReviewRecord ?? ((file, text) => { mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, text); }))(path, `${JSON.stringify(result.record, null, 2)}\n`);
|
|
330
|
+
return { content: [{ type: "text", text: "Review verdicts recorded for this head. They do not merge anything." }], details: { recorded: true, ...result.record } };
|
|
331
|
+
},
|
|
332
|
+
});
|
|
220
333
|
return { registered: true, schema: GUARD_SCHEMA };
|
|
221
334
|
}
|
|
222
335
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
3
|
|
|
4
|
-
// Board-claim bridge for
|
|
4
|
+
// Board-claim bridge for external board clients.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// emits a versioned claim request
|
|
6
|
+
// A client reads its TASKS.md, selects the next eligible governed card, and
|
|
7
|
+
// emits a versioned claim request. This
|
|
8
8
|
// module services that request by DELEGATING to the existing board authority
|
|
9
9
|
// implementation — claimCard in task_board_core_pi.js — which already owns
|
|
10
10
|
// the writer-lock CAS, the claims-file HMAC, expiry/single-attempt, and
|
|
@@ -41,8 +41,8 @@ import {
|
|
|
41
41
|
import { readFileSync, existsSync } from "node:fs";
|
|
42
42
|
|
|
43
43
|
export const BOARD_CLAIM_BRIDGE_SCHEMA = "agentic-driver.board-claim-bridge.v1";
|
|
44
|
-
export const BOARD_CLAIM_RECEIPT_SCHEMA = "
|
|
45
|
-
export const REQUEST_SCHEMA = "
|
|
44
|
+
export const BOARD_CLAIM_RECEIPT_SCHEMA = "agentic-driver.board-claim-receipt.v2";
|
|
45
|
+
export const REQUEST_SCHEMA = "agentic-driver.board-claim-request.v2";
|
|
46
46
|
|
|
47
47
|
const ROLE_NAME_RE = /^[A-Za-z][A-Za-z0-9._-]{0,63}$/;
|
|
48
48
|
const ROUTE_SOURCE_KINDS = new Set(["explicit-board-route", "jev-fallback"]);
|
|
@@ -98,16 +98,16 @@ export function verifyRequestedCard({ boardPath, request }) {
|
|
|
98
98
|
*/
|
|
99
99
|
export function bridgeRouteFor({ request, repository }) {
|
|
100
100
|
return {
|
|
101
|
-
seatId: `
|
|
101
|
+
seatId: `board-${request.route.seatAlias}`,
|
|
102
102
|
accountId: null,
|
|
103
|
-
provider: "
|
|
103
|
+
provider: "board-router",
|
|
104
104
|
model: request.route.seatAlias,
|
|
105
105
|
role: request.route.seat,
|
|
106
106
|
effort: "default",
|
|
107
107
|
containmentTier: "testudo",
|
|
108
108
|
phase: "implement",
|
|
109
109
|
routeDecisionDigest: sha256Hex(canonicalJsonString({
|
|
110
|
-
schema: "
|
|
110
|
+
schema: "agentic-driver.board-claim-route.v2",
|
|
111
111
|
cardId: request.cardId,
|
|
112
112
|
seatAlias: request.route.seatAlias,
|
|
113
113
|
seat: request.route.seat,
|
|
@@ -125,9 +125,9 @@ export function bridgeRouteFor({ request, repository }) {
|
|
|
125
125
|
* observation carrying the card binding, the claim/envelope identity, the
|
|
126
126
|
* route source and model, the moat result, and the branch/base/scope.
|
|
127
127
|
*/
|
|
128
|
-
export function
|
|
128
|
+
export function claimFromBoardRequest({ boardPath, request, policy = null, now = null, backendBaseUrlFor = null, moatPolicy = null }) {
|
|
129
129
|
if (request?.schema !== REQUEST_SCHEMA) {
|
|
130
|
-
return fail("request-schema-invalid", `
|
|
130
|
+
return fail("request-schema-invalid", `unsupported request schema "${String(request?.schema)}"; expected ${REQUEST_SCHEMA}`);
|
|
131
131
|
}
|
|
132
132
|
if (request.boardPathBasename !== "TASKS.md") {
|
|
133
133
|
return fail("request-board-invalid", "the request must name the canonical TASKS.md board");
|