Skip to content

Keep a conversation alive when nobody takes the wheel - #147

Merged
davidmckayv merged 2 commits into
mainfrom
fix/a-declined-handover-does-not-kill-the-thread
Aug 22, 2026
Merged

Keep a conversation alive when nobody takes the wheel#147
davidmckayv merged 2 commits into
mainfrom
fix/a-declined-handover-does-not-kill-the-thread

Conversation

@davidmckayv

Copy link
Copy Markdown
Contributor

Closes #140.

What was broken

Ask a Bot to open a page that wants a sign-in and it calls computer_request_help, which is the right thing to do. If nobody takes the wheel, that tool call is never answered. The assistant message holding it stays in the thread forever with no matching tool message.

Every later turn then fails at the provider:

400 An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'

So declining a handover once does not just abandon that one task, it destroys the conversation. Nothing you type afterwards gets an answer, and there is no way back other than starting a new chat.

The fix

When the history is rebuilt for a run, any tool call with no answer gets one: a synthetic tool message placed immediately after the assistant message that made the call.

It says the truth rather than a fake success:

No result. The person did not answer this, and the run it belonged to has ended. Do not wait for it and do not assume it succeeded. Carry on without it, and say plainly what you could not do if it mattered.

A fake success would have the Bot claim it read a page it never reached. This has it carry on and be honest about the gap.

Why the file moved

toLangChainMessages now lives in agent-langgraph/src/history.ts. It was in index.ts, which calls serve() at module scope, so importing it from a test binds port 3001 and the test cannot run at all. agent-computer already makes the same split for control.ts.

Driven in Chrome

Against a running deployment, in the Browser Bot:

  1. "Open https://github.com/settings/profile and tell me what my display name is set to." Bot navigates, meets the GitHub sign-in wall, calls computer_request_help, the take-control prompt appears.
  2. Decline it. Send "Never mind, don't bother with that. What is 17 times 3?" Before this change: 400. After: 17 × 3 = 51.
  3. "Did you ever manage to read my GitHub display name?" Answer: "No. GitHub required sign-in, and the sign-in help request wasn't completed, so I never reached the profile settings page or read your display name." It did not invent a result.
  4. Re-verified after the module split, in the same thread that still carries the unanswered call: "What is the capital of Portugal?" → "The capital of Portugal is Lisbon."

Tests

agent-langgraph/tests/history.test.ts, 4 tests, each proven to fail without the change:

  • an unanswered call gets a synthetic answer
  • an answered call is left alone
  • the synthetic answer lands directly after the assistant message that made the call
  • several unanswered calls in one assistant message each get their own answer

A Bot that asks for help with a sign-in and never gets it leaves an
assistant message holding a tool call that no tool message ever answers.
Every later turn in that thread then dies at the provider with "an
assistant message with 'tool_calls' must be followed by tool messages
responding to each 'tool_call_id'", so declining to take the wheel once
destroys the conversation for good.

Answer the unanswered calls when the history is rebuilt. Each one gets a
synthetic tool message saying plainly that no result came, that the run
it belonged to has ended, and that the Bot should carry on without it and
say what it could not do. The Bot is told the truth rather than a fake
success, so it does not report work it never did.

The history builder moves to its own module. Importing it from index.ts
would bind port 3001, because that file calls serve() at module scope,
so a test could not reach it where it was. This mirrors the split
agent-computer already makes for control.ts.
Its history tests import @langchain/core, which lives in that Bot's own
tree. The Bot is not a root workspace and keeps its own lockfile, so a
root install leaves it empty and the test file throws on import.

agent-bot already has this step for the same reason.
@davidmckayv
davidmckayv merged commit a358fec into main Aug 22, 2026
9 checks passed
@davidmckayv
davidmckayv deleted the fix/a-declined-handover-does-not-kill-the-thread branch August 22, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An unresolved take-the-wheel leaves the conversation permanently broken

1 participant