Client & Decorators¶
The entry points you call directly, in the order you actually use them. See the Integration Guide for the full walkthrough.
Entry points¶
init() isn't required before use — raise_interrupt auto-initializes the
client from HGATEWAY_AGENT_API_KEY on first call. Call it yourself only if
you need to inject a custom transport (e.g. a fake for tests) or to set
default routing/ttl for the whole agent.
init, hitl_node, and raise_interrupt all accept the same feature kwargs
(channels, users, secondary_channels, secondary_users, ttl_seconds,
on_expiry, default_response, or a full features=RunFeatures(...)). They
form a 3-scope cascade — init is the lowest-priority default, hitl_node
overrides it per node, and the raise_interrupt/raise_* call site overrides
both. Routing and Ttl are mandatory: a HITL cannot be raised unless both
resolve to a concrete value from some scope. Use CLEAR_FEATURE to
explicitly unset a value inherited from a lower scope. See
Features & Routing for the full cascade
reference.
Decorate a node with hitl_node before calling raise_interrupt inside it —
it binds the current LangGraph state, config, and this node's feature
defaults into contextvars so the SDK can build the runtime context and
resolve the cascade automatically.
raise_interrupt is the call that actually registers a HITL with the gateway
and suspends the node. Must be called from inside a @hitl_node-decorated
node. For the common content types, prefer the raise_* wrapper functions
(raise_approval, raise_pick_one_option, ...) documented in
Wrapper Functions — they build the content
object for you.
hgateway_sdk.init ¶
init(transport: Optional[GatewayTransport] = None, *, channels: ChannelsInput = None, users: UsersInput = None, secondary_channels: ChannelsInput = None, secondary_users: UsersInput = None, ttl_seconds: TtlSecondsInput = None, on_expiry: OnExpiryInput = None, default_response: DefaultResponseInput = None, features: Optional[RunFeatures] = None) -> GatewayClient
Initialise the SDK and return the active GatewayClient.
Reads HGATEWAY_AGENT_API_KEY (env var or .env); the gateway URL/endpoint are fixed, not configurable. Call once at startup, before any node runs — calling it again replaces the cached client every raise_interrupt uses. channels/users/ttl_seconds/on_expiry/etc. set here are the cascade's lowest-priority scope, overridable per hitl_node or per call site (see resolve_features). Routing and ttl must each resolve to a value from some scope before any HITL can be raised — they don't have to be set here.
Example::
import hgateway_sdk as hg
hg.init(channels=["#ops"], ttl_seconds=3600, on_expiry="default-response")
hgateway_sdk.hitl_node ¶
hitl_node(fn=None, *, channels: ChannelsInput = None, users: UsersInput = None, secondary_channels: ChannelsInput = None, secondary_users: UsersInput = None, ttl_seconds: TtlSecondsInput = None, on_expiry: OnExpiryInput = None, default_response: DefaultResponseInput = None, features: Optional[RunFeatures] = None)
Bind graph state/config into SDK contextvars for the wrapped node.
Apply to the exact function or method that calls raise_interrupt — not to a class, since the decorator must see the specific callable LangGraph invokes to know which positional arg is state. Feature kwargs set here apply to every raise_interrupt call inside this node, overridable per call site (see resolve_features).
hgateway_sdk.raise_interrupt ¶
raise_interrupt(hitl_key: str, content: HitlContent[TResp], features: Optional[RunFeatures] = None, *, fallback: Optional[dict] = None, channels: ChannelsInput = None, users: UsersInput = None, secondary_channels: ChannelsInput = None, secondary_users: UsersInput = None, ttl_seconds: TtlSecondsInput = None, on_expiry: OnExpiryInput = None, default_response: DefaultResponseInput = None) -> TResp
Raise a human-in-the-loop interrupt and pause the graph for a response.
hitl_key identifies this call site (not a single run) — every HITL raised from this exact spot, across every run, shares this key; never derive it from per-run values. If the gateway is unreachable, fallback (if given) takes over locally instead of the HITL just hanging. Must be called inside a @hg.hitl_node-decorated node. channels/users/ttl_seconds/on_expiry/etc. override this call's inherited cascade values (see resolve_features); for the common content types, prefer the hitl_wrapper functions (raise_approval, raise_pick_one_option, ...) over constructing content directly.
Example::
import hgateway_sdk as hg
from hgateway_sdk import BinaryApprovalContent, Choice
hg.init(channels=["#ops"], ttl_seconds=3600, on_expiry="default-response")
@hg.hitl_node
def review_node(state):
resp = hg.raise_interrupt(
"approve-action",
BinaryApprovalContent(prompt="Approve?", choices=[Choice("approve", "Approve")]),
fallback={"decision": "reject"},
)
return {"decision": resp["decision"]}
Raises:
| Type | Description |
|---|---|
HitlNodeRequired
|
If called outside a |
FeatureValidationError
|
If routing or ttl has no value from any cascade scope. |
Advanced¶
You won't normally touch these directly — raise_interrupt uses them under
the hood. Documented here for anyone injecting a custom transport or reading
the runtime context by hand.
GatewayClient is the stateful object init() builds and raise_interrupt
delegates to. Its own raise_interrupt method is the same call documented
above under Entry points — not repeated here.
HttpGatewayTransport is the default transport, used unless you pass a
custom one to init().
hgateway_sdk.GatewayClient ¶
GatewayClient(transport: GatewayTransport, *, agent_api_key: str, default_feature_kwargs: Optional[dict] = None)
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).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transport
|
GatewayTransport
|
Transport implementation used to send the interrupt spec to the gateway. |
required |
agent_api_key
|
str
|
Bearer token sent with every request to authenticate the agent with the gateway. |
required |
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.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wire
|
dict
|
The fully serialised interrupt envelope as a plain dict
(produced by :func: |
required |
agent_api_key
|
str
|
Bearer token for authenticating the agent with the gateway service. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
An |
Ack
|
class: |
Ack
|
accepted ownership of the interrupt. |
Raises:
| Type | Description |
|---|---|
GatewayUnreachable
|
If the gateway cannot be reached (network error, timeout, DNS failure, etc.). |
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.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_url
|
str
|
Root URL of the gateway service (trailing slash stripped). |
required |
register_endpoint
|
str
|
Path to the interrupt-registration endpoint (leading
slash stripped and appended to |
required |
timeout
|
float
|
Socket timeout in seconds for the HTTP request. |
required |
register ¶
register(wire: dict, *, agent_api_key: str) -> Ack
POST the interrupt spec to the gateway and return the acknowledgement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wire
|
dict
|
Serialised interrupt envelope dict. |
required |
agent_api_key
|
str
|
Bearer token sent in the |
required |
Returns:
| Type | Description |
|---|---|
Ack
|
class: |
Ack
|
|
Raises:
| Type | Description |
|---|---|
GatewayUnreachable
|
For network-level failures ( |
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:
| Name | Type | Description |
|---|---|---|
thread_id |
str
|
LangGraph thread identifier (from
|
run_id |
str
|
LangGraph run identifier (from |
checkpoint_ns |
str
|
LangGraph per-task checkpoint namespace (from
|
occurrence |
int
|
0-based counter tracking how many times this particular
|
state |
dict
|
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. |