agenr 1.9.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.9.2] - 2026-04-12
6
+
7
+ Surgeon proposal-resolution hardening and claim-key progress-output patch release.
8
+
9
+ ### Changed
10
+
11
+ - **Claim-key preview progress output is less misleading.** Preview-only `claim_key_quality` lines no longer print applied or proposal counts before entry adjudication has actually started.
12
+
13
+ ### Fixed
14
+
15
+ - **Malformed eligible proposals no longer fail the whole autonomous run.** Proposal resolution now rejects invalid auto-apply candidates per proposal and continues through the rest of the backlog.
16
+ - **Ambiguous entity-family convergence proposals stay on the manual-review path.** Multi-target family-convergence proposals are no longer marked eligible for autonomous apply.
17
+
18
+ ### Validation
19
+
20
+ Changes since last push to `origin/master`:
21
+
22
+ - Fix surgeon proposal resolution and progress output
23
+
24
+ ## [1.9.1] - 2026-04-12
25
+
26
+ Supersession stall-recovery and review-order guard patch release.
27
+
28
+ ### Changed
29
+
30
+ - **Supersession review order is now enforced at the tool boundary.** The supersession query tool now blocks widening into lower-confidence subject sweeps until the same run has actually exhausted claim-key work, keeping the pass aligned with its intended review order.
31
+
32
+ ### Fixed
33
+
34
+ - **Supersession passes no longer self-poison completion state by widening too early.** The surgeon now avoids the stuck `widenedBeforeClaimKeyExhausted` path that could leave a run unable to complete cleanly after later claim-key review caught up.
35
+ - **Supersession completion feedback is more actionable.** Rejected `complete_pass` calls now tell the model how to count reviewed-but-intentionally-unlinked clusters via `entries_skipped`, reducing repeated no-progress bounded slices.
36
+
37
+ ### Validation
38
+
39
+ Changes since last push to `origin/master`:
40
+
41
+ - Fix surgeon supersession stall handling
42
+
5
43
  ## [1.9.0] - 2026-04-12
6
44
 
7
45
  Surgeon autonomy, review-flow hardening, and CLI/runtime polish release.
@@ -1055,7 +1055,7 @@ function registerAgenrOpenClawTools(api, servicesPromise, logger) {
1055
1055
  var openclaw_plugin_default = {
1056
1056
  id: "agenr",
1057
1057
  name: "agenr",
1058
- version: "1.9.0",
1058
+ version: "1.9.1",
1059
1059
  description: "agenr memory plugin for OpenClaw",
1060
1060
  kind: "memory",
1061
1061
  contracts: {
package/dist/cli.js CHANGED
@@ -8204,7 +8204,7 @@ async function runClaimKeyQualityPass(options, deps) {
8204
8204
  rationale: buildSurgeonProposalLifecycleRationale(buildEntityFamilyConvergenceRationale(candidate), proposalLifecycle),
8205
8205
  confidence: candidate.confidence,
8206
8206
  source: canonicalEntityPrefix ? "entity_family_canonical_candidate" : "entity_family_ambiguous",
8207
- eligibleForApply: canonicalEntityPrefix !== null,
8207
+ eligibleForApply: proposedClaimKeys.length === 1,
8208
8208
  createdAt: options.now().toISOString()
8209
8209
  });
8210
8210
  await persistProposal(proposal, {
@@ -8247,7 +8247,7 @@ async function runClaimKeyQualityPass(options, deps) {
8247
8247
  ),
8248
8248
  confidence: candidate.confidence,
8249
8249
  source: "entity_family_collision",
8250
- eligibleForApply: true,
8250
+ eligibleForApply: false,
8251
8251
  createdAt: options.now().toISOString()
8252
8252
  });
8253
8253
  await persistProposal(proposal, {
@@ -10817,10 +10817,10 @@ function describeSupersessionRejection(progress, input) {
10817
10817
  return "Completion rejected: the review widened beyond claim_key clusters before the claim_key sweep was exhausted.";
10818
10818
  }
10819
10819
  if (!progress.claimKeyScopeExhausted) {
10820
- return input.budgetUsedPct === null ? `Completion rejected: ${progress.claimKeyClustersRemaining} claim_key clusters still remain in the current sweep.` : `Completion rejected: ${progress.claimKeyClustersRemaining} claim_key clusters still remain in the current sweep and only ${input.budgetUsedPct}% of the cost budget has been used.`;
10820
+ return input.budgetUsedPct === null ? `Completion rejected: ${progress.claimKeyClustersRemaining} claim_key clusters still remain in the current sweep. For reviewed-but-intentionally-unlinked clusters, include one paged entry_id per cluster in entries_skipped.` : `Completion rejected: ${progress.claimKeyClustersRemaining} claim_key clusters still remain in the current sweep and only ${input.budgetUsedPct}% of the cost budget has been used. For reviewed-but-intentionally-unlinked clusters, include one paged entry_id per cluster in entries_skipped.`;
10821
10821
  }
10822
10822
  if (input.subjectTotal > 0 && !progress.subjectScopeExhausted) {
10823
- return input.budgetUsedPct === null ? `Completion rejected: the claim_key sweep is exhausted, but ${progress.subjectClustersRemaining} subject clusters still remain.` : `Completion rejected: the claim_key sweep is exhausted, but ${progress.subjectClustersRemaining} subject clusters still remain and only ${input.budgetUsedPct}% of the cost budget has been used.`;
10823
+ return input.budgetUsedPct === null ? `Completion rejected: the claim_key sweep is exhausted, but ${progress.subjectClustersRemaining} subject clusters still remain. For reviewed-but-intentionally-unlinked clusters, include one paged entry_id per cluster in entries_skipped.` : `Completion rejected: the claim_key sweep is exhausted, but ${progress.subjectClustersRemaining} subject clusters still remain and only ${input.budgetUsedPct}% of the cost budget has been used. For reviewed-but-intentionally-unlinked clusters, include one paged entry_id per cluster in entries_skipped.`;
10824
10824
  }
10825
10825
  if (!input.budgetForcedStop) {
10826
10826
  return input.budgetUsedPct === null ? "Completion rejected: the supersession sweep still has unfinished work." : `Completion rejected: the supersession sweep still has unfinished work and only ${input.budgetUsedPct}% of the cost budget has been used.`;
@@ -11482,12 +11482,25 @@ function createQuerySupersessionCandidatesTool(deps) {
11482
11482
  const limit = normalizeLimit4(params.limit);
11483
11483
  const offset = normalizeOffset3(params.offset);
11484
11484
  const type = normalizeOptionalString11(params.type);
11485
+ const progress = deps.completionGuards?.supersession.snapshot();
11486
+ if ((scope === "subject" || scope === "all") && shouldBlockLowerConfidenceScope(progress)) {
11487
+ return toolResult({
11488
+ clusters: [],
11489
+ count: 0,
11490
+ scope,
11491
+ limit,
11492
+ offset,
11493
+ claimKeyClusterCount: progress?.claimKeyClustersRemaining ?? 0,
11494
+ subjectClusterCount: progress?.subjectClustersRemaining ?? 0,
11495
+ blocked: true,
11496
+ message: buildClaimKeyFirstMessage(progress.claimKeyClustersRemaining)
11497
+ });
11498
+ }
11485
11499
  const counts = await deps.port.countSupersessionCandidates({
11486
11500
  type,
11487
11501
  skipRecentlyEvaluatedDays: deps.skipRecentlyEvaluatedDays,
11488
11502
  now: deps.now()
11489
11503
  });
11490
- const progress = deps.completionGuards?.supersession.snapshot();
11491
11504
  const claimKeyClusters = scope === "subject" ? [] : await deps.port.listSupersessionCandidates({
11492
11505
  scope: "claim_key",
11493
11506
  type,
@@ -11546,6 +11559,15 @@ function buildEmptyResultMessage(scope) {
11546
11559
  }
11547
11560
  return "No more supersession clusters match the current filters. The review pool appears exhausted.";
11548
11561
  }
11562
+ function shouldBlockLowerConfidenceScope(progress) {
11563
+ if (!progress) {
11564
+ return false;
11565
+ }
11566
+ return !progress.claimKeyScopeExhausted && progress.claimKeyClustersRemaining > 0;
11567
+ }
11568
+ function buildClaimKeyFirstMessage(remainingClaimKeyClusters) {
11569
+ return remainingClaimKeyClusters === 1 ? "The subject sweep is blocked until the claim_key sweep is exhausted. One claim_key cluster still remains - continue with scope = 'claim_key'." : `The subject sweep is blocked until the claim_key sweep is exhausted. ${remainingClaimKeyClusters} claim_key clusters still remain - continue with scope = 'claim_key'.`;
11570
+ }
11549
11571
  function normalizeLimit4(value) {
11550
11572
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
11551
11573
  return 20;
@@ -12724,6 +12746,7 @@ function buildContinuationPrompt(input) {
12724
12746
  "Continue the supersession pass.",
12725
12747
  progressReminder,
12726
12748
  "Keep paginating claim_key clusters while any remain. Once the claim_key sweep returns no remaining clusters, query scope = 'subject' to confirm whether lower-confidence work remains.",
12749
+ "Viewed supersession clusters do not count as adjudicated unless you mutate them or include one paged entry_id from the cluster in entries_skipped during complete_pass.",
12727
12750
  "If both claim_key and subject sweeps are exhausted, call complete_pass and include any reviewed but intentionally unlinked clusters in entries_skipped.",
12728
12751
  "Avoid no-op metadata actions that do not change persisted state."
12729
12752
  ] : [
@@ -12744,6 +12767,7 @@ async function runProposalResolutionPass(input, deps) {
12744
12767
  });
12745
12768
  let appliedCount = 0;
12746
12769
  let rejectedInactiveCount = 0;
12770
+ let rejectedInvalidCount = 0;
12747
12771
  let noChangeCount = 0;
12748
12772
  const updatedEntryIds = /* @__PURE__ */ new Set();
12749
12773
  emitProposalResolutionProgress(input.reportProgress, {
@@ -12753,6 +12777,7 @@ async function runProposalResolutionPass(input, deps) {
12753
12777
  processedProposals: 0,
12754
12778
  appliedCount,
12755
12779
  rejectedInactiveCount,
12780
+ rejectedInvalidCount,
12756
12781
  noChangeCount,
12757
12782
  targetedEntryCount: updatedEntryIds.size
12758
12783
  });
@@ -12766,7 +12791,35 @@ async function runProposalResolutionPass(input, deps) {
12766
12791
  }
12767
12792
  for (const [index, item] of backlog.entries()) {
12768
12793
  const proposal = item.proposal;
12769
- const targetClaimKey = resolveSurgeonProposalApplyTarget(proposal);
12794
+ let targetClaimKey;
12795
+ try {
12796
+ targetClaimKey = resolveSurgeonProposalApplyTarget(proposal);
12797
+ } catch (error) {
12798
+ const message = error instanceof Error ? error.message : String(error);
12799
+ await deps.port.reviewProposal({
12800
+ proposalId: proposal.id,
12801
+ status: "rejected",
12802
+ reason: message,
12803
+ reviewedAt: input.now().toISOString(),
12804
+ appliedActionCount: 0
12805
+ });
12806
+ rejectedInvalidCount += 1;
12807
+ emitProposalResolutionProgress(input.reportProgress, {
12808
+ apply: input.apply,
12809
+ status: "proposal_processed",
12810
+ totalProposals: backlog.length,
12811
+ processedProposals: index + 1,
12812
+ appliedCount,
12813
+ rejectedInactiveCount,
12814
+ rejectedInvalidCount,
12815
+ noChangeCount,
12816
+ targetedEntryCount: updatedEntryIds.size,
12817
+ proposalId: proposal.id,
12818
+ issueKind: proposal.issueKind,
12819
+ outcome: "rejected_invalid"
12820
+ });
12821
+ continue;
12822
+ }
12770
12823
  const reasoning = buildSurgeonProposalReviewReason(proposal, "Autonomous eligible proposal resolution.");
12771
12824
  const proposalEntryIds = [];
12772
12825
  if (input.apply) {
@@ -12807,6 +12860,7 @@ async function runProposalResolutionPass(input, deps) {
12807
12860
  processedProposals: index + 1,
12808
12861
  appliedCount,
12809
12862
  rejectedInactiveCount,
12863
+ rejectedInvalidCount,
12810
12864
  noChangeCount,
12811
12865
  targetedEntryCount: updatedEntryIds.size,
12812
12866
  proposalId: proposal.id,
@@ -12822,6 +12876,7 @@ async function runProposalResolutionPass(input, deps) {
12822
12876
  processedProposals: index + 1,
12823
12877
  appliedCount,
12824
12878
  rejectedInactiveCount,
12879
+ rejectedInvalidCount,
12825
12880
  noChangeCount,
12826
12881
  targetedEntryCount: updatedEntryIds.size,
12827
12882
  proposalId: proposal.id,
@@ -12870,6 +12925,7 @@ async function runProposalResolutionPass(input, deps) {
12870
12925
  processedProposals: index + 1,
12871
12926
  appliedCount,
12872
12927
  rejectedInactiveCount,
12928
+ rejectedInvalidCount,
12873
12929
  noChangeCount,
12874
12930
  targetedEntryCount: updatedEntryIds.size,
12875
12931
  proposalId: proposal.id,
@@ -12877,7 +12933,7 @@ async function runProposalResolutionPass(input, deps) {
12877
12933
  outcome: input.apply ? "applied" : "dry_run"
12878
12934
  });
12879
12935
  }
12880
- if (appliedCount === 0 && rejectedInactiveCount === 0) {
12936
+ if (appliedCount === 0 && rejectedInactiveCount === 0 && rejectedInvalidCount === 0) {
12881
12937
  emitProposalResolutionProgress(input.reportProgress, {
12882
12938
  apply: input.apply,
12883
12939
  status: "stalled",
@@ -12885,6 +12941,7 @@ async function runProposalResolutionPass(input, deps) {
12885
12941
  processedProposals: backlog.length,
12886
12942
  appliedCount,
12887
12943
  rejectedInactiveCount,
12944
+ rejectedInvalidCount,
12888
12945
  noChangeCount,
12889
12946
  targetedEntryCount: updatedEntryIds.size
12890
12947
  });
@@ -12902,6 +12959,7 @@ async function runProposalResolutionPass(input, deps) {
12902
12959
  processedProposals: backlog.length,
12903
12960
  appliedCount,
12904
12961
  rejectedInactiveCount,
12962
+ rejectedInvalidCount,
12905
12963
  noChangeCount,
12906
12964
  targetedEntryCount: updatedEntryIds.size
12907
12965
  });
@@ -12913,7 +12971,10 @@ async function runProposalResolutionPass(input, deps) {
12913
12971
  observations: [
12914
12972
  `Processed ${appliedCount} eligible surgeon proposal${appliedCount === 1 ? "" : "s"}.`,
12915
12973
  `${updatedEntryIds.size} entr${updatedEntryIds.size === 1 ? "y was" : "ies were"} targeted by proposal resolution.`,
12916
- ...rejectedInactiveCount > 0 ? [`Rejected ${rejectedInactiveCount} stale eligible proposal${rejectedInactiveCount === 1 ? "" : "s"} whose target entries were no longer active.`] : []
12974
+ ...rejectedInactiveCount > 0 ? [`Rejected ${rejectedInactiveCount} stale eligible proposal${rejectedInactiveCount === 1 ? "" : "s"} whose target entries were no longer active.`] : [],
12975
+ ...rejectedInvalidCount > 0 ? [
12976
+ `Rejected ${rejectedInvalidCount} malformed eligible proposal${rejectedInvalidCount === 1 ? "" : "s"} that did not resolve to exactly one safe claim-key target.`
12977
+ ] : []
12917
12978
  ],
12918
12979
  recommendations: ["Leave non-eligible surgeon proposals on the manual review path."]
12919
12980
  },
@@ -12930,6 +12991,7 @@ function emitProposalResolutionProgress(reporter, input) {
12930
12991
  processedProposals: input.processedProposals,
12931
12992
  appliedCount: input.appliedCount,
12932
12993
  rejectedInactiveCount: input.rejectedInactiveCount,
12994
+ rejectedInvalidCount: input.rejectedInvalidCount,
12933
12995
  noChangeCount: input.noChangeCount,
12934
12996
  targetedEntryCount: input.targetedEntryCount,
12935
12997
  proposalId: input.proposalId,
@@ -16290,8 +16352,8 @@ function handleClaimKeyQualityEvent(event, verbose) {
16290
16352
  const previewTotal = formatOptionalCount(event.previewTotal);
16291
16353
  const previewCompleted = formatOptionalCount(event.previewCompleted);
16292
16354
  const previewSuffix = previewTotal > 0 ? `, preview ${previewCompleted}/${previewTotal}` : "";
16293
- const progressMsg = `${stageLabel}: ${event.completed}/${event.total} ${event.unitLabel}${previewSuffix}, ${appliedTotal} applied, ${event.counts.proposalsEmitted} proposals${skippedSummary}, ${formatElapsed(event.elapsedMs)}`;
16294
- if (verbose) {
16355
+ const progressMsg = event.status === "preview_progress" ? `${stageLabel}: ${event.completed}/${event.total} ${event.unitLabel}${previewSuffix}${skippedSummary}, ${formatElapsed(event.elapsedMs)}` : `${stageLabel}: ${event.completed}/${event.total} ${event.unitLabel}${previewSuffix}, ${appliedTotal} applied, ${event.counts.proposalsEmitted} proposals${skippedSummary}, ${formatElapsed(event.elapsedMs)}`;
16356
+ if (verbose && event.status !== "preview_progress") {
16295
16357
  const detail = ` (normalize ${event.counts.appliedNormalizations}/${event.counts.identifiedNormalizations}, backfill ${event.counts.appliedBackfills}/${event.counts.identifiedBackfills}, metadata ${event.counts.appliedMetadataRewrites}/${event.counts.identifiedMetadataRewrites}, family ${event.counts.appliedEntityFamilyConvergences}/${event.counts.identifiedEntityFamilyConvergences})`;
16296
16358
  writeStderr(` ${ui.dim(progressMsg + detail)}`);
16297
16359
  } else {
@@ -16311,18 +16373,18 @@ function handleProposalResolutionEvent(event, verbose) {
16311
16373
  const outcome = event.outcome ? `, ${formatProposalResolutionOutcome(event.outcome)}` : "";
16312
16374
  const verboseSuffix = verbose && event.proposalId ? ` ${ui.dim(`(${event.proposalId}${event.issueKind ? `, ${event.issueKind}` : ""})`)}` : "";
16313
16375
  writeStderr(
16314
- ` ${ui.dim(`proposal_resolution: ${event.processedProposals}/${event.totalProposals} proposals, applied ${event.appliedCount}, inactive ${event.rejectedInactiveCount}, no-op ${event.noChangeCount}, targeted ${event.targetedEntryCount}${outcome}`)}${verboseSuffix}`
16376
+ ` ${ui.dim(`proposal_resolution: ${event.processedProposals}/${event.totalProposals} proposals, applied ${event.appliedCount}, inactive ${event.rejectedInactiveCount}, invalid ${event.rejectedInvalidCount}, no-op ${event.noChangeCount}, targeted ${event.targetedEntryCount}${outcome}`)}${verboseSuffix}`
16315
16377
  );
16316
16378
  return;
16317
16379
  }
16318
16380
  case "completed":
16319
16381
  writeStderr(
16320
- ` ${ui.success(`proposal_resolution complete: applied ${event.appliedCount}, inactive ${event.rejectedInactiveCount}, no-op ${event.noChangeCount}, targeted ${event.targetedEntryCount}`)}`
16382
+ ` ${ui.success(`proposal_resolution complete: applied ${event.appliedCount}, inactive ${event.rejectedInactiveCount}, invalid ${event.rejectedInvalidCount}, no-op ${event.noChangeCount}, targeted ${event.targetedEntryCount}`)}`
16321
16383
  );
16322
16384
  return;
16323
16385
  case "stalled":
16324
16386
  writeStderr(
16325
- ` ${ui.warn(`proposal_resolution stalled: ${event.processedProposals}/${event.totalProposals} proposals, applied ${event.appliedCount}, inactive ${event.rejectedInactiveCount}, no-op ${event.noChangeCount}, targeted ${event.targetedEntryCount}`)}`
16387
+ ` ${ui.warn(`proposal_resolution stalled: ${event.processedProposals}/${event.totalProposals} proposals, applied ${event.appliedCount}, inactive ${event.rejectedInactiveCount}, invalid ${event.rejectedInvalidCount}, no-op ${event.noChangeCount}, targeted ${event.targetedEntryCount}`)}`
16326
16388
  );
16327
16389
  return;
16328
16390
  default:
@@ -16464,6 +16526,8 @@ function formatProposalResolutionOutcome(outcome) {
16464
16526
  return "previewed";
16465
16527
  case "rejected_inactive":
16466
16528
  return "rejected inactive";
16529
+ case "rejected_invalid":
16530
+ return "rejected invalid";
16467
16531
  case "no_change":
16468
16532
  return "no change";
16469
16533
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "Agent memory - local-first knowledge infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "bin": {