Skip to content

LangGraph integration #72

@lan17

Description

@lan17

Summary

  • Add a native Python LangGraph integration so users do not have to manually wrap each tool with @control().
  • The integration should discover tool metadata from LangGraph/LangChain tool objects, instrument actual tool execution using framework-native hooks, and manage Agent Control registration under the hood.
  • Users should not have to call initAgent directly for this integration. The integration can still call initAgent under the hood if needed.

Motivation

  • The current LangGraph example in the repo is functional, but the user experience is still manual: tools are wrapped individually with @control() and step registration relies on the existing decorator-based path.
  • LangGraph/LangChain already expose native tool-call interception seams, so we should be able to support the framework more directly instead of forcing users to adapt their code to the SDK’s decorator model.
  • A native integration would make Agent Control feel like a first-class framework integration rather than a generic decorator bolted onto framework tools.

Current behavior

  • Python users protect LangGraph/LangChain tools by manually wrapping functions with @control() and then exposing those wrapped functions as framework tools.
  • Step metadata is auto-registered today only because the wrapped Python functions participate in the existing @control() registry.
  • agent_control.init(...) calls /api/v1/agents/initAgent, merges explicit and decorator-discovered steps, and caches returned controls for runtime use.
  • Runtime enforcement semantics today live in the @control() path.

Relevant repo examples:

  • examples/langchain/langgraph_auto_schema_agent.py
  • examples/langchain/sql_agent_protection.py

Expected behavior

  • A LangGraph integration should allow users to register/protect framework tools natively, without manually slapping @control() on each tool implementation.
  • The integration should automatically derive step/tool metadata from framework tool objects and register that metadata with Agent Control.
  • The integration should automatically instrument actual tool execution using the framework’s native interception seam.
  • The integration should manage registration under the hood so users do not have to explicitly call initAgent as part of framework setup.
  • If the integration discovers a previously unseen tool after initial setup, it should be able to re-sync registration by re-running initAgent under the hood with the full merged step set before first execution of that unseen tool.

Reproduction (if bug)

  1. This is an enhancement request, not a bug report.

Proposed solution (optional)

  • Add a Python LangGraph/LangChain integration package or module behind an optional install boundary.
  • Use framework-native tool interception instead of manual decorator wrapping.
  • Under the hood, keep Agent Control semantics in the SDK:
    • discover/normalize step metadata from framework tool objects
    • call initAgent under the hood during setup to register the full step set and load controls
    • on newly discovered tools, re-run initAgent with the full merged step set rather than a delta payload
    • reuse the core Python runtime evaluation path, including check_evaluation_with_local(), so execution="sdk" controls keep working
    • translate deny/steer outcomes into framework-appropriate propagation instead of assuming raw ControlViolationError is always the correct UX

Suggested new-tool detection / resync behavior:

  • The adapter should maintain a local registry/fingerprint of the currently known tools, keyed by tool identity plus schema fingerprint.
  • During setup, the adapter discovers the initial tool set and registers the full normalized step list via initAgent under the hood.
  • At the tool-call interception seam, such as LangChain’s wrap_tool_call or LangGraph’s ToolCallWrapper, the adapter compares the incoming tool against the known registry.
  • If the tool is unseen, or if its schema fingerprint changed, the adapter should:
    1. re-scan the current tool set from the framework runtime
    2. regenerate the full normalized step list
    3. re-run initAgent under the hood with the full merged list
    4. refresh cached controls from the response
    5. only then proceed to pre-check / execution for that tool
  • If the re-sync fails for an unseen tool, the safest default is fail-closed before executing that tool.

Tradeoffs / constraints:

  • The integration should hide manual initAgent from the user, but it probably should not try to eliminate initAgent internally in v1 because the current SDK flow uses it for both registration and control loading.
  • True incremental late-bound step registration without initAgent is probably follow-up work.
  • Initial scope should stay tools-only, not generic graph/node abstractions.
  • LangChain’s agents docs already discuss dynamic tools / runtime tool registration, so the integration should align with that model rather than assume a fully static tool set forever.

Additional context

  • Current parent design discussion in Shortcut: sc-57847
  • Current LangGraph example relying on manual @control() wrapping: examples/langchain/langgraph_auto_schema_agent.py
  • Current Python SDK registration flow: sdks/python/src/agent_control/__init__.py
  • Current Python runtime enforcement path: sdks/python/src/agent_control/control_decorators.py
  • LangChain middleware reference: https://reference.langchain.com/python/langchain/agents/middleware

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions