{"record":{"id":"e31b98d76f1238c9","repo":"run-llama/llama_index","slug":"chat-stream-is-none-cannot-write-to-history-witho","errorCode":null,"errorMessage":"chat_stream is None. Cannot write to history without chat_stream.","messagePattern":"chat_stream is None\\. Cannot write to history without chat_stream\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"llama-index-core/llama_index/core/chat_engine/types.py","lineNumber":179,"sourceCode":"    def put_in_queue(self, delta: Optional[str]) -> None:\n        self.queue.put_nowait(delta)\n        self.is_function_not_none_thread_event.set()\n\n    def aput_in_queue(self, delta: Optional[str]) -> None:\n        assert self.aqueue is not None\n        assert self.new_item_event is not None\n\n        self.aqueue.put_nowait(delta)\n        self.new_item_event.set()\n\n    @dispatcher.span\n    def write_response_to_history(\n        self,\n        memory: BaseMemory,\n        on_stream_end_fn: Optional[Callable] = None,\n    ) -> None:\n        if self.chat_stream is None:\n            raise ValueError(\n                \"chat_stream is None. Cannot write to history without chat_stream.\"\n            )\n\n        # try/except to prevent hanging on error\n        dispatcher.event(StreamChatStartEvent())\n        try:\n            final_text = \"\"\n            for chat in self.chat_stream:\n                self.is_function = is_function(chat.message)\n                if chat.delta:\n                    dispatcher.event(\n                        StreamChatDeltaReceivedEvent(\n                            delta=chat.delta,\n                        )\n                    )\n                    self.put_in_queue(chat.delta)\n                final_text += chat.delta or \"\"\n            if self.is_function is not None:  # if loop has gone through iteration","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/run-llama/llama_index/blob/afd0fef371831f9bda13e5af7167cf4e981278ab/llama-index-core/llama_index/core/chat_engine/types.py#L161-L197","documentation":"StreamingAgentChatResponse.write_response_to_history() consumes the synchronous LLM stream (self.chat_stream) to persist the final message into memory. The object is created with chat_stream=None by default, so calling this method before the engine has attached a stream is a programming error — there is literally nothing to write to history.","triggerScenarios":"Calling write_response_to_history(memory) on a StreamingAgentChatResponse obtained via agent.astream_chat() (which sets achat_stream, not chat_stream); calling it on a manually constructed StreamingAgentChatResponse(); calling it twice after the stream was already consumed elsewhere.","commonSituations":"Mixing sync and async code paths — obtaining the response from astream_chat() but using the sync write_response_to_history; building custom agents/workflows that construct StreamingAgentChatResponse() directly and forget to pass chat_stream; re-processing a response object after iteration completed.","solutions":["Use the matching method: astream_chat() responses need await response.awrite_response_to_history(memory), stream_chat() responses need response.write_response_to_history(memory)","If you built the response yourself, pass the generator: StreamingAgentChatResponse(chat_stream=llm.stream_chat(...))","In normal agent usage you rarely call this directly — let agent.stream_chat() handle history writing and just iterate response.response_gen"],"exampleFix":"// before\nresponse = await agent.astream_chat(\"hi\")\nresponse.write_response_to_history(memory)  # ValueError: chat_stream is None\n\n// after\nresponse = await agent.astream_chat(\"hi\")\nawait response.awrite_response_to_history(memory)","handlingStrategy":"validation","validationCode":"if response.chat_stream is None:\n    raise RuntimeError(\"response has no sync stream; use awrite_response_to_history\")","typeGuard":"from llama_index.core.chat_engine.types import StreamingAgentChatResponse\n\ndef has_sync_stream(resp: StreamingAgentChatResponse) -> bool:\n    return resp.chat_stream is not None","tryCatchPattern":"try:\n    response.write_response_to_history(memory)\nexcept ValueError as e:\n    if \"chat_stream is None\" in str(e):\n        await response.awrite_response_to_history(memory)  # async fallback\n    else:\n        raise","preventionTips":["Pair sync calls with sync writers: stream_chat -> write_response_to_history","Prefer letting the agent itself manage history; iterate response_gen instead of calling history writers directly","In custom agents, always pass the stream used to construct the response object"],"tags":["streaming","chat-engine","agents","sync-async-mismatch"],"backgroundTag":null,"analyzedSha":"afd0fef371831f9bda13e5af7167cf4e981278ab","analyzedAt":"2026-08-15T05:42:58.429Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}