← Developers
Level 1: code Framework

Python: LangChain and LangGraph

Level 0 is complete once the runtime resolves through a Securd policy. This page covers Level 1: tool code that distinguishes a held destination from an unavailable service and escalates instead of retrying.

Configuration and reference code for Python: LangChain and LangGraph

The guard

Held and blocked names resolve to the policy's block page address. The guard resolves the hostname and compares the answer against that address.

securd_guard.py
# securd_guard.py
# Held and blocked names resolve to the policy's block page address.
# Put that address (from the console, Sites > your site) in SECURD_BLOCK_ADDRS.
import os
import socket
import logging

SECURD_BLOCK_ADDRS = {a for a in os.getenv("SECURD_BLOCK_ADDRS", "").split(",") if a}
log = logging.getLogger("securd")


class SecurdHeld(Exception):
    """Destination is held at the Greywall or blocked by the policy for this agent role."""


def securd_guard(hostname: str, agent_role: str) -> None:
    try:
        answers = {info[4][0] for info in socket.getaddrinfo(hostname, 443)}
    except socket.gaierror as exc:
        log.warning("securd.unresolved", extra={"agent_role": agent_role, "host": hostname})
        raise SecurdHeld(f"{hostname} did not resolve inside {agent_role}") from exc
    if answers & SECURD_BLOCK_ADDRS:
        log.warning("securd.held", extra={"agent_role": agent_role, "host": hostname})
        raise SecurdHeld(f"{hostname} is held or blocked for {agent_role}")

Wrap a tool

Call the guard at the start of any tool that reaches the network. Raise a typed exception so the agent loop can escalate to a human instead of retrying.

tools.py
from urllib.parse import urlparse
import httpx
from langchain_core.tools import tool
from securd_guard import securd_guard, SecurdHeld

AGENT_ROLE = "research-agent"

@tool
def fetch_page(url: str) -> str:
    """Fetch a web page the agent has been approved to read."""
    host = urlparse(url).hostname or ""
    try:
        securd_guard(host, AGENT_ROLE)
    except SecurdHeld as held:
        # Return a message the model can act on. Do not retry.
        return f"BLOCKED: {held}. Ask an operator to approve {host} for {AGENT_ROLE}."
    return httpx.get(url, timeout=20).text[:8000]

LangGraph: route a hold to a human node

Route a held destination to a human review node rather than back to the model. The hold is already recorded in the Securd log; repeated attempts add no information.

graph.py
from langgraph.graph import StateGraph, END

def should_continue(state):
    last = state["messages"][-1].content
    return "needs_approval" if last.startswith("BLOCKED:") else "continue"

graph = StateGraph(dict)
graph.add_node("agent", call_model)
graph.add_node("tools", run_tools)
graph.add_node("needs_approval", ask_operator)  # posts to your review channel
graph.add_conditional_edges("tools", should_continue, {
    "continue": "agent",
    "needs_approval": END,
})

Initial allow list

Import the OpenAI or Anthropic scope template, plus the LangSmith template if tracing is enabled. All other destinations the agent resolves during initial deployment appear in the review queue.

Evaluate Agent DNS with your own agent traffic

Deploy on a single policy in learning mode and review the recorded destinations with your team.