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

**URL:** <https://forum.langchain.com/t/chat-model-returns-empty-content-when-i-inject-a-delayed-toolmessage-from-a-scheduled-callback/1951>\
**Category:** Talking Shop\
**Tags:** intro-to-langgraph, python-help\
**Created:** [October 26, 2025, 8:20am UTC](https://forum.langchain.com/t/chat-model-returns-empty-content-when-i-inject-a-delayed-toolmessage-from-a-scheduled-callback/1951 "2025-10-26T08:20:40Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![vicky70](https://avatars.discourse-cdn.com/v4/letter/v/4af34b/32.png) [@vicky70](https://forum.langchain.com/u/vicky70)\
**Post date:** [October 26, 2025, 8:20am UTC](https://forum.langchain.com/t/chat-model-returns-empty-content-when-i-inject-a-delayed-toolmessage-from-a-scheduled-callback/1951/1 "2025-10-26T08:20:40Z")

</div>

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:**

```auto
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?

---

<div class="post-metadata">

**Author:** ![pawel-twardziak](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/pawel-twardziak/32/960_2.png) [@pawel-twardziak](https://forum.langchain.com/u/pawel-twardziak)\
**Post date:** [October 26, 2025, 11:49am UTC](https://forum.langchain.com/t/chat-model-returns-empty-content-when-i-inject-a-delayed-toolmessage-from-a-scheduled-callback/1951/2 "2025-10-26T11:49:42Z")

</div>

Hi @vicky70

have you tried this?

```py
# 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)

```

---

<div class="post-metadata">

**Author:** ![vinishiru](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/vinishiru/32/2362_2.png) [@vinishiru](https://forum.langchain.com/u/vinishiru)\
**Post date:** [February 19, 2026, 7:11pm UTC](https://forum.langchain.com/t/chat-model-returns-empty-content-when-i-inject-a-delayed-toolmessage-from-a-scheduled-callback/1951/3 "2026-02-19T19:11:27Z")

</div>

Hello @vicky70 , have you successfully implemented this behavior?  
I also have a tool that returns a partial result from the agent call, but in a later moment I try to inject a ToolMessage (with same tool\_call\_id), but the model generates a empty content response.

I have tried the @pawel-twardziak approach, but the model also generates an empty response, even when updating the graph state as it was resuming the result from the tool node.

I have also used a time-travel approach, search for the state when the tool was originally called, overwriting the tool result, as if it was completed as a direct call, but this way I lose any message exchanged between the user and the agent.

I had better results creating the tool message and a HumanMessage that instructs the model to continue the conversation, both on the same input message array, but oddly the model seens to ignore the ToolMessage data and generate a response that is not present on the appended ToolMessage.

Example:

```python
messages = [
                    ToolMessage(
                        content=ai_agent_prompt_dto.prompt,
                        tool_call_id=ai_agent_prompt_dto.context[
                            "tool_call_id"
                        ], 
                    ),
                    HumanMessage(content="You have received the tool delayed result. You must continue with the conversation."),
                ]

```

---

<div class="post-metadata">

**Author:** ![Bitcot\_Kaushal](https://avatars.discourse-cdn.com/v4/letter/b/59ef9b/32.png) [@Bitcot\_Kaushal](https://forum.langchain.com/u/Bitcot_Kaushal)\
**Post date:** [February 20, 2026, 2:54am UTC](https://forum.langchain.com/t/chat-model-returns-empty-content-when-i-inject-a-delayed-toolmessage-from-a-scheduled-callback/1951/4 "2026-02-20T02:54:49Z")

</div>

Hey @vicky70

This usually happens because the graph isn’t actually being resumed in the same execution context the model expects.

When you inject a delayed `ToolMessage`, the model will only react if:

1. The `tool_call_id` matches **exactly** the one from the original `AIMessage.tool_calls`.

2. The message is appended as coming from the `"tools"` node.

3. The graph is resumed from the correct next edge (not restarted with a fresh invoke).

In **LangGraph** , a `ToolMessage` by itself doesn’t trigger reasoning — it only works if the graph state machine thinks it just finished executing a tool. If that edge isn’t active anymore (because time passed or the flow completed), the model can legitimately return `AIMessage(content="")`.

That empty response usually means:

> “There is no pending tool result expected in this state.”

* * *

### Why your HumanMessage workaround behaves oddly

When you add:

```auto
HumanMessage("You have received the tool delayed result...")

```

you’re effectively starting a **new reasoning turn**.  
So the model ignores the tool linkage and just treats it like fresh input.

That’s why it doesn’t behave like native tool completion.

* * *

### What works more reliably in production

Instead of trying to “resume” the original tool call hours later:

✅ Treat the reminder as a **new user event**  
→ Inject a `HumanMessage` like:

> “System reminder: It’s time to do X.”

and let the agent respond normally.

This avoids fighting the internal state machine.

* * *

### Why this happens (architecturally)

LangGraph’s execution model assumes:

```auto
AI → tool_call → ToolMessage → AI (immediate continuation)

```

It is not designed for:

```auto
AI → tool_call → [hours later] → ToolMessage → AI

```

Once the run is complete, there is no suspended edge waiting for that tool result.

* * *

In short:  
You’re not doing it wrong — you’re hitting a lifecycle limitation.  
Delayed tool reinjection only works if the graph is still in a waiting state. Otherwise, treat it as a new conversational turn.
