7. Human approval¶
Finding the best index set is automatic. Creating those indexes on a production table is not free — it takes space and slows writes. So before applying them, the optimizer pauses for a human to approve.
Runnable file: examples/tutorial/queryopt/ch07_human.py.
Pause with ctx.interrupt¶
async def approve(state, ctx) -> dict:
inc = state["search"]["incumbent"]
answer = await ctx.interrupt({
"question": "apply these indexes?",
"indexes": list(inc["indexes"]),
"value": inc["value"],
})
return {"applied": str(answer).lower() in ("y", "yes", "apply")}
On a fresh run ctx.interrupt suspends and checkpoints; a later resume feeds the answer back and the call returns it. Interrupting needs a checkpointer, because the paused state must live somewhere.
await app.invoke(init, thread="run-1") # runs until the pause
state = await app.get_state("run-1") # state.interrupted is True
req = state.interrupt_payload # the indexes awaiting approval
out = await app.resume("run-1", value="yes") # apply them
The same mechanism the agent uses¶
In chapter 5 the Codex agent ran with AllowAll(). Switch its policy to Interactive and every index the agent wants to create becomes a ctx.interrupt a human approves or denies — the identical pause-and-resume machinery, now driven by the agent's permission policy instead of a node you wrote. Approval is one mechanism whether you write the interrupt or an agent's policy writes it for you.
What you learned¶
ctx.interrupt(payload)pauses the run for a human;resume(thread, value=...)continues it with the answer.- Interrupting requires a checkpointer — the paused state has to persist.
- An agent backend's
Interactivepolicy turns tool/index requests into the same interrupt automatically.
In chapter 8 the search survives a restart, and a token budget guards an LLM-backed run.