Repository navigation
feat(middleware): add on_check_permission hook - #2001
Conversation
Add opt-in, read-only middleware hooks that make every permission decision and user-confirmation outcome observable through extra_agent_middlewares, closing the audit blind spot where ASK/DENY and BYPASS-suppressed decisions were invisible to middleware. ## Permission decision observability (on_permission_decision) - PermissionEvaluation dataclass (effective + candidate decision + PermissionResolution enum) returned by new evaluate_permission; check_permission delegates to it for backward compatibility. - All five _check_<mode> methods preserve the candidate ASK when a mode transforms it: BYPASS_ASK_SUPPRESSED, ASK_CONVERTED_TO_DENY, ASK_OVERRIDDEN_BY_ALLOW_RULE, USER_CONFIRMED, DIRECT. - on_permission_decision read-only hook fires before the agent consumes the decision; Agent covers the user-confirmed reuse path. ## Permission confirmation observability (on_permission_confirmation) - Separate hook for user approval/rejection results. Confirmation is user input, not an engine decision, so it is kept out of PermissionEvaluation. - Fires before state transition, add_rule, and execution. ## Read-only contract enforced by isolation Both hooks deep-copy evaluation/tool_input/tool_call (decision) and tool_call/rules (confirmation) per observer, so observers cannot mutate the consumed result, executed input, or applied rules. agent and tool are passed as-is (live runtime context and toolkit-shared instance, matching every other middleware hook's convention). ## Tests - Engine: per-mode evaluation semantics (effective/candidate/resolution) including BYPASS suppression, DONT_ASK conversion, and allow-rule override. - Agent integration: ALLOW/DENY/ASK/BYPASS-suppress/USER_CONFIRMED/ external/multi-call/no-observer/exception/mutation-isolation. - check_permission backward-compat regression. - No real destructive commands run — side-effect-free demo tools emit safety ASKs instead. No changes to event/message/storage/UI schemas or permission behavior. ## Runnable permission audit example - Includes examples/permission_audit_service and focused importlib-based tests demonstrating permission decision and confirmation audit records.
|
I pulled this branch locally and did a quick validation pass for #2000. Passing: .venv/bin/python -m pytest -p no:cacheprovider tests/permission_engine_test.py tests/permission_mode_test.py tests/middleware_test.py tests/agent_basic_test.py tests/permission_audit_example_test.py -q
# 152 passed in 1.13sOne thing that looks pending before review/merge: file-scoped repo pre-commit currently fails locally: .venv/bin/pre-commit run --files src/agentscope/agent/_agent.py src/agentscope/middleware/_base.py src/agentscope/permission/__init__.py src/agentscope/permission/_engine.py src/agentscope/permission/_evaluation.py tests/middleware_test.py tests/permission_audit_example_test.py tests/permission_engine_test.py tests/permission_mode_test.pyObserved failures:
The behavior shape looks good from the first pass; this is mostly local gate hygiene since GitHub currently only shows the lightweight |
Add type annotations to nested middleware test subclasses and example hook overrides (on_permission_decision / on_acting / on_permission_confirmation) to satisfy mypy --disallow-untyped-defs. Add Google-style docstrings to permission_audit_example_test tests. Replace a direct __call__ invocation with direct call (pylint C2801). Assert spec is not None before module_from_spec. No functional logic changed; 151 passed, 1 skipped.
|
Thanks @Premsenareddy for the validation pass. The pre-commit issues you flagged are fixed in
|
|
@Miracle778 Plz see comments on issue #2000 |
|
@Miracle778, thanks for the detailed implementation. A few points after reviewing the PR and our discussion in #2000: 1. Hook type: use onion pattern, not read-only notification Suggest making the hook an onion-pattern hook (with 2. Remove Adding a parallel abstraction layer on top of
Keep 3. Decision path observability: defer to a separate PR / issue I agree that exposing the decision transformation path is valuable for debugging and attribution. However, this should not be part of this PR — let's keep this PR focused on the middleware hook only. Please open a new issue to discuss how to record decision paths (e.g., whether to add fields to 4. As discussed, |
|
Thanks @DavdGao, understood. I’ll revise this PR to:
I’ll update the implementation, tests, example, and PR description accordingly. |
…ck-permission # Conflicts: # src/agentscope/agent/_agent.py # src/agentscope/permission/_engine.py # tests/permission_mode_test.py
|
@DavdGao The requested scope update is now complete. The current revision:
I have also updated the issue and PR titles and descriptions to match the The latest example includes a bilingual README and screenshots covering ASK Ready for another review when you have time. Thanks! |
Extract `_check_permission_impl` as a dedicated method mirroring `_acting_impl` / `_compress_context_impl`, and restructure `_check_permission` to the canonical `if not middlewares / else: execute_chain()` shape (no-arg chain entry, deepcopy hoisted ahead of the chain). Behavior and copy semantics are unchanged. Also drop the permission middleware example and its dedicated test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Built and live-verified a small The middleware calls Tested live against both cases from the tool description above (real HTTP calls, not mocked): Code: https://gist.github.com/babyblueviper1/cd6e4666bcf81f3ad139450fba53a0e2 (happy to open a PR against Small thing I noticed while building this — the PR description above lists |
…6-08-06 Core moved while the v1 draft sat; the big deltas: - agentscope-ai#1995 closed 'core gap 1' (hard interrupt): stop reason now derives from ReplyEndEvent.finished_reason; parked replies abort via the new UserInterruptEvent input - agentscope-ai#2117: DEFAULT-mode read-only fast path (Read never prompts), batch confirmation de-duplication, bypass-immune safety ASKs - agentscope-ai#2001: on_check_permission middleware — documented as an alternative permission-bridge design; park/resume kept, with rationale - op-id binding (invariant c) redesigned: consumer-side ContextVar timing cannot reach concurrent tool tasks; a tool middleware claims pending calls from inside the tool's own context instead - corrections: Glob uses the bundled _glob_helper.py (never find); stop_on_reject is dead config; SDK pin -> 0.12.0 and v1 schema/SDK version facts fixed; workspace backend list extended; new channel subsystem (agentscope-ai#1997) added to the adapter framing
--------- Co-authored-by: DavdGao <gaodawei.gdw@alibaba-inc.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Note
Scope update
This PR originally implemented structured decision-path observability and
separate read-only hooks for permission decisions and confirmations.
Following the maintainer feedback in #2000, the PR has been narrowed to one
standard onion-style
on_check_permissionhook. The structured evaluationtypes and confirmation hook have been removed. Decision-path observability
will be discussed separately.
AgentScope Version
2.0.4.post1
Description
Closes #2000.
Adds a standard onion-style
on_check_permissionmiddleware hook aroundpermission checking.
The hook provides a public extension point for both permission auditing and
application-owned authorization policies, while keeping
PermissionEngine.check_permissionas the sole built-in engine interface.Background
AgentScope performs permission checking after tool resolution and input
validation, but before
on_acting.This means existing middleware cannot observe the permission decision itself:
on_acting;on_acting, but only as an already-permitted execution;the built-in permission check through a public middleware interface.
Changes
on_check_permissionmiddleware hookAdds the following hook to
MiddlewareBase:The hook runs after tool resolution and input validation, and before Agent
consumes the returned decision.
Its
input_kwargscontains:tool_call;tool;tool_input.As a standard middleware onion, a middleware may:
next_handler(**input_kwargs);The built-in
PermissionEngine.check_permissionis the innermost handler.With no permission middleware registered, Agent follows the existing direct
engine path.
Confirmed tool calls
A tool call resumed after user confirmation still traverses
on_check_permissionimmediately before execution.The innermost handler returns the already-confirmed ALLOW without re-evaluating
the built-in engine. This keeps application policies and auditing active while
preserving the existing confirmation behavior.
Input isolation
The middleware chain receives copies of
tool_callandtool_input.Middleware may forward modified copies to downstream permission middleware or
the built-in permission check, but those changes do not alter the tool call or
input eventually executed by Agent.
Runnable example
Adds
examples/permission_middleware_service/with:PermissionAuditMiddleware, which records the final decision returned bythe complete permission chain;
UserToolPolicyMiddleware, which deniesPermissionDemoToolfor aconfigured application user before invoking the built-in engine;
PermissionDemoToolcovering ASK, DENY, and ALLOW;screenshots.
The example demonstrates both primary uses of the hook:
Tests
Tests cover:
Backward compatibility
PermissionEngine.check_permissionremains the sole engine interface.PermissionDecisionsemantics areunchanged.
on_check_permissionmiddleware retain the existingexecution path.
Security boundary
A middleware that replaces or short-circuits a permission decision becomes part
of the application's trusted authorization boundary.
Returning without calling
next_handlerintentionally bypasses the built-inpermission engine for that call. This behavior is documented explicitly in the
hook contract.
Out of scope
observable through
on_reply.How to test
pre-commit run --files $(git diff --name-only origin/main...HEAD)Run the example with Redis and an LLM configured:
cd examples/permission_middleware_service python main.pyThen connect the existing
examples/web_uifrontend tohttp://localhost:8000.Checklist
CONTRIBUTING.md