feat(langchain): implement native GovernanceMiddleware via AgentMiddleware#1585
Conversation
…eware Replaces fragile proxy-based wrapping with LangChain's native AgentMiddleware system (wrap_tool_call / wrap_model_call). Changes: - Add GovernanceMiddleware class implementing wrap_tool_call and wrap_model_call lifecycle hooks for native governance gating - Add as_middleware() factory on LangChainKernel for clean integration - Implement blocked-pattern checks on both tool and model outputs - Support pre_execute/post_execute Cedar/OPA gates on tool calls - Support content filtering on model inputs and outputs - Deprecate LangChainKernel.wrap() and module-level wrap() with clear migration path to as_middleware() - Add comprehensive test suite (39 tests) covering: * Tool allowlist/blocklist enforcement * Blocked pattern detection in args, tool names, and outputs * Model input/output content filtering * Cedar evaluator passthrough * Shared kernel state across middleware instances * Deprecation warning verification * Full backward compatibility with existing wrap() API Resolves microsoft#1584
🤖 AI Agent: docs-sync-checker — Docs SyncDocs Sync
|
🤖 AI Agent: security-scanner — View detailsNo security issues found. |
🤖 AI Agent: breaking-change-detector — API CompatibilityAPI Compatibility
|
🤖 AI Agent: test-generator — `agent_os/integrations/langchain_adapter.py`Test Coverage Analysis
|
🤖 AI Agent: code-reviewer — Review SummaryReview SummaryThis PR introduces a significant refactor to the LangChain integration by replacing the existing proxy-based The PR is well-documented, includes comprehensive test coverage, and adheres to the project's design principles. However, there are some areas that require attention, particularly around security, backward compatibility, and potential improvements. Key FindingsCRITICAL: Security Issues
WARNING: Backward Compatibility
SUGGESTION: Improvements
Suggested ChangesCode Changes
Documentation Updates
ConclusionThis PR is a significant improvement to the LangChain integration, addressing many of the limitations of the previous proxy-based approach. However, the identified security issues (CRITICAL) must be addressed before merging. Additionally, the backward compatibility concerns (WARNING) and suggested improvements should be considered to ensure a smooth transition for users and enhance the overall robustness of the implementation. |
PR Review Summary
Verdict: ❌ Changes needed |
imran-siddique
left a comment
There was a problem hiding this comment.
Code Review: LangChain GovernanceMiddleware
Great PR description and migration guide, @miyannishar. The middleware architecture is solid. A few issues to address:
Blocking
1. Missing async wrap_tool_call / wrap_model_call hooks
Only sync versions are implemented. LangChain agents commonly run in async contexts (ainvoke, async tool execution). The deprecated wrap() API already supports async via ainvoke with asyncio.wait_for timeout. The new middleware path loses this. Both sibling integrations (ADK, OpenAI) provide async paths. Add awrap_tool_call and awrap_model_call.
Security Concerns
2. Tool arguments logged in plaintext at DEBUG level
tool_args may contain API keys, credentials, or PII. Log only argument keys, not values:
python logger.debug("[%s] wrap_tool_call: tool=%s args_keys=%s", self._name, tool_name, list(tool_args.keys()) if isinstance(tool_args, dict) else "<opaque>")
3. Non-string model responses bypass output filtering entirely
When response.message.content is a list (e.g., tool_use blocks), isinstance(output_text, str) fails and all output filtering is skipped. Structured content containing blocked patterns passes undetected. Coerce with str() before scanning, matching the pattern used in input scanning.
Warnings
4. max_tool_calls now counts model calls too
Both wrap_tool_call and wrap_model_call call post_execute, incrementing ctx.call_count. This means model calls consume the tool-call budget, which is a behavioral change from wrap(). Either use separate contexts, skip post_execute in wrap_model_call, or document this explicitly.
5. Double deprecation warning from module-level wrap()
Module-level wrap() emits its own warning, then calls LangChainKernel.wrap() which emits another. Suppress the inner one.
The missing async hooks (#1) are the main blocker for production use.
imran-siddique
left a comment
There was a problem hiding this comment.
Updated review (condensed):
TL;DR: 1 blocker (no async support), 2 security concerns. Fix #1 and this ships.
| # | Sev | Issue | Where |
|---|---|---|---|
| 1 | Block | No async wrap_tool_call/wrap_model_call -- async LangChain agents will block or fail |
GovernanceMiddleware |
| 2 | Sec | Full tool_args logged at DEBUG level -- may leak secrets/PII |
wrap_tool_call |
| 3 | Sec | Non-string model responses (lists, dicts) skip output filtering entirely | wrap_model_call |
| 4 | Warn | Model calls consume max_tool_calls budget (behavioral change from wrap()) |
wrap_model_call |
| 5 | Warn | Double deprecation warning from module-level wrap() |
wrap() |
#1: Add awrap_tool_call/awrap_model_call. The deprecated wrap() already supports async.
#2: Log list(tool_args.keys()) instead of values.
#3: Coerce with str() before scanning, matching the input-side pattern.
imran-siddique
left a comment
There was a problem hiding this comment.
Approving native middleware migration.
…eware (microsoft#1585) Replaces fragile proxy-based wrapping with LangChain's native AgentMiddleware system (wrap_tool_call / wrap_model_call). Changes: - Add GovernanceMiddleware class implementing wrap_tool_call and wrap_model_call lifecycle hooks for native governance gating - Add as_middleware() factory on LangChainKernel for clean integration - Implement blocked-pattern checks on both tool and model outputs - Support pre_execute/post_execute Cedar/OPA gates on tool calls - Support content filtering on model inputs and outputs - Deprecate LangChainKernel.wrap() and module-level wrap() with clear migration path to as_middleware() - Add comprehensive test suite (39 tests) covering: * Tool allowlist/blocklist enforcement * Blocked pattern detection in args, tool names, and outputs * Model input/output content filtering * Cedar evaluator passthrough * Shared kernel state across middleware instances * Deprecation warning verification * Full backward compatibility with existing wrap() API Resolves microsoft#1584 Co-authored-by: Nishar <you@example.com>
…eware (microsoft#1585) Replaces fragile proxy-based wrapping with LangChain's native AgentMiddleware system (wrap_tool_call / wrap_model_call). Changes: - Add GovernanceMiddleware class implementing wrap_tool_call and wrap_model_call lifecycle hooks for native governance gating - Add as_middleware() factory on LangChainKernel for clean integration - Implement blocked-pattern checks on both tool and model outputs - Support pre_execute/post_execute Cedar/OPA gates on tool calls - Support content filtering on model inputs and outputs - Deprecate LangChainKernel.wrap() and module-level wrap() with clear migration path to as_middleware() - Add comprehensive test suite (39 tests) covering: * Tool allowlist/blocklist enforcement * Blocked pattern detection in args, tool names, and outputs * Model input/output content filtering * Cedar evaluator passthrough * Shared kernel state across middleware instances * Deprecation warning verification * Full backward compatibility with existing wrap() API Resolves microsoft#1584 Co-authored-by: Nishar <you@example.com>
Summary
Replaces the fragile proxy-based wrapping in
LangChainKernelwith LangChain's nativeAgentMiddlewaresystem, enabling non-invasive governance viawrap_tool_callandwrap_model_calllifecycle hooks.This mirrors the architecture established by the Google ADK refactor and OpenAI Agents SDK refactor, completing the standardization of native middleware across all three major framework integrations.
Resolves #1584
What Changed
New:
GovernanceMiddleware(AgentMiddleware)wrap_tool_call: Intercepts every tool execution with:pre_executegatepost_executewrap_model_call: Intercepts every model invocation with:wrap():New:
LangChainKernel.as_middleware(name="governance")Factory method that returns a
GovernanceMiddlewareinstance ready forcreate_agent(middleware=[...]).Deprecated:
LangChainKernel.wrap()and module-levelwrap()Both now emit
DeprecationWarningpointing users toas_middleware(). Existing functionality is fully preserved for backward compatibility.Updated:
__init__.pyexportsGovernanceMiddlewareis now exported asLangChainGovernanceMiddlewarefromagent_os.integrations.Migration Path
Test Results
39/39 new tests pass + 18/18 existing LangChain tests pass (full backward compatibility).
wrap_tool_callgovernancewrap_model_callgovernanceas_middleware()integrationDesign Decisions
PolicyViolationError. No silent failures.GovernanceMiddlewareinherits fromAgentMiddlewarewhen LangChain is installed, falls back toobjectotherwise — the module stays importable in all environments.BaseIntegration.post_execute()only handles drift/checkpointing.GovernanceMiddlewareinstances can be stacked, each sharing the kernel's audit state.