Real Cases

Backoffice Agent

An admin workflow with side effects, approvals, audit records, and idempotency.

This pattern is for internal operations agents that can change product state. The harness must treat every write as a product operation with permissions, approvals, idempotency, and audit records.

Scenario

A support lead asks an agent to resolve a billing ticket, issue a refund, and notify the customer. The model can help coordinate the work, but application code owns every restricted operation.

When to Use It

Use this pattern when the agent can:

  • issue refunds
  • close or transition tickets
  • update accounts
  • send customer messages
  • trigger jobs or webhooks
  • access admin-only data

Architecture Shape

LayerResponsibility
runnerresolve admin actor, ticket, tenant, approval run, trace metadata
agentcoordinate the workflow and call tools
write toolsvalidate inputs and call product services
approval runtimestore decisions, notify reviewers, wait or resume
service layerpermissions, transactions, idempotency, audit
storageticket state, operation records, audit records, traces

Code Example

import { AgentBuilder, Message, createHook } from "@anvia/core";
import { model } from "./model";
import { createBackofficeTools } from "./tools";

export async function runBackofficeResolution(input: BackofficeInput) {
  const actor = await input.auth.requireAdmin();
  const ticket = await input.tickets.getForTenant(input.ticketId, actor.tenantId);

  const approvalHook = createHook({
    async onToolCall({ toolName, tool }) {
      if (!["issue_refund", "send_customer_email", "close_account"].includes(toolName)) {
        return tool.run();
      }

      const approved = await input.approvals.waitForDecision({
        actorId: actor.id,
        tenantId: actor.tenantId,
        ticketId: ticket.id,
        toolName,
      });

      return approved ? tool.run() : tool.cancel("Operation was not approved.");
    },
  });

  const agent = new AgentBuilder("backoffice", model)
    .instructions(`
Help resolve backoffice tickets.
Use tools for account data and product changes.
Never claim that a side effect happened unless the tool result confirms it.
    `)
    .tools(
      createBackofficeTools({
        actorId: actor.id,
        tenantId: actor.tenantId,
        ticketId: ticket.id,
        billing: input.services.billing,
        tickets: input.services.tickets,
        messages: input.services.messages,
        audit: input.audit,
      }),
    )
    .hook(approvalHook)
    .defaultMaxTurns(5)
    .build();

  const response = await agent
    .prompt([
      Message.user(`Ticket ${ticket.id}: ${ticket.summary}`),
      Message.user(input.instruction),
    ])
    .withTrace({
      name: "backoffice-resolution",
      userId: actor.id,
      metadata: {
        tenantId: actor.tenantId,
        ticketId: ticket.id,
      },
    })
    .send();

  await input.audit.write({
    actorId: actor.id,
    tenantId: actor.tenantId,
    action: "backoffice.agent_run",
    targetId: ticket.id,
  });

  return response.output;
}

Write Tool Shape

async execute({ orderId, amount, reason }) {
  const operationId = `refund:${scope.tenantId}:${orderId}:${amount}`;

  const result = await scope.billing.issueRefund({
    actorId: scope.actorId,
    tenantId: scope.tenantId,
    orderId,
    amount,
    reason,
    operationId,
  });

  await scope.audit.write({
    actorId: scope.actorId,
    tenantId: scope.tenantId,
    action: "refund.issued",
    targetId: orderId,
    operationId,
  });

  return result;
}

Failure Modes

FailureFix
duplicate refundidempotency key in service layer
model performs write without approvaltool approval metadata or onToolCall hook
approval blocks HTTP request too longstore approval and resume asynchronously
audit only appears in traceswrite product audit records
admin data leaksenforce actor and tenant permissions in services

Test Checklist

  • Test admin auth and tenant scoping.
  • Test approval approved, rejected, and timed out paths.
  • Test write tools call services with idempotency keys.
  • Test audit records for each successful side effect.
  • Test runner maps cancellation to a product-safe response.
  • Use Studio streaming runs to inspect approval behavior locally.