Architecture & Lifecycle¶
Every interrupt you register is owned by Ved, the hgateway agent that runs its lifecycle end-to-end — from delivery through resolution and back into your graph.
sequenceDiagram
participant Agent as Agent (LangGraph + SDK)
participant Ved as Ved (hgateway)
participant Human as Human (Slack)
participant BFA as Your BFA
Agent->>Ved: register interrupt (HitlSpec)
Ved-->>Agent: accepted — graph suspends
Ved->>Human: notify (routing, channel)
Note over Ved: TTL / escalation / reminders<br/>tracked while waiting
Human-->>Ved: response
Ved->>BFA: signed resume callback
Note over BFA: resumes the graph with<br/>the typed response
What's a BFA?¶
BFA = backend for agent — your own service that owns the LangGraph checkpointer and process running the agent. It's not a gateway concept with its own SDK class; it's just the name for "your backend" in this flow, because it's the thing that:
- runs the graph and calls
raise_interrupt, - owns a
callback_urlthe gateway will POST a resume to, and - is responsible for turning that resume payload back into a LangGraph
Command(resume=...)that continues the suspended graph.
Ved looks up your BFA's callback_url from the Agent record tied to
your HGATEWAY_AGENT_API_KEY — it's configured once per agent, not passed on
every call.
The full lifecycle¶
Raised¶
Your agent calls raise_interrupt. The SDK POSTs the typed HitlSpec to
Ved's register endpoint, authenticated with your agent API key (see
Auth: register below). Ved then either
accepts (owns the interaction from here) or declines/is unreachable
(SDK falls back to a local interrupt(fallback) — see the
Integration Guide). If accepted, the SDK suspends
the graph with the sentinel
{"__hgateway_owned__": True, "hitl_instance_id": "..."} — your BFA checks
this to know not to render its own UI (see
Integration Guide, step 4).
Delivered¶
Ved delivers an interactive message (Slack today) to whoever your
RunFeatures.routing pointed at. Optionally runs LLM-assisted deliberation
before or alongside human resolution.
Interaction¶
If you set Ttl, Ved tracks expiry in the background while waiting on the
human and, on expiry, either applies a default response or forwards to
Routing.secondary per OnExpiry — the proactive staleness handling and
on-the-fly re-routing described in Why hgateway.
Nothing waiting on a human silently vanishes.
Resolved¶
Ved maps the raw Slack interaction back into the typed response shape your
content class expects (BinaryApprovalResponse, SingleDecisionResponse,
etc.).
Resumed¶
Ved POSTs the resume to your BFA's callback_url:
{
"hitl_instance_id": "…",
"thread_id": "…",
"run_id": "…",
"resume": { "decision": "approve" }
}
authenticated with an HMAC signature your BFA must verify (see
Auth: resume below). Your BFA takes the
resume value and calls graph.invoke(Command(resume=...), config) (or
equivalent) to continue the graph with thread_id.
If your BFA's endpoint doesn't return 2xx, Ved retries with exponential backoff (up to 5 attempts, capped at 30 minutes between tries). After the final attempt fails, the interrupt is marked failed rather than retried forever — check your gateway dashboard if a resume never arrives.
Post-Resolution¶
Independently of the resume dispatch above, this is where Ved's reasoning
capture stack runs — recording the responder's reasoning alongside their
resolution for future HITLs to draw on. This is newer surface area (see
Why hgateway); check
reference/responses.md for what's exposed today before depending on a
specific field.
Auth: register (SDK → gateway)¶
The SDK sends your HGATEWAY_AGENT_API_KEY as a standard bearer token:
Authorization: Bearer <HGATEWAY_AGENT_API_KEY>
The gateway hashes the incoming key and looks it up against your agent's stored key hash — never a plaintext comparison. An invalid or inactive key is rejected before the interrupt is ever registered.
Auth: resume (gateway → BFA)¶
The gateway signs every resume callback the same way Slack signs its webhooks, so a BFA that already verifies Slack signatures can reuse that logic. Two headers are sent alongside the JSON body:
X-Ved-Signature: v0=<hex hmac-sha256>
X-Ved-Request-Timestamp: <unix timestamp>
The signature is computed as HMAC-SHA256(secret, "v0:{timestamp}:{raw_body}"),
hex-encoded, prefixed v0=. To verify on your BFA:
- Reject the request if
X-Ved-Request-Timestampis more than 5 minutes from your clock (replay protection). - Recompute
HMAC-SHA256(your_shared_secret, f"v0:{timestamp}:{raw_body}")over the exact raw request body bytes (not a re-serialized copy). - Constant-time-compare your result to
X-Ved-Signature.
The shared secret is the same one configured for your agent's Slack signature verification — one secret, reused for both.
Common pitfalls¶
- Resolving your own copy of a gateway-owned interrupt — always check the
__hgateway_owned__sentinel before your BFA does anything with a suspended graph (see Raised above). - Verifying the signature against a re-serialized body — HMAC over
json.dumps(parsed_body)instead of the raw bytes will mismatch if key order or whitespace differs. Verify against the raw request body. - No response within 5 attempts — if your BFA endpoint is down across all
retries, the resume is marked failed, not retried indefinitely. Make sure
your
callback_urlis reliably reachable.
Where the dashboard fits¶
A separate ops dashboard (BFF + SPA) gives visibility into pending/resolved interrupts across agents. It's not part of the integration path described in this site — nothing here requires it.
Next: Getting Started to install the SDK, then the Integration Guide for the step-by-step.