nisse-review: a review refused for an open point posts once the point is answered
On #166 the reviewer called post_review with two points unanswered, was refused, answered them, then only skipped already-answered points until the 40-turn cap: the review went out as partial, its summary lost. A refused ending call is now held, and the driver ends the run with it as soon as the last point is settled (a new due hook in nisse-core's driver; nisse-dev doesn't use it). A held call that is wrong for another reason by then (sign_off over drafted comments) is left to the model. With nothing open, reply and skip say which tool ends the run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
1 parent
be81adc185
commit
26ef125d1e
5 files changed
+221
-22
No files matched your search
@@ -18,7 +18,7 @@ from nisse_core.knowledge.handlers import Knowledge
|
||||
from nisse_core.log.canonical import Fact
|
||||
from nisse_core.loop.driver import Driver, RunOutcome
|
||||
from nisse_core.loop.expert import BudgetExhausted, ExpertDesk
|
||||
from nisse_core.loop.tools import ASK_EXPERT, tool
|
||||
from nisse_core.loop.tools import ASK_EXPERT, names, tool
|
||||
from nisse_core.models.llm import LLMError
|
||||
from nisse_core.oplog import OpLog
|
||||
from nisse_core.state.store import Transcript
|
||||
@@ -168,6 +168,10 @@ class Draft:
|
||||
points: dict[str, Said] = field(default_factory=dict) # P1 -> the point
|
||||
replied: list[str] = field(default_factory=list) # keys answered
|
||||
skipped: list[str] = field(default_factory=list) # keys left, with reason
|
||||
# An ending call refused only because points were open: it goes ahead
|
||||
# once the last is answered or skipped, not left to the model to repeat.
|
||||
held: tuple[str, dict[str, Any]] | None = None
|
||||
ends: tuple[str, ...] = () # the run's ending tools, for the messages
|
||||
|
||||
def open_points(self) -> list[str]:
|
||||
settled = {*self.replied, *self.skipped}
|
||||
@@ -283,19 +287,53 @@ class Reviewer:
|
||||
elif kind == LATER:
|
||||
tools.append(STILL_HAPPY)
|
||||
|
||||
def answered_first(args: dict[str, Any]) -> str | None:
|
||||
left = draft.open_points()
|
||||
if left:
|
||||
return "first answer or skip each point said to you: " + ", ".join(left)
|
||||
return None
|
||||
draft.ends = tuple(t for t in names(tools) if t in WAITING)
|
||||
|
||||
def no_comments(args: dict[str, Any]) -> str | None:
|
||||
if draft.comments:
|
||||
def answered_first(tool: str) -> Callable[[dict[str, Any]], str | None]:
|
||||
def check(args: dict[str, Any]) -> str | None:
|
||||
left = draft.open_points()
|
||||
if not left:
|
||||
return None
|
||||
draft.held = (tool, args)
|
||||
return (
|
||||
f"you drafted {len(draft.comments)} comment(s): post them "
|
||||
"with post_review"
|
||||
"first answer or skip each point said to you: "
|
||||
+ ", ".join(left)
|
||||
+ f". Your {tool} is held: it goes ahead once the last is "
|
||||
"answered or skipped"
|
||||
)
|
||||
return answered_first(args)
|
||||
|
||||
return check
|
||||
|
||||
def no_comments(tool: str) -> Callable[[dict[str, Any]], str | None]:
|
||||
def check(args: dict[str, Any]) -> str | None:
|
||||
if draft.comments:
|
||||
return (
|
||||
f"you drafted {len(draft.comments)} comment(s): post them "
|
||||
"with post_review"
|
||||
)
|
||||
return answered_first(tool)(args)
|
||||
|
||||
return check
|
||||
|
||||
checks = {
|
||||
"post_review": answered_first("post_review"),
|
||||
"sign_off": no_comments("sign_off"),
|
||||
"still_happy": no_comments("still_happy"),
|
||||
"done": answered_first("done"),
|
||||
}
|
||||
|
||||
def due() -> tuple[str, dict[str, Any], str] | None:
|
||||
if draft.held is None or draft.open_points():
|
||||
return None
|
||||
tool, args = draft.held
|
||||
draft.held = None
|
||||
if checks[tool](args) is not None:
|
||||
return None # wrong for another reason now: the model's to fix
|
||||
return (
|
||||
tool,
|
||||
args,
|
||||
f"that was the last open point, so your held {tool} went ahead",
|
||||
)
|
||||
|
||||
outcome = self._driver.run(
|
||||
transcript,
|
||||
@@ -304,13 +342,9 @@ class Reviewer:
|
||||
waiting=WAITING,
|
||||
workspace=repo,
|
||||
handlers=handlers,
|
||||
checks={
|
||||
"post_review": answered_first,
|
||||
"sign_off": no_comments,
|
||||
"still_happy": no_comments,
|
||||
"done": answered_first,
|
||||
},
|
||||
checks=checks,
|
||||
max_turns=self._max_turns,
|
||||
due=due,
|
||||
)
|
||||
return outcome, draft
|
||||
|
||||
@@ -535,8 +569,14 @@ class Reviewer:
|
||||
wanted = f"P{wanted}"
|
||||
if wanted in draft.open_points():
|
||||
return wanted, draft.points[wanted]
|
||||
left = ", ".join(draft.open_points()) or "none"
|
||||
return f"no open point {wanted or '(none given)'}; open: {left}"
|
||||
left = draft.open_points()
|
||||
if not left:
|
||||
return (
|
||||
f"no open point {wanted or '(none given)'}: every point said to "
|
||||
"you is answered or skipped. End your run with "
|
||||
+ " or ".join(draft.ends or ("your ending tool",))
|
||||
)
|
||||
return f"no open point {wanted or '(none given)'}; open: {', '.join(left)}"
|
||||
|
||||
def _comment(
|
||||
self, repo: Repository, draft: Draft, args: dict[str, Any]
|
||||
|
||||
Reference in new issue
Block a user