How to Fix 'state not updating' in LlamaIndex (TypeScript)
When you see state not updating in a LlamaIndex TypeScript app, it usually means your workflow or agent is mutating state in a way LlamaIndex does not track. In practice, this shows up when you expect a step to persist data across runs, but the value gets lost because you reassigned local variables, returned the wrong object, or updated state outside the workflow’s state container.
This is most common in Workflow-based code, especially when using Context, step, and custom state objects. The symptom is usually: no crash, but downstream steps read stale values or empty state.
The Most Common Cause
The #1 cause is mutating a local copy of state instead of updating the workflow context/state object that LlamaIndex actually persists.
Here’s the broken pattern:
| Broken | Fixed |
|---|---|
| ```ts | |
| import { Workflow, step } from "llamaindex"; |
type MyState = { userMessage?: string; };
const myWorkflow = new Workflow<MyState>({ initialState: {}, });
myWorkflow.addStep(
step(async ({ context }) => {
const state = context.state; // local reference
state.userMessage = "hello"; // looks right, often not enough
return { ok: true };
})
);
|ts
import { Workflow, step } from "llamaindex";
type MyState = { userMessage?: string; };
const myWorkflow = new Workflow<MyState>({ initialState: {}, });
myWorkflow.addStep( step(async ({ context }) => { context.state.userMessage = "hello"; await context.saveState?.(); // if your workflow version exposes persistence hooks return { ok: true }; }) );
The key issue is that some LlamaIndex TS patterns expect state changes to happen through the framework-managed object and be persisted by the workflow runtime. If you destructure too early or replace the object entirely, later steps may still see the old value.
A second broken pattern is replacing the whole state object:
```ts
// Broken
context.state = {
...context.state,
userMessage: "hello",
};
In many workflow implementations, this breaks tracking because the runtime still holds the original reference. Prefer direct field mutation on the managed state object unless your specific version documents immutable replacement support.
Other Possible Causes
1) You forgot to initialize the state shape
If initialState does not include the field you later read, your logic may behave like state is “not updating” even though it was never defined properly.
// Broken
const wf = new Workflow({
initialState: {},
});
// Fixed
const wf = new Workflow({
initialState: {
userMessage: "",
retries: 0,
},
});
2) You are reading from a different Context instance
This happens when one step updates one context object and another step reads from a fresh instance created elsewhere.
// Broken
const ctx1 = new Context();
ctx1.state.userMessage = "hello";
const ctx2 = new Context();
console.log(ctx2.state.userMessage); // undefined
// Fixed
const ctx = new Context();
ctx.state.userMessage = "hello";
console.log(ctx.state.userMessage); // hello
3) Your async step returns before the update finishes
If you fire-and-forget a promise, the workflow may move on before state changes land.
// Broken
step(async ({ context }) => {
fetch("https://example.com").then(() => {
context.state.status = "done";
});
});
// Fixed
step(async ({ context }) => {
await fetch("https://example.com");
context.state.status = "done";
});
4) You’re mixing mutable state with serialized outputs incorrectly
If you expect an output payload to automatically become workflow state, it won’t unless you assign it explicitly.
// Broken
step(async () => {
return { userMessage: "hello" };
});
// Fixed
step(async ({ context }) => {
const result = { userMessage: "hello" };
context.state.userMessage = result.userMessage;
return result;
});
How to Debug It
- •
Log the exact state before and after each step
- •Print
context.stateat entry and exit. - •If it changes inside one step but not in the next, you have a persistence/reference problem.
- •Print
- •
Check whether you are mutating or replacing
- •Search for assignments like
context.state = .... - •Prefer field-level updates unless your version explicitly supports full replacement.
- •Search for assignments like
- •
Verify all steps use the same workflow/context instance
- •Look for accidental re-instantiation inside handlers.
- •This is common in Express routes or serverless functions where every request creates fresh objects.
- •
Inspect async boundaries
- •Make sure every network call or file operation affecting state is awaited.
- •If you see
Promise.then(...)withoutawait, that’s a red flag.
A useful debugging pattern:
step(async ({ context }) => {
console.log("before", JSON.stringify(context.state));
context.state.userMessage = "hello";
console.log("after", JSON.stringify(context.state));
});
If “after” looks correct but downstream steps don’t see it, focus on persistence and instance reuse. If “after” never changes, focus on mutation logic and initialization.
Prevention
- •Define your full workflow state up front with a typed
initialState. - •Update fields on the managed
context.stateobject directly instead of swapping objects around. - •Await every async operation that contributes to state changes.
- •Add logs around each step while building workflows; remove them only after you verify persistence across runs.
If you’re using LlamaIndex TS workflows in production, treat state like infrastructure: initialize it explicitly, mutate it predictably, and never assume local variables will survive past a step boundary.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit