feat(policy): intent/scope, decision trace, and stable reason codes#78
Merged
Conversation
Adds three additive surfaces to the policy layer, all closing open issues on the same files and the same conceptual area: #72 — Intent and scope on CapabilityRequest - New optional fields ``intent: str | None`` and ``scope: dict[str, Any]`` on ``CapabilityRequest`` complement the existing free-text ``goal``. - ``DeclarativePolicyEngine`` rules can match on these via new ``intent: [...]`` and ``scope: {key: value}`` clauses (``"*"`` = require key with any value). Intent-aware rules fail closed: a request without an intent never matches a rule that requires one. #73 — Structured policy decision trace - New dataclasses ``PolicyTraceStep`` and ``PolicyDecisionTrace`` record every step a policy engine walked (rule considered, outcome, constraint applied, terminal reason code). - Both built-in engines attach a trace to every ``PolicyDecision`` on allow and deny paths. - ``DryRunResult.policy_decision`` now also carries a synthesized single-step ``token_verified`` trace so dry-run consumers see a uniformly-shaped trace field. - Traces are safe to log/serialize: rule names, condition names, and codes only — never raw argument values. #77 — Stable denial reason codes - New ``agent_kernel.policy_reasons`` module exposes ``DenialReason`` and ``AllowReason`` enums (str-compatible across Python 3.10+). - Every built-in denial path on ``DefaultPolicyEngine`` and ``DeclarativePolicyEngine`` populates ``reason_code`` on ``PolicyDecision``, ``DenialExplanation``, ``FailedCondition``, and ``PolicyDenied``. - ``PolicyDenied`` gained an optional ``reason_code`` keyword and attribute, so callers can branch on the code instead of matching the human-readable message. Tests - 46 new tests across ``test_policy.py``, ``test_models.py``, and ``test_kernel.py`` exercise every built-in deny path's code, the trace shape on allow/deny for both engines, intent/scope matching and explain coverage, DSL parser validation for ``intent``/``scope``, and ``PolicyDenied`` round-trip. - Full suite: 450 passed (was 404). ``make ci`` green: ruff format + ruff check + mypy strict + pytest --cov + examples. Docs / changelog - ``docs/architecture.md`` gains sections for intent/scope, reason codes (with a table), and the decision trace. - ``CHANGELOG.md`` ``[Unreleased]`` documents the three changes and new public exports (``DenialReason``, ``AllowReason``, ``PolicyDecisionTrace``, ``PolicyTraceStep``). Closes #72, #73, #77.
There was a problem hiding this comment.
Pull request overview
This PR extends the policy layer with three additive, backward-compatible surfaces: structured request metadata (intent/scope), stable allow/deny reason codes, and a structured policy decision trace that is attached to built-in policy decisions (including a synthesized trace for dry-run decisions in the Kernel).
Changes:
- Add
CapabilityRequest.intentandCapabilityRequest.scope, and extend the declarative policy DSL to match onintentandscope. - Introduce
AllowReason/DenialReasonenums and propagatereason_codethroughPolicyDecision,DenialExplanation,FailedCondition, andPolicyDenied. - Add
PolicyDecisionTrace/PolicyTraceStepand populate traces inDefaultPolicyEngine,DeclarativePolicyEngine, and Kernel dry-run results.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_policy.py | Adds coverage for reason codes, traces, and intent/scope matching + DSL validation. |
| tests/test_models.py | Adds model-level tests for new request fields, trace dataclasses, and reason-code round trips. |
| tests/test_kernel.py | Verifies Kernel propagation of intent/scope, reason codes, and dry-run synthesized trace. |
| src/agent_kernel/policy.py | Adds reason codes + structured trace emission to DefaultPolicyEngine. |
| src/agent_kernel/policy_dsl.py | Adds intent/scope matching, reason codes, and structured trace emission to DeclarativePolicyEngine. |
| src/agent_kernel/policy_reasons.py | New module defining stable AllowReason / DenialReason enums. |
| src/agent_kernel/models.py | Extends request/decision/explanation models with intent/scope, reason_code, and trace types. |
| src/agent_kernel/kernel.py | Synthesizes a single-step policy trace for dry-run results and sets reason_code accordingly. |
| src/agent_kernel/errors.py | Extends PolicyDenied to carry an optional reason_code. |
| src/agent_kernel/init.py | Exports new public surfaces (reason enums + trace dataclasses). |
| docs/architecture.md | Documents intent/scope, reason codes, and decision trace. |
| CHANGELOG.md | Documents new surfaces and behavior under [Unreleased]. |
…e reason codes - Redact scope values in PolicyDecisionTrace: store only scope_keys (list of dimension names) instead of full scope dict. Traces are now genuinely safe to log/serialize as documented. - Remove implicit str() coercion in scope matching (_matches and explain). Non-string scope values now fail-closed instead of silently matching via string conversion. - Add AllowReason.TOKEN_VERIFIED enum member; replace magic string in kernel.py dry-run path. - Update docs/architecture.md to reflect scope_keys and new allow code. - Fix max_rows detail string to avoid leaking constraint values. Addresses review comments on #78.
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.
Adds three additive surfaces to the policy layer, all closing open issues
on the same files and the same conceptual area:
#72 — Intent and scope on CapabilityRequest
intent: str | Noneandscope: dict[str, Any]on
CapabilityRequestcomplement the existing free-textgoal.DeclarativePolicyEnginerules can match on these via newintent: [...]andscope: {key: value}clauses ("*"=require key with any value).
matches a rule that requires one.
don't match the expected type are denied (fail-closed).
#73 — Structured policy decision trace
PolicyTraceStepandPolicyDecisionTracerecordevery step a policy engine walked (rule considered, outcome,
constraint applied, terminal reason code).
PolicyDecisiononallow and deny paths.
DryRunResult.policy_decisioncarries a synthesized single-steptoken_verifiedtrace so dry-run consumers see a uniformly-shapedtrace field.
names, IDs, and codes only — never raw argument values.
scope_keysstores only dimension names (values redacted).#77 — Stable denial reason codes
agent_kernel.policy_reasonsmodule exposesDenialReasonand
AllowReasonenums (str-compatible across Python 3.10+ viaa
(str, Enum)mixin with__str__override).DefaultPolicyEngineandDeclarativePolicyEnginepopulatesreason_codeonPolicyDecision,DenialExplanation,FailedCondition, andPolicyDenied.PolicyDeniedgained an optionalreason_codekeyword andattribute, so callers can branch on the code instead of matching
the human-readable message.
Tests
test_policy.py,test_models.py, andtest_kernel.pyexercise every built-in deny path's code, thetrace shape on allow/deny for both engines, intent/scope matching
and explain coverage, DSL parser validation for
intent/scope,and
PolicyDeniedround-trip.ruff format + ruff check + mypy strict + pytest --cov + examples.
Docs / changelog
docs/architecture.mdgains sections for intent/scope, reasoncodes (with a full table of all codes), and the decision trace.
CHANGELOG.md[Unreleased]documents the three changes andnew public exports (
DenialReason,AllowReason,PolicyDecisionTrace,PolicyTraceStep).Closes #72, #73, #77.