Description
ToolMessage.error is a first-class field of the AG-UI message schema, and the spec is explicit about its purpose — the messages concept page says verbatim error?: string // Optional error message if the tool execution failed and lists "Use error to indicate tool execution failures" in its key points.
Both first-party LangGraph adapters discard it when converting an incoming AG-UI tool message to a LangChain ToolMessage. LangChain has a field built for exactly this (status: Literal["success", "error"], defaulting to "success"), so the information has a destination — it is simply never carried across.
The result: a frontend tool that reports failure is delivered to the model as a successful tool result. The only thing distinguishing failure from success is whatever the client happened to put in content.
This is the one direction where the protocol can express failure. ToolCallResultEvent has no equivalent field (its documented properties are messageId, toolCallId, content, role), so agent→client failures are already untyped by design. Client→agent is the direction that has the field — and it is being dropped.
Where
Python — integrations/langgraph/python/ag_ui_langgraph/utils.py (agui_messages_to_langchain), as of ag-ui-langgraph 0.0.41 and unchanged in 0.0.42:
elif role == "tool":
langchain_messages.append(ToolMessage(
id=message.id,
content=message.content,
tool_call_id=message.tool_call_id,
))
message.error is never read. ag_ui/core/types.py defines it:
class ToolMessage(ConfiguredBaseModel):
id: str
role: Literal["tool"] = "tool"
content: str
tool_call_id: str
error: Optional[str] = None # <-- dropped
encrypted_value: Optional[str] = None
TypeScript — integrations/langgraph/typescript/src/agent.ts does not even model the field:
type ToolMessageFieldsWithToolCallId = {
type?: string;
tool_call_id: string;
name?: string;
content: unknown;
id?: string;
};
There is no read of .error on an incoming tool message anywhere in that file, so the loss is not Python-specific.
Why it matters
LangChain providers already consume status. langchain_anthropic maps it onto the Anthropic tool-result flag:
"is_error": curr.status == "error", # langchain_anthropic/chat_models.py
So the plumbing is complete end-to-end and broken by one omitted field in the adapter. On Anthropic, a client that correctly reports a failed frontend tool call still has that failure presented to the model as a success.
The impact is provider-dependent, which is probably why this has gone unnoticed: langchain_google_genai never reads status at all — a tool message becomes a function_response carrying content and name — so on Gemini the bug is invisible. It surfaces on providers that honor the flag.
Steps to reproduce
The converter can be exercised directly — no graph or model needed:
from ag_ui.core import ToolMessage as AguiToolMessage
from ag_ui_langgraph.utils import agui_messages_to_langchain
incoming = AguiToolMessage(
id="t-1",
role="tool",
content="Tool failed: invalid id",
tool_call_id="tc-1",
error="invalid id",
)
out = agui_messages_to_langchain([incoming])[0]
print(repr(out.status), out.additional_kwargs)
Output on ag-ui-langgraph 0.0.41:
AG-UI in : {'id': 't-1', 'role': 'tool', 'content': 'Tool failed: invalid id',
'tool_call_id': 'tc-1', 'error': 'invalid id', 'encrypted_value': None}
LangChain out: ToolMessage | status = 'success' | additional_kwargs = {}
Expected: status == "error".
Actual: status == "success" (the default), and error's text is not preserved
anywhere either — additional_kwargs is empty, so the failure is unrecoverable
downstream rather than merely untyped.
In a real run this is reached whenever a client answers a yielded frontend tool call with a
failure — the useFrontendTool / human-in-the-loop path — but the loss is entirely in the
converter, so the snippet above is sufficient.
Suggested fix
Map the presence of error onto LangChain's status in both adapters, e.g. in the Python converter:
elif role == "tool":
langchain_messages.append(ToolMessage(
id=message.id,
content=message.content,
tool_call_id=message.tool_call_id,
status="error" if message.error else "success",
))
Two open questions for maintainers, since they affect the shape of the fix:
- Should
error's text be preserved as well as the flag? LangChain has no dedicated slot for it, so the options are to fold it into content (changes what the model reads) or to keep it in additional_kwargs (preserves round-tripping without altering the prompt). The flag alone is the minimum useful fix.
- Should the reverse direction round-trip it? Since
ToolCallResultEvent cannot express failure, a ToolMessage with status == "error" emitted back to the client currently loses the distinction, so error is not durable across a MESSAGES_SNAPSHOT. That may warrant a separate protocol discussion rather than being folded in here.
Documentation note
The spec is internally inconsistent about this field, which likely contributes to it being under-implemented: the messages concept page documents error and calls it out in its key points, while the tools concept page's "Tool Results" example omits it entirely. Aligning those would make the field harder to miss for other integrations.
Environment
ag-ui-protocol 0.1.19
ag-ui-langgraph 0.0.41 (verified unchanged in 0.0.42)
langchain-core 1.2.24
Description
ToolMessage.erroris a first-class field of the AG-UI message schema, and the spec is explicit about its purpose — the messages concept page says verbatimerror?: string // Optional error message if the tool execution failedand lists "Useerrorto indicate tool execution failures" in its key points.Both first-party LangGraph adapters discard it when converting an incoming AG-UI tool message to a LangChain
ToolMessage. LangChain has a field built for exactly this (status: Literal["success", "error"], defaulting to"success"), so the information has a destination — it is simply never carried across.The result: a frontend tool that reports failure is delivered to the model as a successful tool result. The only thing distinguishing failure from success is whatever the client happened to put in
content.This is the one direction where the protocol can express failure.
ToolCallResultEventhas no equivalent field (its documented properties aremessageId,toolCallId,content,role), so agent→client failures are already untyped by design. Client→agent is the direction that has the field — and it is being dropped.Where
Python —
integrations/langgraph/python/ag_ui_langgraph/utils.py(agui_messages_to_langchain), as ofag-ui-langgraph0.0.41 and unchanged in 0.0.42:message.erroris never read.ag_ui/core/types.pydefines it:TypeScript —
integrations/langgraph/typescript/src/agent.tsdoes not even model the field:There is no read of
.erroron an incoming tool message anywhere in that file, so the loss is not Python-specific.Why it matters
LangChain providers already consume
status.langchain_anthropicmaps it onto the Anthropic tool-result flag:So the plumbing is complete end-to-end and broken by one omitted field in the adapter. On Anthropic, a client that correctly reports a failed frontend tool call still has that failure presented to the model as a success.
The impact is provider-dependent, which is probably why this has gone unnoticed:
langchain_google_genainever readsstatusat all — a tool message becomes afunction_responsecarryingcontentandname— so on Gemini the bug is invisible. It surfaces on providers that honor the flag.Steps to reproduce
The converter can be exercised directly — no graph or model needed:
Output on
ag-ui-langgraph0.0.41:Expected:
status == "error".Actual:
status == "success"(the default), anderror's text is not preservedanywhere either —
additional_kwargsis empty, so the failure is unrecoverabledownstream rather than merely untyped.
In a real run this is reached whenever a client answers a yielded frontend tool call with a
failure — the
useFrontendTool/ human-in-the-loop path — but the loss is entirely in theconverter, so the snippet above is sufficient.
Suggested fix
Map the presence of
erroronto LangChain'sstatusin both adapters, e.g. in the Python converter:Two open questions for maintainers, since they affect the shape of the fix:
error's text be preserved as well as the flag? LangChain has no dedicated slot for it, so the options are to fold it intocontent(changes what the model reads) or to keep it inadditional_kwargs(preserves round-tripping without altering the prompt). The flag alone is the minimum useful fix.ToolCallResultEventcannot express failure, aToolMessagewithstatus == "error"emitted back to the client currently loses the distinction, soerroris not durable across aMESSAGES_SNAPSHOT. That may warrant a separate protocol discussion rather than being folded in here.Documentation note
The spec is internally inconsistent about this field, which likely contributes to it being under-implemented: the messages concept page documents
errorand calls it out in its key points, while the tools concept page's "Tool Results" example omits it entirely. Aligning those would make the field harder to miss for other integrations.Environment
ag-ui-protocol0.1.19ag-ui-langgraph0.0.41 (verified unchanged in 0.0.42)langchain-core1.2.24