Le notebook construit un score décomposé, traite l’éligibilité comme un filtre et non comme une pénalité, et vérifie que l’action au gain maximal est écartée pour dépassement d’échéance. Il exécute le meilleur coup admissible, ferme sa lacune dans le graphe, relit l’état, reclasse, puis boucle jusqu’à ce qu’aucune action éligible ne franchisse le seuil. La sortie finale est une abstention calibrée : une lacune à fort impact reste ouverte, échéance et budget à l’appui.
next-best-evidence/notebook.ipynbEXTRAIT / 12 CELLULES
MARKDOWN [0]Reproduisez le scénario sur une copie de travail et conservez les identifiants de preuves, de session et de snapshot dans le résultat.
CODE [1] / PYTHONimport json, os, pathlib, time
import requests
import urllib3
# The quick-start server uses a self-signed certificate; disable verification
# for the local playbook only, never against a real deployment.
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BASE_URL = os.environ.get('CORROBORE_URL', 'https://127.0.0.1:8080')
TOKEN = os.environ.get('CORROBORE_HTTP_AUTH_TOKEN', 'change-me')
http = requests.Session()
http.verify = False
http.headers.update({'Authorization': f'Bearer {TOKEN}', 'Content-Type': 'application/json'})
class MemoryError_(RuntimeError):
"""Carries the stable v1 error taxonomy instead of a bare HTTP status."""
def __init__(self, code, message, status):
super().__init__(f'{code}: {message}')
self.code, self.message, self.status = code, message, status
def memory_op(operation, payload, idempotency_key=None, expect_error=False):
"""POST /v1/memory/operations and unwrap the typed result."""
body = {'contract_version': 'v1', 'operation': operation, 'input': payload}
if idempotency_key is not None:
body['idempotency_key'] = idempotency_key
for attempt in range(12):
response = http.post(f'{BASE_URL}/v1/memory/operations', data=json.dumps(body))
# Protected routes share a global token bucket (50 rps sustained, 200 burst by
# default) that a bulk load will hit. Two details matter here: the 429 body is
# plain text, not the JSON error envelope, and `Retry-After` can be `0` — so
# honour it as a floor, never as the whole wait.
if response.status_code == 429:
hinted = float(response.headers.get('Retry-After', 0) or 0)
time.sleep(max(hinted, 0.2 * (attempt + 1)))
continue
break
if response.status_code != 200:
try:
error = response.json().get('error', {})
except ValueError: # 429 and other transport rejections are not JSON
error = {}
failure = MemoryError_(error.get('code', 'UNKNOWN'), error.get('message', response.text), response.status_code)
if expect_error:
return failure
raise failure
if expect_error:
raise AssertionError(f'{operation} unexpectedly succeeded')
return response.json()['result']['result']
ready = http.get(f'{BASE_URL}/health/ready').json()
version = http.get(f'{BASE_URL}/version').json()
print('ready :', json.dumps(ready)[:160])
print('version:', json.dumps(version)[:160])
CODE [2] / PYTHONIMPACT_WEIGHT = {'high': 1.0, 'medium': 0.6, 'low': 0.3}
DEADLINE_HOURS = 48
BUDGET = 1.0
def assess(actions, gaps, closes, budget=BUDGET, deadline=DEADLINE_HOURS):
rows = []
for key, record in actions.items():
props = record['content']['value']['properties']
target = closes.get(key)
open_target = target in gaps and gaps[target]['content']['value']['properties']['status'] == 'open'
impact = gaps[target]['content']['value']['properties']['impact'] if open_target else None
blockers = []
if props['hours'] > deadline:
blockers.append(f"exceeds deadline ({props['hours']}h > {deadline}h)")
if props['authority'] != 'granted':
blockers.append(f"authority {props['authority']}")
if props['cost'] > budget:
blockers.append('over budget')
if not open_target:
blockers.append('closes no open gap')
weight = IMPACT_WEIGHT.get(impact, 0.0)
score = (props['expected_gain'] * weight) / (props['cost'] + 0.1) * (1 - props['risk'])
rows.append({
'action': key, 'gain': props['expected_gain'], 'cost': props['cost'],
'hours': props['hours'], 'risk': props['risk'], 'closes': target,
'impact': impact, 'score': round(score, 3),
'eligible': not blockers, 'blockers': blockers,
})
return sorted(rows, key=lambda r: (-r['eligible'], -r['score']))
ranking = assess(actions, gaps, closes)
print(f"{'action':<28}{'gain':>6}{'cost':>6}{'hours':>7}{'score':>8} eligibility")
print('-' * 84)
for row in ranking:
verdict = 'eligible' if row['eligible'] else '; '.join(row['blockers'])
print(f"{row['action']:<28}{row['gain']:>6}{row['cost']:>6}{row['hours']:>7}"
f"{row['score']:>8} {verdict}")
CODE [3] / PYTHONtop = next(r for r in ranking if r['eligible'])
highest_gain = max(ranking, key=lambda r: r['gain'])
print('highest expected gain :', highest_gain['action'], highest_gain['gain'])
print(' eligible? :', highest_gain['eligible'], highest_gain['blockers'])
print('chosen action :', top['action'], 'score', top['score'])
# Ranking by gain alone would have picked an action that misses the deadline.
assert highest_gain['eligible'] is False
assert top['action'] != highest_gain['action']
# The two busywork actions are excluded for closing nothing, not for scoring badly.
busywork = [r['action'] for r in ranking if 'closes no open gap' in r['blockers']]
print('excluded as busywork :', busywork)
assert len(busywork) == dataset['facts']['actions_closing_nothing']
CODE [4] / PYTHONdef close_gap(gap_key, note):
ladder = memory_op('trace', {'target': {'kind': 'memory', 'id': ids[gap_key]}})
return memory_op('update', {
'target': {'kind': 'memory', 'id': ids[gap_key]},
'expected_version': ladder['versions'][-1]['version'],
'patch': {
'content': {'format': 'text_and_properties',
'value': {'text': note, 'properties': {'impact': 'high', 'status': 'closed'}}},
'confidence': 0.95,
'add_provenance': [{'source_id': 'collection', 'locator': gap_key, 'observed_at': None}],
'add_tags': ['closed'],
},
}, idempotency_key=f'nbe:close:{gap_key}')
closed = close_gap(top['closes'], f"Gap closed by {top['action']}.")
print('closed :', top['closes'], '-> version', closed['record']['version'])
print('status :', closed['record']['content']['value']['properties']['status'])
actions, gaps, closes = read_state()
ranking = assess(actions, gaps, closes, budget=BUDGET - top['cost'])
print()
print('re-ranked after acting:')
for row in ranking[:4]:
verdict = 'eligible' if row['eligible'] else '; '.join(row['blockers'])
print(f" {row['action']:<28}{row['score']:>8} {verdict}")
# The action we just performed is no longer a candidate: its gap is closed.
performed = next(r for r in ranking if r['action'] == top['action'])
assert performed['eligible'] is False
assert 'closes no open gap' in performed['blockers']
CODE [5] / PYTHONMIN_SCORE = 0.35
budget, spent, performed_actions = BUDGET, 0.0, []
while True:
actions, gaps, closes = read_state()
ranking = assess(actions, gaps, closes, budget=budget - spent)
candidate = next((r for r in ranking if r['eligible'] and r['score'] >= MIN_SCORE), None)
if candidate is None:
break
close_gap(candidate['closes'], f"Gap closed by {candidate['action']}.")
spent += candidate['cost']
performed_actions.append(candidate['action'])
print(f"performed {candidate['action']:<28} score {candidate['score']:<7} spent {spent:.2f}")
actions, gaps, closes = read_state()
still_open = {k: v['content']['value']['properties']
for k, v in gaps.items()
if v['content']['value']['properties']['status'] == 'open'}
print()
print('stopped after', len(performed_actions), 'actions, budget spent', round(spent, 2))
print()
print('calibrated abstention:')
for gap, props in sorted(still_open.items()):
reasons = [r['blockers'] for r in assess(actions, gaps, closes, budget=budget - spent)
if closes.get(r['action']) == gap]
flat = sorted({b for group in reasons for b in group})
print(f" {gap} ({props['impact']} impact) remains open because: {', '.join(flat) or 'no action targets it'}")
assert still_open, 'this investigation cannot be fully closed within its constraints'