Skip to content

feat(policy): intent/scope, decision trace, and stable reason codes#78

Merged
dgenio merged 3 commits into
mainfrom
claude/triage-issues-J4v3C
May 20, 2026
Merged

feat(policy): intent/scope, decision trace, and stable reason codes#78
dgenio merged 3 commits into
mainfrom
claude/triage-issues-J4v3C

Conversation

@dgenio
Copy link
Copy Markdown
Owner

@dgenio dgenio commented May 20, 2026

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.
  • Scope matching uses strict equality — no type coercion. Values that
    don't match the expected type are denied (fail-closed).

#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 carries a synthesized single-step
    token_verified trace so dry-run consumers see a uniformly-shaped
    trace field.
  • Traces are safe to log/serialize: they contain rule names, condition
    names, IDs, and codes only — never raw argument values.
    scope_keys stores only dimension names (values redacted).

#77 — Stable denial reason codes

  • New agent_kernel.policy_reasons module exposes DenialReason
    and AllowReason enums (str-compatible across Python 3.10+ via
    a (str, Enum) mixin with __str__ override).
  • 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). 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 full table of all codes), and the decision trace.
  • CHANGELOG.md [Unreleased] documents the three changes and
    new public exports (DenialReason, AllowReason,
    PolicyDecisionTrace, PolicyTraceStep).

Closes #72, #73, #77.

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.
Copilot AI review requested due to automatic review settings May 20, 2026 10:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.intent and CapabilityRequest.scope, and extend the declarative policy DSL to match on intent and scope.
  • Introduce AllowReason / DenialReason enums and propagate reason_code through PolicyDecision, DenialExplanation, FailedCondition, and PolicyDenied.
  • Add PolicyDecisionTrace / PolicyTraceStep and populate traces in DefaultPolicyEngine, 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].

Comment thread src/agent_kernel/policy.py Outdated
Comment thread src/agent_kernel/policy_dsl.py Outdated
Comment thread src/agent_kernel/models.py
Comment thread docs/architecture.md Outdated
Comment thread src/agent_kernel/policy_dsl.py
Comment thread src/agent_kernel/kernel.py Outdated
dgenio added 2 commits May 20, 2026 15:29
…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.
@dgenio dgenio merged commit 0b30506 into main May 20, 2026
4 checks passed
@dgenio dgenio deleted the claude/triage-issues-J4v3C branch May 20, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add intent and scope fields to policy decisions

3 participants