Skip to content

Human-in-the-loop

A tool or middleware can pause a run for a human, and the pause is durable — it can outlive the process because the resume token points at a checkpoint.

Approval gate

One flag turns on approval for a tool:

from spine_core import Agent, tool

@tool(approve=True)
async def transfer_funds(amount: int, to: str) -> str:
    """Move money — requires approval."""
    ...

res = await agent.run("pay invoice 7781")
if res.interrupted:
    # show res.interrupt to a human, persist res.resume_token
    res = await agent.resume(res.resume_token, decision="approve")

decision accepts "approve" / "reject" (or any truthy/falsey value). On reject the tool is not executed and the rejection is fed back to the model.

Manual interrupt

A tool can pause itself and let the human supply the result:

from spine_core import Interrupt

@tool
async def ask_human(question: str) -> str:
    """Escalate to a person."""
    raise Interrupt(payload={"question": question})

# ... later, the decision becomes the tool result:
res = await agent.resume(res.resume_token, decision="ship it")

Surviving a restart

The resume token is the run's session id, backed by the checkpoint store. A brand-new process with the same durable store resumes the pause:

from spine_backends import SQLiteCheckpoint

store = SQLiteCheckpoint("runs.db")
# process A
res = await Agent("openai:gpt-4o-mini", tools=[transfer_funds], checkpoint=store).run("pay")
session_id = res.state.session_id

# process B (after a restart) — same db file
res = await Agent("openai:gpt-4o-mini", tools=[transfer_funds], checkpoint=store).resume(session_id, "approve")

This is what makes long-lived approvals (hours, days) safe.