Ask a human
The smallest integration - an agent asks a question, a human answers in the inbox, and the answer becomes the tool result.
Sometimes the agent does not need permission - it needs an answer. This example has a single tool, ask_human, that never executes: the only decision a reviewer can take is respond, and whatever they type is returned to the model as the tool's result. Source: Vigilator/examples/ask-a-human.
What you'll see
- Inbox - an interrupt whose action request is the question itself, with a single Respond box.
- Live View - a
policy-helpersession before the question and another after it.
Run it
Install and configure
git clone https://github.com/Vigilator/examples
cd examples/ask-a-human
uv sync
cp .env.example .env # VIGILATOR_API_KEY and ANTHROPIC_API_KEYRun with polling
uv run main.pyThe agent asks what the returns window is and prints the interrupt id. Answer in the inbox; the script resumes and the agent writes the reply using your answer verbatim.
Or resume by webhook
uv run fastapi dev webhook.py
svix listen http://localhost:8000/webhooks/vigilatorRegister the relay's URL for interrupt.answered, put the signing secret in .env, and POST /runs.
How it works
Three lines do the pausing:
@tool
def ask_human(question: str) -> str:
"""Ask a human colleague a question and wait for their answer."""
raise RuntimeError("ask_human is answered by a human, not executed")
INTERRUPT_ON = {"ask_human": {"allowed_decisions": ["respond"]}}
middleware = [HumanInTheLoopMiddleware(interrupt_on=INTERRUPT_ON)]When the model calls ask_human, the middleware raises an interrupt instead of running it. The bridge sees a request whose only allowed decision is respond and follows Vigilator's convention for questions - an action request named after the question with allowedDecisions=[respond]:
ActionRequest(
name=args["question"], # the question is the action request
description="Asked by the agent through its `ask_human` tool.",
allowedDecisions=[AllowedDecision.respond],
)The reviewer's responseText comes back as a respond decision, and the middleware hands it to the model as the tool's result:
Command(resume={"decisions": [{"type": "respond", "message": action.response_text}]})Adapting it
- Keep the tool's docstring specific about when to ask; the model decides to call it.
- Several questions in one turn become several action requests on one interrupt; the reviewer answers each and the bridge maps them back in order.
- To mix questions with approvals, see LangChain; for the same pattern in TypeScript, the
ask_humantool in LangGraph.js orask-humanin Mastra.