blun-king-cli 9.1.562 → 9.1.563

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.
@@ -8,9 +8,12 @@ import {
8
8
  recordActivity, resolveAttention, upsertAttention
9
9
  } from "./lib/attention.js";
10
10
  import {
11
- addLearningEvidence, configureLearning, deleteLearning, evaluateLearning,
11
+ addLearningEvidence, beginLearningRevalidation, configureLearning, deleteLearning, evaluateLearning,
12
12
  learningContext, learningOutcomeStatus, loadLearning, proposeLearning,
13
- recordLearningOutcome, reviewLearning, rollbackLearning
13
+ purgeStaleLearningApplications, purgeStaleLearningMeasurements, recordLearningMeasurement,
14
+ recordLearningOutcome, registerLearningEvaluation, registerLearningEvaluator, renewLearningValidation, revokeLearningEvaluator,
15
+ reviewLearning, revokeLearningApplication, revokeLearningDelivery, revokeLearningEvaluation, revokeLearningEvidence, revokeLearningMeasurement, revokeLearningOutcome, revokeLearningTrialFailure, revokeLearningValidation,
16
+ rollbackLearning
14
17
  } from "./lib/learning.js";
15
18
  import { configureContinuity, loadContinuity, purgeContinuity } from "./lib/continuity.js";
16
19
  import {
@@ -109,6 +112,14 @@ function hasLearningScope(flags) {
109
112
  return ["persona", "user", "tenant", "project", "group", "task"].some((name) => flags[name] !== undefined);
110
113
  }
111
114
 
115
+ function evaluatorRootsFlag(value) {
116
+ return String(value || "").split(",").filter(Boolean).map((entry) => {
117
+ const separator = entry.lastIndexOf("=");
118
+ if (separator <= 0) throw new Error("evaluator roots must use evaluator-id=sha256 entries");
119
+ return { evaluatorId: entry.slice(0, separator), principalDigest: entry.slice(separator + 1) };
120
+ });
121
+ }
122
+
112
123
  function help() {
113
124
  return `AgentSpine ${VERSION}
114
125
 
@@ -134,11 +145,27 @@ Usage:
134
145
  agentspine attention-config [root] [--enabled true|false] [--quiet-start 22 --quiet-end 7 --utc-offset 120]
135
146
  agentspine learn-propose [id] --kind preference|behavior --claim text --evidence text [--persona id --user id --tenant id --project id --group id --task id]
136
147
  agentspine learn-evidence <id> --summary text [--type interaction] [--source path.md]
148
+ agentspine learn-evidence-revoke <learning-id> --evidence-id id --reason-code retracted|source-invalid|measurement-invalid|duplicate|other --reason text --confirm-local-evidence
137
149
  agentspine learn-review <id> --decision accept|reject --reason text [--confirmed-by-user]
138
150
  agentspine learn-context [root] [--group id] [--include-private] [--kind preference,goal]
139
151
  agentspine learn-evaluate [root]
140
- agentspine learn-outcome <id> --phase before|after --metric name --direction higher|lower --value 0..1 --evaluator id --measurement objective|user-feedback|model-suggestion [--blocking-defects n]
152
+ agentspine learn-evaluator-register <id> --principal-digest sha256 --confirm-local-evaluator
153
+ agentspine learn-evaluator-revoke <id> --reason text --confirm-local-evaluator
154
+ agentspine learn-evaluation <id> --learning id --metric name --direction higher|lower --task-digest sha256 --dataset-digest sha256 --protocol-digest sha256 --min-cases n --evaluators id,id --evaluator-roots id=sha256,id=sha256 [--expires-at date] [--retry-trial-failure id --confirm-local-trial-retry] --confirm-local-evaluation
155
+ agentspine learn-evaluation-revoke <evaluation-id> --reason-code benchmark-invalid|protocol-invalid|scope-invalid|threshold-invalid|duplicate|other --reason text --confirm-local-evaluation-revocation
156
+ agentspine learn-validation-revoke <validation-lease-id> --reason-code decision-invalid|cohort-invalid|binding-invalid|scope-invalid|duplicate|other --reason text --confirm-local-validation-revocation
157
+ agentspine learn-trial-failure-revoke <trial-failure-id> --reason-code clock-invalid|host-invalid|receipt-invalid|scope-invalid|duplicate|other --reason text --confirm-local-trial-failure-revocation
158
+ agentspine learn-revalidation-start <learning-id> --confirm-local-validation
159
+ agentspine learn-revalidate <learning-id> --measurements id,id --applications id,id --deliveries id,id --confirm-local-validation
160
+ agentspine learn-measurement <id> --learning id --evaluation id --phase before|after --metric name --direction higher|lower --value 0..1 --measurement objective|user-feedback|model-suggestion --evaluator id --run id --source-digest sha256 --dataset-digest sha256 --case-count n --confirm-local-measurement
161
+ agentspine learn-measurement-revoke <measurement-id> --reason-code source-invalid|evaluator-invalid|protocol-invalid|duplicate|other --reason text --confirm-local-measurement-revocation
162
+ agentspine learn-application-revoke <application-id> --reason-code preflight-invalid|scope-invalid|projection-invalid|duplicate|other --reason text --confirm-local-application-revocation
163
+ agentspine learn-delivery-revoke <delivery-id> --reason-code host-invalid|session-invalid|hook-invalid|duplicate|other --reason text --confirm-local-delivery-revocation
164
+ agentspine learn-outcome-revoke <outcome-id> --reason-code binding-invalid|phase-invalid|scope-invalid|duplicate|other --reason text --confirm-local-outcome-revocation
165
+ agentspine learn-outcome <learning-id> --id id --evaluation id --measurement-receipt id [--application id --delivery id]
141
166
  agentspine learn-status [root] [--persona id --user id --tenant id --project id --group id --task id]
167
+ agentspine learn-delivery-purge [root] --confirm-local-purge
168
+ agentspine learn-measurement-purge [root] --confirm-local-purge
142
169
  agentspine learn-rollback <id> --reason text
143
170
  agentspine learn-delete <id>
144
171
  agentspine learn-config [root] [--auto-promote true|false] [--min-confidence 0.85] [--min-outcomes 2 --min-improvement 0.05 --canary-receipts 2 --canary-ttl-days 14]
@@ -429,6 +456,15 @@ export async function run(argv = process.argv.slice(2)) {
429
456
  }), json);
430
457
  }
431
458
 
459
+ if (command === "learn-evidence-revoke") {
460
+ return output(await revokeLearningEvidence({
461
+ root: flags.root || process.cwd(), learningId: positional[0], evidenceId: flags["evidence-id"],
462
+ reasonCode: flags["reason-code"], reason: flags.reason,
463
+ confirmation: booleanFlag(flags["confirm-local-evidence"])
464
+ ? "local-evidence-revocation-confirmed" : null
465
+ }), json);
466
+ }
467
+
432
468
  if (command === "learn-review") {
433
469
  return output(await reviewLearning({
434
470
  root: flags.root || process.cwd(), id: positional[0], decision: flags.decision,
@@ -451,11 +487,147 @@ export async function run(argv = process.argv.slice(2)) {
451
487
  return output(await evaluateLearning({ root: positional[0] || process.cwd() }), json);
452
488
  }
453
489
 
490
+ if (command === "learn-evaluator-register") {
491
+ return output(await registerLearningEvaluator({
492
+ root: flags.root || process.cwd(), id: positional[0], principalDigest: flags["principal-digest"],
493
+ confirmLocalEvaluator: booleanFlag(flags["confirm-local-evaluator"])
494
+ }), json);
495
+ }
496
+
497
+ if (command === "learn-evaluator-revoke") {
498
+ return output(await revokeLearningEvaluator({
499
+ root: flags.root || process.cwd(), id: positional[0], reason: flags.reason,
500
+ confirmLocalEvaluator: booleanFlag(flags["confirm-local-evaluator"])
501
+ }), json);
502
+ }
503
+
504
+ if (command === "learn-evaluation") {
505
+ return output(await registerLearningEvaluation({
506
+ root: flags.root || process.cwd(), id: positional[0], learningId: flags.learning,
507
+ scope: learningScope(flags), metric: { name: flags.metric, direction: flags.direction },
508
+ benchmark: {
509
+ taskDigest: flags["task-digest"], datasetDigest: flags["dataset-digest"],
510
+ protocolDigest: flags["protocol-digest"], minCases: Number(flags["min-cases"])
511
+ },
512
+ evaluatorIds: String(flags.evaluators || "").split(",").filter(Boolean),
513
+ evaluatorRoots: evaluatorRootsFlag(flags["evaluator-roots"]),
514
+ expiresAt: flags["expires-at"] || null,
515
+ retryTrialFailureId: flags["retry-trial-failure"] || null,
516
+ confirmLocalTrialRetry: booleanFlag(flags["confirm-local-trial-retry"]),
517
+ confirmLocalEvaluation: booleanFlag(flags["confirm-local-evaluation"])
518
+ }), json);
519
+ }
520
+
521
+ if (command === "learn-revalidation-start") {
522
+ return output(await beginLearningRevalidation({
523
+ root: flags.root || process.cwd(), learningId: positional[0],
524
+ confirmLocalValidation: booleanFlag(flags["confirm-local-validation"])
525
+ }), json);
526
+ }
527
+
528
+ if (command === "learn-revalidate") {
529
+ const measurements = String(flags.measurements || "").split(",").filter(Boolean);
530
+ const applications = String(flags.applications || "").split(",").filter(Boolean);
531
+ const deliveries = String(flags.deliveries || "").split(",").filter(Boolean);
532
+ if (!measurements.length || measurements.length !== applications.length || measurements.length !== deliveries.length) {
533
+ throw new Error("revalidation requires equally sized measurement, application, and delivery lists");
534
+ }
535
+ return output(await renewLearningValidation({
536
+ root: flags.root || process.cwd(), learningId: positional[0],
537
+ evidence: measurements.map((measurementId, index) => ({ measurementId,
538
+ applicationId: applications[index], deliveryId: deliveries[index] })),
539
+ confirmLocalValidation: booleanFlag(flags["confirm-local-validation"])
540
+ }), json);
541
+ }
542
+
543
+ if (command === "learn-measurement") {
544
+ return output(await recordLearningMeasurement({
545
+ root: flags.root || process.cwd(), id: positional[0], learningId: flags.learning,
546
+ evaluationId: flags.evaluation, phase: flags.phase, scope: learningScope(flags),
547
+ metric: {
548
+ name: flags.metric, direction: flags.direction, value: Number(flags.value),
549
+ blockingDefects: Number(flags["blocking-defects"] ?? 0)
550
+ },
551
+ measurement: {
552
+ kind: flags.measurement || "objective", evaluatorId: flags.evaluator,
553
+ runId: flags.run, sourceDigest: flags["source-digest"]
554
+ },
555
+ coverage: { datasetDigest: flags["dataset-digest"], caseCount: Number(flags["case-count"]) },
556
+ measuredAt: flags.at,
557
+ confirmLocalMeasurement: booleanFlag(flags["confirm-local-measurement"])
558
+ }), json);
559
+ }
560
+
561
+ if (command === "learn-measurement-revoke") {
562
+ return output(await revokeLearningMeasurement({
563
+ root: flags.root || process.cwd(), measurementId: positional[0], reasonCode: flags["reason-code"],
564
+ reason: flags.reason,
565
+ confirmation: booleanFlag(flags["confirm-local-measurement-revocation"])
566
+ ? "local-measurement-revocation-confirmed" : null
567
+ }), json);
568
+ }
569
+
570
+ if (command === "learn-evaluation-revoke") {
571
+ return output(await revokeLearningEvaluation({
572
+ root: flags.root || process.cwd(), evaluationId: positional[0], reasonCode: flags["reason-code"],
573
+ reason: flags.reason,
574
+ confirmation: booleanFlag(flags["confirm-local-evaluation-revocation"])
575
+ ? "local-evaluation-revocation-confirmed" : null
576
+ }), json);
577
+ }
578
+
579
+ if (command === "learn-validation-revoke") {
580
+ return output(await revokeLearningValidation({
581
+ root: flags.root || process.cwd(), validationLeaseId: positional[0], reasonCode: flags["reason-code"],
582
+ reason: flags.reason,
583
+ confirmation: booleanFlag(flags["confirm-local-validation-revocation"])
584
+ ? "local-validation-revocation-confirmed" : null
585
+ }), json);
586
+ }
587
+
588
+ if (command === "learn-trial-failure-revoke") {
589
+ return output(await revokeLearningTrialFailure({
590
+ root: flags.root || process.cwd(), trialFailureId: positional[0], reasonCode: flags["reason-code"],
591
+ reason: flags.reason,
592
+ confirmation: booleanFlag(flags["confirm-local-trial-failure-revocation"])
593
+ ? "local-trial-failure-revocation-confirmed" : null
594
+ }), json);
595
+ }
596
+
597
+ if (command === "learn-application-revoke") {
598
+ return output(await revokeLearningApplication({
599
+ root: flags.root || process.cwd(), applicationId: positional[0], reasonCode: flags["reason-code"],
600
+ reason: flags.reason,
601
+ confirmation: booleanFlag(flags["confirm-local-application-revocation"])
602
+ ? "local-application-revocation-confirmed" : null
603
+ }), json);
604
+ }
605
+
606
+ if (command === "learn-delivery-revoke") {
607
+ return output(await revokeLearningDelivery({
608
+ root: flags.root || process.cwd(), deliveryId: positional[0], reasonCode: flags["reason-code"],
609
+ reason: flags.reason,
610
+ confirmation: booleanFlag(flags["confirm-local-delivery-revocation"])
611
+ ? "local-delivery-revocation-confirmed" : null
612
+ }), json);
613
+ }
614
+
615
+ if (command === "learn-outcome-revoke") {
616
+ return output(await revokeLearningOutcome({
617
+ root: flags.root || process.cwd(), outcomeId: positional[0], reasonCode: flags["reason-code"],
618
+ reason: flags.reason,
619
+ confirmation: booleanFlag(flags["confirm-local-outcome-revocation"])
620
+ ? "local-outcome-revocation-confirmed" : null
621
+ }), json);
622
+ }
623
+
454
624
  if (command === "learn-outcome") {
625
+ const lineage = flags["measurement-receipt"] || null;
455
626
  return output(await recordLearningOutcome({
456
627
  root: flags.root || process.cwd(), learningId: positional[0], id: flags.id,
457
- phase: flags.phase, scope: learningScope(flags),
458
- metric: {
628
+ evaluationId: flags.evaluation, measurementReceiptId: lineage,
629
+ applicationId: flags.application || null, deliveryId: flags.delivery || null,
630
+ ...(lineage ? {} : { phase: flags.phase, scope: learningScope(flags), metric: {
459
631
  name: flags.metric, direction: flags.direction, value: Number(flags.value),
460
632
  blockingDefects: Number(flags["blocking-defects"] ?? 0)
461
633
  },
@@ -463,7 +635,11 @@ export async function run(argv = process.argv.slice(2)) {
463
635
  kind: flags.measurement || "objective", evaluatorId: flags.evaluator,
464
636
  sourceDigest: flags["source-digest"] || null
465
637
  },
466
- measuredAt: flags.at
638
+ coverage: {
639
+ datasetDigest: flags["dataset-digest"],
640
+ caseCount: Number(flags["case-count"])
641
+ },
642
+ measuredAt: flags.at })
467
643
  }), json);
468
644
  }
469
645
 
@@ -473,6 +649,20 @@ export async function run(argv = process.argv.slice(2)) {
473
649
  }), json);
474
650
  }
475
651
 
652
+ if (command === "learn-delivery-purge") {
653
+ return output(await purgeStaleLearningApplications({
654
+ root: positional[0] || process.cwd(),
655
+ confirmation: booleanFlag(flags["confirm-local-purge"]) ? "local-user-purge-confirmed" : null
656
+ }), json);
657
+ }
658
+
659
+ if (command === "learn-measurement-purge") {
660
+ return output(await purgeStaleLearningMeasurements({
661
+ root: positional[0] || process.cwd(),
662
+ confirmation: booleanFlag(flags["confirm-local-purge"]) ? "local-user-purge-confirmed" : null
663
+ }), json);
664
+ }
665
+
476
666
  if (command === "learn-rollback") {
477
667
  return output(await rollbackLearning({
478
668
  root: flags.root || process.cwd(), id: positional[0], reason: flags.reason
@@ -496,6 +686,9 @@ export async function run(argv = process.argv.slice(2)) {
496
686
  if (flags["outcome-max-age-days"] !== undefined) config.outcomeMaxAgeDays = Number(flags["outcome-max-age-days"]);
497
687
  if (flags["canary-receipts"] !== undefined) config.canaryReceipts = Number(flags["canary-receipts"]);
498
688
  if (flags["canary-ttl-days"] !== undefined) config.canaryTtlDays = Number(flags["canary-ttl-days"]);
689
+ if (flags["initial-trial-outcome-timeout-minutes"] !== undefined) {
690
+ config.initialTrialOutcomeTimeoutMinutes = Number(flags["initial-trial-outcome-timeout-minutes"]);
691
+ }
499
692
  if (!Object.keys(config).length) return output((await loadLearning(root)).learning.config, json);
500
693
  return output(await configureLearning({ root, config }), json);
501
694
  }
@@ -1005,12 +1198,202 @@ export async function run(argv = process.argv.slice(2)) {
1005
1198
  let learningOutcomes;
1006
1199
  try {
1007
1200
  const status = await learningOutcomeStatus({ root: positional[0] || process.cwd() });
1201
+ const unboundAfterReceipts = status.records.reduce((sum, item) => sum + item.afterReceipts - item.boundAfterReceipts, 0);
1202
+ const undeliveredAfterReceipts = status.records.reduce((sum, item) => sum + item.afterReceipts - item.deliveredAfterReceipts, 0);
1203
+ const stalePendingApplications = status.records.reduce((sum, item) => sum + item.stalePendingApplications, 0);
1204
+ const totalOutcomeReceipts = status.records.reduce((sum, item) => sum + item.beforeReceipts + item.afterReceipts, 0);
1205
+ const plannedOutcomeReceipts = status.records.reduce((sum, item) => sum + item.plannedOutcomeReceipts, 0);
1206
+ const coverageBoundReceipts = status.records.reduce((sum, item) => sum + item.coverageBoundReceipts, 0);
1207
+ const legacyCoverageReceipts = status.records.reduce((sum, item) => sum + item.legacyCoverageReceipts, 0);
1208
+ const provenanceBoundReceipts = status.records.reduce((sum, item) => sum + item.provenanceBoundReceipts, 0);
1209
+ const legacyProvenanceReceipts = status.records.reduce((sum, item) => sum + item.legacyProvenanceReceipts, 0);
1210
+ const measurementReceipts = status.records.reduce((sum, item) => sum + item.measurementReceipts, 0);
1211
+ const measurementLineageReceipts = status.records.reduce((sum, item) => sum + item.measurementLineageReceipts, 0);
1212
+ const consumedMeasurementReceipts = status.records.reduce((sum, item) => sum + item.consumedMeasurementReceipts, 0);
1213
+ const staleUnconsumedMeasurements = status.records.reduce((sum, item) => sum + item.staleUnconsumedMeasurements, 0);
1214
+ const lineageBoundReceipts = status.records.reduce((sum, item) => sum + item.lineageBoundReceipts, 0);
1215
+ const pairedOutcomeReceipts = status.records.reduce((sum, item) => sum + item.pairedOutcomeReceipts, 0);
1216
+ const pairedEvaluatorPairs = status.records.reduce((sum, item) => sum + item.pairedEvaluatorPairs, 0);
1217
+ const evaluatorRootBoundReceipts = status.records.reduce((sum, item) => sum + item.evaluatorRootBoundReceipts, 0);
1218
+ const independentEvaluatorRoots = status.records.reduce((sum, item) => sum + item.independentEvaluatorRoots, 0);
1219
+ const evaluatorRegistryContracts = status.records.reduce((sum, item) => sum + item.evaluatorRegistryContracts, 0);
1220
+ const inactiveEvaluatorRegistryContracts = status.records.reduce((sum, item) => sum + item.inactiveEvaluatorRegistryContracts, 0);
1221
+ const currentValidatedLessons = status.records.filter((item) => item.validationLeaseStatus === "current-validated").length;
1222
+ const staleValidatedLessons = status.records.filter((item) => item.validationLeaseStatus === "stale-validated").length;
1223
+ const unprovenValidatedLessons = status.records.filter((item) => item.validationLeaseStatus === "unproven-validated").length;
1224
+ const revokedValidatedLessons = status.records.filter((item) => item.validationLeaseStatus === "revoked-validated").length;
1225
+ const renewedValidationLeases = status.records.filter((item) =>
1226
+ ["agentspine.learning-validation/v2", "agentspine.learning-validation/v3",
1227
+ "agentspine.learning-validation/v4", "agentspine.learning-validation/v5"].includes(item.validationLeaseSchema)).length;
1228
+ const fixedCohortValidationLeases = status.records.filter((item) =>
1229
+ ["agentspine.learning-validation/v3", "agentspine.learning-validation/v4",
1230
+ "agentspine.learning-validation/v5"]
1231
+ .includes(item.validationLeaseSchema)).length;
1232
+ const admissionBoundValidationLeases = status.records.filter((item) =>
1233
+ ["agentspine.learning-validation/v4", "agentspine.learning-validation/v5"]
1234
+ .includes(item.validationLeaseSchema)).length;
1235
+ const trialBoundValidationLeases = status.records.filter((item) =>
1236
+ item.validationLeaseSchema === "agentspine.learning-validation/v5").length;
1237
+ const activeRevalidations = status.records.filter((item) => item.revalidationStatus === "active").length;
1238
+ const staleRevalidations = status.records.filter((item) => item.revalidationStatus === "stale").length;
1239
+ const fixedCohortRevalidations = status.records.filter((item) =>
1240
+ item.revalidationStatus === "active"
1241
+ && ["first-completed-turns", "first-admitted-turns",
1242
+ "first-admitted-trials"].includes(item.revalidationSelectionMode)).length;
1243
+ const admissionBoundRevalidations = status.records.filter((item) =>
1244
+ item.revalidationStatus === "active" && ["first-admitted-turns", "first-admitted-trials"]
1245
+ .includes(item.revalidationSelectionMode)).length;
1246
+ const trialBoundRevalidations = status.records.filter((item) =>
1247
+ item.revalidationStatus === "active" && item.revalidationSelectionMode === "first-admitted-trials").length;
1248
+ const requiredRevalidationDeliveries = status.records.reduce((sum, item) =>
1249
+ sum + item.revalidationRequiredDeliveries, 0);
1250
+ const completedRevalidationDeliveries = status.records.reduce((sum, item) =>
1251
+ sum + item.revalidationCompletedDeliveries, 0);
1252
+ const admittedRevalidationApplications = status.records.reduce((sum, item) =>
1253
+ sum + item.revalidationAdmittedApplications, 0);
1254
+ const initialTrialContracts = status.records.filter((item) => item.initialTrialMode === "first-admitted-trials").length;
1255
+ const requiredInitialTrials = status.records.reduce((sum, item) => sum + item.initialTrialSlots, 0);
1256
+ const admittedInitialApplications = status.records.reduce((sum, item) =>
1257
+ sum + item.initialAdmittedApplications, 0);
1258
+ const completedInitialDeliveries = status.records.reduce((sum, item) =>
1259
+ sum + item.initialCompletedDeliveries, 0);
1260
+ const incompleteInitialAdmissions = status.records.reduce((sum, item) =>
1261
+ sum + item.incompleteInitialAdmissions, 0);
1262
+ const targetBoundEvaluationContracts = status.records.reduce((sum, item) =>
1263
+ sum + item.targetBoundEvaluationContracts, 0);
1264
+ const targetBoundApplications = status.records.reduce((sum, item) =>
1265
+ sum + item.targetBoundApplications, 0);
1266
+ const deadlineBoundEvaluationContracts = status.records.reduce((sum, item) =>
1267
+ sum + item.deadlineBoundEvaluationContracts, 0);
1268
+ const stalenessBoundEvaluationContracts = status.records.reduce((sum, item) =>
1269
+ sum + item.stalenessBoundEvaluationContracts, 0);
1270
+ const promotionBoundEvaluationContracts = status.records.reduce((sum, item) =>
1271
+ sum + item.promotionBoundEvaluationContracts, 0);
1272
+ const candidateAdmissionEvaluationContracts = status.records.reduce((sum, item) =>
1273
+ sum + item.candidateAdmissionEvaluationContracts, 0);
1274
+ const candidateEvidenceCohortEvaluationContracts = status.records.reduce((sum, item) =>
1275
+ sum + item.candidateEvidenceCohortEvaluationContracts, 0);
1276
+ const blockingDefectBoundEvaluationContracts = status.records.reduce((sum, item) =>
1277
+ sum + item.blockingDefectBoundEvaluationContracts, 0);
1278
+ const evidenceSourceBoundEvaluationContracts = status.records.reduce((sum, item) =>
1279
+ sum + item.evidenceSourceBoundEvaluationContracts, 0);
1280
+ const blockingDefectOutcomeReceipts = status.records.reduce((sum, item) =>
1281
+ sum + item.blockingDefectOutcomeReceipts, 0);
1282
+ const deadlineBoundApplications = status.records.reduce((sum, item) =>
1283
+ sum + item.deadlineBoundApplications, 0);
1284
+ const trialRetryEvaluationContracts = status.records.reduce((sum, item) =>
1285
+ sum + item.trialRetryEvaluationContracts, 0);
1286
+ const comparableTrialRetryEvaluationContracts = status.records.reduce((sum, item) =>
1287
+ sum + item.comparableTrialRetryEvaluationContracts, 0);
1288
+ const boundedTrialRetryEvaluationContracts = status.records.reduce((sum, item) =>
1289
+ sum + item.boundedTrialRetryEvaluationContracts, 0);
1290
+ const trialRetryExhaustionReceipts = status.records.reduce((sum, item) =>
1291
+ sum + item.trialRetryExhaustionReceipts, 0);
1292
+ const trialFailureReceipts = status.records.reduce((sum, item) => sum + item.trialFailureReceipts, 0);
1293
+ const trialFailureRevocationReceipts = status.records.reduce((sum, item) =>
1294
+ sum + item.trialFailureRevocationReceipts, 0);
1295
+ const evaluationRevocationReceipts = status.records.reduce((sum, item) => sum + item.evaluationRevocationReceipts, 0);
1296
+ const validationRevocationReceipts = status.records.reduce((sum, item) => sum + item.validationRevocationReceipts, 0);
1297
+ const evidenceRevocationReceipts = status.records.reduce((sum, item) => sum + item.evidenceRevocationReceipts, 0);
1298
+ const measurementRevocationReceipts = status.records.reduce((sum, item) => sum + item.measurementRevocationReceipts, 0);
1299
+ const applicationRevocationReceipts = status.records.reduce((sum, item) => sum + item.applicationRevocationReceipts, 0);
1300
+ const deliveryRevocationReceipts = status.records.reduce((sum, item) => sum + item.deliveryRevocationReceipts, 0);
1301
+ const outcomeRevocationReceipts = status.records.reduce((sum, item) => sum + item.outcomeRevocationReceipts, 0);
1302
+ const deliveryTimeoutFailures = status.records.reduce((sum, item) => sum + item.deliveryTimeoutFailures, 0);
1303
+ const outcomeTimeoutFailures = status.records.reduce((sum, item) => sum + item.outcomeTimeoutFailures, 0);
1304
+ const pendingInitialOutcomes = status.records.reduce((sum, item) => sum + item.pendingInitialOutcomes, 0);
1305
+ const staleInitialOutcomes = status.records.reduce((sum, item) => sum + item.staleInitialOutcomes, 0);
1306
+ const unplannedOutcomeReceipts = totalOutcomeReceipts - plannedOutcomeReceipts;
1008
1307
  learningOutcomes = {
1009
- status: status.records.some((item) => item.canaryStatus === "stale") ? "degraded" : "healthy",
1308
+ status: status.records.some((item) => ["stale", "revoked", "revoked-evaluation", "revoked-validation", "revoked-evidence", "revoked-measurement", "revoked-application", "revoked-delivery", "revoked-outcome", "unproven", "failed-trial"].includes(item.canaryStatus))
1309
+ || unboundAfterReceipts > 0 || undeliveredAfterReceipts > 0 || unplannedOutcomeReceipts > 0
1310
+ || stalePendingApplications > 0 || staleUnconsumedMeasurements > 0
1311
+ || inactiveEvaluatorRegistryContracts > 0 || staleRevalidations > 0
1312
+ || staleInitialOutcomes > 0 || trialFailureRevocationReceipts > 0 ? "degraded" : "healthy",
1010
1313
  candidates: status.records.length,
1011
1314
  activeCanaries: status.records.filter((item) => item.canaryStatus === "active").length,
1012
1315
  validatedCanaries: status.records.filter((item) => item.canaryStatus === "validated").length,
1013
1316
  staleCanaries: status.records.filter((item) => item.canaryStatus === "stale").length,
1317
+ awaitingApplication: status.records.filter((item) => item.canaryStatus === "active" && item.applicationReceipts === 0).length,
1318
+ applicationReceipts: status.records.reduce((sum, item) => sum + item.applicationReceipts, 0),
1319
+ deliveryReceipts: status.records.reduce((sum, item) => sum + item.deliveryReceipts, 0),
1320
+ pendingApplications: status.records.reduce((sum, item) => sum + item.pendingApplications, 0),
1321
+ stalePendingApplications,
1322
+ evaluationContracts: status.records.reduce((sum, item) => sum + item.evaluationContracts, 0),
1323
+ targetBoundEvaluationContracts,
1324
+ targetBoundApplications,
1325
+ deadlineBoundEvaluationContracts,
1326
+ stalenessBoundEvaluationContracts,
1327
+ promotionBoundEvaluationContracts,
1328
+ candidateAdmissionEvaluationContracts,
1329
+ candidateEvidenceCohortEvaluationContracts,
1330
+ blockingDefectBoundEvaluationContracts,
1331
+ evidenceSourceBoundEvaluationContracts,
1332
+ blockingDefectOutcomeReceipts,
1333
+ deadlineBoundApplications,
1334
+ trialRetryEvaluationContracts,
1335
+ comparableTrialRetryEvaluationContracts,
1336
+ boundedTrialRetryEvaluationContracts,
1337
+ trialRetryExhaustionReceipts,
1338
+ trialFailureReceipts,
1339
+ trialFailureRevocationReceipts,
1340
+ evaluationRevocationReceipts,
1341
+ validationRevocationReceipts,
1342
+ evidenceRevocationReceipts,
1343
+ measurementRevocationReceipts,
1344
+ applicationRevocationReceipts,
1345
+ deliveryRevocationReceipts,
1346
+ outcomeRevocationReceipts,
1347
+ deliveryTimeoutFailures,
1348
+ outcomeTimeoutFailures,
1349
+ pendingInitialOutcomes,
1350
+ staleInitialOutcomes,
1351
+ plannedOutcomeReceipts,
1352
+ coverageBoundReceipts,
1353
+ legacyCoverageReceipts,
1354
+ provenanceBoundReceipts,
1355
+ legacyProvenanceReceipts,
1356
+ measurementReceipts,
1357
+ measurementLineageReceipts,
1358
+ consumedMeasurementReceipts,
1359
+ staleUnconsumedMeasurements,
1360
+ lineageBoundReceipts,
1361
+ pairedOutcomeReceipts,
1362
+ pairedEvaluatorPairs,
1363
+ evaluatorRootBoundReceipts,
1364
+ independentEvaluatorRoots,
1365
+ activeEvaluatorRoots: status.evaluatorRegistry.active,
1366
+ revokedEvaluatorRoots: status.evaluatorRegistry.revoked,
1367
+ evaluatorRegistryBindings: status.evaluatorRegistry.bindings,
1368
+ validationLeases: status.evaluatorRegistry.validationLeases,
1369
+ renewedValidationLeases,
1370
+ fixedCohortValidationLeases,
1371
+ admissionBoundValidationLeases,
1372
+ trialBoundValidationLeases,
1373
+ initialTrialContracts,
1374
+ requiredInitialTrials,
1375
+ admittedInitialApplications,
1376
+ completedInitialDeliveries,
1377
+ incompleteInitialAdmissions,
1378
+ activeRevalidations,
1379
+ staleRevalidations,
1380
+ fixedCohortRevalidations,
1381
+ admissionBoundRevalidations,
1382
+ trialBoundRevalidations,
1383
+ requiredRevalidationDeliveries,
1384
+ admittedRevalidationApplications,
1385
+ completedRevalidationDeliveries,
1386
+ currentValidatedLessons,
1387
+ staleValidatedLessons,
1388
+ unprovenValidatedLessons,
1389
+ revokedValidatedLessons,
1390
+ evaluatorRegistryContracts,
1391
+ inactiveEvaluatorRegistryContracts,
1392
+ unplannedOutcomeReceipts,
1393
+ boundAfterReceipts: status.records.reduce((sum, item) => sum + item.boundAfterReceipts, 0),
1394
+ unboundAfterReceipts,
1395
+ deliveredAfterReceipts: status.records.reduce((sum, item) => sum + item.deliveredAfterReceipts, 0),
1396
+ undeliveredAfterReceipts,
1014
1397
  contradictions: status.records.filter((item) => item.conflictsWith.length > 0).length,
1015
1398
  authority: "context-only"
1016
1399
  };
@@ -8,6 +8,7 @@ import { canonicalPath } from "./lib/paths.js";
8
8
  import { resolveHostSourceCatalog } from "./lib/source-roots.js";
9
9
  import { sessionBriefing } from "./lib/briefing.js";
10
10
  import { captureContinuityPrompt, loadContinuity } from "./lib/continuity.js";
11
+ import { recordLearningApplications, recordLearningDeliveries } from "./lib/learning.js";
11
12
  import {
12
13
  authorizeJobEffect, checkpointJobEffect, closeJobLease, resolveSessionJob, startOrResumeJob
13
14
  } from "./lib/selfstarter.js";
@@ -26,15 +27,25 @@ const KNOWN_EVENTS = new Set([
26
27
  const ID_RE = /^[A-Za-z0-9][A-Za-z0-9:_.@/-]{0,127}$/;
27
28
  const ATTENTION_WRITE_EVENTS = new Set(["UserPromptSubmit", "PostToolUse", "Stop", "SubagentStop"]);
28
29
  const SELFSTART_EVENTS = new Set(["SessionStart", "PostCompact"]);
30
+ const SILENT_OVERSIZE_POST_TOOL_USE = Symbol("silent-oversize-post-tool-use");
31
+ const SILENT_OVERSIZE_POST_TOOL_USE_ARG = "--silent-oversize-post-tool-use";
29
32
 
30
- async function readStdin() {
31
- let value = "";
33
+ async function readStdin({ silentOversizePostToolUse = false } = {}) {
34
+ const chunks = [];
32
35
  let bytes = 0;
36
+ let oversized = false;
33
37
  for await (const chunk of process.stdin) {
34
- bytes += Buffer.byteLength(chunk);
35
- if (bytes > MAX_STDIN_BYTES) throw new Error("hook input exceeds the 64 KiB limit");
36
- value += chunk;
38
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
39
+ const remaining = Math.max(0, MAX_STDIN_BYTES - bytes);
40
+ if (remaining) chunks.push(buffer.subarray(0, remaining));
41
+ bytes += buffer.length;
42
+ if (bytes > MAX_STDIN_BYTES) oversized = true;
37
43
  }
44
+ if (oversized) {
45
+ if (silentOversizePostToolUse) return SILENT_OVERSIZE_POST_TOOL_USE;
46
+ throw new Error("hook input exceeds the 64 KiB limit");
47
+ }
48
+ const value = Buffer.concat(chunks, bytes).toString("utf8");
38
49
  const parsed = value.trim() ? JSON.parse(value) : {};
39
50
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("hook input must be one JSON object");
40
51
  return parsed;
@@ -196,6 +207,7 @@ function renderContext(event, catalog, briefing, signal = null, attentionEvent =
196
207
  createdAt: preflight.receipt.createdAt,
197
208
  expiresAt: preflight.receipt.expiresAt,
198
209
  policy: preflight.policy,
210
+ learningApplications: preflight.learningApplications || null,
199
211
  pendingMustRemember: preflight.pendingMustRemember ? {
200
212
  id: preflight.pendingMustRemember.candidate?.id || null,
201
213
  status: preflight.pendingMustRemember.candidate?.status || (preflight.pendingMustRemember.rejected ? "rejected" : null),
@@ -501,8 +513,9 @@ function blockPrompt(reason) {
501
513
  })}\n`);
502
514
  }
503
515
 
504
- export async function runHook(payload = null) {
505
- const input = payload || await readStdin();
516
+ export async function runHook(payload = null, options = {}) {
517
+ const input = payload || await readStdin(options);
518
+ if (input === SILENT_OVERSIZE_POST_TOOL_USE) return;
506
519
  if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("hook input must be one JSON object");
507
520
  const event = input.hook_event_name || input.event_name || "";
508
521
  if (!KNOWN_EVENTS.has(event)) throw new Error(`unsupported hook event: ${event || "missing"}`);
@@ -560,6 +573,7 @@ export async function runHook(payload = null) {
560
573
  let scope = null;
561
574
  let selfstarter = null;
562
575
  let channelEvent = null;
576
+ let learningDelivery = null;
563
577
 
564
578
  if (event === "PreToolUse" && isMutationTool(input.tool_name)) {
565
579
  const { graph } = await loadGraph(root, catalog);
@@ -637,6 +651,19 @@ export async function runHook(payload = null) {
637
651
  if (["Stop", "SubagentStop"].includes(event)) {
638
652
  scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
639
653
  const exact = await selfstarterScope(input, scope, root, "resume");
654
+ if (exact && !scope.currentTaskId) scope.currentTaskId = exact.taskId;
655
+ try {
656
+ learningDelivery = await recordLearningDeliveries({
657
+ root, sessionId: sessionId(input), hookEvent: event,
658
+ scope: {
659
+ personaId: scope.entityId, userId: scope.userId, tenantId: scope.tenantId,
660
+ projectId: scope.projectId, groupId: scope.groupId, taskId: scope.currentTaskId
661
+ },
662
+ completedAt: input.timestamp || new Date()
663
+ });
664
+ } catch (error) {
665
+ learningDelivery = { status: "degraded", receipts: [], reason: error.message, authority: "context-only" };
666
+ }
640
667
  const requested = selfstarterInput(input);
641
668
  if (exact) {
642
669
  selfstarter = await closeJobLease({
@@ -694,7 +721,7 @@ export async function runHook(payload = null) {
694
721
  catalog, userStateRoot: resolvedSources.userStateRoot, sourceDiagnostics: resolvedSources.diagnostics,
695
722
  prompt: event === "UserPromptSubmit" ? promptFromInput(input) : null
696
723
  });
697
- const context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent, resolvedSources.diagnostics, preflight);
724
+ let context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent, resolvedSources.diagnostics, preflight);
698
725
  if (event === "UserPromptSubmit" && Buffer.byteLength(context) > hostContextLimit(preflight)) {
699
726
  throw new Error("mandatory preflight context exceeds the host hook injection limit");
700
727
  }
@@ -702,6 +729,45 @@ export async function runHook(payload = null) {
702
729
  receipt: preflight.receipt, input, scope, resolvedSources, prompt: promptFromInput(input),
703
730
  now: input.timestamp || new Date(), env: process.env, consume: true
704
731
  })) throw new Error("preflight receipt could not be consumed atomically for this exact turn");
732
+ if (event === "UserPromptSubmit") {
733
+ const activeCanaries = briefing.learning.filter((item) => ["active", "revalidating"].includes(item.outcomeStatus));
734
+ if (!activeCanaries.length) {
735
+ preflight.learningApplications = {
736
+ status: "not-applicable", receipts: [], authority: "context-only"
737
+ };
738
+ } else try {
739
+ const application = await recordLearningApplications({
740
+ root, items: briefing.learning,
741
+ scope: {
742
+ personaId: scope.entityId, userId: scope.userId, tenantId: scope.tenantId,
743
+ projectId: scope.projectId, groupId: scope.groupId, taskId: scope.currentTaskId
744
+ },
745
+ preflightReceipt: preflight.receipt,
746
+ sessionBriefingDigest: createHash("sha256").update(JSON.stringify(briefing)).digest("hex"),
747
+ projectedAt: input.timestamp || new Date()
748
+ });
749
+ preflight.learningApplications = {
750
+ status: application.receipts.length ? "recorded" : "not-applicable",
751
+ receipts: application.receipts.map((item) => ({
752
+ id: item.id, learningId: item.learningId, projectedAt: item.projectedAt, expiresAt: item.expiresAt
753
+ })),
754
+ authority: "context-only"
755
+ };
756
+ } catch (error) {
757
+ briefing.learning = briefing.learning.filter((item) => !["active", "revalidating"].includes(item.outcomeStatus));
758
+ preflight.learningApplications = {
759
+ status: "degraded", receipts: [], reason: error.message, authority: "context-only"
760
+ };
761
+ }
762
+ const enriched = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent,
763
+ resolvedSources.diagnostics, preflight);
764
+ if (Buffer.byteLength(enriched) <= hostContextLimit(preflight)) context = enriched;
765
+ else if (preflight.learningApplications.status === "degraded") {
766
+ preflight.learningApplications = null;
767
+ context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent,
768
+ resolvedSources.diagnostics, preflight);
769
+ }
770
+ }
705
771
  if (payload) return { blocked: false, context, briefing, preflight, signal, attentionEvent, channelEvent, catalogPath };
706
772
  process.stdout.write(`${JSON.stringify(hookOutput(event, context))}\n`);
707
773
  return;
@@ -728,12 +794,16 @@ export async function runHook(payload = null) {
728
794
  }
729
795
  }
730
796
 
731
- if (payload) return { blocked: false, attentionEvent, selfstarter };
797
+ if (payload) return { blocked: false, attentionEvent, selfstarter, learningDelivery };
732
798
  process.stdout.write("{}\n");
733
799
  }
734
800
 
735
801
  if (isMainModule(import.meta.url)) {
736
- runHook().catch((error) => {
802
+ const args = process.argv.slice(2);
803
+ const silentOversizePostToolUse = args.length === 1 && args[0] === SILENT_OVERSIZE_POST_TOOL_USE_ARG;
804
+ const argumentError = args.length && !silentOversizePostToolUse
805
+ ? new Error(`unsupported hook argument: ${args[0]}`) : null;
806
+ (argumentError ? Promise.reject(argumentError) : runHook(null, { silentOversizePostToolUse })).catch((error) => {
737
807
  process.stderr.write(`AgentSpine hook: ${String(error.message).slice(0, 2048)}\n`);
738
808
  // Claude command hooks treat exit 2 as a blocking failure. Exit 1 is
739
809
  // fail-open, which is unsafe for malformed pre-answer payloads.
@@ -12,9 +12,11 @@ export {
12
12
  recordActivity, recordAttentionEvent, resolveAttention, upsertAttention
13
13
  } from "./lib/attention.js";
14
14
  export {
15
- acceptContinuityLearning, addLearningEvidence, configureLearning, deleteLearning, evaluateLearning,
15
+ acceptContinuityLearning, addLearningEvidence, beginLearningRevalidation, configureLearning, deleteLearning, evaluateLearning,
16
16
  learningContext, learningOutcomeStatus, loadLearning, proposeLearning, purgeLearningBySubject,
17
- recordLearningOutcome, reviewLearning, rollbackLearning
17
+ purgeStaleLearningApplications, recordLearningOutcome, registerLearningEvaluation, renewLearningValidation,
18
+ reviewLearning, revokeLearningApplication, revokeLearningDelivery, revokeLearningEvaluation, revokeLearningEvidence, revokeLearningMeasurement, revokeLearningOutcome, revokeLearningTrialFailure, revokeLearningValidation,
19
+ rollbackLearning
18
20
  } from "./lib/learning.js";
19
21
  export {
20
22
  captureContinuityPrompt, configureContinuity, continuityFindings,