Це повна навчальна реалізація прикладу зі статті Що агенту дозволено робити (і що ні).
Якщо ти ще не читав статтю, почни з неї. Тут фокус лише на коді: як система дозволяє або блокує дії агента до виконання.
Що цей приклад демонструє
- Як система ділить дії за рівнями:
read,write,execute,delete - Як policy gateway перевіряє права перед кожним викликом
- Як принцип мінімальних дозволів блокує небезпечні дії
- Як агент може продовжити задачу навіть після блокування частини кроків
Структура проєкту
foundations/
└── allowed-actions/
└── python/
├── main.py # кроки моделі + лог gateway-рішень
├── gateway.py # policy boundary і перевірка дозволів
├── tools.py # інструменти різних рівнів ризику
└── requirements.txt
Як запустити
1. Клонуй репозиторій і перейди в папку:
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd foundations/allowed-actions/python
2. Встанови залежності (для цього прикладу зовнішніх пакетів немає):
pip install -r requirements.txt
3. Запусти демо:
python main.py
Що ми будуємо в коді
Ми будуємо простий policy gateway між моделлю і інструментами.
- модель пропонує дію (
action+parameters) - gateway визначає рівень дії (
read/write/execute/delete) - policy порівнює рівень дії з дозволами агента
- якщо рівень заборонений, система повертає керовану помилку і не виконує інструмент
Ключова ідея: модель пропонує, а policy вирішує.
Код
tools.py — інструменти з різним рівнем ризику
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 (дозволити або заблокувати)
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 — сценарій із дозволеними і забороненими діями
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
# No external dependencies for this learning example.
Приклад виводу
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 ↗