Agent memory, state and streaming

LlamaIndex · Advanced · 14 min

Real assistants must remember the conversation, survive across web requests and show progress while working.

By default each agent.run(...) call starts fresh. To keep a conversation, pass a `Context`:

ctx = Context(agent)
await agent.run("My name is Ana.", ctx=ctx)
await agent.run("What's my name?", ctx=ctx)   # remembers: Ana

The context holds the chat memory and a store of arbitrary state. Read and write it with await ctx.store.get("key", default=None) and await ctx.store.set("key", value). Tools can receive the context too: add a parameter typed ctx: Context and LlamaIndex passes it in, so a tool can update state such as a shopping cart.

Between web requests, turn the context into a plain dictionary with ctx.to_dict(), save it (database, cache), and rebuild it later with Context.from_dict(agent, data). Values in the store must be serialisable (convertible to JSON-like data).

Long conversations are trimmed by the agent's memory to fit a token limit. For control, pass a Memory object (from llama_index.core.memory import Memory), which supports a short-term token limit plus long-term memory blocks such as extracted facts.

Streaming. agent.run(...) returns a handler. Iterate its events before awaiting the result:

handler = agent.run("...", ctx=ctx)
async for ev in handler.stream_events():
    ...
response = await handler

Useful event types: AgentStream (ev.delta is new text), ToolCall and ToolCallResult (ev.tool_name, ev.tool_kwargs, ev.tool_output). Show tool activity in your interface; users trust agents more when they can see what was looked up.

Example

import asyncio
from llama_index.core.agent.workflow import AgentStream, FunctionAgent, ToolCallResult
from llama_index.core.workflow import Context
from llama_index.llms.openai import OpenAI
async def add_to_cart(ctx: Context, item: str) -> str:
    """Add an item to the user's shopping cart."""
    cart = await ctx.store.get("cart", default=[])
    cart.append(item)
    await ctx.store.set("cart", cart)
    return f"Cart now has: {', '.join(cart)}"

agent = FunctionAgent(tools=[add_to_cart], llm=OpenAI(model="gpt-4o-mini"))

async def main():
    ctx = Context(agent)
    handler = agent.run("Add apples and bread to my cart.", ctx=ctx)
    async for ev in handler.stream_events():
        if isinstance(ev, ToolCallResult):
            print(f"[tool] {ev.tool_name}({ev.tool_kwargs}) -> {ev.tool_output}")
        elif isinstance(ev, AgentStream): print(ev.delta, end="", flush=True)
    await handler
    ctx2 = Context.from_dict(agent, ctx.to_dict())         # e.g. saved between requests
    print("\n", await ctx2.store.get("cart", default=[]))             # ['apples', 'bread']

asyncio.run(main())

Remember

  • Pass the same Context to keep conversation and state
  • ctx.store.get/set (awaited) for state; tools can take ctx: Context
  • ctx.to_dict() / Context.from_dict() across requests; stream with stream_events()

Watch out for

  • Creating a new Context on every web request and wondering why the agent forgets the conversation.

Check yourself

  1. How do you continue a conversation in a later HTTP request handled by another server process?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developers.llamaindex.ai/python/framework/understanding/agent/state/