Keep non-goal conflicts out of active Ranker lanes

This commit is contained in:
OpenClaw Bot
2026-05-26 23:37:27 +02:00
parent 7151f0d378
commit 65832f9b56
3 changed files with 10 additions and 2 deletions
+2
View File
@@ -49,6 +49,8 @@ Ranker's continuation job is narrow:
Candidate items may include optional 110 `rankerHints` (`value`, `effort`, `confidence`, `urgency`, `revenue`, `novelty`, `risk`). Ranker blends those explicit Scattermind hints with text heuristics; `effort` is inverted into feasibility. Hints improve the defended order, but `recommendedLane: "defer"` or `"park"` remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send `sourceSection` and `evidenceNeeded` on each active next move. Scattermind can also send `targetAudience`, `constraints`, `assumptions`, and `nonGoals` / `avoid` at the top level, in `featureSet`, or inside `conceptMap.context`; Ranker returns that decision context in `input.decisionContext` and `handoff.decisionContext`, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof). If Scattermind sends duplicate candidate IDs, Ranker keeps the first ID, suffixes later duplicates (`preview-2`), and reports the normalization in `handoff.warnings` / `handoff.itemTrace` so downstream build-order references remain addressable. Candidate items may include optional 110 `rankerHints` (`value`, `effort`, `confidence`, `urgency`, `revenue`, `novelty`, `risk`). Ranker blends those explicit Scattermind hints with text heuristics; `effort` is inverted into feasibility. Hints improve the defended order, but `recommendedLane: "defer"` or `"park"` remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send `sourceSection` and `evidenceNeeded` on each active next move. Scattermind can also send `targetAudience`, `constraints`, `assumptions`, and `nonGoals` / `avoid` at the top level, in `featureSet`, or inside `conceptMap.context`; Ranker returns that decision context in `input.decisionContext` and `handoff.decisionContext`, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof). If Scattermind sends duplicate candidate IDs, Ranker keeps the first ID, suffixes later duplicates (`preview-2`), and reports the normalization in `handoff.warnings` / `handoff.itemTrace` so downstream build-order references remain addressable.
Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; the result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work.
Recommended payload shape: Recommended payload shape:
```json ```json
+4
View File
@@ -268,8 +268,12 @@ try {
const workspace = nonGoal.ranked.find(item => item.id === 'workspace-autopilot'); const workspace = nonGoal.ranked.find(item => item.id === 'workspace-autopilot');
assert.ok(workspace.metrics.nonGoalConflicts.length >= 2); assert.ok(workspace.metrics.nonGoalConflicts.length >= 2);
assert.match(workspace.concern, /Source context says not to do this yet/); assert.match(workspace.concern, /Source context says not to do this yet/);
assert.equal(workspace.lane.id, 'defer', 'source non-goals should keep conflicted candidates out of the active proof slice even with strong hints');
assert.equal(workspace.lane.source, 'source-non-goal');
assert.deepEqual(nonGoal.handoff.itemTrace.find(item => item.id === 'workspace-autopilot').nonGoalConflicts, workspace.metrics.nonGoalConflicts); assert.deepEqual(nonGoal.handoff.itemTrace.find(item => item.id === 'workspace-autopilot').nonGoalConflicts, workspace.metrics.nonGoalConflicts);
assert.ok(!nonGoal.buildOrder.doFirst.includes('workspace-autopilot')); assert.ok(!nonGoal.buildOrder.doFirst.includes('workspace-autopilot'));
assert.ok(!nonGoal.buildOrder.validateNext.includes('workspace-autopilot'));
assert.ok(!nonGoal.handoff.warnings.some(item => /active item workspace-autopilot conflicts/.test(item)), 'conflicted candidates should be demoted before handoff warnings need to flag active-lane conflict');
const mixedWrapperResponse = await fetch(`${base}/api/rank-feedback`, { const mixedWrapperResponse = await fetch(`${base}/api/rank-feedback`, {
method: 'POST', method: 'POST',
+4 -2
View File
@@ -593,11 +593,13 @@ function normalizeLaneHint(value = '') {
function laneFor(option, rankIndex, total) { function laneFor(option, rankIndex, total) {
const hintedLane = normalizeLaneHint(option.factors?.recommendedLane || ''); const hintedLane = normalizeLaneHint(option.factors?.recommendedLane || '');
// Ranker should defend build order, not blindly obey Scattermind. Positive // Ranker should defend build order, not blindly obey Scattermind. Positive
// hints can nudge scoring, but explicit negative hints are safety rails: // hints can nudge scoring, but explicit negative hints and source non-goals
// if the source already marked something as defer/park, never promote it // are safety rails: if the source already marked something as not-now, or
// if the candidate conflicts with the source guardrails, never promote it
// into the active proof slice just because keyword scoring liked it. // into the active proof slice just because keyword scoring liked it.
if (hintedLane === 'park') return { id: 'park', label: 'Park / cut', action: 'Keep out of the active plan', source: 'hint' }; if (hintedLane === 'park') return { id: 'park', label: 'Park / cut', action: 'Keep out of the active plan', source: 'hint' };
if (hintedLane === 'defer') return { id: 'defer', label: 'Defer', action: 'Sequence after proof', source: 'hint' }; if (hintedLane === 'defer') return { id: 'defer', label: 'Defer', action: 'Sequence after proof', source: 'hint' };
if (option.metrics?.nonGoalConflicts?.length) return { id: 'defer', label: 'Defer', action: 'Resolve source guardrail first', source: 'source-non-goal' };
if (rankIndex === 0) return { id: 'do', label: 'Do first', action: 'Build/test now' }; if (rankIndex === 0) return { id: 'do', label: 'Do first', action: 'Build/test now' };
if (hintedLane === 'test') return { id: 'test', label: 'Validate next', action: 'Find evidence', source: 'hint' }; if (hintedLane === 'test') return { id: 'test', label: 'Validate next', action: 'Find evidence', source: 'hint' };
if (rankIndex < Math.max(2, Math.ceil(total * 0.32))) return { id: 'test', label: 'Validate next', action: 'Find evidence' }; if (rankIndex < Math.max(2, Math.ceil(total * 0.32))) return { id: 'test', label: 'Validate next', action: 'Find evidence' };