DEV Community

Cover image for Agent in Control: Safe Execution in Production
Thien Ban BUI
Thien Ban BUI

Posted on

Agent in Control: Safe Execution in Production

The 4 Guarantees of a Hardened Agent Runtime

A production-grade agent runtime cannot treat tool calling as an afterthought. It must enforce four non-negotiable guarantees:

  1. Deterministic Role-Based Access Control (RBAC): Restrict tool availability based on the caller's verified session, not the LLM's imagination.
  2. Anti-IDOR Access Guards: Deterministically check tenant ownership before executing any database read or write.
  3. Dual-Channel Context Isolation: Decouple what the LLM needs to reason from what the client UI needs to render.
  4. Durable Saga Orchestration: Provide compensating rollback actions across multi-step mutations so partial failures never leave corrupted records.
                  ┌──────────────────────────────────────────────┐
                  │             avantGate Agent Runtime          │
                  └──────────────────────┬───────────────────────┘
                                         │
                                (Invokes Tool)
                                         ▼
                     [ 1. dataAccessGuard(args, ctx) ] ──> Fail: Abort (Anti-IDOR)
                                         │ Pass
                                         ▼
                             [ 2. execute(args) ]
                                         │
                  ┌──────────────────────┴──────────────────────┐
                  │                                             │
                  ▼                                             ▼
       [ 🎭 LLM DTO Channel ]                        [ 🚀 Client Channel ]
  (dto.pick: minimal context)                    (clientDto: full payload)
                  │                                             │
                  ▼                                             ▼
      LLM Reasoning Context                           UI WebSocket / SSE Stream

Enter fullscreen mode Exit fullscreen mode

Defining Isolated Tools: createIsolatedTool

Instead of passing raw JavaScript functions to your agent, avantGate wraps every tool in an isolated execution boundary:

import { createIsolatedTool, dto } from "avantgate/agent";
import { z } from "zod";

interface InvoiceRecord {
  invoiceId: string;
  tenantId: string;
  totalAmount: number;
  customerSecretTaxId: string;
  status: string;
}

export const getInvoiceTool = createIsolatedTool({
  name: "get_invoice",
  domain: "billing",
  roles: ["CUSTOMER_SUPPORT", "ADMIN"],
  parameters: z.object({
    invoiceId: z.string().uuid(),
    tenantId: z.string(),
  }),

  // 🛡️ 1. Anti-IDOR: Evaluated deterministically in memory BEFORE DB execution
  async dataAccessGuard(args, context) {
    return args.tenantId === (context?.tenantId as string);
  },

  // ⚙️ 2. The Actual Business Mutation
  async execute(args): Promise<InvoiceRecord> {
    return await db.invoices.findById(args.invoiceId);
  },

  // 🎭 3. Dual-Channel: The LLM ONLY sees what it strictly needs to reason
  llmDto: dto.pick(["invoiceId", "status", "totalAmount"]),

  // 🚀 4. Client Channel: Frontend receives unredacted record out-of-band
  clientDto(rawInvoice) {
    uiSocket.emit("invoice_rendered", rawInvoice);
  },

  // 🧼 5. Recursive Deep Scan for emergent PII in returned data
  sanitizePii: true,
});

Enter fullscreen mode Exit fullscreen mode

Surviving Multi-Step Failure: Durable Workflows with the Saga Pattern

When agents move beyond querying data and start mutating multiple distributed services (e.g., Billing + Inventory + Notifications), standard try-catch blocks fail.

If step 3 fails after steps 1 and 2 succeed:

  • Money is charged, but no seat is reserved.
  • Inventory is deducted, but no tracking shipment is created.

To solve distributed consistency without heavyweight orchestration frameworks like Temporal, avantGate provides an in-process Durable Saga Workflow engine:

[Agent Initiates Workflow]
           │
           ▼
     ┌───────────┐         Success
     │  Step 1   │ ──────────────────────┐
     └─────┬─────┘                       │
           │ Fails                       ▼
           │                       ┌───────────┐         Success
           │                       │  Step 2   │ ──────────────────────┐
           │                       └─────┬─────┘                       │
           │                             │ Fails                       ▼
           │                             │                       ┌───────────┐
           ▼                             ▼                       │  Step 3   │
     [Abort Run]             [Compensate Step 1]                 └─────┬─────┘
                             (e.g., Refund Charge)                     │ Fails
                                                                       ▼
                                                             [Compensate Step 2]
                                                             (Release Inventory)
                                                                       │
                                                             [Compensate Step 1]
                                                             (Refund Charge)

Enter fullscreen mode Exit fullscreen mode

Implementing a Compensating Saga in TypeScript

Using avantGate's createWorkflow, each transactional step defines both an execute handler and an automatic compensate rollback hook:

import { createWorkflow } from "avantgate/workflow";
import { z } from "zod";

export const orderFulfillmentWorkflow = createWorkflow({
  name: "order_fulfillment",
  input: z.object({
    orderId: z.string(),
    customerId: z.string(),
    amount: z.number(),
    sku: z.string(),
  }),

  steps: [
    // Step 1: Capture Payment
    {
      name: "charge_payment",
      async execute({ input }) {
        const payment = await paymentGateway.charge({
          customerId: input.customerId,
          amount: input.amount,
        });
        return { paymentId: payment.id };
      },
      async compensate({ stepOutput }) {
        // Automatically invoked if subsequent steps throw
        await paymentGateway.refund({ paymentId: stepOutput.paymentId });
      },
    },

    // Step 2: Reserve Warehouse Inventory
    {
      name: "reserve_inventory",
      async execute({ input }) {
        const reservation = await warehouse.reserve({
          sku: input.sku,
          quantity: 1,
        });
        return { reservationId: reservation.id };
      },
      async compensate({ stepOutput }) {
        // Releases reserved item if Step 3 fails
        await warehouse.release({ reservationId: stepOutput.reservationId });
      },
    },

    // Step 3: Schedule Delivery Carrier
    {
      name: "dispatch_carrier",
      async execute({ input, steps }) {
        // If carrier API fails, Steps 2 and 1 roll back in reverse order!
        return await carrierService.bookPickup({
          orderId: input.orderId,
          reservationId: steps.reserve_inventory.reservationId,
        });
      },
    },
  ],
});

Enter fullscreen mode Exit fullscreen mode

How the Runtime Enforces Resilience

  1. Forward Execution: The runtime executes steps sequentially, accumulating immutable step outputs in runtime memory.
  2. Reverse Compensation (Rollback Stack): If any step throws an unhandled error or hits a timeout, the engine unwinds the stack in reverse order (Step 2 compensate -> Step 1 compensate).
  3. Idempotency Guarantees: Each step execution is tagged with an execution ID, ensuring retries do not trigger double-charges.

Orchestrating the Complete Secure Runtime

You can wire both isolated tools and saga workflows directly into the avantGate engine:

import { createAvantGate } from "avantgate";
import { getInvoiceTool } from "./tools/invoice";
import { orderFulfillmentWorkflow } from "./workflows/order";

// Initialize the secure runtime
const engine = createAvantGate({
  primary: {
    provider: "deepseek",
    model: "deepseek-chat",
    apiKey: process.env.DEEPSEEK_API_KEY!,
  },
  security: {
    detectPromptInjection: true, // Ingress AI-WAF
    maskPII: true,                // In-flight PII redaction
  },
  maxTokenBudget: 5000,          // Denial-of-Wallet circuit breaker
  maxTurns: 8,                   // Guard against recursive infinite loops
});

// Run an authenticated agent turn
async function handleUserMessage(userQuery: string, session: { tenantId: string; role: string }) {
  try {
    const result = await engine.executeAgent({
      userQuery,
      context: {
        tenantId: session.tenantId,
        role: session.role,
      },
      tools: [getInvoiceTool],
      workflows: [orderFulfillmentWorkflow],
    });

    return result.output;
  } catch (error: any) {
    console.error("Agent execution halted:", error.message);
    return "I encountered a security or budget policy constraint while processing your request.";
  }
}

Enter fullscreen mode Exit fullscreen mode

Why "In-Process" Beats the Proxy Approach

Most modern LLMOps platforms propose deploying separate gateways (proxies, Redis caches, tracing containers).

While centralized logging has its place, tool execution and workflow compensation belong next to your data:

  • 0ms Extra Network Latency: In-process validation, regex compilation, and state rollbacks execute on native V8 runtimes in fractions of a millisecond.
  • Zero Infrastructure Sprawl: You don't need to spin up and maintain external sidecars or workflow daemons just to protect transactions.
  • End-to-End Type Safety: Full TypeScript type inference from Zod schemas down to database records, client events, and workflow step dependencies.

Summary

Autonomous agents don't need to be unpredictable black boxes. By wrapping tool execution in deterministic contracts, validating tenant boundaries with dataAccessGuard, isolating context with the Dual-Channel pattern, and guarding multi-step mutations with Durable Saga Workflows, you can run autonomous systems with zero data leaks and transactional consistency.

  • How are you structuring tool calling, rollback mechanics, and tenant isolation in your current agents? Let's discuss in the comments below! 🛡️

👉 Explore the avantGate codebase & documentation on GitHub: github.com/thienban/avantGate

Top comments (0)