All Posts

A denial with no reason is a bug

Fail-closed authorization still has to explain itself. An AI agent platform should deny when policy, session state, or the Guardian path cannot support an action. The denial should say why. A blank "no" hides the difference between a working control and a broken dependency.

Reasoned denials

A reasoned denial returns the decision and the cause. In AgentTrust ID, an action check can include allowed, guard_tier, reason, elevation_required, and approval_id.

For example, a read-only session that receives a write request should return a reason like:

session is read-only; 'tickets:draft_reply' (mutating) requires elevation

That tells the caller what happened and what path can resolve it.

Blank denial risk

A blank denial can look like an outage. The tool did not run, but the operator cannot tell whether the system denied the action on purpose or failed before it reached policy.

Blank denial weakens audit review. An audit entry that says only "denied" does not explain whether the cause was scope, session mode, agent status, org isolation, Guardian availability, or an approval requirement.

For agents, that matters because denials are part of the product. A developer needs to know whether to request approval, narrow a tool call, fix an agent capability, or repair infrastructure.

Denial reason example

The Python SDK exposes the fields directly on the action-check result.

from agenttrustid import AgentTrustClient

client = AgentTrustClient.from_env()

agent = client.agents.create(
    name="inbox-reader",
    framework="custom",
    capabilities=["gmail.read"],
)

session = client.sessions.init_session(
    agent_id=agent.id,
    server_id="mcp://gmail",
)

decision = client.actions.check(
    agent_id=agent.id,
    session_id=session.session_id,
    tool_name="gmail.send",
    tool_input_summary="Send a message from a read-only inbox session.",
    action_effect="mutating",
)

assert decision.allowed is False
assert decision.guard_tier
assert decision.reason

if decision.elevation_required:
    assert decision.approval_id

This test confirms that the call stops and that the caller receives enough information to act on the denial.

Prevention

Test the non-read path and the read path. Tests should drive a mutation or destructive action end to end and assert three things:

  • The decision matches the expected result.
  • The response contains a reason and guard tier.
  • The audit record is written for the decision.

A suite that checks only allowed reads can pass while the authorization path is broken.

Solving this with AgentTrust ID

AgentTrust ID records the decision reason on action checks and routes early denials through the audit path. The core action-check path logs both allow and deny decisions with metadata, guard tier, confidence, and reasoning.

The dashboard should show the decision reason to an operator. If a denial has no reason, treat it as a defect. Fail closed is the default, but a denial reason is what makes the system usable.

To return denial reasons from tool calls, start with the SDK guide. To talk through denials and audit review, join the waitlist.