Ідея за 30 секунд
Write Access by Default — це анти-патерн, коли агент отримує write-інструменти за замовчуванням, без policy-gate і перевірки перед виконанням дії.
У результаті помилка моделі або хибний маршрут перетворюється не просто на неправильну відповідь, а на реальну зміну зовнішнього стану.
Просте правило: write має бути не "за замовчуванням", а тільки через окремий контрольований шлях із явною перевіркою прав, контексту й умов виконання.
Приклад анти-патерну
Команда будує support-агента, який читає статус замовлення і за потреби може закрити тікет або надіслати лист.
Агент отримує write-tools одразу, без окремого етапу перевірки перед дією.
decision = agent.decide_next_action(user_message)
# route помилковий, але write все одно доступний
result = run_tool(decision.tool, decision.args)
return result
У такій схемі немає базового захисту:
# немає deny-by-default для write-tools
# немає approval_required для ризикових дій
# немає idempotency_key для повторних запусків
# немає жорсткого tenant/env scope
Для цього кейсу потрібен policy-gate перед будь-яким write-кроком:
if decision.tool in WRITE_TOOLS and not is_write_allowed(ctx, decision):
return stop("approval_required")
Якщо умови не виконані, run не має переходити до зовнішньої write-дії.
У цьому випадку Write Access by Default додає:
- ризик небажаних змін у зовнішніх системах
- дубльовані write-операції при retries
- більший blast radius у multi-tenant середовищі
Чому виникає і що йде не так
Цей анти-патерн часто з’являється, коли команда хоче "щоб агент був максимально автономним" і відкриває write-доступ раніше, ніж будує контрольні межі.
Типові причини:
- демо-підхід: спочатку дати всі інструменти, а обмеження додати потім
- відсутність явного розділення
readіwriteмаршрутів - правила доступу описані в prompt, а не примусово застосовані у gateway
- немає обов’язкового
idempotency_keyдля write-операцій
У результаті виникають проблеми:
- небезпечні side effects (зміни стану) — агент може виконати write там, де мав лише читати
- повторні дії — retry або loop повторює ту саму write-операцію
- високий blast radius — без жорсткого scope помилка торкається не того tenant/env
- складний інцидент-аналіз — важко швидко довести, чому write був дозволений
- втрата передбачуваності — команда не контролює, коли саме система переходить до write
На відміну від Blind Tool Trust, тут головна проблема не у валідації payload, а в тому, що write-доступ відкритий за замовчуванням.
Типові production-сигнали, що write-контроль слабкий:
- write-tools викликаються навіть у сценаріях, які мали бути read-only
- у логах багато write-викликів з однаковим
args_hashабо безidempotency_key approval_requiredмайже не з’являється, хоча частка write-операцій висока- blocked write attempts майже не трапляються, хоча система регулярно пропонує risky actions
- audit-логи не показують, яке policy-правило дозволило write
- команда не може чітко пояснити, чому конкретний write був дозволений саме в цьому run
- помилки виявляються вже після зовнішньої дії, а не на policy-gate
Важливо, що кожен write-call змінює зовнішній стан і часто не має простого rollback. Без deny-by-default і контрольованого write-шляху один невдалий inference стає production-інцидентом.
Правильний підхід
Починайте з read-first моделі: read-інструменти доступні за маршрутом, а write-кроки проходять окрему перевірку через policy-gate.
Практична рамка:
- застосовуйте deny-by-default для всіх write-tools
- розділяйте маршрути
read_onlyіwrite_candidate - вимагайте approval для ризикових write-дій
- беріть
tenant_idіenvлише з authenticated context, а не з model output - додавайте
idempotency_keyдо кожної write-операції - логуйте
stop_reasonі рішення policy-gate для кожного write-кроку
WRITE_TOOLS = {"ticket.close", "refund.create", "email.send"}
def execute_action(user_message: str, ctx: dict):
decision = agent.next_action(user_message)
if decision.tool in WRITE_TOOLS:
if not is_write_allowed(ctx, decision): # policy gate: role, route allowlist, tenant/env scope
return stop("approval_required")
scoped_args = enforce_scope(
decision.args,
tenant_id=ctx["tenant_id"],
env=ctx["env"],
)
scoped_args["idempotency_key"] = make_idempotency_key(ctx["run_id"], decision)
return run_tool(decision.tool, scoped_args)
return run_tool(decision.tool, decision.args) # read-only tool from allowed set
У такій схемі write-крок стає контрольованим: система або безпечно виконує дію, або прозоро зупиняє run.
Швидкий тест
Якщо на ці питання відповідь "так", у вас є ризик анти-патерну Write Access by Default:
- Чи write-інструмент може викликатись без явної policy/approval перевірки?
- Чи retry інколи повторює ту саму write-дію без
idempotency_key? - Чи команда не може швидко пояснити, чому конкретний write був дозволений?
Чим відрізняється від інших анти-патернів
Blind Tool Trust vs Write Access by Default
| Blind Tool Trust | Write Access by Default |
|---|---|
| Основна проблема: tool output приймають без валідації. | Основна проблема: write-доступ відкритий за замовчуванням. |
| Коли виникає: коли немає parse/schema/invariant перевірок перед рішенням. | Коли виникає: коли write-крок не проходить через deny-by-default і approval-gate. |
Якщо коротко: Blind Tool Trust — про якість даних перед дією, а Write Access by Default — про права на саму дію.
Agents Without Guardrails vs Write Access by Default
| Agents Without Guardrails | Write Access by Default |
|---|---|
| Основна проблема: загалом відсутні runtime-межі і policy-контроль. | Основна проблема: саме write-операції не мають жорсткого доступного контуру. |
| Коли виникає: коли система не має чітких safety-policy для execution. | Коли виникає: коли write дозволяється як стандартний шлях, а не як виняток через policy-gate. |
Якщо коротко: Agents Without Guardrails — ширша проблема меж виконання, а Write Access by Default — конкретно про небезпечну модель доступу до write.
Tool Calling for Everything vs Write Access by Default
| Tool Calling for Everything | Write Access by Default |
|---|---|
| Основна проблема: tools викликають зайво, навіть коли можна без них. | Основна проблема: коли вже стався tool-call, write може пройти без належного контролю. |
Коли виникає: коли немає стабільного no_tool маршруту для простих кейсів. | Коли виникає: коли система не розділяє read і write рівні доступу. |
Якщо коротко: Tool Calling for Everything збільшує кількість викликів, а Write Access by Default збільшує ціну помилки кожного write-виклику.
Самоперевірка: чи немає у вас цього анти-патерну
Швидка перевірка на anti-pattern Write Access by Default.
Відмічайте пункти під вашу систему і дивіться статус нижче.
Перевірте свою систему:
Прогрес: 0/8
⚠ Є ознаки цього anti-pattern
Спробуйте винести прості кроки у workflow і залишити агента лише для складних рішень.
FAQ
Q: Чи означає це, що агенту взагалі не можна робити write-дії?
A: Ні. Write-дії можливі, але тільки через контрольований шлях: policy-gate, approval (де потрібно), scope enforcement і idempotency.
Q: Чим відрізняються policy-gate і approval?
A: Policy-gate — це детермінована перевірка правил під час виконання. Approval — окреме підтвердження для конкретної ризикової дії. Це різні рівні контролю.
Q: Який мінімум треба впровадити першим?
A: Почніть із deny-by-default для write, обов’язкового idempotency_key, жорсткого tenant/env scope і логування stop_reason для заблокованих write-спроб.
Що далі
Схожі anti-patterns:
- Blind Tool Trust — коли система діє на неперевіреному tool output.
- Agents Without Guardrails — коли execution іде без чітких runtime-меж.
- Tool Calling for Everything — коли tools викликаються без явної потреби.
Що будувати замість цього:
- Allowed Actions — як фіксувати дозволені дії через явні правила.
- Tool Execution Layer — де централізувати policy, scope і idempotency.
- Stop Conditions — як безпечно зупиняти run, коли write не має права пройти.