jeevesagent.security.permissions

Permission decisions for tool calls.

Three modes mirror the Claude Agent SDK so users don’t relearn:

  • DEFAULT — allow non-destructive tools, ask on destructive

  • ACCEPT_EDITS — auto-approve filesystem writes; otherwise like default

  • BYPASS — allow everything (CI / sandbox use only)

Allow- and deny-lists win over modes; deny-list wins over allow-list. The decision flow:

  1. Tool in deny-list → deny

  2. Allow-list set and tool not in it → deny

  3. Mode == BYPASS → allow

  4. Mode == ACCEPT_EDITS and call is a non-destructive edit → allow

  5. Tool is destructive → ask

  6. Otherwise → allow

Classes

AllowAll

Trivial permission policy: every call is allowed.

Mode

Enum where members are also (and must be) strings

PerUserPermissions

Map user_id to a different permission policy.

StandardPermissions

Mode + allow/deny-list permission policy.

Module Contents

class jeevesagent.security.permissions.AllowAll[source]

Trivial permission policy: every call is allowed.

The default for Agent when no permissions are configured.

async check(call: jeevesagent.core.types.ToolCall, *, context: collections.abc.Mapping[str, Any], user_id: str | None = None) jeevesagent.core.types.PermissionDecision[source]
class jeevesagent.security.permissions.Mode[source]

Bases: enum.StrEnum

Enum where members are also (and must be) strings

Initialize self. See help(type(self)) for accurate signature.

ACCEPT_EDITS = 'acceptEdits'
BYPASS = 'bypassPermissions'
DEFAULT = 'default'
class jeevesagent.security.permissions.PerUserPermissions(*, policies: collections.abc.Mapping[str | None, Any], default: Any)[source]

Map user_id to a different permission policy.

The common multi-tenant shape: admins get one policy, paid users get another, free users get a third. Construct with a mapping of user_id -> Permissions plus a default fallback for unmapped users (including None):

from jeevesagent import (
    PerUserPermissions, StandardPermissions, Mode,
)

permissions = PerUserPermissions(
    policies={
        "admin_alice": StandardPermissions(mode=Mode.BYPASS),
        "paid_user_42": StandardPermissions(
            mode=Mode.ACCEPT_EDITS,
        ),
    },
    default=StandardPermissions(
        mode=Mode.DEFAULT,
        denied_tools=["bash", "delete_user"],
    ),
)
agent = Agent("...", permissions=permissions)

Each check call routes to the policy keyed by user_id (the live RunContext’s value, threaded through by the agent loop). When no policy matches, the default decides — most apps want a strict default and add permissive policies for trusted users.

async check(call: jeevesagent.core.types.ToolCall, *, context: collections.abc.Mapping[str, Any], user_id: str | None = None) jeevesagent.core.types.PermissionDecision[source]
class jeevesagent.security.permissions.StandardPermissions(*, mode: Mode = Mode.DEFAULT, allowed_tools: list[str] | None = None, denied_tools: list[str] | None = None)[source]

Mode + allow/deny-list permission policy.

async check(call: jeevesagent.core.types.ToolCall, *, context: collections.abc.Mapping[str, Any], user_id: str | None = None) jeevesagent.core.types.PermissionDecision[source]
classmethod strict() StandardPermissions[source]

Convenience: default-mode permissions with no overrides.