Chat model returns empty content when I inject a delayed ToolMessage from a scheduled callback

Hey!

I have a scheduler that triggers a tool function later (outside the original user–model exchange).
When the scheduled time comes, it sends a ToolMessage back into the chat model — but the model returns an empty message (content="") instead of responding.

This is the flow of messages:

[HumanMessage] → [AIMessage] → [ToolMessage] → [AIMessage] → … → [ToolMessage (from scheduled function)]

The final ToolMessage is the one created when the scheduled callback fires.
That’s where the issue happens — the model simply returns an empty string.

The Scheduled Callback:

def _task_reminder_callback(thread_id: str, task_description: str):
    print(f"\n\n === AGENT: TASK REMINDER! (for thread: {thread_id}) === ")
    print(f" TASK: {task_description} ")
    print(" ======================================================= \n\n")

    config = {"configurable": {"thread_id": thread_id, "user_id": "user_123"}}
    workflow_instance = app_state.get("WORKFLOW")

    state = workflow_instance.get_state(config)

    # Find the most recent tool call for schedule_reminder
    tool_call_id = None
    if state and state.values and "messages" in state.values:
        for msg in reversed(state.values["messages"]):
            if hasattr(msg, 'tool_calls') and msg.tool_calls:
                for tool_call in msg.tool_calls:
                    if tool_call.get('name') == 'schedule_reminder':
                        tool_call_id = tool_call.get('id')
                        break
            if tool_call_id:
                break

    print(f'[DEBUG] tool_call_id: {tool_call_id}')

    if workflow_instance:
        try:
            reminder_message = ToolMessage(
                content=(
                    f"REMINDER: generate a natural sounding message to remind user "
                    f"that it's time to do this task:\n\n {task_description}.\n\n"
                ),
                tool_call_id=tool_call_id
            )

            result = workflow_instance.invoke({"messages": reminder_message}, config=config)

            print(f" === Successfully invoked workflow for reminder in thread {thread_id} === \n\n {result} === \n\n")
        except Exception as e:
            print(f" !!! ERROR in _task_reminder_callback for thread {thread_id}: {e} !!! \n\n")
    else:
        print(f" !!! ERROR: WORKFLOW not initialized. Cannot run reminder for thread {thread_id} !!!")

Problem
When this scheduled function runs and I call workflow_instance.invoke() with a ToolMessage,
the chat model returns an empty content message AIMessage(content="") — it doesn’t “react” like it normally would when the tool message comes directly during conversation flow.
Expected behavior:
The model should interpret the tool message and respond naturally (e.g., “Hey, it’s time to do your scheduled task!”).

My Question:
How can I correctly re-inject a delayed ToolMessage into the workflow so that the model responds like it does during normal tool use?

Is there something special about how the tool_call_id or message history needs to be linked so the model knows what to do?
Or do I need to send a different type of message (like SystemMessage or AssistantMessage) when reactivating the model after a delay?

Hi @vicky70

have you tried this?

# 1) Build the ToolMessage with the SAME tool_call_id produced earlier by the model
reminder_message = ToolMessage(
    content=(
        f"REMINDER: generate a natural sounding message to remind the user "
        f"that it's time to do this task:\n\n{task_description}.\n\n"
    ),
    tool_call_id=tool_call_id,
    name="schedule_reminder",  # include tool name for clarity
)

# 2) Append it to the thread state AS IF it came from the 'tools' node
next_config = workflow_instance.update_state(
    config,
    {"messages": [reminder_message]},
    as_node="tools",  # drives the graph to the agent/model next
)

# 3) Resume the graph so the model reacts to the tool output
result = workflow_instance.invoke(None, config=next_config)
1 Like