Agora

Configuration reference

Every field of AgentConfig — the shape of config/agent.ts — with its type, default, and behavior.

config/agent.ts exports defineConfig(config) where config is an AgentConfig. Only model is required; every other field has a safe, fail-closed default. This page documents all of them.

Full example

config/agent.ts
import { defineConfig, stores, AuthActorResolver } from '@adonis-agora/agent'
import { aiSdkModel } from '@adonis-agora/agent/ai-sdk'
import { openai } from '@ai-sdk/openai'

export default defineConfig({
  model: () => aiSdkModel(openai('gpt-4o-mini')),

  store: 'lucid',
  stores: { memory: stores.memory(), lucid: stores.lucid() },

  actorResolver: new AuthActorResolver(),
  defaultRoles: ['ADMIN'],
  maxSteps: 8,
  path: 'agent',

  governanceAuthorize: (actor) => actor.roles?.includes('ADMIN') ?? false,
  dashboard: { authorize: (actor) => actor.roles?.includes('ADMIN') ?? false },

  defaultAgent: {
    systemPrompt: 'You are a helpful assistant.',
    defaultPersona: 'default',
    personas: [{ id: 'default', label: 'Default', systemPrompt: 'You are a helpful assistant.' }],
  },
})

Fields

model required

ModelProvider | () => ModelProvider | Promise<ModelProvider>. The LLM. Pass a ModelProvider directly, or (recommended) a lazy thunk so the provider SDK peer is imported only at boot. Wrap a Vercel AI SDK model with aiSdkModel(...), or implement the tiny ModelProvider SPI yourself.

store

string. The name of a key in stores to use. Omit for the in-memory store (single-process). See State stores.

stores

Record<string, StoreFactory>. Named store factories built with the stores helper — stores.lucid(...) and/or stores.memory(). Each factory is a lazy thunk, so a driver's peer (@adonisjs/lucid) is imported only when its store is selected.

sink

TokenStreamSink | SinkFactory. The live token transport (data plane). Defaults to the in-process sink (InProcessTokenStreamSink), which buffers per run so a client can re-attach. Use tokenSinks.redis({...}) (aliased streamTransports.redis) for the multi-replica Redis sink so any pod serves any run's SSE stream. See Multi-replica streaming.

quota

QuotaStore | QuotaFactory. The daily token budget. Omit to disable quotas (fail-open on budget). When set, the budget is checked before the model runs (fail-closed). Use quotas.ledger({ limitTokens }) to enforce off the persisted usage ledger, or quotas.memory({ limitTokens }) for a single-process budget. See Quota & cost.

pricingStore

AgentPricingStore | PricingFactory. Prices each turn's tokens into the assistant message's usage.costUsd. A provider-reported (gateway) cost always wins; otherwise the loop estimates from this store's current price rows. Omit → costUsd is always null (never a fabricated 0). Use pricingStores.lucid() (the SQL agent_model_pricing table) or pricingStores.memory(). See Quota & cost.

governanceQueries

AgentGovernanceQueries | GovernanceQueriesFactory | false. The read-model the optional /agent/governance/* routes serve from — cost/usage rollups, run lifecycle, tool stats, reliability, and the approvals inbox. Omitting it does not disable it: it defaults to mirroring a stores.lucid() store on the same connection. Pass a lazy governanceQueries.lucid() to point it elsewhere, or false to actually turn it off. Mounting the routes needs this and governanceAuthorize — a read-model with no gate mounts nothing. See Governance read-model.

governanceAuthorize

AgentGovernanceAuthorize(actor, ctx) => boolean | Promise<boolean>. The authorization gate for the cross-actor /agent/governance/* read routes. It runs after the actor is resolved (the caller is authenticated) and decides whether this actor may read the platform-wide governance read-model — every actor's spend, usage, threads, and pending approvals. Return false (or throw — it's fail-closed) → the route replies 403. Omit → the routes are not mounted at all — every /agent/governance/* path answers 404, and the provider logs a boot warning. Typically you set it to an ADMIN check to mount them gated; set governanceAuthorize: () => true to deliberately restore the old behaviour of letting ANY authenticated actor read them. The dashboard console is a pure consumer of these routes, so it refuses to mount without the gate too. It does not gate the per-actor GET /agent/approvals/mine route, which is unaffected — it stays mounted whenever the read-model resolves — and the same predicate feeds the object-level ownership check (passing it makes a caller cross-actor privileged). Mirrors the dashboard's authorize hook. See Governance route authorization.

retriever / retrievalTopK / retrievalFilter

retriever: Retriever | RetrieverFactory. Enables always-on ("inject") RAG — before each turn the loop retrieves passages for the user message and folds them into the system prompt. Use retrievers.pgvector({...}), retrievers.qdrant({...}), or retrievers.memory({...}). Omit → no injection.

retrievalTopK: number — how many passages inject-mode requests per run (default 5).

retrievalFilter: (actor: Actor) => Record<string, unknown>. Derives the metadata filter for each run's retrieval from the acting identity — the control that keeps one tenant's passages out of another tenant's context window:

retrievalFilter: (actor) => ({ tenantRef: actor.tenantRef }),

Omit and inject-mode retrieval is UNSCOPED: every actor retrieves over the whole corpus. That is fine for a single-tenant corpus and a leak in any other. See RAG & retrieval.

attachmentStaging / attachmentMaxBytes / attachmentAllowedContentTypes

attachmentStaging: AttachmentStagingStore | AttachmentStagingFactory. Upload-side seam for message attachments (image/PDF). When set, the provider mounts POST /agent/attachments. Use attachmentStores.memory() for tests, or your own store. Omit → no upload route. attachmentMaxBytes: per-file cap (default 20 MiB). attachmentAllowedContentTypes: allowed upload types, matched exactly — no wildcards, no prefix rule. The default is these seven:

['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'application/pdf', 'text/plain', 'text/csv']

Anything else is 415, text/markdown included. Passing this replaces the list rather than extending it, so spread the defaults you still want. See Attachments & multimodal.

actorDirectory

ActorDirectory | ActorDirectoryFactory. Read-side lookup from opaque actorRefs to human display labels (the read dual of actorResolver). When bound, every governance route returning an actorRef also returns an actorLabel, resolved in one batched lookup per response. Use actorDirectories.memory({ labels }) or your own over the host's user table. Omit → surfaces render raw refs. See Actor labels.

toolTransientRetry

ToolTransientRetryOptions | false. Retries a tool's own invocation in place on a classified-transient error (DB deadlock, lock-wait timeout, serialization failure). Default ON ({ attempts: 2, backoffMs: 150 }); pass { classify } to change the rule, or false to disable. Non-transient failures are never retried. See Transient tool retries.

historyWindow

HistoryWindow. The ceiling on how much of a thread rides into a turn. Omit → the whole thread, every turn, which is unbounded: a long-lived thread eventually exceeds the model's context limit, and pays for the full transcript until it does.

new SlidingWindowHistory({ maxMessages, maxTokens }) keeps the newest messages that fit a count, a token budget, or both — whichever cuts more wins, and the newest message always rides. Adding summarize: summarizeWithModel(model) folds what the window left out into a leading system summary, at the cost of one extra non-streamed model call per run, recorded as a summary usage row. Pass any HistoryWindow of your own for a window the built-in can't express; estimateMessageTokens and DEFAULT_HISTORY_SUMMARY_INSTRUCTION are exported for composing one. See Bounding the history.

authorizer / rolesPolicy

RolesPolicy. The tool authorization gate; authorizer and rolesPolicy are aliases — pass either. Defaults to DefaultToolAuthorizer (fail-closed, ADMIN-only, role-set intersection). Swap it to plug an ability-aware policy. See Authorization.

defaultRoles

string[]. The roles a tool requires when it declares none. Default ['ADMIN'] — so an ungoverned tool is admin-only by default.

actorResolver

ActorResolver. Resolves the acting identity per request — the identity seam. Defaults to a resolver that THROWS on every request, so the agent never fabricates a caller. Wire AuthActorResolver, HeaderActorResolver, or your own.

Leaving actorResolver unset makes every actor-scoped route reply 401. This is intentional — configure a resolver to serve traffic.

path

string. The route prefix the /agent/* routes mount under. Default 'agent' (→ /agent/chat, /agent/threads, …). Leading/trailing slashes are stripped.

durable

boolean. Run each turn as a replay-safe durable workflow (over @adonis-agora/durable) instead of in-process: LLM/tool steps become memoized checkpoints, HITL approval suspends on a signal (resuming across a restart), and delegation is a tracked child run. Requires @adonis-agora/durable installed and configured (config/durable.ts); if it can't be wired the provider logs a warning and falls back to the inline runner, so setting it is always safe. See The durable runner.

agents

AgentDefinition[]. Additional named agents an orchestrator can delegate to via delegatesTo. See Personas & agents.

defaultAgent

Omit<AgentDefinition, 'name'> & { name?: string }. The implicit single agent's config — its base systemPrompt, personas, defaultPersona, tool allow-list (tools), delegatesTo, modelId, and maxSteps. Omit for a bare assistant ('You are a helpful assistant.'). Its name defaults to 'default'.

tools

BrandedFunctionalTool[]. Static defineTool(...) functional tools to register at boot, in addition to app/agent_tools discovery. See Tools.

maxSteps

number. The cap on model↔tool iterations per turn. Default 8. A per-agent AgentDefinition.maxSteps overrides it for that agent.

dashboard

AgentDashboardConfig. The bundled governance console. Defaults to { enabled: true }, mounting the SPA at <path>/dashboard.

KeyTypeDefaultMeaning
enabledbooleantruefalse keeps the console's routes off entirely.
pathstring<path>/dashboardWhere the SPA mounts.
authorize(actor, ctx) => boolean | Promise<boolean>An extra gate run after the actor resolves. Falsy or throwing → 403. Same shape as governanceAuthorize, so one predicate can gate both.
onUnauthenticated(ctx) => void | Promise<void>Runs when the actor resolver itself rejects the caller. Set a Location header here (ctx.response.redirect('/login')) and the provider stands down so the redirect goes through, instead of writing its 401 JSON.

The console is not mounted without governanceAuthorize, and refuses to mount when governanceQueries: false — it is a browser client of routes that would not exist. Both refusals log a boot warning naming the responsible knob.

The console is not read-only: its Approvals section decides pending HITL calls and its Pricing section writes model rates. authorize is the switch that decides who can do that.

emitDiagnostics

boolean. Emit agora:agent:* lifecycle events when @adonis-agora/diagnostics is installed. Default true (a no-op when the diagnostics package is absent). See Diagnostics & tracing for the event names and payloads.

On this page