[TCB // ENGINE_LOG] index.html
[ENGINE // ENGINE_02]

Architecting Deterministic Multi-Tenant Engines

PUBLISHED: 06.2026

AI agents operating in autonomous environments are notoriously difficult to control. If left unconstrained, they can loop indefinitely, run up massive API bills, or perform unauthorized operations in databases. In a multi-tenant enterprise system, this unpredictability is a liability.

To make agents enterprise-grade, we must wrap them in deterministic state machines and isolate them within strict security sandboxes.


The Architecture of Constraint

A deterministic agent engine consists of three layers:

  1. The Sandbox Layer (Execution): A strictly isolated runtime (like WebAssembly or a micro-container) that limits network, memory, and filesystem access.
  2. The State Machine (Governance): A strict, immutable flow controller that tracks the state transitions of the agent. The agent itself cannot decide what phase to run next; it can only propose transitions that are validated by the host system.
  3. The Token Guard (Fiduciary): An inline proxy that intercepts all outbound LLM requests to check the rate limits, token counts, and cost metrics of the specific tenant before authorizing the call.
                  [State Machine (Host)]
                            │
         ┌──────────────────┴──────────────────┐
         ▼                                     ▼
 [Agent Execution Sandbox]             [Tenant Token Guard]
 (Wasm / Isolated V8)                  (Cost Proxy)
         │                                     │
         └──────────────────┬──────────────────┘
                            ▼
                    [Target Database]

Implementing a Strict State Machine in TypeScript

To ensure that the agent executes predictably, we define the allowed lifecycle states and transitions explicitly. The agent cannot skip steps or act outside the current state.

type AgentState = 'idle' | 'planning' | 'executing' | 'validating' | 'done' | 'failed';

interface AgentContext {
  tenantId: string;
  stepBudget: number;
  stepsTaken: number;
  maxCostCents: number;
  accumulatedCostCents: number;
}

class DeterministicEngine {
  private state: AgentState = 'idle';
  private context: AgentContext;

  constructor(tenantId: string, stepBudget: number, maxCostCents: number) {
    this.context = {
      tenantId,
      stepBudget,
      stepsTaken: 0,
      maxCostCents,
      accumulatedCostCents: 0,
    };
  }

  // Transition validation logic
  transitionTo(nextState: AgentState): void {
    const validTransitions: Record<AgentState, AgentState[]> = {
      idle: ['planning'],
      planning: ['executing', 'failed'],
      executing: ['validating', 'failed'],
      validating: ['executing', 'done', 'failed'],
      done: [],
      failed: []
    };

    if (!validTransitions[this.state].includes(nextState)) {
      throw new Error(`Invalid transition: ${this.state} -> ${nextState}`);
    }

    // Safety checks
    if (this.context.stepsTaken >= this.context.stepBudget && nextState !== 'failed') {
      this.state = 'failed';
      throw new Error('Budget exceeded: step limit reached.');
    }

    this.state = nextState;
    console.log(`[STATE_TRANSITION] Tenant: ${this.context.tenantId} -> State: ${this.state}`);
  }

  incrementBudget(cents: number) {
    this.context.accumulatedCostCents += cents;
    if (this.context.accumulatedCostCents >= this.context.maxCostCents) {
      this.transitionTo('failed');
      throw new Error('Fiduciary breach: Token cost budget exceeded.');
    }
  }

  getCurrentState(): AgentState {
    return this.state;
  }
}

Mitigating Concurrency and State Leaks

In multi-tenant environments, state leaks are a major concern. If Tenant A's variables are accidentally cached in a shared LLM context or global module state, Tenant B might access proprietary data.

To mitigate this:

  • Clean Context Restarts: Every invocation is fed a fresh, sanitized JSON payload containing only the tenant's context.
  • Isolated Embedding Spaces: Vector databases use metadata filters or separate namespaces per tenant, enforced at the query library level, never relying on the LLM to filter the results.
  • Stateless Agent Loops: The agent runs as a stateless function. It takes the previous system history and spits out the next action, saving its progress in an encrypted database that only the tenant's keys can unlock.

By implementing these structural boundaries, we achieve a balance of flexibility (autonomous agent planning) and safety (deterministic execution bounds).