LoopEngineBETA

Docs

Documentation

How LoopEngine works, from scaffold to durable approvals — agents, adapters, permission rules, and abilities.

Multi Tenant

tenant is one of three dimensions on ActAuth's Scope — alongside environment and agent — that permission rules resolve against. run-agent.ts builds it as { tenant: options.tenant ?? 'default', environment: process.env.LOOPENGINE_ENV ?? 'production', agent: config.name } on every call, so an agent invoked with no tenant resolution at all (a standalone script, a CLI call) always runs as the plain 'default' tenant — the same outcome as omitting tenantFor entirely.

Resolving a tenant: tenantFor

Only adapters/http.ts can call AgentConfig.tenantFor — it alone has a request's headers to call it with. It reads from headers, never the request body: tenant feeds permission decisions directly, so it has to come from something verified (an API key, an Authorization header) rather than a raw client-asserted field anyone could set to claim a different tenant's rules.

import type { AgentConfig } from 'loopengine'

// Stands in for a real lookup (a database, an auth provider) a
// production deployment would use instead.
const API_KEY_TENANTS: Record<string, string> = {
  'acme-trusted-key': 'acme-corp',
}

function tenantFor(headers: Record<string, string | string[] | undefined>): string | undefined {
  const apiKey = headers['x-api-key']
  if (apiKey === undefined) return 'default' // no key at all -> the plain default tenant, not a rejection
  if (typeof apiKey !== 'string') return undefined
  return API_KEY_TENANTS[apiKey] // unrecognized key -> undefined -> rejected
}

export const config: AgentConfig = {
  // ...
  tenantFor,
}

(agents/customer-service/index.ts's real version.) Returning undefined here is a genuine auth failure — adapters/http.ts responds 401 — not "fall back to 'default'"; a caller presenting a key at all is asserting an identity, and silently ignoring a bad one is exactly the kind of thing that's confusing to debug. There's no equivalent environment field: environment is a deployment-wide LOOPENGINE_ENV setting, not something that varies by agent or by request.

How tenant actually changes behavior

actauth.yml scopes are written tenant/environment, matched most-specific-first — an exact tenant beats a * wildcard for the same tool. Same rule set, same agent code; the scope resolution alone is what picks a different decision per tenant:

- name: refund-staging-always-allowed
  scope: "*/staging"
  tool: issue_refund
  decision: allow

- name: refund-acme-corp-production-allowed
  scope: acme-corp/production
  tool: issue_refund
  decision: allow

- name: refund-production-needs-approval
  scope: "*/production"
  tool: issue_refund
  decision: ask

(agents/customer-service/actauth.yml's real rules.) Every tenant skips approval in staging; in production, acme-corp — a caller presenting the trusted API key above — gets the same auto-allow a trusted, established account might earn in a real deployment, while every other tenant still needs a human to approve an actual refund.

Tenant also namespaces session storage — see Session Persistence's ${tenant}:${environment}:${agentName}:${rawSessionId} key — so two tenants whose session id happens to collide never read or write the same underlying conversation log.