diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 60b94d4..b77b88c 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -87,6 +87,15 @@ const kbWritePathEnabled = ref(true); // unrelated code through (#2223). Shares top-k, not the threshold. const kbWritePathThreshold = ref("0.68"); const kbRuleHintThreshold = ref("0.72"); +// The two ACT arms no longer share a bar (#3853). A write-path query is a code +// payload; a pre-tool query is a shell command, often under a dozen words — +// less text, less signal, lower scores for the same relevance. Measured at one +// shared 0.72 the two behaved like different subsystems: the write-path arm +// spoke on 37% of its calls, the command arm on 2% of 11,768. +const kbToolRuleThreshold = ref("0.68"); +// And the prompt boundary is a third query shape again — the operator's own +// prose rather than anything a tool produced (#3852). +const kbPromptRuleThreshold = ref("0.72"); // Near-duplicate report floors, one per record kind (services/dedup.py). // Snippets are single-chunk, so their floor sits below the 0.90 write-time // gate and catches what it lets through. Notes/tasks are scored at chunk @@ -153,6 +162,10 @@ async function saveKbInject() { // fires on every write, so a fallback of 0 would attach a standing rule to // every edit in the session. const rhT = Math.min(1, Math.max(0, Number(kbRuleHintThreshold.value) || 0.72)); + // Same `|| default` guard, and the same reason: this arm fires before every + // Bash call, so a fallback of 0 would put a rule in front of every command. + const trT = Math.min(1, Math.max(0, Number(kbToolRuleThreshold.value) || 0.68)); + const prT = Math.min(1, Math.max(0, Number(kbPromptRuleThreshold.value) || 0.72)); kbInjectThreshold.value = String(t); kbInjectTopK.value = String(k); kbDupThresholdSnippet.value = String(dupSnip); @@ -160,6 +173,8 @@ async function saveKbInject() { kbDupThresholdTask.value = String(dupTask); kbWritePathThreshold.value = String(wpT); kbRuleHintThreshold.value = String(rhT); + kbToolRuleThreshold.value = String(trT); + kbPromptRuleThreshold.value = String(prT); savingKbInject.value = true; kbInjectSaved.value = false; try { @@ -176,6 +191,11 @@ async function saveKbInject() { // in services/plugin_context.py for why rules cannot share the // code threshold any more than code could share the prose one. kb_rulehint_threshold: String(rhT), + // A FOURTH and FIFTH bar, and they are separate keys on purpose: the + // whole finding of #3853 is that one number cannot serve arms whose + // queries are different shapes. Moving one must not move the others. + kb_toolrule_threshold: String(trT), + kb_promptrule_threshold: String(prT), kb_duplicate_threshold_snippet: String(dupSnip), kb_duplicate_threshold_note: String(dupNote), kb_duplicate_threshold_task: String(dupTask), @@ -624,6 +644,12 @@ onMounted(async () => { if (allSettings.kb_rulehint_threshold !== undefined) { kbRuleHintThreshold.value = allSettings.kb_rulehint_threshold; } + if (allSettings.kb_toolrule_threshold !== undefined) { + kbToolRuleThreshold.value = allSettings.kb_toolrule_threshold; + } + if (allSettings.kb_promptrule_threshold !== undefined) { + kbPromptRuleThreshold.value = allSettings.kb_promptrule_threshold; + } if (allSettings.kb_writepath_threshold !== undefined) { kbWritePathThreshold.value = allSettings.kb_writepath_threshold; } @@ -1482,14 +1508,54 @@ async function deleteUser(userId: number) { style="max-width: 8rem" />

- The same hint can mention a standing rule whose trigger resembles what's - being written — only rules marked conditional, since always-on - ones are already loaded. Stricter again than the threshold above, because - there are far fewer rules than snippets: with a small set, something - always ranks first, so the bar has to carry more of the judgement. - Raise it if rules keep arriving unread; lower it if a rule you needed - never showed up. Settings → check the pull-through in - retrieval_telemetry to see which is happening. + The same hint can mention a standing rule whose trigger resembles the + code being written. Every rule is eligible — nothing is + preloaded any more, so this is the only way a rule reaches a write. + Stricter than the threshold above, because there are far fewer rules + than snippets: with a small set something always ranks first, so the + bar has to carry more of the judgement. Raise it if rules keep + arriving unread; lower it if a rule you needed never showed up. +

+ +
+ + +

+ The bar for a rule surfacing before a command runs, where the + query is the command text rather than code. Lower than the one above + on purpose: a shell command is short, so it scores lower for the same + relevance — at a shared bar this arm spoke on 2% of calls against the + write path's 37%. Raise it if commands attract rules that do not + apply; lower it if a git push arrives with nothing. +

+
+
+ + +

+ The bar for a rule or preference surfacing against what you just + said, before anything is done. It is the only moment that reaches + a rule about how to answer rather than how to act, so it runs once a + turn rather than once a tool call. A third query shape — your prose, + not a command or a file — which is why it carries its own number.