fix(poker): guarantee hand logging — history was conditioning it away

Logging a stated hand was unreliable and got worse mid-session: same model, same
hand, clean history logged 4/4 but the real session's history logged 0/4. Root
cause: memory.recent() rebuilt past turns as "hand -> narration" with the tool
calls stripped (they live in tool_events), so the model's own context became
few-shot examples training it, mid-conversation, to STOP calling tools. Even a
maximal "LOG FIRST, no exceptions" prompt scored 0/5 — it's structural, not wording.

Two-part fix (both, per the system-of-record frame):
- A (guarantee): chat._ensure_hand_logged — on a HAND turn that's Brian's OWN hand,
  if the model didn't log it, force record_hand (tool_choice). Guarded to hero hands
  (looks_like_hero_hand) so an observed hand is never force-logged as his. Adds
  tool_choice passthrough to llm.chat_call; surfaces msg_type on TurnContext.
- B (heal forward): mind._history_with_tools makes each assistant turn's tool calls
  visible in reconstructed history ("record_hand -> Hand #62"), so the demonstrated
  pattern stops being "hand -> narrate". Recovers natural logging as logs accumulate.

Verified: force guard returns record_hand on the polluted context; full respond_stream
logs Hand #63 end-to-end on a clean session. 7 guard tests; suite 219 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 20:48:04 +00:00
parent 366e71a384
commit 96a44365d9
5 changed files with 197 additions and 6 deletions
+3 -1
View File
@@ -114,7 +114,7 @@ def complete_with_fallback(messages: list[Message], backend: Backend, model: str
def chat_call(
messages: list, backend: Backend = "cloud", model: str | None = None,
tools: list | None = None,
tools: list | None = None, tool_choice: str | dict | None = None,
) -> tuple[dict, list | None]:
"""One chat turn that may request tool calls (OpenAI-style backends only).
@@ -136,6 +136,8 @@ def chat_call(
kwargs: dict = {"model": mdl, "messages": messages}
if tools:
kwargs["tools"] = tools
if tool_choice: # e.g. force a specific tool: {"type":"function","function":{"name":...}}
kwargs["tool_choice"] = tool_choice
logbus.log("info", "llm call", kind="chat", backend=backend, model=mdl, tok=_approx_tok(messages))
t0 = time.monotonic()
msg = client.chat.completions.create(**kwargs).choices[0].message