Skip to content

feat(agents): human-in-the-loop interrupts and resume (#296) - #348

Open
codeforstartups wants to merge 1 commit into
developmentfrom
feat/hitl-interrupts
Open

codeforstartups wants to merge 1 commit into
developmentfrom
feat/hitl-interrupts

Conversation

@codeforstartups

Copy link
Copy Markdown
Owner

Closes #296. Supersedes #332 by @Uzmaa7 (credited as co-author) — #332's branch rewrote react.py on the pre-#328 loop, which removed the #334 checkpointer and #345 shared-state support (6 failing tests). This ports the feature onto the current loop instead.

What it does

from dynavec.agents import ReActAgent, interrupt, tool

@tool()
def transfer_funds(amount: int) -> str:
    decision = interrupt("approve_transfer", {"amount": amount})   # pauses here
    return f"Transferred {amount}." if decision == "approved" else "Cancelled."

paused = agent.run("pay rent", thread_id="t1")          # termination_reason == "interrupted"
paused.interrupt_payload["payload"]                      # {"amount": 120}
final = agent.resume(thread_id="t1", human_decision="approved")   # with a checkpointer
# or: agent.resume(paused.interrupt_payload["messages"], human_decision="approved")

Changes vs #332

  • interrupt(node_id, payload) — feat(orchestration): implement human-in-the-loop interrupts and resume workflow (#296) #332's interrupt(thread_id, node_id, payload) was being called as interrupt("approval_node", "Confirm?"), so the arguments landed in the wrong fields. The agent already knows the thread.
  • Resume re-runs the paused tool and interrupt() returns the decision, which becomes that tool call's result. feat(orchestration): implement human-in-the-loop interrupts and resume workflow (#296) #332 appended the decision as a user message after an unanswered tool call — OpenAI/Anthropic reject that ordering, and the approved action never ran.
  • Survives a restart: with a checkpointer the paused state is saved (interrupted in the checkpoint) and resume(thread_id=...) works from a fresh agent; run() on a paused thread raises with a hint to call resume().
  • Remaining tool calls in the same batch run after the decision; a second interrupt in the batch pauses again.
  • run/arun/resume/aresume share one _loop/_aloop (behaviour of checkpointing and shared state unchanged; existing tests untouched and passing).
  • Left AccessDeniedError/ValidationError (from fix(s3vectors): map client errors to dynavec exceptions #346) as they are.

Tests

#332's three tests kept, plus 9 new: payload contents, decision reaching the tool (approved/rejected), resume from checkpoint in a new agent, run() refusing a paused thread, nothing-to-resume error, second interrupt in a batch, provider-valid message ordering, tool errors still becoming observations. Local: ruff clean, strict mypy clean, 1124 passed.

🤖 Generated with Claude Code

interrupt(node_id, payload) inside a tool pauses a ReActAgent run and returns
termination_reason="interrupted" with an interrupt_payload. resume()/aresume()
re-run the paused tool so interrupt() returns the human decision, record it as
that tool call's result, finish the rest of the batch and continue the loop.
With a checkpointer the paused state is saved and resume(thread_id=...) works
from a fresh agent; run() refuses a paused thread.

Ported onto the current agent loop (keeps the #334 checkpointer and #345 shared
state) from Uzmaa Khan's #332, with run/arun/resume sharing one loop.

Co-authored-by: Uzmaa7 <135528011+Uzmaa7@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Human-in-the-loop / interrupts

1 participant