mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
Read a default-filled author disposition as no disposition
A model that fills every optional schema key sent
{"author_disposition": {"disposition": "accepted", "rationale": ""}, "items": [],
"review_fingerprint": ""} beside its FIRST plan_task envelope and looped on
PLAN_REVIEW_DISPOSITION_MIXED_ENVELOPE (seen live on the E2E stand, 17 calls).
An empty fingerprint names no wave and an empty rationale states nothing, so the
value carries no disposition: it is ignored beside a plan exactly like the
already-vacuous {fingerprint: "", items: []}. A rationale, an item or a
fingerprint is meaning and keeps the existing refusal.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
parent
2cf5b5108e
commit
1eca97ddaf
2 changed files with 36 additions and 3 deletions
|
|
@ -300,10 +300,20 @@ def _vacuous(name: str, value: object) -> bool:
|
|||
|
||||
def _vacuous_disposition(value: object) -> bool:
|
||||
"""A schema-shaped but empty disposition (models fill optional objects with defaults).
|
||||
An UNKNOWN key or a non-empty items list is never vacuous: refused, not ignored."""
|
||||
if not isinstance(value, dict) or set(value) - {"review_fingerprint", "items"}:
|
||||
An UNKNOWN key or a non-empty items list is never vacuous: refused, not ignored.
|
||||
A default-filled ``author_disposition`` ({"disposition": "accepted", "rationale": ""})
|
||||
beside an EMPTY fingerprint names no wave and answers no finding, so it carries
|
||||
nothing either: without this a model that fills every schema key sent it with
|
||||
its first plan and looped on PLAN_REVIEW_DISPOSITION_MIXED_ENVELOPE (seen live)."""
|
||||
if not isinstance(value, dict) or set(value) - {"review_fingerprint", "items", "author_disposition"}:
|
||||
return False
|
||||
return not str(value.get("review_fingerprint") or "").strip() and not value.get("items")
|
||||
author = value.get("author_disposition")
|
||||
author_vacuous = author is None or (
|
||||
isinstance(author, dict) and set(author) <= {"disposition", "rationale"}
|
||||
and not str(author.get("rationale") or "").strip()
|
||||
)
|
||||
return (author_vacuous and not str(value.get("review_fingerprint") or "").strip()
|
||||
and not value.get("items"))
|
||||
|
||||
def _typed_refusal(ctx: ToolContext, code: str, text: str) -> str:
|
||||
"""Publish a refusal the producer ALREADY knows about (D02). The text ABI is
|
||||
|
|
|
|||
|
|
@ -752,6 +752,29 @@ class TestPlanReviewDispositionEnvelope(unittest.TestCase):
|
|||
self.assertEqual(out, "reviewed")
|
||||
run.assert_called_once()
|
||||
|
||||
def test_default_filled_author_disposition_beside_a_plan_is_ignored(self):
|
||||
"""Seen live: a model that fills every schema key sent
|
||||
{"author_disposition": {"disposition": "accepted", "rationale": ""},
|
||||
"items": [], "review_fingerprint": ""} with its FIRST plan and looped on
|
||||
MIXED_ENVELOPE. No fingerprint names no wave, so it carries nothing."""
|
||||
import ouroboros.tools.plan_review as pr
|
||||
from ouroboros.tools.registry import ToolContext
|
||||
|
||||
ctx = ToolContext(repo_dir=pathlib.Path("."), drive_root=pathlib.Path("."))
|
||||
ctx.task_id = "parent"
|
||||
filler = {"author_disposition": {"disposition": "accepted", "rationale": ""},
|
||||
"items": [], "review_fingerprint": ""}
|
||||
with patch.object(pr, "_run_plan_review_async", return_value="reviewed") as run:
|
||||
out = pr._handle_plan_task(ctx, plan="P", goal="G", spec={}, review_disposition=filler)
|
||||
self.assertEqual(out, "reviewed")
|
||||
run.assert_called_once()
|
||||
# A rationale is a statement; with it the disposition is real and still refused beside a plan.
|
||||
spoken = {**filler, "author_disposition": {"disposition": "rejected", "rationale": "no"}}
|
||||
with patch.object(pr, "_run_plan_review_async") as run:
|
||||
out = pr._handle_plan_task(ctx, plan="P", goal="G", spec={}, review_disposition=spoken)
|
||||
self.assertIn("PLAN_REVIEW_DISPOSITION_MIXED_ENVELOPE", out)
|
||||
run.assert_not_called()
|
||||
|
||||
def test_duplicate_plan_calls_use_existing_sequential_tool_lane(self):
|
||||
from ouroboros.loop_tool_execution import tool_calls_can_run_parallel
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue