Pause and resume
Sigil agents can suspend a run pending an external decision and resume later with that decision injected back into the LLM conversation. This is the foundation of:
- The Reviews queue for human-gated transactions
- Cross-agent delegation that survives long-running children
- (Future) Wait-for-signature flows
Two pause flavours, one primitive
| Status | Trigger | Resume |
|---|---|---|
awaiting_review | A tool calls request_human_review | Operator approves/blocks in the portal Reviews queue |
awaiting_delegate | call_agent spawned a child run | Child’s terminal state automatically resumes the parent |
Both use the same persistence (paused_messages + pause_tool_call_id)
and the same resume path — only the trigger differs.
Lifecycle
running ──┐ │ tool returns PauseForReview │ ▼ │awaiting_review │ status persisted + │ POST /reviews/{runID}/resume │ paused_messages JSON ▼ │pending (resumed_at set) │ │ runner trigger │ ▼ │running ──┘ loop continues with synthetic tool_result injected against pause_tool_call_idThe run row stores enough state to drop the Cloud Run instance entirely between pause and resume — no idle compute, runs can sit awaiting a human reviewer indefinitely.
Cascading pauses
Orchestrator (awaiting_delegate) │ ▼ parent_run_idCompliance Agent (awaiting_review) │ ▼ paused_messages includes the orchestrator's call_agent contextReviewer approves │ ▼Compliance resumes → finishes → triggers Orchestrator resume │ ▼Orchestrator continues with compliance's verdict as the tool_resultof its call_agent.You write each agent assuming it is the only one running. The platform handles the cascade.
Resuming from the API
External integrations can resume programmatically using the same endpoint the portal uses:
curl -X POST https://api.sigilkeys.com/v1/orgs/$ORG/reviews/$RUN/resume \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $SESSION_COOKIE" \ -d '{ "decision": "allow", "notes": "Cleared after manual KYC review." }'Decision is a strict enum (allow | block | review). Notes are
length-capped at 4 KB. The decision is wrapped with an explicit
_kind: "sigil.human_review_decision" marker before being injected as
the LLM’s tool_result so prompt-injection from reviewer notes cannot
pose as a system directive.
Idempotency
Two reviewers approving the same case concurrently is a real race. The
backend returns 409 Conflict with code already_resumed when a
second resume call finds the run no longer in an awaiting state. Clients
should refresh the Reviews list and re-render — do not retry blindly.
Designing tools that pause
Any tool can suspend by returning tools.PauseForReview(title, payload):
func (RequestHumanReview) Execute(ctx context.Context, in map[string]any) (Result, error) { return Result{}, PauseForReview(title, map[string]any{ "title": title, "report_md": report, "payload": structuredData, })}The payload renders in the Reviews queue detail page. Markdown in
report_md is rendered to the reviewer; everything in payload is
shown as structured JSON.