Дозволи агента у Python: що агенту дозволено робити (повний приклад)

Навчальний runnable приклад, який показує policy boundary, рівні доступу і принцип мінімальних дозволів.
На цій сторінці
  1. Що цей приклад демонструє
  2. Структура проєкту
  3. Як запустити
  4. Що ми будуємо в коді
  5. Код
  6. tools.py — інструменти з різним рівнем ризику
  7. gateway.py — policy boundary (дозволити або заблокувати)
  8. main.py — сценарій із дозволеними і забороненими діями
  9. requirements.txt
  10. Приклад виводу
  11. Що видно на практиці
  12. Що змінити в цьому прикладі
  13. Повний код на GitHub

Це повна навчальна реалізація прикладу зі статті Що агенту дозволено робити (і що ні).

Якщо ти ще не читав статтю, почни з неї. Тут фокус лише на коді: як система дозволяє або блокує дії агента до виконання.


Що цей приклад демонструє

  • Як система ділить дії за рівнями: read, write, execute, delete
  • Як policy gateway перевіряє права перед кожним викликом
  • Як принцип мінімальних дозволів блокує небезпечні дії
  • Як агент може продовжити задачу навіть після блокування частини кроків

Структура проєкту

TEXT
foundations/
└── allowed-actions/
    └── python/
        ├── main.py           # кроки моделі + лог gateway-рішень
        ├── gateway.py        # policy boundary і перевірка дозволів
        ├── tools.py          # інструменти різних рівнів ризику
        └── requirements.txt

Як запустити

1. Клонуй репозиторій і перейди в папку:

BASH
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd foundations/allowed-actions/python

2. Встанови залежності (для цього прикладу зовнішніх пакетів немає):

BASH
pip install -r requirements.txt

3. Запусти демо:

BASH
python main.py

Що ми будуємо в коді

Ми будуємо простий policy gateway між моделлю і інструментами.

  • модель пропонує дію (action + parameters)
  • gateway визначає рівень дії (read/write/execute/delete)
  • policy порівнює рівень дії з дозволами агента
  • якщо рівень заборонений, система повертає керовану помилку і не виконує інструмент

Ключова ідея: модель пропонує, а policy вирішує.


Код

tools.py — інструменти з різним рівнем ризику

PYTHON
from typing import Any

USERS: dict[int, dict[str, Any]] = {
    42: {"id": 42, "name": "Anna", "status": "active"},
}


def read_user(user_id: int) -> dict[str, Any]:
    user = USERS.get(user_id)
    if not user:
        return {"ok": False, "error": f"user {user_id} not found"}
    return {"ok": True, "user": dict(user)}


def update_user_status(user_id: int, status: str) -> dict[str, Any]:
    user = USERS.get(user_id)
    if not user:
        return {"ok": False, "error": f"user {user_id} not found"}
    user["status"] = status
    return {"ok": True, "user": dict(user)}


def send_webhook(event: str) -> dict[str, Any]:
    return {"ok": True, "sent": event}


def delete_user(user_id: int) -> dict[str, Any]:
    if user_id not in USERS:
        return {"ok": False, "error": f"user {user_id} not found"}
    del USERS[user_id]
    return {"ok": True, "deleted": user_id}

gateway.py — policy boundary (дозволити або заблокувати)

PYTHON
from typing import Any

from tools import delete_user, read_user, send_webhook, update_user_status

TOOL_REGISTRY = {
    "read_user": read_user,
    "update_user_status": update_user_status,
    "send_webhook": send_webhook,
    "delete_user": delete_user,
}

TOOL_LEVEL = {
    "read_user": "read",
    "update_user_status": "write",
    "send_webhook": "execute",
    "delete_user": "delete",
}

# Least-privilege policy for this agent.
AGENT_ALLOWED_LEVELS = {"read", "write"}


def execute_action(call: dict[str, Any], history: list[dict[str, Any]]) -> dict[str, Any]:
    action = str(call.get("action") or "")
    params = call.get("parameters") or {}
    level = TOOL_LEVEL.get(action, "unknown")

    history.append({"action": action, "level": level, "status": "requested"})

    tool = TOOL_REGISTRY.get(action)
    if tool is None:
        history.append({"action": action, "level": level, "status": "blocked"})
        return {
            "ok": False,
            "action": action,
            "error": f"action '{action}' is not found",
            "history": list(history),
        }

    if level not in AGENT_ALLOWED_LEVELS:
        history.append({"action": action, "level": level, "status": "blocked"})
        return {
            "ok": False,
            "action": action,
            "error": f"action '{action}' is blocked by policy (level={level})",
            "history": list(history),
        }

    result = tool(**params)
    history.append({"action": action, "level": level, "status": "allowed"})

    return {
        "ok": True,
        "action": action,
        "result": result,
        "history": list(history),
    }

main.py — сценарій із дозволеними і забороненими діями

PYTHON
import json

from gateway import execute_action

MODEL_CALLS = [
    {"action": "read_user", "parameters": {"user_id": 42}},
    {"action": "send_webhook", "parameters": {"event": "user-reviewed"}},
    {"action": "update_user_status", "parameters": {"user_id": 42, "status": "paused"}},
    {"action": "delete_user", "parameters": {"user_id": 42}},
]


def compact_result(execution: dict) -> str:
    base = (
        '{'
        f'"ok": {str(bool(execution.get("ok"))).lower()}, '
        f'"action": {json.dumps(execution.get("action"), ensure_ascii=False)}, '
        '"history": [{...}]'
    )

    if execution.get("ok"):
        return (
            base
            + ', '
            + '"result": '
            + json.dumps(execution.get("result"), ensure_ascii=False)
            + '}'
        )

    return (
        base
        + ', '
        + '"error": '
        + json.dumps(execution.get("error"), ensure_ascii=False)
        + '}'
    )


def run() -> None:
    history: list[dict] = []

    for step, call in enumerate(MODEL_CALLS, start=1):
        print(f"\n=== STEP {step} ===")
        print("Model call:", json.dumps(call, ensure_ascii=False))

        execution = execute_action(call, history)
        print("Gateway result:", compact_result(execution))

    print("\nDone: policy boundary demonstrated.")


if __name__ == "__main__":
    run()

requirements.txt

TEXT
# No external dependencies for this learning example.

Приклад виводу

TEXT
python main.py

=== STEP 1 ===
Model call: {"action": "read_user", "parameters": {"user_id": 42}}
Gateway result: {"ok": true, "action": "read_user", "history": [{...}], "result": {"ok": true, "user": {"id": 42, "name": "Anna", "status": "active"}}}

=== STEP 2 ===
Model call: {"action": "send_webhook", "parameters": {"event": "user-reviewed"}}
Gateway result: {"ok": false, "action": "send_webhook", "history": [{...}], "error": "action 'send_webhook' is blocked by policy (level=execute)"}

=== STEP 3 ===
Model call: {"action": "update_user_status", "parameters": {"user_id": 42, "status": "paused"}}
Gateway result: {"ok": true, "action": "update_user_status", "history": [{...}], "result": {"ok": true, "user": {"id": 42, "name": "Anna", "status": "paused"}}}

=== STEP 4 ===
Model call: {"action": "delete_user", "parameters": {"user_id": 42}}
Gateway result: {"ok": false, "action": "delete_user", "history": [{...}], "error": "action 'delete_user' is blocked by policy (level=delete)"}

Done: policy boundary demonstrated.

Примітка: у виводі history навмисно показано у скороченому вигляді — "history": [{...}].
Головне в цьому демо: read/write проходять, а execute/delete блокує policy gateway.


Що видно на практиці

Без policy boundaryЗ policy boundary
Модель може запустити execute/delete
Є явне правило least privilege
Є керований fallback при забороненій дії

Що змінити в цьому прикладі

  • Дозволь тільки read і перевір, що навіть update_user_status почне блокуватись
  • Додай окремий allowlist по конкретних action (не лише по level)
  • Додай dry_run режим, де write проходить валідацію, але не змінює стан
  • Додай human-approval крок перед будь-яким delete

Повний код на GitHub

У репозиторії лежить повна версія цього демо: рівні доступу, policy boundary і short-history логування.

Переглянути повний код на GitHub ↗
⏱️ 5 хв читанняОновлено 4 березня 2026 р.Складність: ★☆☆
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Дозволами на інструменти (allowlist / blocklist)
  • Kill switch та аварійна зупинка
  • Ідемпотентність і dedupe
  • Audit logs та трасування
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.

Автор

Микола — інженер, який будує інфраструктуру для продакшн AI-агентів.

Фокус: патерни агентів, режими відмов, контроль рантайму та надійність систем.

🔗 GitHub: https://github.com/mykolademyanov


Редакційна примітка

Ця документація підготовлена з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Контент базується на реальних відмовах, постмортемах та операційних інцидентах у розгорнутих AI-агентних системах.