An AI operations agent prepares a command to restart a struggling service. The syntax is valid. The service name looks right. The workflow that produced it is approved.

The target is the production cluster instead of staging.

If the next system trusts the command because it came from an approved agent, a plausible answer becomes a production incident. The failure is not bad grammar or malformed JSON. It is misplaced trust at the handoff.

Agent output is untrusted input to the system that receives it. Validate it where text, JSON, SQL, a deployment file, or a tool argument becomes an action.

AI agent output validation gate

approved workflow does not mean approved action

Teams often secure the front of an agent workflow. They authenticate the user, filter retrieved data, choose an approved model, and restrict the available tools. Those controls matter, but they do not prove that every generated argument is safe.

The agent may misunderstand the environment, copy a stale value from retrieved documentation, combine two valid fields into an invalid operation, or choose a wider target than the task requires. A compromised tool response can also place instructions in context that later appear in the agent’s output.

The receiving system knows facts the model does not reliably know. It knows which operations exist, which resources belong to the task, which environment the requester may change, and whether the action needs approval. That system should make the final decision.

Consider a deployment proposal:

{
  "operation": "restart_service",
  "service": "payments-api",
  "environment": "production",
  "region": "eu-west-1",
  "reason": "verify retry configuration"
}

Every field is readable. The operation may even be allowed in general. But a task to verify staging behavior does not authorize a production restart. Schema validation alone will accept the payload. The policy gate must compare the proposed action with the task contract and the requester’s authority.

This is the same boundary problem I described in testing the full AI agent tool path. A sensible final answer cannot prove that the path used to produce or execute it was safe.

give the receiver a narrow contract

Do not let the agent invent the shape of an executable request. Define a versioned contract and reject fields or operations outside it.

action_contract:
  version: deploy-control-v8
  task_id: OPS-1842
  requester: engineer-381
  allowed_operations:
    - inspect_status
    - propose_restart
  allowed_targets:
    environments:
      - staging
    services:
      - payments-api
    regions:
      - eu-west-1
  argument_limits:
    timeout_seconds:
      minimum: 5
      maximum: 60
  production_mutation: forbidden
  dry_run: required
  approval_for_execution: platform-on-call
  expires_at: 2026-07-28T11:30:00Z

The contract separates proposing an action from executing it. The agent may produce propose_restart, but it cannot convert that proposal into restart_service through wording or an extra field. A trusted component performs that transition after the checks pass.

Use a strict parser. Reject unknown properties rather than ignoring them. Ignoring an extra force: true field can create different behavior when another service later adds support for it. Version the schema so the receiver and policy engine agree on the request they are evaluating.

Free-form text belongs in explanatory fields that cannot alter execution. The command, operation, target, resource identifiers, limits, and requested capabilities should use typed fields with explicit bounds.

check meaning after checking shape

A valid schema proves that the payload has the expected structure. It does not prove that the action belongs to this run.

The receiver should evaluate at least these questions:

Is the operation allowed for this task?
Is every target inside the approved scope?
Does the requester have authority for the proposed action?
Did the agent request a broader capability than the task needs?
Is the policy version current?
Has the task approval expired?
Can the action run safely in dry-run mode first?
Does the result require a named human decision?

Keep resource identity separate from friendly names. A service label such as payments-api may exist in several accounts or clusters. Resolve it to an approved resource identifier before execution, and confirm that the identifier belongs to the environment in the contract.

Apply the same rule to SQL. Parsing can prove that a statement is valid SQL. A policy check must still reject unrestricted updates, forbidden tables, cross-tenant predicates, or a query that returns more data than the task allows.

For shell commands, avoid passing generated text to a shell when a typed API can perform the operation. If a command runner is unavoidable, use a fixed executable and separately validated arguments. Do not let the model assemble pipes, redirects, command substitution, or environment overrides as one trusted string.

For MCP and other tool calls, validate the method and arguments at the server boundary. A client-side allowlist is useful, but the server still owns the protected resource. It must not assume that a call is safe because an approved agent selected the tool.

preview the effect before committing it

A dry run is useful when it shows the actual effect of the proposed action, not merely a second rendering of the same generated text.

A deployment preview should resolve the account, cluster, service, version, and planned changes. A database preview should show the affected table, tenant boundary, estimated row count, and lock risk. A ticketing preview should show the destination queue, customer visibility, and fields that will change.

Return that preview as evidence:

action_preview:
  task_id: OPS-1842
  proposal_id: proposal-7751
  operation: restart_service
  resolved_target:
    account: production-eu
    cluster: prod-eu-3
    service: payments-api
  policy_result: denied
  failed_checks:
    - environment_not_allowed
    - operation_not_allowed
  effects_applied: false
  approval_requested: false

The failed preview should not create a convenient button that asks someone to approve a wider action without context. If an exception is necessary, create a new, narrow contract for one proposal, one target, and a short period. Record who approved it and why.

For irreversible or high-impact actions, bind human approval to the exact proposal and preview. If the agent changes an argument after approval, expire the decision. The principle matches binding test and review evidence to an exact commit: approval belongs to an immutable candidate, not a conversation that can keep changing.

keep rejected output in the run record

Rejections are useful security and quality evidence. They show where the agent’s model of the task differs from policy or reality.

Record the original proposal, its hash, the contract and policy versions, resolved targets, failed checks, whether any effect occurred, and the next safe step. Redact secrets and customer data, but do not reduce the event to validation failed.

validation_receipt:
  run_id: agent-run-5518
  proposal_id: proposal-7751
  proposal_hash: sha256:8b31...
  contract_version: deploy-control-v8
  policy_version: production-actions-v14
  decision: denied
  reasons:
    - production_mutation_forbidden
    - target_outside_task_scope
  side_effects: none
  reviewer: none
  next_action: create a staging-only proposal

These receipts feed evals. Test whether the agent repeatedly widens targets, chooses forbidden operations, hides uncertainty in free text, or retries denied actions with slightly different arguments. A denial is not always an agent failure. Sometimes the task contract is wrong or stale. The record gives an operator enough detail to tell the difference.

Also test the receiver directly. Send unknown fields, mixed environment identifiers, oversized arguments, expired approvals, encoded shell operators, renamed tools, and a proposal whose hash changes after approval. The expected result is a clean refusal before any side effect.

where the books fit

I wrote Securing Enterprise AI Agents for teams building controls around these handoffs. It covers bounded authority, agent identity, MCP and RAG governance, policy enforcement, approval, evidence, revocation, and incident response. Get the security book on LeanPub.

Claude Code: Building Production Agents That Actually Scale covers the engineering side of the same problem: task contracts, scoped tools, evals, review packets, rollback, and release gates. The Kindle edition is available on Amazon.

If you own both the agent workflow and the systems that act on its output, the Enterprise AI Agents in Production bundle joins the production engineering and security control models.

A fluent proposal can still be unsafe. Make the receiving system prove that the proposed action fits the task before it lets the action happen.