Skip to main content

The problem

Your agent proposes the next action:
{
  "action": "close_ticket",
  "status": "needs_review",
  "note": null
}
The JSON is valid, but the action conflicts with current state. A ticket that still needs review should not be closed. The same pattern matters for higher-risk tools: refunds, permissions, state transitions, account changes, and any action where a plausible-looking model response can trigger a real side effect.

The contract

Fetch policy and current state before the contract. Then keep the rules deterministic and synchronous.
import { z } from "zod";
import { defineContract } from "@withboundary/contract";

export const actionSchema = z.object({
  action: z.enum(["close_ticket", "escalate", "respond", "refund_user", "reassign"]),
  status: z.enum(["open", "needs_review", "in_progress", "resolved"]),
  note: z.string().nullable(),
  amount: z.number().optional(),
});

export const actionContract = defineContract({
  name: "agent-action",
  schema: actionSchema,
  rules: [
    {
      name: "cant_close_in_review",
      description: "Tickets in review cannot be closed",
      fields: ["action", "status"],
      check: (action) =>
        !(action.action === "close_ticket" && action.status === "needs_review")
          || 'cannot close a ticket with status "needs_review"',
    },
    {
      name: "escalation_requires_note",
      description: "Escalations include an explanation",
      fields: ["action", "note"],
      check: (action) =>
        action.action !== "escalate" || (action.note !== null && action.note.trim().length > 0)
          || "escalation requires a note explaining why",
    },
    {
      name: "refund_within_limit",
      description: "Refund amounts stay under the per-action ceiling",
      fields: ["action", "amount"],
      check: (action) =>
        action.action !== "refund_user" || (action.amount !== undefined && action.amount <= 100)
          || `refund amount ${action.amount ?? "missing"} must be between 0 and 100`,
    },
    {
      name: "resolved_ticket_actions",
      description: "Resolved tickets can only be closed or answered",
      fields: ["status", "action"],
      check: (action) =>
        action.status !== "resolved" || ["close_ticket", "respond"].includes(action.action)
          || `status is "resolved" but action is "${action.action}"; use close_ticket or respond`,
    },
  ],
});

Run the model

const result = await actionContract.accept(async (attempt) => {
  const response = await callYourLLM({
    messages: [
      {
        role: "user",
        content: [
          "Choose the next support action as JSON.",
          attempt.instructions,
          `Ticket status: ${ticket.status}`,
          `Refund limit: ${refundLimit}`,
          `Latest customer message: ${ticket.latestMessage}`,
        ].join("\n\n"),
      },
      ...attempt.repairs,
    ],
  });

  return response.text;
});
The model can still propose the action. Boundary decides whether that action is consistent with the state and policy you passed in.

Accept or reject

if (result.ok) {
  await agentTools.execute(result.data);
  return { status: "executed", action: result.data.action };
}

await humanReview.add({
  ticketId,
  proposedActionError: result.error.message,
  attempts: result.error.attempts,
});

return { status: "blocked_for_review" };
Do not execute a rejected action. Use the attempt history to ask for review, retry with more state, or return a safe fallback.

When to use this pattern

  • Agent tool calls that mutate state
  • Refunds, account credits, and billing adjustments
  • Support workflow transitions
  • Permission or access changes
  • Any action where policy depends on current state