mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
P2-S6: Name the host's own typed failure and the leaf's model at a nanny terminal
A nanny that died on its own Codex lane was reported by the reviewer role it
played (73c216b8, "c-final-fable-review"), so the owner asked why Fable was
broken while the Fable run was alive and then cancelled by I2. Two facts
were missing, and they are separate facts: the HOST's last typed error, and
the LEAF's identity.
Both land where the reader already looks, on projections that already exist,
with no new host_route sub-dict (disposition R7). model_execution_projection
gains exactly one typed key, last_llm_error_kind, beside used_model and
provider; it reads the same usage['_last_llm_error_kind'] the loop already
keeps and a successful send already clears, so it names the failure of the
host's LAST model call rather than a sticky stale one. Each terminal_runs
row grows from {run_id, state} to carry model, profile_id and
selected_subagent_id, all already on the replayed RunCustody, so the sharing
costs zero extra reads; terminal_custody_notice names the leaf model when
the row carried one.
The precondition is covered, not assumed: half of the leaf exists only once
delegate_terminal_reconciliation has been persisted, and on the live row it
was null. With no persisted audit the notice renders nothing and the host
half stands alone; an older custody row without a model simply says less.
Deliberately not in outcome_axes: outcomes.py is at its line ceiling and the
axis statuses are not this reader's path. terminal_runs is in
_ENVELOPE_DISCLOSURE_FIELDS, so stored envelopes compare unequal once and
are rewritten by the audit-only refresh. gateway/contracts.py is frozen and
needs no edit: model_execution is already NotRequired[Dict[str, Any]].
Tests: the pinned terminal_runs shapes carry the leaf fields and the notice
names the leaf model with no live join; a dead host lane carries the typed
error kind on model_execution while an absent reconciliation renders only
the host half; the solve half is still preserved through an empty call,
which now visibly moves the error kind alone.
This commit is contained in:
parent
0a1b5464e0
commit
e9e739c11e
5 changed files with 65 additions and 7 deletions
|
|
@ -128,8 +128,13 @@ def _audit_task_custody(drive_root: Any, mine: str, result: Dict[str, Any], *,
|
|||
terminal_runs = []
|
||||
try:
|
||||
if not audit_failure:
|
||||
# The LEAF's own identity, already on the replayed row (zero extra
|
||||
# reads): without it a nanny terminal names only the role the host
|
||||
# played and the reader asks why the leaf's model was broken (I9).
|
||||
terminal_runs = sorted((
|
||||
{"run_id": str(row.run_id), "state": str(row.terminal_state)}
|
||||
{"run_id": str(row.run_id), "state": str(row.terminal_state),
|
||||
"model": str(row.model), "profile_id": str(row.profile_id),
|
||||
"selected_subagent_id": str(row.selected_subagent_id)}
|
||||
for row in state.values()
|
||||
if row.task_id == mine and row.settled and row.terminal_state in custody.TERMINAL_STATES
|
||||
), key=lambda row: row["run_id"])
|
||||
|
|
@ -182,7 +187,12 @@ def terminal_custody_notice(result: Mapping[str, Any]) -> str:
|
|||
return ""
|
||||
lines = []
|
||||
if non_success:
|
||||
shown = "; ".join(f"{row.get('run_id')}: {row.get('state')}" for row in non_success[:10])
|
||||
# The leaf's model when the replayed row carried one, never a live join
|
||||
# and never a guess: an older row without it simply says less.
|
||||
shown = "; ".join(
|
||||
f"{row.get('run_id')}: {row.get('state')}"
|
||||
+ (f" on {row.get('model')}" if row.get("model") else "")
|
||||
for row in non_success[:10])
|
||||
omitted = len(non_success) - 10
|
||||
lines.append("Confirmed delegated terminal receipts: " + shown
|
||||
+ (f" (+{omitted} more in task details)" if omitted > 0 else "") + ".")
|
||||
|
|
|
|||
|
|
@ -527,6 +527,10 @@ def model_execution_projection(usage: Dict[str, Any]) -> Dict[str, Any] | None:
|
|||
"used_local": call.get("use_local"),
|
||||
"provider": call.get("provider"),
|
||||
"llm_call_id": call.get("llm_call_id"),
|
||||
# The host's OWN last typed failure, beside the model it was running.
|
||||
# A nanny that died on its own lane used to be reported by the reviewer
|
||||
# role it played, so its death read as the delegated leaf's fault (I9).
|
||||
"last_llm_error_kind": usage.get("_last_llm_error_kind") or None,
|
||||
"source": "usable_solve_response" if call else "not_observed",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,8 @@ def test_boot_backfill_fixes_row_settled_in_a_previous_generation(tmp_path):
|
|||
assert custody == {
|
||||
"unreconciled": [], "trigger": "boot_backfill", "audit_status": "ok",
|
||||
"open_run_ids": [], "pending_invocation_ids": [], "undisposed_patch_run_ids": [],
|
||||
"terminal_runs": [{"run_id": "run-1", "state": "succeeded"}],
|
||||
"terminal_runs": [{"run_id": "run-1", "state": "succeeded", "model": "",
|
||||
"profile_id": "", "selected_subagent_id": ""}],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -40,17 +40,27 @@ def test_real_dispatch_preserves_last_solve_through_empty_and_forced_calls(tmp_p
|
|||
"used_model": "fallback", "reported_model": "Provider display alias",
|
||||
"used_local": False, "provider": "openrouter",
|
||||
"llm_call_id": ctx.accumulated_usage["llm_call_refs"][-1]["llm_call_id"],
|
||||
"last_llm_error_kind": None,
|
||||
"source": "usable_solve_response",
|
||||
}
|
||||
ctx.round_idx += 1
|
||||
failed = dispatch(ctx, "empty", message={"role": "assistant", "content": "", "tool_calls": []})
|
||||
assert failed[0] is None
|
||||
assert model_execution_projection(ctx.accumulated_usage) == observed
|
||||
after_empty = model_execution_projection(ctx.accumulated_usage)
|
||||
# The solve half is preserved. The host's OWN last typed failure is a
|
||||
# separate fact on the same projection and does move (I9): a nanny that
|
||||
# died on its own lane must be readable without guessing at the leaf.
|
||||
assert {k: v for k, v in after_empty.items() if k != "last_llm_error_kind"} == {
|
||||
k: v for k, v in observed.items() if k != "last_llm_error_kind"}
|
||||
assert after_empty["last_llm_error_kind"] == "provider_incomplete_response"
|
||||
assert observed["last_llm_error_kind"] is None
|
||||
assert not ctx.accumulated_usage["llm_call_refs"][-1].get("usable_solve_response")
|
||||
# Forced/post-task calls use the same call recorder but not ordinary dispatch.
|
||||
call_llm_with_retry(Model({"role": "assistant", "content": "wrap up"}, {}),
|
||||
ctx.messages, "forced", [], "high", 1, ctx.drive_logs,
|
||||
ctx.task_id, 3, None, ctx.accumulated_usage, attempt_cap=1)
|
||||
# ...and a successful send clears the stale typed error, so the projection
|
||||
# returns to the solve half alone.
|
||||
assert model_execution_projection(ctx.accumulated_usage) == observed
|
||||
refs = collect_trace_refs(ctx.accumulated_usage, {})["llm_call_refs"]
|
||||
assert len(refs) == 3
|
||||
|
|
@ -194,3 +204,28 @@ def test_owner_wait_source_retains_initial_route_and_marked_calls(tmp_path, monk
|
|||
resume_native_loop(registry, saved, [], {}, restored, set())
|
||||
assert model_execution_projection(restored) == expected
|
||||
assert ctx.active_model == "fallback" and ctx.owner_wait_resume is None
|
||||
|
||||
|
||||
def test_dead_host_lane_renders_its_own_half_without_inventing_the_leaf():
|
||||
"""I9: the host's typed failure and the leaf's identity are SEPARATE facts.
|
||||
|
||||
A nanny that died on its own Codex lane used to be reported by the reviewer
|
||||
role it played, so the owner asked why the leaf's model was broken while
|
||||
that leaf was alive. The host half now rides model_execution beside the
|
||||
model it was running; when the delegated reconciliation was never persisted
|
||||
(it was null on the live row) the custody notice stays silent rather than
|
||||
guessing at the leaf.
|
||||
"""
|
||||
from ouroboros.task_finalization import terminal_host_notice_text
|
||||
|
||||
usage = {
|
||||
"initial_model_request": {"model": "primary", "use_local": False},
|
||||
"llm_call_refs": [{"model": "host-lane-model", "llm_call_id": "call-1",
|
||||
"provider": "claudexor", "usable_solve_response": True}],
|
||||
"_last_llm_error_kind": "provider_outcome_unknown",
|
||||
}
|
||||
projected = model_execution_projection(usage)
|
||||
assert projected["used_model"] == "host-lane-model"
|
||||
assert projected["provider"] == "claudexor"
|
||||
assert projected["last_llm_error_kind"] == "provider_outcome_unknown"
|
||||
assert terminal_host_notice_text({"model_execution": projected}) == ""
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ def _cancelled_with_patch(tmp_path):
|
|||
continuation_narrative={"text": text})
|
||||
assert custody.emit(tmp_path, custody.STARTED, {
|
||||
"run_id": "run-one", "task_id": "root", "route": "fixture",
|
||||
"model": "fixture-model", "profile_id": "fixture-profile",
|
||||
"selected_subagent_id": "fixture-actor",
|
||||
"snapshot_id": "snapshot-one", "shape": {},
|
||||
})
|
||||
assert custody.emit(tmp_path, custody.SETTLED, {
|
||||
|
|
@ -32,7 +34,10 @@ def test_cleanup_receipt_is_carried_before_final_delivery_without_rewriting_answ
|
|||
audit = stored["delegate_terminal_reconciliation"]
|
||||
assert audit["open_run_ids"] == audit["pending_invocation_ids"] == []
|
||||
assert audit["undisposed_patch_run_ids"] == ["run-one"]
|
||||
assert audit["terminal_runs"] == [{"run_id": "run-one", "state": "cancelled"}]
|
||||
assert audit["terminal_runs"] == [{
|
||||
"run_id": "run-one", "state": "cancelled", "model": "fixture-model",
|
||||
"profile_id": "fixture-profile", "selected_subagent_id": "fixture-actor",
|
||||
}]
|
||||
before = copy.deepcopy(stored)
|
||||
usage = {"terminal_origin": "model_final", "terminal_host_notice": "Budget stop retained."}
|
||||
event = prepare_terminal_send_event(
|
||||
|
|
@ -42,7 +47,9 @@ def test_cleanup_receipt_is_carried_before_final_delivery_without_rewriting_answ
|
|||
)
|
||||
assert event["text"] == text
|
||||
assert event["terminal_host_notice"].startswith("Budget stop retained.")
|
||||
assert "run-one: cancelled" in event["terminal_host_notice"]
|
||||
# The leaf's own model rides the replayed row, so the nanny's terminal is
|
||||
# not read as a verdict about the role the host played (I9).
|
||||
assert "run-one: cancelled on fixture-model" in event["terminal_host_notice"]
|
||||
assert "Pending patch decisions: run-one" in event["terminal_host_notice"]
|
||||
assert load_task_result(tmp_path, "root") == before
|
||||
|
||||
|
|
@ -91,7 +98,8 @@ def test_custody_notice_is_bounded_and_does_not_duplicate_on_public_projection()
|
|||
"delegate_terminal_reconciliation": {
|
||||
"audit_status": "ok", "open_run_ids": [], "pending_invocation_ids": [],
|
||||
"undisposed_patch_run_ids": [f"run-{i}" for i in range(12)],
|
||||
"terminal_runs": [{"run_id": f"run-{i}", "state": "cancelled"} for i in range(12)],
|
||||
"terminal_runs": [{"run_id": f"run-{i}", "state": "cancelled",
|
||||
"model": "leaf-model"} for i in range(12)],
|
||||
}}
|
||||
public = public_task_result(row)
|
||||
assert "+2 more in task details" in public["terminal_host_notice"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue