mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(gateway): rejection envelopes always carry a human-readable message
tracing_gap and incomplete_input rejections set missing and remediate but left message null. The audit log records message (not remediate) and agents keyed on message, so a rejected agent saw a null reason and retried the same verb until it burned out instead of reading remediate and self-correcting. Both builders (and from_decision) now derive a non-null message that folds the missing tokens and the actionable remediate into one line, so the agent and the audit trail always see what was missing and how to fix it.
This commit is contained in:
@@ -67,6 +67,21 @@ class Envelope:
|
|||||||
context_briefing=context_briefing or {},
|
context_briefing=context_briefing or {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _missing_message(label: str, missing: list[str], remediate: str) -> str:
|
||||||
|
"""Human-readable rejection summary so `message` is never null.
|
||||||
|
|
||||||
|
`tracing_gap`/`incomplete_input` historically left `message` unset, so
|
||||||
|
the agent (and the audit log, which records `message`) saw `null` and
|
||||||
|
could not see what to do — the agent then retried the same verb until it
|
||||||
|
burned out. This folds the missing tokens + the actionable `remediate`
|
||||||
|
into one line carried by `message`, so a client reading only `message`
|
||||||
|
still gets the full picture.
|
||||||
|
"""
|
||||||
|
joined = ", ".join(missing) if missing else "(unspecified)"
|
||||||
|
base = f"blocked: {label} missing — {joined}"
|
||||||
|
return f"{base}. {remediate}" if remediate else base
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tracing_gap(
|
def tracing_gap(
|
||||||
cls,
|
cls,
|
||||||
@@ -77,6 +92,7 @@ class Envelope:
|
|||||||
) -> Envelope:
|
) -> Envelope:
|
||||||
return cls(
|
return cls(
|
||||||
error="tracing_gap",
|
error="tracing_gap",
|
||||||
|
message=cls._missing_message("required tracing", missing, remediate),
|
||||||
missing=missing,
|
missing=missing,
|
||||||
remediate=remediate,
|
remediate=remediate,
|
||||||
context_briefing=context_briefing or {},
|
context_briefing=context_briefing or {},
|
||||||
@@ -99,6 +115,7 @@ class Envelope:
|
|||||||
"""
|
"""
|
||||||
return cls(
|
return cls(
|
||||||
error="incomplete_input",
|
error="incomplete_input",
|
||||||
|
message=cls._missing_message("required input fields", missing, remediate),
|
||||||
missing=missing,
|
missing=missing,
|
||||||
field_hints=field_hints,
|
field_hints=field_hints,
|
||||||
remediate=remediate,
|
remediate=remediate,
|
||||||
@@ -181,10 +198,13 @@ class Envelope:
|
|||||||
ctx = briefing or {}
|
ctx = briefing or {}
|
||||||
kind = decision.rejection_kind
|
kind = decision.rejection_kind
|
||||||
if kind == "tracing_gap":
|
if kind == "tracing_gap":
|
||||||
|
missing = list(decision.missing)
|
||||||
|
remediate = decision.remediate or ""
|
||||||
return cls(
|
return cls(
|
||||||
error="tracing_gap",
|
error="tracing_gap",
|
||||||
missing=list(decision.missing),
|
message=cls._missing_message("required tracing", missing, remediate),
|
||||||
remediate=decision.remediate or "",
|
missing=missing,
|
||||||
|
remediate=remediate,
|
||||||
context_briefing=ctx,
|
context_briefing=ctx,
|
||||||
)
|
)
|
||||||
if kind == "self_review":
|
if kind == "self_review":
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ class TestEnvelopeError:
|
|||||||
assert body["error"] == "tracing_gap"
|
assert body["error"] == "tracing_gap"
|
||||||
assert body["missing"] == ["progress>=1", "journal:reflect"]
|
assert body["missing"] == ["progress>=1", "journal:reflect"]
|
||||||
assert "note(scope='reflect'" in body["remediate"]
|
assert "note(scope='reflect'" in body["remediate"]
|
||||||
|
# message must NOT be null — it carries the missing tokens + remediate so
|
||||||
|
# the agent and the audit log can see what to do (no more silent flailing).
|
||||||
|
assert body["message"] is not None
|
||||||
|
assert "progress>=1" in body["message"]
|
||||||
|
assert "journal:reflect" in body["message"]
|
||||||
|
assert "note(scope='reflect'" in body["message"]
|
||||||
|
|
||||||
def test_invalid_state(self) -> None:
|
def test_invalid_state(self) -> None:
|
||||||
env = Envelope.invalid_state(
|
env = Envelope.invalid_state(
|
||||||
|
|||||||
Reference in New Issue
Block a user