maybeFailureNudge appends one system-role note once the same target has failed maxToolFailStreak times running, then resets the streak so it fires at most once per run of failures. A nudge, not a yield: the model stays in control and decides whether to pivot or stop and tell the user.
()
| 1165 | msg, ok := newestAssistant(history) |
| 1166 | return ok && strings.Contains(strings.ToLower(msg.Content), "unverified") |
| 1167 | } |
| 1168 | |
| 1169 | // toolCallLeakWarning returns a user-facing diagnostic when the newest assistant |
| 1170 | // message carries a tool-call opener (`<tool_call>`) in its text instead of |
| 1171 | // structured tool_calls, the dominant local-hosting failure: a |
| 1172 | // misconfigured/missing server parser leaks the call as content with |
| 1173 | // finish_reason "stop", so the turn ends silently with the tool intent stranded. |
| 1174 | // The bare `<tool_call>` opener covers both shapes the target servers emit: the |
| 1175 | // XML body (`<function=…`) and the general JSON body |
| 1176 | // (`{"name":…`): gating on the literal tag alone catches both while staying |
| 1177 | // specific enough that ordinary prose can't trip it. A message that carried a |
| 1178 | // real structured call never leaked, even if its prose quotes the tag, so a |
| 1179 | // non-empty ToolCalls short-circuits to clean. codehamr stays wire-only (it does |
| 1180 | // not parse or run the leaked call); it points the user at the server-side fix. |
| 1181 | // Empty string when there is nothing to warn. |
| 1182 | func toolCallLeakWarning(history []chmctx.Message) string { |