Prioritize investigation hypotheses by confidence after bootstrap
This commit is contained in:
1 parent
f4082585ae
commit
c44ccd5ce3
5 files changed
+128
-2
No files matched your search
@@ -0,0 +1,35 @@
|
||||
# Confidence-prioritized investigation scheduling
|
||||
|
||||
The controller gives every active, eligible hypothesis one bootstrap turn in
|
||||
numeric ID order. After that it chooses by qualitative confidence:
|
||||
strongly_supported > supported > plausible > unassessed > weak > weakened.
|
||||
Equal levels use numeric hypothesis ID (H1 before H2 before H10), not rotation.
|
||||
These ranks are ordering values, not probabilities or a new model-owned budget.
|
||||
|
||||
Selection is recomputed between turns. A strengthening hypothesis can continue;
|
||||
a weakened hypothesis yields to a higher-ranked alternative without losing its
|
||||
remaining budget or evidence. Initial turns generally issue queries: their results
|
||||
are semantically assessed on a subsequent turn, so the bootstrap does not claim
|
||||
that all hypotheses are already evaluated after three calls.
|
||||
|
||||
Existing eligibility checks, positive action costs, rewards, iteration safeguards,
|
||||
terminal dispositions and final pending-result assessments remain in force. An
|
||||
interrupted/pending active turn resumes as before; it is not preempted or rewritten.
|
||||
Existing cases adopt the scheduler at their next selection boundary. Archived
|
||||
states, contexts and model request hashes are not changed. The historical cursor
|
||||
field is retained for file compatibility but no longer determines selection.
|
||||
|
||||
## Completion remains separate from selection
|
||||
|
||||
The model may finish its selected hypothesis with remaining credit, or reject it.
|
||||
Supported confidence requires stored evidence. This does not itself globally
|
||||
finish the case. The controller transitions to BUILDING_DIAGNOSIS after all
|
||||
hypotheses have terminal accounts and pending results have been handled, whether
|
||||
through model finish/reject, budget exhaustion or controller limits. No rule
|
||||
requires spending every allocated unit. A weak hypothesis is deprioritized, not
|
||||
automatically rejected just because another ranks higher.
|
||||
|
||||
The final diagnosis evaluates all alternatives, root-cause evidence and adequacy
|
||||
of a countercheck. Only an accepted supported diagnosis can proceed to a coding
|
||||
change request; inconclusive cases go to review. An automatic global early finish
|
||||
on a single high-confidence declaration is not introduced by this scheduler.
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Deterministic scheduling, independent of model calls and budget accounting."""
|
||||
|
||||
CONFIDENCE_ORDER = {
|
||||
'strongly_supported': 5,
|
||||
'supported': 4,
|
||||
'plausible': 3,
|
||||
'unassessed': 2,
|
||||
'weak': 1,
|
||||
'weakened': 0,
|
||||
}
|
||||
|
||||
|
||||
def priority_order(hypotheses):
|
||||
"""One bootstrap turn each, then confidence with numeric ID tie-breaking.
|
||||
|
||||
Eligibility, pending-action checks and iteration/budget limits remain the
|
||||
controller's responsibility. An interrupted active turn is never rescheduled.
|
||||
These ranks order qualitative levels; they are not confidence probabilities.
|
||||
"""
|
||||
ids = sorted(hypotheses, key=lambda hid:int(hid[1:]))
|
||||
positions = {hid:index for index,hid in enumerate(ids)}
|
||||
def rank(hid):
|
||||
hypothesis = hypotheses[hid]
|
||||
return (0 if hypothesis['iterations']==0 else 1,
|
||||
0 if hypothesis['iterations']==0 else -CONFIDENCE_ORDER[hypothesis['confidence']],
|
||||
int(hid[1:]))
|
||||
return [(positions[hid],hid) for hid in sorted(ids,key=rank)]
|
||||
@@ -239,8 +239,9 @@ class Investigator:
|
||||
snapshot=budget.snapshot(case_id,package_id)
|
||||
ids=sorted(state["hypotheses"],key=lambda value:int(value[1:]))
|
||||
selected=None
|
||||
for offset in range(len(ids)):
|
||||
pos=(state["cursor"]+offset)%len(ids);hid=ids[pos];h=state["hypotheses"][hid];account=snapshot.state["accounts"][hid]
|
||||
from .investigation_scheduler import priority_order
|
||||
for pos,hid in priority_order(state['hypotheses']):
|
||||
h=state["hypotheses"][hid];account=snapshot.state["accounts"][hid]
|
||||
if h["disposition"]!="active":continue
|
||||
if any(a["status"]!="settled" for a in account["actions"].values()) and h["last_record_ref"] is None:
|
||||
raise InvestigationError("INVESTIGATION_UNOWNED_PENDING_ACTION")
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import copy
|
||||
|
||||
from viano_bug_analysis.analysis.investigation_scheduler import priority_order
|
||||
|
||||
|
||||
def hypothesis(confidence='unassessed',iterations=1):
|
||||
return {'confidence':confidence,'iterations':iterations}
|
||||
|
||||
|
||||
def order(hypotheses):
|
||||
return [hid for _,hid in priority_order(hypotheses)]
|
||||
|
||||
|
||||
def test_bootstrap_turns_precede_already_strong_hypotheses():
|
||||
states={'H1':hypothesis('strongly_supported'),'H2':hypothesis(iterations=0),'H3':hypothesis(iterations=0)}
|
||||
assert order(states)==['H2','H3','H1']
|
||||
states['H2']['iterations']=1
|
||||
assert order(states)==['H3','H1','H2']
|
||||
|
||||
|
||||
def test_confidence_rises_and_falls_without_changing_budget_or_state():
|
||||
states={'H1':hypothesis('plausible'),'H2':hypothesis('supported'),'H3':hypothesis('weak')}
|
||||
before=copy.deepcopy(states)
|
||||
assert order(states)==['H2','H1','H3']
|
||||
assert states==before
|
||||
states['H2']['confidence']='weakened'
|
||||
assert order(states)==['H1','H3','H2']
|
||||
states['H3']['confidence']='strongly_supported'
|
||||
assert order(states)==['H3','H1','H2']
|
||||
|
||||
|
||||
def test_equal_confidence_uses_numeric_hypothesis_id_not_rotation_or_string_sort():
|
||||
states={hid:hypothesis('supported') for hid in ['H10','H5','H3','H2','H1']}
|
||||
assert order(states)==['H1','H2','H3','H5','H10']
|
||||
assert dict(priority_order(states))[0]=='H1'
|
||||
|
||||
|
||||
def test_unassessed_alternative_is_preferred_over_weakened_candidate():
|
||||
assert order({'H1':hypothesis('weak'),'H2':hypothesis(),'H3':hypothesis('weakened')})==['H2','H1','H3']
|
||||
@@ -270,3 +270,27 @@ def test_provider_configuration_error_blocks_without_abandoning_hypotheses(tool_
|
||||
@pytest.mark.parametrize("name",["investigation-output","investigation-state","investigation-context"])
|
||||
def test_loop_schemas_valid(name):
|
||||
Draft202012Validator.check_schema(json.loads(files("viano_bug_analysis.schemas").joinpath(name+'-v1.schema.json').read_text()))
|
||||
|
||||
|
||||
def test_confidence_scheduler_keeps_growing_candidate_then_switches_after_weakening(tool_case):
|
||||
def change(reply,context):
|
||||
hid=context['selected_hypothesis']
|
||||
turns=sum(c['selected_hypothesis']==hid for c in adapter.contexts)
|
||||
if hid=='H1' and turns in (2,3):
|
||||
reply['update']['confidence']='supported' if turns==2 else 'weakened'
|
||||
reply['next']={'kind':'tool','operation':'source.read_context',
|
||||
'arguments':{'source_ref':'tool/code.cpp','start_line':1,'end_line':2}}
|
||||
adapter=LoopAdapter(change)
|
||||
loop=Investigator(tool_case[0],gateway(adapter))
|
||||
first=run(loop,steps=4)
|
||||
assert first.status=='YIELDED'
|
||||
assert [c['selected_hypothesis'] for c in adapter.contexts]==['H1','H2','H3','H1']
|
||||
# Resume the persisted confidence state; neither cursor nor a restart forces rotation.
|
||||
result=run(Investigator(tool_case[0],gateway(adapter)),steps=2)
|
||||
assert [c['selected_hypothesis'] for c in adapter.contexts]==['H1','H2','H3','H1','H1','H2']
|
||||
saved=state(result)
|
||||
assert saved['hypotheses']['H1']['confidence']=='weakened'
|
||||
assert saved['hypotheses']['H1']['disposition']=='active'
|
||||
account=BudgetManager(tool_case[0]).snapshot(*CASE).state['accounts']['H1']
|
||||
assert account['remaining']>0 and account['status']=='investigating'
|
||||
assert PackageValidator().validate(result.package).valid
|
||||
Reference in new issue
Block a user