Repository navigation
[AAASM-6445] 🐛 (python-sdk): Govern LangChain tool calls from init_assembly() - #389
Open
Chisanan232 wants to merge 9 commits into
Open
Chisanan232 wants to merge 9 commits into
Chisanan232 wants to merge 9 commits into
Conversation
init_assembly() stored an AssemblyCallbackHandler but never gave it to LangChain, so a denied tool still ran. Register a process-wide proxy handler through langchain_core's public register_configure_hook (default-valued ContextVar so plain threads are covered; handle_class dedupes an explicitly passed handler). raise_error stays True so any handler failure blocks the tool. Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
| def _run() -> None: | ||
| try: | ||
| _write_tool(file_effect).invoke({"content": "denied"}) | ||
| except BaseException as exc: # noqa: BLE001 - captured for the assertion below |
| default=ProcessGovernanceHandler(), # noqa: B039 | ||
| ) | ||
| context.register_configure_hook(var, True, AssemblyCallbackHandler) | ||
| _registered_var = var |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…lready allowed handle_class made LangChain drop the process-wide proxy whenever any AssemblyCallbackHandler was in callbacks, so a handler built around a weaker interceptor silently suppressed the active governance. Track allowed runs by interceptor instead. Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
Also narrow the runtime-failure assertion to ToolExecutionBlockedError. Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
Without it an explicit handler on the active interceptor ended each run twice (duplicate audit/budget side effects). Mark skipped runs, clear on end/error, and use a unique miss sentinel so a registry miss never equals an interceptor. Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
Refs AAASM-6445 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Behaviour change
init_assembly()alone now governs LangChain tool calls, as the README Quick Start has always claimed. Before, the SDK only stored anAssemblyCallbackHandlersingleton and never gave it to LangChain, so a denied tool still executed (QA J08:TOOL_EXECUTED True). Now an allowed tool runs, a denied tool never runs, and a runtime or handler failure underenforceblocks the tool. No application change and no global monkey-patching: the adapter registers one process-wide proxy handler withlangchain_core's publicregister_configure_hook.Owner decision 2026-11-06 applied: original J08 outcome kept, no docs-only reduction. ADR 0033 E2 ("checkpoint reachable only if the agent opts in") is satisfied by calling
init_assembly(); no ADR conflict, no init-contract change.Evidence (real stack, exact README quick start)
Real
aa-gateway+aa-runtime(F3 binaries, isolated HOME, harnessLocalBackend), policy durable viaAA_POLICY(notaasm policy apply), README FakeListLLM code verbatim (denied variant only renames the tool todelete_fileand adds a sentinel file write). Wheel = candidate rc.8 wheel with these two Python files overlaid (see "Not yet verified").whoami: outputalice is in engineering, sentinel written; auditToolCallIntercepted action=whoami verdict=allow.delete_file:ToolExecutionBlockedError: tool denied by policy, sentinel absent; auditPolicyViolation action=delete_file verdict=deny policy_rule=tool:delete_file.runtime unreachable; failing closed under enforce, sentinel absent.Design
ContextVar(default=ProcessGovernanceHandler())registered once withregister_configure_hook(var, True, AssemblyCallbackHandler). The default (not aset()) is deliberate: a plainthreading.Threadhas an empty context and would skip the gate.handle_classmakes LangChain skip the proxy if the app passes a handler itself (no double check). The proxy forwards tool start/end to the active handler and does nothing after shutdown (the hook cannot be unregistered).raise_error=Trueis inherited so LangChain never swallows a failure.Rejected: explicit
callbacks=[handler]in the README (rewrites the promise, an API change); global monkey-patching ofBaseTool.Tests
test/unit/test_quickstart_langchain_governs_on_init.py(needslangchain-testgroup; skipped in the default suite): allow, deny, plain thread, async, no double check, runtime query failure, handler exception, observe, shutdown. Full suite 1454 passed; ruff clean; mypy clean on the changed files (5 pre-existingagent_assembly._coreimport errors in a worktree without the native build).Not yet verified
The rebuilt maturin wheel: the E2E used the candidate wheel plus a Python overlay. Rebuild and re-pin to confirm on the final artifact. LLM-scan callbacks (
on_llm_start) are intentionally not enabled globally.Other PRs
Supersedes #387 (docs-only; its README text is now false, close it). Independent of #388 (snippet resync); README Quick Start code is unchanged here, so either order merges.
Jira: https://lightning-dust-mite.atlassian.net/browse/AAASM-6445
🤖 Generated with Claude Code
https://claude.ai/code/session_01TLpw696KjUDUg4QjH83En4