@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.
Files changed (114) hide show
  1. package/.agentic-driver/loop-until-done.defaults.json +5 -0
  2. package/README.md +302 -41
  3. package/config/merge-policy.v1.example.json +4 -0
  4. package/config/relay-authority.v1.example.json +7 -0
  5. package/config/treehouse.v1.json +4 -0
  6. package/docs/durable-driver-api.md +23 -0
  7. package/docs/durable-persistence.md +32 -0
  8. package/docs/merge-policy.md +45 -0
  9. package/docs/relay-authority.md +9 -0
  10. package/extensions/aidr.ts +3 -0
  11. package/extensions/harvest-lessons.ts +8 -0
  12. package/extensions/herdr-dispatch.ts +32 -3
  13. package/extensions/loop-until-done.ts +7 -0
  14. package/extensions/recovery-evidence.ts +3 -3
  15. package/extensions/treehouse-recycle.ts +47 -0
  16. package/janus/README.md +2 -4
  17. package/janus/janus/config.ts +2 -2
  18. package/janus/janus/providers.ts +2 -4
  19. package/janus/tests/providers.test.ts +2 -2
  20. package/{extensions → lib}/task-store-adapter.ts +1 -5
  21. package/package.json +158 -4
  22. package/scripts/enforcement/approval_ledger_pi.js +135 -0
  23. package/scripts/enforcement/attended_authority_guard.js +113 -0
  24. package/scripts/enforcement/board_claim_bridge_pi.js +10 -10
  25. package/scripts/enforcement/durable_assignment_pi.js +488 -0
  26. package/scripts/enforcement/durable_driver_pi.d.ts +51 -0
  27. package/scripts/enforcement/durable_driver_pi.js +254 -0
  28. package/scripts/enforcement/herdr_async_dispatch_pi.js +86 -20
  29. package/scripts/enforcement/herdr_async_seam_pi.js +90 -3
  30. package/scripts/enforcement/herdr_communication_pi.js +126 -12
  31. package/scripts/enforcement/herdr_lifecycle_pi.js +730 -81
  32. package/scripts/enforcement/linux_microvm_cutover_pi.js +3 -3
  33. package/scripts/enforcement/linux_microvm_remote_fixture.sh +0 -6
  34. package/scripts/enforcement/loop_until_done_guide.md +13 -0
  35. package/scripts/enforcement/loop_until_done_pi.js +214 -0
  36. package/scripts/enforcement/merge_policy.js +181 -0
  37. package/scripts/enforcement/outward_effect_pi.js +156 -0
  38. package/scripts/enforcement/recovery_evidence_review_pi.js +31 -3
  39. package/scripts/enforcement/storage_owner_lock_pi.js +66 -0
  40. package/scripts/enforcement/task_board_core_pi.js +13 -100
  41. package/scripts/enforcement/task_board_hash_pi.js +86 -0
  42. package/scripts/enforcement/treehouse_recycle_pi.js +145 -0
  43. package/scripts/harvest_lessons.js +219 -0
  44. package/scripts/relay/record_approval.mjs +10 -0
  45. package/scripts/relay/send_request.mjs +26 -0
  46. package/skills/architect-pi/SKILL.md +44 -0
  47. package/skills/arena-pi/SKILL.md +46 -0
  48. package/skills/automate-me-pi/SKILL.md +37 -0
  49. package/skills/benchmark-checklist-pi/SKILL.md +35 -0
  50. package/skills/blast-radius-pi/SKILL.md +24 -0
  51. package/skills/bro-pi/SKILL.md +17 -0
  52. package/skills/correct-pi/SKILL.md +36 -0
  53. package/skills/create-verification-skill-pi/LICENSE +21 -0
  54. package/skills/create-verification-skill-pi/SKILL.md +24 -0
  55. package/skills/decision-to-spec/SKILL.md +5 -5
  56. package/skills/figure-it-out-pi/SKILL.md +37 -0
  57. package/skills/harvest-lessons/SKILL.md +56 -0
  58. package/skills/harvest-lessons/harvest.example.json +8 -0
  59. package/skills/how/LICENSE +24 -0
  60. package/skills/how/WORKFLOW_BOUNDARIES.md +19 -0
  61. package/skills/how-pi/SKILL.md +33 -0
  62. package/skills/in-your-own-words-pi/SKILL.md +15 -0
  63. package/skills/interrogate-pi/SKILL.md +35 -0
  64. package/skills/ketchup-pi/SKILL.md +21 -0
  65. package/skills/maintain-verification-skill-pi/SKILL.md +37 -0
  66. package/skills/make-bot-ui-pi/SKILL.md +33 -0
  67. package/skills/no-comments-pi/SKILL.md +33 -0
  68. package/skills/poteto-help-pi/SKILL.md +50 -0
  69. package/skills/poteto-mode-pi/LICENSE +21 -0
  70. package/skills/poteto-mode-pi/SKILL.md +41 -0
  71. package/skills/poteto-mode-pi/playbooks.md +95 -0
  72. package/skills/principle-attack-the-premise-pi/SKILL.md +17 -0
  73. package/skills/principle-boundary-discipline-pi/SKILL.md +19 -0
  74. package/skills/principle-build-the-lever-pi/SKILL.md +19 -0
  75. package/skills/principle-encode-lessons-in-structure-pi/SKILL.md +17 -0
  76. package/skills/principle-exhaust-the-design-space-pi/SKILL.md +17 -0
  77. package/skills/principle-experience-first-pi/SKILL.md +17 -0
  78. package/skills/principle-explain-the-number-pi/SKILL.md +21 -0
  79. package/skills/principle-fix-root-causes-pi/SKILL.md +21 -0
  80. package/skills/principle-foundational-thinking-pi/SKILL.md +21 -0
  81. package/skills/principle-guard-the-context-window-pi/SKILL.md +19 -0
  82. package/skills/principle-laziness-protocol-pi/SKILL.md +19 -0
  83. package/skills/principle-make-operations-idempotent-pi/SKILL.md +21 -0
  84. package/skills/principle-migrate-callers-then-delete-legacy-apis-pi/SKILL.md +19 -0
  85. package/skills/principle-minimize-reader-load-pi/SKILL.md +19 -0
  86. package/skills/principle-model-the-domain-pi/SKILL.md +19 -0
  87. package/skills/principle-never-block-on-the-human-pi/SKILL.md +19 -0
  88. package/skills/principle-outcome-oriented-execution-pi/SKILL.md +19 -0
  89. package/skills/principle-prove-it-works-pi/SKILL.md +19 -0
  90. package/skills/principle-redesign-from-first-principles-pi/SKILL.md +19 -0
  91. package/skills/principle-separate-before-serializing-shared-state-pi/SKILL.md +19 -0
  92. package/skills/principle-sequence-verifiable-units-pi/SKILL.md +19 -0
  93. package/skills/principle-subtract-before-you-add-pi/SKILL.md +19 -0
  94. package/skills/principle-test-behavior-not-implementation-pi/SKILL.md +21 -0
  95. package/skills/principle-type-system-discipline-pi/SKILL.md +19 -0
  96. package/skills/public-pr-wording/SKILL.md +69 -0
  97. package/skills/public-pr-wording/pr_wording_config.example.json +22 -0
  98. package/skills/public-pr-wording/pr_wording_lint.py +249 -0
  99. package/skills/public-pr-wording/pr_wording_rules.json +60 -0
  100. package/skills/pulse/SKILL.md +6 -0
  101. package/skills/recall-pi/SKILL.md +29 -0
  102. package/skills/reflect-pi/SKILL.md +27 -0
  103. package/skills/setup-pstack-pi/SKILL.md +23 -0
  104. package/skills/setup-pstack-pi/assets/pstack-model-roles.v1.example.json +7 -0
  105. package/skills/sharpen-pi/SKILL.md +19 -0
  106. package/skills/show-me-your-work-pi/SKILL.md +27 -0
  107. package/skills/swarm-pi/SKILL.md +27 -0
  108. package/skills/tasks/SKILL.md +8 -2
  109. package/skills/tdd-pi/SKILL.md +21 -0
  110. package/skills/teach-pi/SKILL.md +21 -0
  111. package/skills/technical-writing-pi/SKILL.md +27 -0
  112. package/skills/typescript-best-practices-pi/SKILL.md +21 -0
  113. package/skills/unslop-pi/SKILL.md +27 -0
  114. package/skills/why-pi/SKILL.md +37 -0
@@ -219,7 +219,7 @@ function boundedString(value, field, maxBytes = MAX_PROCESS_OUTPUT_BYTES) {
219
219
  return value;
220
220
  }
221
221
 
222
- function expectedRepository(context) {
222
+ export function expectedRepository(context) {
223
223
  const value = context?.repository ?? context?.cwd;
224
224
  if (typeof value !== "string" || !value.trim()) {
225
225
  throw communicationError("repository_unavailable", "the current repository is unavailable");
@@ -303,7 +303,7 @@ function checkedRegistryPath(primary) {
303
303
  // Registry lookup order (coordinator decision): the session repository's
304
304
  // own config first; when missing (ENOENT only) the active Pi profile's
305
305
  // shared config under {PI_CODING_AGENT_DIR}/config/ (fallback
306
- // ~/.pi/agent/config/). When neither exists, return the repo-local path so
306
+ // the installed agent config directory). When neither exists, return the repo-local path so
307
307
  // the caller's primary-only fallback applies exactly as before.
308
308
  const profileDir = process.env.PI_CODING_AGENT_DIR;
309
309
  const base = typeof profileDir === "string" && profileDir.trim()
@@ -1257,10 +1257,81 @@ export function extractLatestHerdrReport(text, role) {
1257
1257
  // ---------------------------------------------------------------------------
1258
1258
 
1259
1259
  const deliveries = new Map();
1260
+ let offlineDeliveries = new WeakMap();
1260
1261
  let deliverySeq = 0;
1261
1262
 
1263
+ function deliveryRecords(options) {
1264
+ if (!options.offlinePersistence) return deliveries;
1265
+ if (!offlineDeliveries.has(options.offlinePersistence)) offlineDeliveries.set(options.offlinePersistence, new Map());
1266
+ return offlineDeliveries.get(options.offlinePersistence);
1267
+ }
1268
+
1269
+ function requireOfflinePersistence(options) {
1270
+ const store = options.offlinePersistence;
1271
+ if (!store) return;
1272
+ if (store.schema !== "agentic-driver.offline-seam-store.v1" || typeof options.runProcess !== "function" ||
1273
+ ["get", "reserve", "update"].some((name) => typeof store[name] !== "function")) {
1274
+ throw communicationError("offline_persistence_required", "persistence requires the offline store and injected process adapter", "denied");
1275
+ }
1276
+ }
1277
+
1278
+ async function deliverPersistedPrompt(operation, request, context, options, signal, repositories) {
1279
+ const repository = expectedRepository(context);
1280
+ const digest = createHash("sha256").update(JSON.stringify({ repository, role: request.role, prompt: request.prompt })).digest("hex");
1281
+ const requestId = options.offlineRequestId ?? digest;
1282
+ if (typeof requestId !== "string" || !/^[a-z0-9-]{1,128}$/.test(requestId)) throw communicationError("offline_request_invalid", "bounded offline request identity required", "denied");
1283
+ const identity = createHash("sha256").update(`${repository}\u0000${requestId}`).digest("hex");
1284
+ // Retain the existing correlation grammar; the suffix is opaque, not a clock.
1285
+ const id = `dlv-${identity.slice(0, 16)}-${identity.slice(16, 32)}`;
1286
+ // Validate the complete framed brief before leaving a persistent tombstone.
1287
+ promptWithReportRequirement(request.role, request.prompt, id);
1288
+ const store = options.offlinePersistence;
1289
+ const reserved = await store.reserve("delivery", { id, digest, repository, role: request.role, createdAt: Date.now(), state: "queued" });
1290
+ if (!reserved.created) {
1291
+ const accepted = ["delivered", "answered"].includes(reserved.record.state);
1292
+ return { schema: HERDR_COMMUNICATION_SCHEMA, ok: accepted, status: accepted ? "accepted" : "refused", operation,
1293
+ deliveryId: id, role: request.role, deliveryState: reserved.record.state, acceptedAt: reserved.record.acceptedAt,
1294
+ duplicate: true, promptSent: false, held: !accepted, persisted: true, nonAuthorizing: true, authorityCreated: false };
1295
+ }
1296
+ await options.offlineBoundary?.("after-delivery-intent", { deliveryId: id });
1297
+ const sent = await deliverPrompt(operation, request, context, { ...options, offlineDeliveryId: id }, signal, repositories);
1298
+ await options.offlineBoundary?.("after-delivery-send", { deliveryId: id });
1299
+ const latest = await store.get("delivery", id);
1300
+ const patch = { state: latest.state === "held" ? "held" : sent.deliveryState ?? "failed" };
1301
+ if (sent.acceptedAt) patch.acceptedAt = sent.acceptedAt;
1302
+ if (sent.code) patch.code = sent.code;
1303
+ const committed = await store.update("delivery", id, digest, patch);
1304
+ await options.offlineBoundary?.("after-delivery-ack", { deliveryId: id });
1305
+ return { ...sent, ...(committed.state === "held" ? { ok: false, status: "refused", held: true, deliveryState: "unknown" } : {}), deliveryId: id, persisted: true };
1306
+ }
1307
+
1308
+ async function queryPersistedDelivery(request, context, options, signal, repositories) {
1309
+ const store = options.offlinePersistence;
1310
+ const saved = await store.get("delivery", request.deliveryId);
1311
+ if (!saved) return deliveryRefusal("delivery", communicationError("delivery_unknown", "no persisted delivery is known"));
1312
+ if (saved.repository !== expectedRepository(context)) throw communicationError("repository_mismatch", "delivery belongs to another repository", "denied");
1313
+ requireRole(saved.role);
1314
+ if (saved.state === "held") return deliverySnapshot({ ...saved, deliveryId: saved.id, state: "unknown" }, { held: true, code: saved.code ?? "delivery-held", persisted: true });
1315
+ if (saved.state === "answered" && saved.proof === "snapshot-backed-v1") {
1316
+ return deliverySnapshot({ ...saved, deliveryId: saved.id }, { report: saved.report, reportMarkers: reportMarkersForRole(saved.role), answeredAt: saved.answeredAt, persisted: true });
1317
+ }
1318
+ if (!deliveryRecords(options).has(saved.id)) {
1319
+ // Neither terminal snapshots nor framed echoes are restored from disk.
1320
+ // Original proof lost means hold, even if the role is now idle/done.
1321
+ if (saved.state !== "failed" && saved.state !== "held") await store.update("delivery", saved.id, saved.digest, { state: "held", code: "attribution-lost" });
1322
+ return deliverySnapshot({ ...saved, deliveryId: saved.id, state: saved.state === "failed" ? "failed" : "unknown" },
1323
+ { held: saved.state !== "failed", code: saved.state === "failed" ? saved.code : "attribution-lost", persisted: true });
1324
+ }
1325
+ const observed = await queryDelivery(request, context, options, signal, repositories);
1326
+ if (observed.deliveryState === "answered") {
1327
+ await store.update("delivery", saved.id, saved.digest, { state: "answered", report: observed.report, answeredAt: observed.answeredAt, proof: "snapshot-backed-v1" });
1328
+ }
1329
+ return { ...observed, persisted: true };
1330
+ }
1331
+
1262
1332
  export function resetHerdrDeliveriesForTests() {
1263
1333
  deliveries.clear();
1334
+ offlineDeliveries = new WeakMap();
1264
1335
  }
1265
1336
 
1266
1337
  function evictDeliveries() {
@@ -1280,12 +1351,12 @@ function stateChangeSeq(value) {
1280
1351
  return Number.isSafeInteger(seq) && seq >= 0 ? seq : undefined;
1281
1352
  }
1282
1353
 
1283
- function createDelivery(role, prompt, pre, seqBefore) {
1284
- evictDeliveries();
1354
+ function createDelivery(role, prompt, pre, seqBefore, options = {}) {
1355
+ if (!options.offlinePersistence) evictDeliveries();
1285
1356
  const createdAt = Date.now();
1286
1357
  const seq = (deliverySeq += 1);
1287
1358
  const digest = createHash("sha256").update(`${role}\u0000${createdAt}\u0000${seq}`).digest("hex");
1288
- const deliveryId = `dlv-${digest.slice(0, 16)}-${createdAt.toString(36)}`;
1359
+ const deliveryId = options.offlineDeliveryId ?? `dlv-${digest.slice(0, 16)}-${createdAt.toString(36)}`;
1289
1360
  const sentPrompt = promptWithReportRequirement(role, prompt, deliveryId);
1290
1361
  const record = {
1291
1362
  deliveryId,
@@ -1304,7 +1375,7 @@ function createDelivery(role, prompt, pre, seqBefore) {
1304
1375
  code: undefined,
1305
1376
  reason: undefined,
1306
1377
  };
1307
- deliveries.set(record.deliveryId, record);
1378
+ deliveryRecords(options).set(record.deliveryId, record);
1308
1379
  return record;
1309
1380
  }
1310
1381
 
@@ -1370,7 +1441,7 @@ async function deliverPrompt(operation, request, context, options, signal, repos
1370
1441
  // unconfirmed returns the existing delivery instead of re-handing off.
1371
1442
  // After the window, or once the prior delivery is confirmed
1372
1443
  // (delivered/answered/failed), a repeat is a genuinely new delivery.
1373
- const duplicate = findUnconfirmedDelivery(role, request.prompt);
1444
+ const duplicate = options.offlinePersistence ? undefined : findUnconfirmedDelivery(role, request.prompt);
1374
1445
  if (duplicate) {
1375
1446
  if (duplicate.state === "queued") {
1376
1447
  return {
@@ -1419,7 +1490,11 @@ async function deliverPrompt(operation, request, context, options, signal, repos
1419
1490
  } catch {
1420
1491
  pre = undefined;
1421
1492
  }
1422
- record = createDelivery(role, request.prompt, pre, stateChangeSeq(current));
1493
+ record = createDelivery(role, request.prompt, pre, stateChangeSeq(current), options);
1494
+ if (options.offlinePersistence) {
1495
+ const latest = await options.offlinePersistence.get("delivery", record.deliveryId);
1496
+ if (latest?.state !== "queued") throw communicationError("delivery_held", "persisted delivery is held; no handoff started");
1497
+ }
1423
1498
  // Handoff: `agent prompt` without --wait returns as soon as the CLI
1424
1499
  // accepts the brief. The acceptance window is internal
1425
1500
  // (COMMAND_TIMEOUT_MS); timeoutMs never schedules a model round-trip.
@@ -1602,7 +1677,7 @@ function regionBody(text, role, region, correlationLine) {
1602
1677
  }
1603
1678
 
1604
1679
  async function queryDelivery(request, context, options, signal, repositories) {
1605
- const record = typeof request.deliveryId === "string" ? deliveries.get(request.deliveryId) : undefined;
1680
+ const record = typeof request.deliveryId === "string" ? deliveryRecords(options).get(request.deliveryId) : undefined;
1606
1681
  if (!record) {
1607
1682
  return deliveryRefusal("delivery", communicationError("delivery_unknown", "no live delivery record is known for this deliveryId (bounded, in-memory)"));
1608
1683
  }
@@ -1704,6 +1779,36 @@ async function queryDelivery(request, context, options, signal, repositories) {
1704
1779
  // Legacy blocking exchange (prompt → wait → provenanced report read). It is
1705
1780
  // no longer reachable from the tool surface; the dispatch journey keeps its
1706
1781
  // single-exchange semantics through this entry point unchanged.
1782
+ export async function inspectHerdrJourneyMarker(role, requestId, context, options = {}, signal) {
1783
+ requireRole(role);
1784
+ if (typeof requestId !== "string" || !/^[a-zA-Z0-9_-]{1,100}:[1-9][0-9]{0,2}$/.test(requestId)) {
1785
+ throw communicationError("journey-id-invalid", "bounded journey request ID required", "denied");
1786
+ }
1787
+ const repositories = trustedRepositories(expectedRepository(context));
1788
+ const current = await invokeHerdr("get", { action: "get", role }, context, options, signal);
1789
+ publicAgentObservation(extractAgent(current, "agent_info"), role, repositories);
1790
+ const history = readText(await invokeHerdr("read", { action: "read", role, readLines: MAX_REPORT_READ_LINES }, context, options, signal));
1791
+ // Absence is never evidence that an asynchronous pane send did not land.
1792
+ const lines = history.split("\n");
1793
+ const tag = `[journey:${requestId}]`;
1794
+ const occurrences = lines.filter((line) => line.trim() === tag).length;
1795
+ if (occurrences !== 1) return { present: false };
1796
+ const from = history.indexOf(tag) + tag.length;
1797
+ const tail = history.slice(from);
1798
+ // One report only, with no later journey prompt. An unrelated or ambiguous
1799
+ // pane exchange is not attributable merely because it is the latest text.
1800
+ if (/^\[journey:[^\]]+\]$/m.test(tail)) return { present: true };
1801
+ const markers = reportMarkersForRole(role);
1802
+ const relevant = allMarkerOccurrences(tail, true, markers)
1803
+ .filter((item) => (item.marker === markers.open || item.marker === markers.close)
1804
+ && !isPromptContractMarker(history, { index: item.index + from, end: item.end + from }, markers));
1805
+ if (relevant.length !== 2 || relevant[0].marker !== markers.open || relevant[1].marker !== markers.close) {
1806
+ return { present: true };
1807
+ }
1808
+ try { return { present: true, report: extractReportFromSegment(history, role, from) }; }
1809
+ catch { return { present: true }; }
1810
+ }
1811
+
1707
1812
  export async function executeHerdrPromptExchange(params, context, options = {}, signal) {
1708
1813
  let request;
1709
1814
  const operation = "prompt";
@@ -1802,6 +1907,10 @@ export async function executeHerdrCommunication(params, context, options = {}, s
1802
1907
  try {
1803
1908
  request = validateParams(params);
1804
1909
  operation = request.action;
1910
+ requireOfflinePersistence(options);
1911
+ if (options.offlinePersistence && !["prompt", "submit", "delivery"].includes(operation)) {
1912
+ throw communicationError("offline_delivery_required", "offline persistence requires a delivery-bound operation; role-keyed fallback is refused", "denied");
1913
+ }
1805
1914
  const repository = expectedRepository(context);
1806
1915
  const repositories = trustedRepositories(repository);
1807
1916
  if (operation === "list") {
@@ -1818,11 +1927,16 @@ export async function executeHerdrCommunication(params, context, options = {}, s
1818
1927
  // return an explicit receipt immediately. No model round-trip happens
1819
1928
  // on this path; settlement is observed through the `delivery` action,
1820
1929
  // get, wait, and read.
1821
- return await deliverPrompt(operation, request, context, options, signal, repositories);
1930
+ return options.offlinePersistence
1931
+ ? await deliverPersistedPrompt(operation, request, context, options, signal, repositories)
1932
+ : await deliverPrompt(operation, request, context, options, signal, repositories);
1822
1933
  }
1823
1934
  if (operation === "delivery") {
1824
- // Read-only, non-authorizing per-delivery state query.
1825
- return await queryDelivery(request, context, options, signal, repositories);
1935
+ // Non-authorizing observation. Offline mode may persist proof or an
1936
+ // attribution-loss hold, but never sends; default mode is unchanged.
1937
+ return options.offlinePersistence
1938
+ ? await queryPersistedDelivery(request, context, options, signal, repositories)
1939
+ : await queryDelivery(request, context, options, signal, repositories);
1826
1940
  }
1827
1941
  if (operation === "wait") {
1828
1942
  const raw = await invokeHerdr(operation, request, context, options, signal);