Address #132 review: one verb list, self-reporting tools, rune-safe log
Build image / build-and-push (push) Successful in 8s
Build image / build-and-push (push) Successful in 8s
- changeClaim is built from one changeVerbs list; the opener is just done/fixed/undone so an informational "Updated totals:" can't trip it. - public_link (get reads) and undo_change (nothing left to revert) are self-reporting: their success no longer counts as a change by name; the adapter says whether they changed something (noteChange / didChange). - whenMissing covers the object and plant tools too (move/update/delete object, clear/remove plantings by object, update/delete plant). - The step summary cuts on a rune boundary. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
+30
-16
@@ -169,7 +169,7 @@ func (r *Runner) Run(ctx context.Context, actorID, gardenID int64, message, toda
|
||||
if result != nil {
|
||||
turn.Reply = result.Output
|
||||
turn.Steps = len(result.Steps)
|
||||
if corrected := honestReply(turn.Reply, result); corrected != turn.Reply {
|
||||
if corrected := honestReply(turn.Reply, result, tools); corrected != turn.Reply {
|
||||
// The steps are logged so the mechanism can be read off the log
|
||||
// next time — which tool it tried, what came back, what it said.
|
||||
slog.Warn("agent: reply claimed a change no tool made", "run", runID, "garden", gardenID, "steps", describeSteps(result))
|
||||
@@ -190,12 +190,23 @@ var readOnlyTools = map[string]bool{
|
||||
"list_shares": true,
|
||||
}
|
||||
|
||||
// changeClaim matches a reply that reports a change as made: a "Done"/"Fixed"
|
||||
// opener, or a first-person past-tense claim ("I've deleted", "I moved"). A
|
||||
// question or an offer ("want me to delete it?", "I'll remove it") does not
|
||||
// match — only a claim of something already done.
|
||||
var changeClaim = regexp.MustCompile(`(?i)(?:^\s*(?:done|fixed|undone|deleted|removed|updated|changed|added|saved|moved|planted|filled|cleared|corrected|recorded|marked|shared|renamed|reverted)\b` +
|
||||
`|\bI(?:'ve| have)? (?:just |now |also |already )?(?:deleted|removed|updated|changed|added|saved|moved|planted|filled|cleared|corrected|recorded|marked|shared|renamed|reverted|undone|set|put|pulled|replaced|swapped|rotated|rewrote|rewritten|edited|created|started|attached|restored|made)\b)`)
|
||||
// selfReportingTools succeed without necessarily changing anything —
|
||||
// public_link with action=get reads, and undo_change with nothing left to
|
||||
// revert reverts nothing — so their results don't count; the adapter says
|
||||
// whether they changed something (adapter.changed).
|
||||
var selfReportingTools = map[string]bool{"public_link": true, "undo_change": true}
|
||||
|
||||
// changeVerbs are the past participles a claim of change is made of. One
|
||||
// list, used by both shapes the claim takes.
|
||||
const changeVerbs = `deleted|removed|updated|changed|added|saved|moved|planted|filled|cleared|corrected|recorded|marked|shared|renamed|reverted|undone|set|put|pulled|replaced|swapped|rotated|rewrote|rewritten|edited|created|started|attached|restored|made`
|
||||
|
||||
// changeClaim matches a reply that reports a change as made: a "Done"/"Fixed"/
|
||||
// "Undone" opener, or a first-person past-tense claim ("I've deleted", "I
|
||||
// moved"). A question or an offer ("want me to delete it?", "I'll remove it")
|
||||
// does not match — only a claim of something already done. The opener list is
|
||||
// short on purpose: "Updated totals:" opening a read-only answer must not
|
||||
// trip it, and those replies say "I've …" when they mean a change.
|
||||
var changeClaim = regexp.MustCompile(`(?i)(?:^\s*(?:done|fixed|undone)\b|\bI(?:'ve| have)? (?:just |now |also |already )?(?:` + changeVerbs + `)\b)`)
|
||||
|
||||
// unbackedClaim is what the person reads under a claim no tool backs up.
|
||||
const unbackedClaim = "\n\n_Correction: nothing actually changed in this turn — no tool call that changes anything succeeded. Ask again and I'll do it properly._"
|
||||
@@ -215,8 +226,10 @@ func describeSteps(r *agent.Result) string {
|
||||
}
|
||||
if st.Response != nil {
|
||||
if text := strings.Join(strings.Fields(st.Response.Text()), " "); text != "" {
|
||||
if len(text) > 80 {
|
||||
text = text[:80] + "…"
|
||||
// Cut on a rune boundary, like turnSummary: a byte slice can
|
||||
// split a multibyte character and log invalid UTF-8.
|
||||
if runes := []rune(text); len(runes) > 80 {
|
||||
text = string(runes[:80]) + "…"
|
||||
}
|
||||
fmt.Fprintf(&b, " %q", text)
|
||||
}
|
||||
@@ -226,17 +239,18 @@ func describeSteps(r *agent.Result) string {
|
||||
return strings.Join(parts, " | ")
|
||||
}
|
||||
|
||||
// acted reports whether any tool call in the run succeeded at something that
|
||||
// is not read-only.
|
||||
func acted(r *agent.Result) bool {
|
||||
// acted reports whether the run changed anything: a successful call to a tool
|
||||
// that is neither read-only nor self-reporting, or a self-reporting tool that
|
||||
// told the adapter it changed something.
|
||||
func acted(r *agent.Result, tools *adapter) bool {
|
||||
for _, st := range r.Steps {
|
||||
for _, res := range st.Results {
|
||||
if !res.IsError && !readOnlyTools[res.Name] {
|
||||
if !res.IsError && !readOnlyTools[res.Name] && !selfReportingTools[res.Name] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return tools != nil && tools.didChange()
|
||||
}
|
||||
|
||||
// honestReply appends a correction to a reply that claims a change when no
|
||||
@@ -247,8 +261,8 @@ func acted(r *agent.Result) bool {
|
||||
// discover it. A reply that claims nothing, or a run in which some change
|
||||
// succeeded, passes through unchanged — this cannot tell a true claim from a
|
||||
// false one once anything at all was done, so it only speaks when nothing was.
|
||||
func honestReply(reply string, r *agent.Result) string {
|
||||
if r == nil || acted(r) || !changeClaim.MatchString(reply) {
|
||||
func honestReply(reply string, r *agent.Result, tools *adapter) string {
|
||||
if r == nil || acted(r, tools) || !changeClaim.MatchString(reply) {
|
||||
return reply
|
||||
}
|
||||
return reply + unbackedClaim
|
||||
|
||||
Reference in New Issue
Block a user