Edit and respond
Edit and respond are two of a reviewer's four decisions at a pause: edit changes a tool call's arguments before it runs, and respond answers in the tool's place.
Last updated: 27 Sep, 2026 · LangChain 1.4
Lesson 23 paused a run so a person could approve or reject a refund. A reviewer has two more choices: change a call before it runs, or hand back an answer as if the tool had produced it.
Resume decisions: approve, edit, respond
# after a pause, resume with one decision per waiting action, in order:
Command(resume={"decisions": [
{"type": "approve"}, # run the call as written
{"type": "edit", "edited_action": {"name": "refund_order",
"args": {"order_id": "C41"}}}, # change the arguments
{"type": "respond", "message": "..."}, # answer in the tool's place
]})The agent with approval
Start with lesson 23's agent. interrupt_on makes it pause before every refund_order call, and the checkpointer lets it stop and resume.
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
from shop_model import ShopModel
from tools import lookup_order, refund_order
approval = HumanInTheLoopMiddleware(interrupt_on={"refund_order": True}) # pause before each refund
agent = create_agent(ShopModel(), tools=[lookup_order, refund_order],
middleware=[approval], checkpointer=InMemorySaver())Pausing on two refunds
Ask for two refunds in one message. The run stops and the waiting calls sit in result.interrupts.
thread = {"configurable": {"thread_id": "two-refunds"}}
ask = {"messages": [{"role": "user", "content": "Please refund A17 and C40"}]}
result = agent.invoke(ask, thread, version="v2") # stops at the refunds
for action in result.interrupts[0].value["action_requests"]:
print(action["name"], action["args"]) # the calls awaiting a decisionApproving one and editing the other
Resume with a list of decisions, one per waiting action, in the same order.
approve = {"type": "approve"} # run it unchanged
fixed = {"name": "refund_order", "args": {"order_id": "C41"}}
edit = {"type": "edit", "edited_action": fixed} # replace the arguments
agent.invoke(Command(resume={"decisions": [approve, edit]}), thread, version="v2")The two calls awaiting a decision
thread = {"configurable": {"thread_id": "two-refunds"}}
ask = {"messages": [{"role": "user", "content": "Please refund A17 and C40"}]}
result = agent.invoke(ask, thread, version="v2")
for action in result.interrupts[0].value["action_requests"]:
print(action["name"], action["args"])One pause, two actions waiting: a reviewer answers both in one go, in the order they are listed.
One decision per call, in order
Send one decision when two actions are waiting and the invoke refuses. This is the error to see before the fix.
decision = Command(resume={"decisions": [{"type": "approve"}]})
agent.invoke(decision, thread, version="v2")Two actions were waiting and one decision was sent, so the invoke raises. The list has to be as long as the list of actions, and in the same order.
Editing a call
The customer meant C41, not C40. An edit decision replaces the call's arguments before it runs.
fixed = {"name": "refund_order", "args": {"order_id": "C41"}}
decisions = [{"type": "approve"}, {"type": "edit", "edited_action": fixed}]
result = agent.invoke(Command(resume={"decisions": decisions}), thread, version="v2")
for message in result.value["messages"]:
if message.type == "tool":
print(message.text.splitlines()[-1])A17 was refunded as asked and C41 instead of C40. The tool message for the edited call also tells the model that a person replaced its call, so it does not try the original again. Edit conservatively: a large change can make the model rethink its plan.
Answering in the tool's place
respond skips the tool and returns the reviewer's text as its result. It is for tools whose real answer is a person, such as a warehouse check. Configured with a dictionary, interrupt_on also limits which decisions are allowed.
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
from shop_model import ShopModel
from tools import lookup_order
ask_warehouse = HumanInTheLoopMiddleware(interrupt_on={"lookup_order": {"allowed_decisions": ["respond"]}})
agent = create_agent(ShopModel(), tools=[lookup_order], middleware=[ask_warehouse],
checkpointer=InMemorySaver())thread = {"configurable": {"thread_id": "warehouse"}}
agent.invoke({"messages": [{"role": "user", "content": "Where is A17?"}]}, thread, version="v2")
reply = {"type": "respond", "message": "A17 is on the van, arriving today."}
result = agent.invoke(Command(resume={"decisions": [reply]}), thread, version="v2")
print(result.value["messages"][-1].text)The model treats a respond message as a successful tool result. That is why the documentation warns against using it to turn down a refund: the model would believe the refund happened. Use reject for that.
What each decision returned
- Approve runs the call as the model wrote it.
- Edit replaces the arguments before the call runs; the edited call's tool message also tells the model a person changed it, so it does not retry the original.
- Respond skips the tool and returns the reviewer's text as the tool's result, for calls whose real answer is a person.
- Decisions match actions by position, so the list must be the same length as the waiting actions and in the same order, which is why one decision for two actions raised.
Edit vs respond
| edit | respond | |
|---|---|---|
| What runs | The tool, with new arguments | Nothing; the reviewer's text is the result |
| What the model sees | A note that a person changed the call | A normal, successful tool result |
| Use it for | A wrong argument, such as C40 for C41 | A tool whose answer is a person, such as a warehouse check |
Where editing a call fits
- A refund or payment step where a person fixes the amount or the account before it runs.
- A lookup whose real source is a colleague, answered in the tool's place.
respond message looks like a successful tool result to the model, so do not use it to turn a refund down: the model will believe the refund happened. Use reject to refuse.Related
- Previous: Asking a human first
- Next: Guardrails
- Reference: Human-in-the-loop
- Send a
rejectdecision to the warehouse agent and read the error. - Edit the A17 refund to C41 and approve the C40 one, and check the order of the results.
- Allow both
respondandapprovefor the lookup and approve it instead.
Slow is fine. Stopping is the only problem.