Anti-Pattern Write Access by Default: write access by default

Why default write access is a dangerous agent anti-pattern, and how to replace it with approvals, scopes, and least privilege.
On this page
  1. Idea In 30 Seconds
  2. Anti-Pattern Example
  3. Why It Happens And What Goes Wrong
  4. Correct Approach
  5. Quick Test
  6. How It Differs From Other Anti-Patterns
  7. Blind Tool Trust vs Write Access by Default
  8. Agents Without Guardrails vs Write Access by Default
  9. Tool Calling for Everything vs Write Access by Default
  10. Self-Check: Do You Have This Anti-Pattern?
  11. FAQ
  12. What Next

Idea In 30 Seconds

Write Access by Default is an anti-pattern where an agent gets write tools by default, without a policy gate and checks before executing the action.

As a result, a model error or wrong route becomes not just a wrong answer, but a real external state change.

Simple rule: write should not be "default". It should go through a separate controlled path with explicit checks of rights, context, and execution conditions.


Anti-Pattern Example

The team builds a support agent that reads order status and can close a ticket or send an email when needed.

The agent gets write tools immediately, with no dedicated check stage before action.

PYTHON
decision = agent.decide_next_action(user_message)
# route is wrong, but write is still available
result = run_tool(decision.tool, decision.args)
return result

In this setup, baseline protection is missing:

PYTHON
# no deny-by-default for write-tools
# no approval_required for risky actions
# no idempotency_key for retries
# no strict tenant/env scope

For this case, you need a policy gate before any write step:

PYTHON
if decision.tool in WRITE_TOOLS and not is_write_allowed(ctx, decision):
    return stop("approval_required")

If conditions are not met, the run must not proceed to external write action.

In this case, Write Access by Default adds:

  • risk of unwanted changes in external systems
  • duplicated write operations during retries
  • larger blast radius in multi-tenant environments

Why It Happens And What Goes Wrong

This anti-pattern often appears when a team wants the agent to be "maximally autonomous" and opens write access before building control boundaries.

Typical causes:

  • demo-first approach: grant all tools first, add limits later
  • no explicit separation of read and write routes
  • access rules are described in prompt, not enforced in gateway
  • no mandatory idempotency_key for write operations

As a result, teams face:

  • unsafe side effects (state changes) - the agent can write where it should only read
  • repeated actions - retry or loop repeats the same write operation
  • high blast radius - without strict scope, failure hits the wrong tenant/env
  • hard incident analysis - difficult to prove why write was allowed
  • loss of predictability - the team cannot control when the system moves to write

Unlike Blind Tool Trust, the core issue here is not payload validation, but default-open write access.

Typical production signals that write control is weak:

  • write tools are called even in scenarios that should be read-only
  • logs contain many write calls with the same args_hash or without idempotency_key
  • approval_required almost never appears even with high write share
  • blocked write attempts are rare although the system proposes risky actions regularly
  • audit logs do not show which policy rule allowed a write
  • the team cannot clearly explain why a specific write was allowed in this run
  • failures are discovered after the external action, not at policy gate

Important: every write call changes external state and often has no easy rollback. Without deny-by-default and a controlled write path, one bad inference becomes a production incident.

Correct Approach

Start with a read-first model: read tools are route-available, while write steps pass dedicated checks through a policy gate.

Practical framework:

  • enforce deny-by-default for all write tools
  • split routes into read_only and write_candidate
  • require approval for risky write actions
  • derive tenant_id and env only from authenticated context, never from model output
  • add idempotency_key to every write operation
  • log stop_reason and policy-gate decisions for each write step
PYTHON
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

In this setup, the write step is controlled: the system either executes safely or transparently stops the run.

Quick Test

If the answer to these questions is "yes", you have Write Access by Default anti-pattern risk:

  • Can a write tool be called without explicit policy/approval checks?
  • Do retries sometimes repeat the same write action without idempotency_key?
  • Can the team not quickly explain why a specific write was allowed?

How It Differs From Other Anti-Patterns

Blind Tool Trust vs Write Access by Default

Blind Tool TrustWrite Access by Default
Main problem: tool output is accepted without validation.Main problem: write access is open by default.
When it appears: when parse/schema/invariant checks are missing before decisions.When it appears: when write does not pass through deny-by-default and approval gate.

In short: Blind Tool Trust is about data quality before action, while Write Access by Default is about permissions for the action itself.

Agents Without Guardrails vs Write Access by Default

Agents Without GuardrailsWrite Access by Default
Main problem: runtime boundaries and policy control are broadly missing.Main problem: write operations specifically have no strict access contour.
When it appears: when the system lacks clear safety policy for execution.When it appears: when write is allowed as a standard route instead of an exception via policy gate.

In short: Agents Without Guardrails is a broader execution-boundary issue, while Write Access by Default is specifically about unsafe write access model.

Tool Calling for Everything vs Write Access by Default

Tool Calling for EverythingWrite Access by Default
Main problem: tools are called unnecessarily, even when avoidable.Main problem: once a tool call happens, write can pass without sufficient control.
When it appears: when there is no stable no_tool route for simple cases.When it appears: when the system does not separate read and write access levels.

In short: Tool Calling for Everything increases call count, while Write Access by Default increases failure cost of each write call.

Self-Check: Do You Have This Anti-Pattern?

Quick check for anti-pattern Write Access by Default.
Mark items for your system and check status below.

Check your system:

Progress: 0/8

⚠ There are signs of this anti-pattern

Move simple steps into a workflow and keep the agent only for complex decisions.

FAQ

Q: Does this mean agents should never perform write actions?
A: No. Write actions are possible, but only through a controlled path: policy gate, approval where needed, scope enforcement, and idempotency.

Q: What is the difference between policy gate and approval?
A: Policy gate is deterministic rule enforcement at runtime. Approval is a separate confirmation for a specific risky action. They are different control layers.

Q: What is the minimum to implement first?
A: Start with deny-by-default for write, mandatory idempotency_key, strict tenant/env scope, and stop_reason logging for blocked write attempts.


What Next

Related anti-patterns:

What to build instead:

⏱️ 8 min read β€’ Updated March 17, 2026Difficulty: β˜…β˜…β˜…
Implement in OnceOnly
Safe defaults for tool permissions + write gating.
Use in OnceOnly
# onceonly guardrails (concept)
version: 1
tools:
  default_mode: read_only
  allowlist:
    - search.read
    - kb.read
    - http.get
writes:
  enabled: false
  require_approval: true
  idempotency: true
controls:
  kill_switch: { enabled: true, mode: disable_writes }
audit:
  enabled: true
Integrated: production controlOnceOnly
Add guardrails to tool-calling agents
Ship this pattern with governance:
  • Budgets (steps / spend caps)
  • Tool permissions (allowlist / blocklist)
  • Kill switch & incident stop
  • Idempotency & dedupe
  • Audit logs & traceability
Integrated mention: OnceOnly is a control layer for production agent systems.

Author

Nick β€” engineer building infrastructure for production AI agents.

Focus: agent patterns, failure modes, runtime control, and system reliability.

πŸ”— GitHub: https://github.com/mykolademyanov


Editorial note

This documentation is AI-assisted, with human editorial responsibility for accuracy, clarity, and production relevance.

Content is grounded in real-world failures, post-mortems, and operational incidents in deployed AI agent systems.