Skip to main content

LangGraph connector for Noxy human-in-the-loop.

Project description

Noxy LangGraph Connector

LangGraph connector for Noxy human-in-the-loop guardrails. Pauses agent graphs with interrupt(), routes encrypted approval prompts to all devices registered for the identity (web, iOS, Android, Telegram), and resumes execution when you poll relay for the settled outcome via noxy-sdk (GetDecisionOutcome).

Installing langgraph-noxy pulls in noxy-sdk automatically; you do not need a separate checkout of the Noxy SDK.

Flow

sequenceDiagram
    participant G as LangGraph
    participant SDK as noxy-sdk
    participant N as Noxy Relay
    participant D as User Devices

    G->>SDK: send_decision
    SDK->>N: RouteDecision
    N->>D: Deliver to registered devices
    G->>G: interrupt() — state saved to checkpointer
    Note over G: Graph suspended

    loop Poll GetDecisionOutcome
        SDK->>N: get_decision_outcome
        N-->>SDK: pending / approved / rejected / expired
    end

    SDK->>G: Command(resume=outcome)
    G->>G: Continue with decision in state
  1. Graph reaches the HITL node.
  2. Noxy routes an encrypted actionable to all devices registered for the identity.
  3. The node calls interrupt() — LangGraph suspends and persists state via a checkpointer.
  4. User responds on any registered device or the decision TTL expires on relay.
  5. Your process calls wait_for_decision_outcome (SDK) or bridge.wait_and_resume(...).
  6. The graph continues with the human decision (or timeout default) in state.

Relay delivers outcomes via gRPC polling (GetDecisionOutcome), with exponential backoff in the SDK.

Requirements

  • Python >= 3.10
  • A LangGraph graph compiled with a checkpointer (required for interrupt())
  • A Noxy app token and target identity (phone, email, user id, or wallet 0x…)

Installation

pip install langgraph-noxy

Optional FastAPI example server:

pip install "langgraph-noxy[examples]"

Configuration

Set credentials in your environment or pass them to NoxyConfig:

Variable Required Default Description
NOXY_APP_TOKEN Yes App token from the Noxy dashboard (Bearer auth to relay)
NOXY_IDENTITY_ID Yes* Target identity: phone, email, user id, or wallet address
NOXY_ENDPOINT No https://relay.noxy.network Relay gRPC endpoint

*Identity is passed to NoxyLangGraphBridge(client, identity_id) in code; use the env var only if your app reads it from the environment.

Copy .env.example to .env when running the repository examples locally (never commit real tokens).

import os
from noxy import NoxyConfig, init_noxy_agent_client

client = init_noxy_agent_client(
    NoxyConfig(
        endpoint=os.environ.get("NOXY_ENDPOINT", "https://relay.noxy.network"),
        auth_token=os.environ["NOXY_APP_TOKEN"],
        decision_ttl_seconds=3600,
    )
)

Quick start

import uuid
from typing import Optional, TypedDict

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.constants import END, START
from langgraph.graph import StateGraph
from noxy import NoxyConfig, init_noxy_agent_client

from langgraph_noxy import NoxyLangGraphBridge, build_tool_call_actionable


class State(TypedDict, total=False):
    task: str
    noxy_decision: Optional[dict]
    _noxy_sent_decision_id: Optional[str]


def build_actionable(state: State) -> dict:
    return build_tool_call_actionable(
        tool="run_task",
        args={"task": state["task"]},
        title="Approve task?",
        summary=state["task"],
    )


client = init_noxy_agent_client(
    NoxyConfig(
        endpoint="https://relay.noxy.network",
        auth_token="your-app-token",
        decision_ttl_seconds=3600,
    )
)
identity = "user@example.com"
bridge = NoxyLangGraphBridge(client, identity)

builder = StateGraph(State)
builder.add_node("noxy_hitl", bridge.create_hitl_node(build_actionable))
builder.add_edge(START, "noxy_hitl")
builder.add_edge("noxy_hitl", END)

graph = builder.compile(checkpointer=InMemorySaver())

config = {"configurable": {"thread_id": str(uuid.uuid4())}}
paused = graph.invoke({"task": "Send 1 wei"}, config)
decision_id = paused["__interrupt__"][0].value["decision_id"]

# Poll relay until approved / rejected / expired (SDK exponential backoff)
final = bridge.wait_and_resume(graph, decision_id)

Manual polling

If you already poll elsewhere, resume from a single get_decision_outcome response:

from noxy.decision_outcome import WaitForDecisionOutcomeOptions

resume_handler = bridge.create_resume_handler(graph)
response = client.wait_for_decision_outcome(
    WaitForDecisionOutcomeOptions(decision_id=decision_id, identity_id=identity)
)
final = resume_handler.resume_from_poll_response(
    response, decision_id=decision_id, identity_id=identity
)

Graph state

Include optional _noxy_sent_decision_id in your state schema. The resume handler sets it via Command(update=...) so the HITL node does not re-route the decision when LangGraph re-executes the node after resume.

from langgraph_noxy import NOXY_SENT_DECISION_ID_KEY

class State(TypedDict, total=False):
    ...
    _noxy_sent_decision_id: Optional[str]  # or use NOXY_SENT_DECISION_ID_KEY

Poll tuning

Pass WaitForDecisionOutcomeOptions to bridge.wait_and_resume (same fields as noxy-sdk):

Field Default Description
initial_poll_interval_ms 400 First delay between polls
max_poll_interval_ms 30000 Cap between polls
max_wait_ms 900000 Stop polling and resume with timeout outcome
backoff_multiplier 1.6 Exponential backoff factor

On timeout (poll budget exceeded), pass an on_timeout callback to create_hitl_node:

def on_timeout(state, resume):
    return {"noxy_decision": resume.to_state(), "approved": False}

bridge.create_hitl_node(build_actionable, on_timeout=on_timeout)

API

Symbol Description
NoxyLangGraphBridge Wires client, registry, HITL node, and wait_and_resume
create_noxy_hitl_node(...) Low-level HITL node factory
NoxyGraphResumeHandler.wait_and_resume SDK poll loop + Command(resume=...)
NoxyGraphResumeHandler.resume_from_poll_response Resume from one terminal poll
PendingInterruptRegistry Maps decision_idthread_id for resume
build_tool_call_actionable(...) Standard propose_tool_call payload builder
parse_webhook_payload(...) Optional: parse webhook-shaped JSON if you bridge events yourself

Examples

Example scripts are maintained in the GitHub repository (they are not shipped inside the PyPI wheel). Clone the repo to run them:

git clone https://github.com/noxy-network/langgraph-connector.git
cd langgraph-connector
pip install ".[examples]"
cp .env.example .env   # set NOXY_APP_TOKEN and NOXY_IDENTITY_ID
  • examples/basic.py — mock client, no relay required
  • examples/poll_resume_server.py — FastAPI: POST /runs, then POST /runs/wait
python examples/basic.py

export NOXY_APP_TOKEN="your-app-token"
export NOXY_IDENTITY_ID="user@example.com"
uvicorn examples.poll_resume_server:app --reload

Development

For contributors working on this repository:

git clone https://github.com/noxy-network/langgraph-connector.git
cd langgraph-connector
pip install ".[dev,examples]"
make test
make build
make publish-check

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langgraph_noxy-1.0.2.tar.gz (15.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langgraph_noxy-1.0.2-py3-none-any.whl (12.9 kB view details)

Uploaded Python 3

File details

Details for the file langgraph_noxy-1.0.2.tar.gz.

File metadata

  • Download URL: langgraph_noxy-1.0.2.tar.gz
  • Upload date:
  • Size: 15.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for langgraph_noxy-1.0.2.tar.gz
Algorithm Hash digest
SHA256 8c7866d8f626eaf5a3c3730185ad8a0fa282b714912dd93fc5e06b88a7a1c940
MD5 48c364c1d0c5a6f7c3d4e733436b65c6
BLAKE2b-256 515e257fbaedd414f455281ccd1778daca4c3645d2049fdf483ed6233fa60cff

See more details on using hashes here.

File details

Details for the file langgraph_noxy-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: langgraph_noxy-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 12.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for langgraph_noxy-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9c91e8e63bf6c9f3789617b586da9f6ec76a15cbe0bdb379ca3e9be6f735ae09
MD5 148c2c9c956441d53521ff8ae1d3a72a
BLAKE2b-256 6e3c1a0c2822a63f1ccb7c1e2498ee46c5cb7d1c38fdafc2a34b0a3e91e396db

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page