Skip to content

Documentation navigation

Permissions & Dry-run

Permission flags, destructive markers, and the dry-run contract.

#Permissions and dry-run

A tool that reads your calendar is very different from a tool that empties it. mAIvn makes that difference explicit: every tool declares what kind of work it does, so the agent host can enforce least privilege, warn a user before something irreversible runs, or refuse to register a destructive tool without operator approval. By default maivn-tools assumes a tool is read-only — anything that mutates provider state has to opt in.

This page covers the two mechanisms behind that contract: permission flags (what a tool is allowed to touch) and dry-run (let a tool describe its changes without making them).

The permission types live in the core SDK (maivn), so a custom toolset does not need to install maivn-tools just to declare permissions on its own methods:

Python
from maivn import PermissionFlag, PermissionSet, require_permissions, toolify, toolset

For callers that depend on the original layout, maivn_tools.core.permissions re-exports PermissionFlag, PermissionSet, and require_permissions. New code should import the permission types and decorators from maivn directly.

#Permission flags

PermissionFlag is a Flag enum: combine values with |.

FlagMeaning
READReads existing data.
WRITECreates or updates data.
DELETEDeletes or archives data.
EXPORTBulk exports data.
IMPORTBulk imports or replaces data.
ADMINTouches admin-only surfaces (users, roles, audit).
IMPERSONATEActs on behalf of another principal.

PermissionSet wraps a flag bitfield and offers ergonomic helpers:

Python
from maivn import PermissionFlag, PermissionSet, require_permissions

write_only = PermissionSet(PermissionFlag.WRITE)
all_perms = PermissionSet.all()

assert PermissionFlag.WRITE in write_only
assert write_only.to_list() == ["write"]
assert PermissionSet.from_names(["read", "write"]).flags == (
    PermissionFlag.READ | PermissionFlag.WRITE
)
assert PermissionSet(PermissionFlag.DELETE).is_destructive()

Hosts can run their own gate:

Python
require_permissions(
    granted=PermissionSet(PermissionFlag.READ),
    required=PermissionSet(PermissionFlag.READ | PermissionFlag.WRITE),
)
# Raises PermissionError: Missing required permissions: ['write']

#Declaring tool permissions

Generic adapters expose permission attributes for host inspection. For SDK registration, declare custom-tool metadata with @toolify:

Python
@toolify(
    permissions=PermissionSet(PermissionFlag.WRITE | PermissionFlag.DELETE),
    destructive=True,
)
def archive_message(message_id: str) -> dict:
    return provider_api.archive(message_id)

Metadata describes intended behavior; it does not enforce provider authorization. An include_tags=["read"] filter also matches tools tagged with both READ and WRITE. Generic query or workflow tools can perform operations beyond their tag. For read-only use, choose explicit methods and restrict provider credentials.

destructive=True is a coarser hint than permissions. It is the right signal for a host UI to warn users; permissions is the right signal for machine-readable gating.

#Dry run

Write-capable tools should accept a dry_run flag. When it is true the tool must:

  1. Validate the inputs as if the call were real.
  2. Compute the change set it would apply.
  3. Return a DryRunOutcome describing the planned change.
  4. Make no provider mutation.
Python
from maivn_tools import DryRunOutcome, DryRunPlan, dry_run_capable


@dry_run_capable
def archive_message(message_id: str, *, dry_run: bool = False) -> DryRunOutcome | dict:
    if dry_run:
        return DryRunOutcome(
            tool="archive_message",
            plans=(
                DryRunPlan(
                    operation="archive",
                    target=f"messages/{message_id}",
                    before={"folder": "inbox"},
                    after={"folder": "archive"},
                ),
            ),
        )
    return provider_api.archive(message_id)

@dry_run_capable is a marker decorator. It does not change behavior but attaches __maivn_dry_run_capable__ = True to the callable so hosts and tests can discover which tools honor the contract without executing them.

  • Declare permissions for each tool; do not rely on a default READ marker to establish safety.
  • Write tools opt in to WRITE only.
  • Delete, export, import, and admin tools opt in explicitly to those flags in addition to destructive=True.
  • Tools that act on behalf of another user set IMPERSONATE and document the impersonation contract.