Client & Decorators¶
The entry points you call directly: initialize once, decorate your node, raise the interrupt. See the Integration Guide for the order these are actually used in.
hgateway_sdk.GatewayClient ¶
GatewayClient(transport: GatewayTransport, *, agent_api_key: str)
Stateful client that registers HITL interrupts with the hgateway service.
Instantiated by :func:~hgateway_sdk.init and cached as a module-level
singleton in entrypoint. You generally interact with this class
indirectly through :func:~hgateway_sdk.raise_interrupt, but you can
obtain the client instance directly from init() if you prefer explicit
dependency injection.
The client is transport-agnostic: pass any object implementing the
:class:~hgateway_sdk.GatewayTransport protocol to replace the default
HTTP transport (useful for testing).
Args: transport: Transport implementation used to send the interrupt spec to the gateway. agent_api_key: Bearer token sent with every request to authenticate the agent with the gateway.
Source code in .venv/lib/python3.12/site-packages/hgateway_sdk/client/gateway_client.py
43 44 45 | |
raise_interrupt ¶
raise_interrupt(hitl_key: str, content: HitlContent[TResp], features: Optional[RunFeatures] = None, *, fallback: Optional[dict] = None) -> TResp
Register an interrupt with the gateway and suspend the LangGraph node.
Builds the interrupt envelope (:class:~hgateway_sdk.HitlSpec) from
the supplied arguments and the runtime context captured by the
@hg.hitl_node decorator, serialises it to a wire dict, and
attempts to register it with the gateway.
Ownership decision
- If the transport call succeeds and
ack.acceptedisTrue, the gateway owns the interrupt. The method still raises a LangGraph interrupt (so the graph checkpoints and pauses for the gateway to resume) but surfaces a sentinel value —{"__hgateway_owned__": True, "hitl_instance_id": ...}— rather than bareNone, so a backend consuming the stream can detect and ignore gateway-owned interrupts. The operator's response is delivered on resume regardless of the surfaced value. - If :class:
~hgateway_sdk.GatewayUnreachableis raised or the gateway returnsaccepted=False, the method falls back to a localinterrupt(fallback or wire)so the agent's own backend handles it.
Args:
hitl_key: Logical name for this interrupt type, e.g.
"approve-deployment". Combined with the occurrence counter
to form a dedup ID.
content: Typed content describing the review task (a subclass of
:class:~hgateway_sdk.HitlContent).
features: Optional :class:~hgateway_sdk.RunFeatures (routing,
TTL, recipients, cross-feature rules). Defaults to an empty
RunFeatures() if None.
fallback: Raw dict passed to interrupt() when the gateway does
not take ownership. If None, the serialised wire spec is
used.
Returns: The typed operator response on graph resume.
Raises:
HitlNodeRequired: If the method is called outside a node decorated
with @hg.hitl_node (the runtime contextvars are unset).
Source code in .venv/lib/python3.12/site-packages/hgateway_sdk/client/gateway_client.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
hgateway_sdk.init ¶
init(transport: Optional[GatewayTransport] = None) -> GatewayClient
Initialise the SDK and return the active :class:GatewayClient.
Reads the HGATEWAY_AGENT_API_KEY environment variable (or a .env
file via python-dotenv); the gateway URL and register endpoint are fixed,
not read from the environment. Call this once at application startup —
typically in your agent's main or lifespan hook — before any node
runs.
If transport is omitted, an :class:~hgateway_sdk.HttpGatewayTransport
is constructed from the loaded settings. Pass a custom transport to inject
a test double or an alternative HTTP implementation.
The returned client is stored in a module-level singleton; subsequent calls
to :func:raise_interrupt use it automatically. Calling init a second
time replaces the singleton.
Args:
transport: Optional custom transport implementing
:class:~hgateway_sdk.GatewayTransport. If None, the default
HTTP transport is used.
Returns:
The newly created and cached :class:GatewayClient.
Raises:
GatewayBootstrapError: If required environment variables are missing
and cannot be resolved from a .env file.
Source code in .venv/lib/python3.12/site-packages/hgateway_sdk/client/entrypoint.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | |
hgateway_sdk.raise_interrupt ¶
raise_interrupt(hitl_key: str, content: HitlContent[TResp], features: Optional[RunFeatures] = None, *, fallback: Optional[dict] = None) -> TResp
Register a HITL interrupt with the gateway and pause the graph.
Convenience wrapper around :meth:GatewayClient.raise_interrupt. Must be
called from inside a node decorated with :func:~hgateway_sdk.hitl_node;
the decorator populates the contextvars that this call reads to build the
runtime context (thread ID, run ID, occurrence counter).
Gateway-ownership vs fallback behaviour
- The interrupt spec is serialised and sent to the gateway via the configured transport.
- If the gateway responds with
accepted=True, the SDK callslanggraph.types.interrupt(None)— the graph suspends and control returns to the LangGraph server, which will resume the node with the operator's response once the gateway notifies it. - If the gateway is unreachable (:class:
~hgateway_sdk.GatewayUnreachable) or returnsaccepted=False, the SDK falls back to a local interrupt:interrupt(fallback)iffallbackis provided, otherwiseinterrupt(wire)wherewireis the full serialised spec dict.
Must be called inside a @hg.hitl_node-decorated node.
Example::
import hgateway_sdk as hg
from hgateway_sdk import BinaryApprovalContent, Choice, RunFeatures, Routing, Recipients
hg.init() # reads HGATEWAY_AGENT_API_KEY (or .env); gateway URL/endpoint are fixed
@hg.hitl_node
def review_node(state):
content = BinaryApprovalContent(
prompt=f"Approve this action? {state['summary']}",
choices=[Choice("approve", "Approve"), Choice("reject", "Reject")],
)
resp = hg.raise_interrupt(
"approve-action",
content,
features=RunFeatures(routing=Routing(primary=Recipients(channels=["#ops"]))),
fallback={"decision": "reject"}, # surfaced if the gateway is unreachable/declines
)
return {"decision": resp["decision"]} # BinaryApprovalResponse is a TypedDict: {"decision": str}
Args:
hitl_key: Logical, stable identifier for this interrupt type, e.g.
"approve-sql-query". Used as part of dedup-ID generation
(with checkpoint_ns and occurrence). Treat it as
immutable: use a fixed literal per interrupt site and never derive
it from per-run values (UUIDs, timestamps, state). The same logical
interrupt must pass the same key on every (re)run, because the node
is replayed on resume and a changed key reads as a different
interrupt — breaking dedup and any in-flight gateway interaction.
See :class:~hgateway_sdk.HitlSpec for the full rationale.
content: Typed content object (a :class:~hgateway_sdk.HitlContent
subclass) describing what the operator needs to review.
features: Optional :class:~hgateway_sdk.RunFeatures controlling
routing, TTL, recipients, and other gateway features.
fallback: Raw dict passed to interrupt() when the gateway does not
take ownership. If None, the full wire-format spec dict is
used instead.
Returns:
The typed response object from the operator (a subclass of the response
type parameterised by content), delivered on graph resume.
Raises:
HitlNodeRequired: If called outside a @hg.hitl_node-decorated node
(the runtime contextvars are unset).
Source code in .venv/lib/python3.12/site-packages/hgateway_sdk/client/entrypoint.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
hgateway_sdk.hitl_node ¶
hitl_node(fn)
Decorator that makes a LangGraph node HITL-aware.
Wraps a node function so that, before the body executes, the current graph
state and LangGraph config are bound into SDK contextvars via
:func:~hgateway_sdk.runtime.bind. This binding is required by
:func:~hgateway_sdk.raise_interrupt, which reads those vars to populate
the :class:~hgateway_sdk.RuntimeContext (thread ID, run ID, occurrence
counter).
The decorator is transparent to LangGraph: it preserves the function name,
signature, and return value. Both synchronous and async def nodes are
supported — an async node is wrapped in an async wrapper so the contextvar
binding stays active across await points within the node's task. The
config argument is optional — if the wrapped function does not declare
it, the decorator still fetches the active config via
langgraph.config.get_config and uses it for the runtime context without
passing it to fn.
Calling :func:~hgateway_sdk.raise_interrupt outside a @hitl_node-
decorated node raises :class:~hgateway_sdk.HitlNodeRequired.
Usage::
import hgateway_sdk as hg
@hg.hitl_node
def my_review_node(state):
resp = hg.raise_interrupt("review", MyContent(...))
return {"result": resp}
Args:
fn: The node function to wrap. Must accept state as its first
positional argument; may optionally accept config as a second
argument.
Returns: A wrapped function with identical semantics, safe to register as a LangGraph node.
Source code in .venv/lib/python3.12/site-packages/hgateway_sdk/decorators.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | |
hgateway_sdk.GatewayTransport ¶
Bases: Protocol
Protocol for sending a HITL interrupt spec to the gateway.
Any object that implements register with this exact signature satisfies
the protocol and can be passed as the transport argument to
:func:~hgateway_sdk.init or :class:~hgateway_sdk.GatewayClient.
Use this protocol to inject a custom transport — for example, a test
double that returns a controlled :class:~hgateway_sdk.Ack without making
real HTTP calls::
class AlwaysAcceptTransport:
def register(self, wire: dict, *, agent_api_key: str) -> Ack:
return Ack(accepted=True, hitl_instance_id="test-id", raw={})
client = hg.init(transport=AlwaysAcceptTransport())
The protocol is @runtime_checkable, so isinstance(obj, GatewayTransport)
works for basic structural verification.
register ¶
register(wire: dict, *, agent_api_key: str) -> Ack
Send the serialised interrupt spec to the gateway.
Args:
wire: The fully serialised interrupt envelope as a plain dict
(produced by :func:~hgateway_sdk.utils.serialization.spec_to_wire).
agent_api_key: Bearer token for authenticating the agent with the
gateway service.
Returns:
An :class:~hgateway_sdk.Ack indicating whether the gateway
accepted ownership of the interrupt.
Raises: GatewayUnreachable: If the gateway cannot be reached (network error, timeout, DNS failure, etc.).
Source code in .venv/lib/python3.12/site-packages/hgateway_sdk/transport/gateway_transport.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | |
hgateway_sdk.HttpGatewayTransport ¶
HttpGatewayTransport(base_url: str, register_endpoint: str, timeout: float)
Default transport that registers interrupts over HTTPS/JSON.
Constructed automatically by :func:~hgateway_sdk.init from settings
resolved by :func:~hgateway_sdk.config.load_settings. You can also
construct it directly if you need fine-grained control over the endpoint
or timeout::
transport = HttpGatewayTransport(
base_url="https://gateway.example.com",
register_endpoint="/api/v1/sdk/hitl/register",
timeout=5.0,
)
client = hg.init(transport=transport)
A HTTP 2xx response with body {"hitl_instance_id": ..., "status":
"raised"} is treated as accepted=True (gateway-owned). HTTP errors
are treated as accepted=False (the gateway declined) and their
structured error body (code/message/request_id/details) is
preserved on Ack.raw; network/DNS failures and non-JSON 2xx bodies
raise :class:~hgateway_sdk.GatewayUnreachable.
Args:
base_url: Root URL of the gateway service (trailing slash stripped).
register_endpoint: Path to the interrupt-registration endpoint (leading
slash stripped and appended to base_url).
timeout: Socket timeout in seconds for the HTTP request.
Source code in .venv/lib/python3.12/site-packages/hgateway_sdk/transport/http_gateway_transport.py
50 51 52 | |
register ¶
register(wire: dict, *, agent_api_key: str) -> Ack
POST the interrupt spec to the gateway and return the acknowledgement.
Args:
wire: Serialised interrupt envelope dict.
agent_api_key: Bearer token sent in the Authorization header.
Returns:
:class:~hgateway_sdk.Ack with accepted=True for HTTP 2xx,
accepted=False for HTTP errors.
Raises:
GatewayUnreachable: For network-level failures (URLError,
timeouts, DNS errors) and for a 2xx response whose body is not
valid JSON — an unintelligible gateway response means ownership
cannot be confirmed, so the SDK fails safe to its fallback path
rather than pausing indefinitely on a malfunctioning gateway.
Source code in .venv/lib/python3.12/site-packages/hgateway_sdk/transport/http_gateway_transport.py
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
hgateway_sdk.RuntimeContext
dataclass
¶
RuntimeContext(thread_id: str, run_id: str, occurrence: int, checkpoint_ns: str = '', state: dict = dict())
LangGraph runtime identifiers and graph state for a single interrupt.
Automatically populated by the SDK decorator from the LangGraph
RunnableConfig and interrupt counter. Developers do not create this
manually.
Attributes:
thread_id: LangGraph thread identifier (from
config["configurable"]["thread_id"]). The gateway uses this
to correlate the interrupt with the correct graph run.
run_id: LangGraph run identifier (from config["run_id"] /
config["configurable"]["run_id"]). Note this changes on every
resume (each invoke is a new run), so it is not part of the dedup
identity.
checkpoint_ns: LangGraph per-task checkpoint namespace (from
config["configurable"]["checkpoint_ns"], formatted
"<node>:<task-uuid>"). It is stable when the same interrupt
is replayed on resume but distinct on each graph-cycle re-entry, so
together with hitl_key and occurrence it gives the gateway
a dedup identity that is replay-stable yet unique per loop iteration.
Defaults to an empty string when unavailable.
occurrence: 0-based counter tracking how many times this particular
hitl_key has been raised within the current node execution.
Disambiguates multiple raises of the same name inside one node body;
combined with checkpoint_ns for cross-cycle uniqueness.
state: Snapshot of the LangGraph graph state at the moment the
interrupt is raised. Forwarded to the gateway so operators and
routing rules can inspect graph context. Defaults to an empty dict.