DeepAgents
A Deep Agent with a research subagent that writes a report and needs a human's approval to publish it.
A report writer built with create_deep_agent. It delegates fact-finding to a researcher subagent, writes the report with the built-in file tools, and pauses on the one step with consequences: publish_report. Deep Agents use the same human-in-the-loop middleware as create_agent, so the Vigilator bridge works unchanged. Source: Vigilator/examples/deepagents.
What you'll see
- Inbox - an interrupt for
publish_reportwith its arguments and the transcript, including the report the agent wrote, with Approve and Reject. - Live View - a
report-writersession where the subagent's work is folded behind thetasktool chip and the report appears as thewrite_filecall.
Run it
Install
git clone https://github.com/Vigilator/examples
cd examples/deepagents
uv syncDeep Agents need Python 3.11 or newer.
Run with polling
uv run main.pyThe agent researches, writes /reports/q3-support.md, and asks to publish it. Approve and the run completes; reject with a reason and the agent keeps the draft and explains why it stopped.
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
interrupt_on gates the tool; the subagent and the built-in file tools run unreviewed:
RESEARCHER = {
"name": "researcher",
"description": "Finds facts and figures in the team's notes.",
"system_prompt": "Search the notes and reply with the facts you found as bullets.",
"tools": [search_notes],
}
agent = create_deep_agent(
model=MODEL,
tools=[publish_report],
system_prompt=SYSTEM_PROMPT,
subagents=[RESEARCHER],
interrupt_on={"publish_report": {"allowed_decisions": ["approve", "reject"]}},
checkpointer=checkpointer,
)The interrupt payload is the same action_requests / review_configs shape as LangChain, so the bridge opens the Vigilator interrupt and resumes with Command(resume={"decisions": [...]}) without any Deep Agent specifics.
Streaming needs one allowance: Deep Agent updates carry more than messages. The middleware nodes emit None and the tools node also emits files, so the bridge forwards only the messages it finds:
for chunk in agent.stream(input_, config, stream_mode="updates"):
for node, update in chunk.items():
if node == "__interrupt__":
pending = update[0]
continue
if isinstance(update, dict) and update.get("messages"):
live.push(update["messages"])Adapting it
- Gate built-in tools the same way, for example
"write_file": {"allowed_decisions": ["approve", "edit", "reject"]}, and add the tool's schema toARGS_SCHEMASif you allow edits. - Subagents may have gated tools of their own; their interrupts surface through the parent run on the same thread.
- Stream with
subgraphs=Trueto see the subagent's own turns in Live View rather than just thetaskresult.