Threads, commands and labels on the new loop (M2.3)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 3m45s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 3m45s
- Commands that name their item ("go #14", "abandon #14", "stop #14")
apply to it wherever they're said; go, approve and abandon alone at top
level are asked about (L5). stop is never taken as an answer.
- A plain-language reply to a restatement is read by GoAheadReader, one
model call that agrees only on a clear yes; questions never reach it. The
agent says "taking that as a go-ahead on rev-N" first, bound to the
revision on screen at arrival (L3, L5, D1).
- Labels steer (D1): `bug` goes ahead after tuning.loop.bug_grace_s
(default 600 s) unless the operator replies; `discuss` isn't offered
restate until the operator has spoken; `bug`, `discuss` and `design` add
to the driver's instructions. A restatement asks no questions.
Tests: label and command unit tests, the property walk with labelled
issues, plain yeses, named gos and time passing (grace_only_unanswered,
discuss_talks_first), and e2e tests for a bug proceeding after its grace
period and a plain-language go-ahead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
1 parent
a504526da2
commit
4d9c1d7529
13 files changed
+793
-35
No files matched your search
@@ -0,0 +1,81 @@
|
||||
"""Reading a plain-language reply as a go-ahead (L5, D1).
|
||||
|
||||
"Yep, that's it" in reply to a restatement may be taken as `go`, because a
|
||||
wrong reading costs only time. One short model call decides, and it decides
|
||||
conservatively: only a clear yes counts. A question is never a go-ahead and
|
||||
never reaches the model (L5: questions never change state).
|
||||
|
||||
The driver's model reads for now; the classifier role takes over in M2.6.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from nisse_dev.canonical import StoredEvent, ToolCall, UserMessage
|
||||
from nisse_dev.llm import LLMClient, LLMError
|
||||
|
||||
VERDICT = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "verdict",
|
||||
"description": "Your reading of the operator's reply.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answer": {
|
||||
"type": "string",
|
||||
"enum": ["yes", "no", "unclear"],
|
||||
"description": "yes: a clear go-ahead on the proposal as it "
|
||||
"stands. no: it asks for changes, disagrees or says to wait. "
|
||||
"unclear: anything else.",
|
||||
}
|
||||
},
|
||||
"required": ["answer"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SYSTEM = """You read one reply from an operator to a software agent. The agent
|
||||
put a proposal up for agreement; decide whether the reply clearly agrees to the
|
||||
proposal as it stands, so the agent may start. A reply that asks for any
|
||||
change, asks a question, adds a condition, or is about something else is not a
|
||||
go-ahead. When in doubt, it is not. Answer with the verdict tool."""
|
||||
|
||||
|
||||
def is_question(text: str) -> bool:
|
||||
return text.rstrip().endswith("?")
|
||||
|
||||
|
||||
class GoAheadReader:
|
||||
def __init__(self, client: LLMClient, model: str) -> None:
|
||||
self._client = client
|
||||
self._model = model
|
||||
|
||||
def agrees(self, proposal: str, reply: str) -> bool:
|
||||
if is_question(reply):
|
||||
return False
|
||||
prompt = f"The proposal:\n\n{proposal}\n\nThe operator's reply:\n\n{reply}"
|
||||
try:
|
||||
produced = self._client.complete(
|
||||
[
|
||||
StoredEvent(
|
||||
id=0,
|
||||
ts="",
|
||||
event=UserMessage(text=prompt, source="go-ahead reader"),
|
||||
actor="orchestrator",
|
||||
)
|
||||
],
|
||||
tools=[VERDICT],
|
||||
model=self._model,
|
||||
system=SYSTEM,
|
||||
)
|
||||
except LLMError:
|
||||
return False # unread: the driver reads it as an answer instead
|
||||
return any(_yes(event) for event in produced)
|
||||
|
||||
|
||||
def _yes(event: Any) -> bool:
|
||||
return (
|
||||
isinstance(event, ToolCall)
|
||||
and event.tool == "verdict"
|
||||
and event.arguments.get("answer") == "yes"
|
||||
)
|
||||
Reference in new issue
Block a user