Zero-Cost Pauses & Hibernation
Real-world agent workflows frequently require Human-in-the-Loop approvals (e.g. reviewing a $10,000 refund, issuing high-risk database migrations, or approving production code changes) or waiting for external asynchronous decisions.
In standard architectures, holding a worker container in memory while waiting for human sign-off wastes significant cloud budget. Agent Substrate provides true Scale-to-Zero Durable Hibernation via DurableApproval.
Suspending Execution
When an agent invokes a tool whose ToolRisk meets or exceeds the agent's approval threshold:
- Dormant Persistence: The run goes dormant directly in the durable Store (
./.substratelocally or PostgreSQL in production). - Resource Release: No background threads or memory locks are held in the worker process.
- 0 CPU · 0 MB RAM: The agent consumes zero active server resources while waiting.
- Cross-Process Resumption: An approver in a web dashboard, Slack app, or CLI can inspect and resolve the pending request hours or days later from any process connected to the store.
Usage Example
import asyncio
from substrate.agents import ReActAgent
from substrate.runtime import Runtime
from substrate.tools import ApprovalDecision, DurableApproval, ToolRisk, tool
from substrate.types import RunLogKind
@tool(risk=ToolRisk.CRITICAL, idempotent=False)
def wire_money(amount: int, to: str) -> str:
"""Send money to a vendor."""
return f"Transferred ${amount} to {to}"
async def main() -> None:
agent = ReActAgent(
"treasurer",
model=my_chat_model,
tools=[wire_money],
approval_handler=DurableApproval(),
)
async with Runtime.open("./.substrate") as runtime:
# Start the run on a named thread
work = asyncio.create_task(
runtime.run(agent, "Pay ACME $5,000 for invoice 1042.", thread="payments")
)
# The run halts before executing wire_money and awaits human decision
run = await runtime.active_run_for_thread("payments")
pending = await runtime.pending_approvals(run.run_id)
request = pending[0]
print(f"Pending: {request.tool_name}({request.args}) risk={request.risk}")
# Any process or dashboard can decide the approval later
await runtime.decide(
run.run_id,
request.request_id,
ApprovalDecision.APPROVED,
by="alice@acme.corp",
reason="Matches verified invoice #1042",
)
outcome = await work
print(outcome.output)Hot Resumption & Audit Trail
When runtime.decide() is invoked:
- The waiting run immediately resumes from its exact checkpoint.
- An
APPROVAL_DECIDEDevent is appended to the immutable journal recording the approver's identity (by), timestamp, decision, and rationale (reason). - If rejected with
ApprovalDecision.DENIED, the refusal observation is routed back to the agent so it can notify the user or pursue an alternate strategy.
Was this page helpful?