Getting Started
Run your first governed agent in an AdonisJS app — install, configure a model and store, run the migration, define a tool, and stream a chat over SSE.
This guide gets a governed, streaming agent running in an AdonisJS app in a few minutes. You'll wire a model, persist to SQL with Lucid, declare one tool, and stream a reply from POST /agent/chat.
1. Install & configure
node ace add @adonis-agora/agentadd installs the package and then runs the same codemods configure does:
- registers
@adonis-agora/agent/agent_providerand@adonis-agora/agent/dashboard_providerinadonisrc.ts; - registers the Assembler
inithook that generates the typedapp/agent_toolsbarrel; - publishes
config/agent.tsandconfig/mcp.ts; - publishes two migrations: the agent tables, and the pgvector RAG chunk table.
The MCP provider is not registered — it is opt-in, and config/mcp.ts carries the one-line snippet for adonisrc.ts when you want it. The pgvector migration is Postgres-only; delete it if you are not using pgvector RAG.
You'll also need an LLM provider SDK for the AI SDK adapter — for example @ai-sdk/openai:
npm i ai @ai-sdk/openai zod2. Configure a model, store, and identity
Only model is required. Below we also select the lucid store (SQL persistence) and wire AuthActorResolver, which reads your authenticated principal off ctx.auth.user.
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({
// A lazy thunk keeps the provider SDK peer imported only at boot.
model: () => aiSdkModel(openai('gpt-4o-mini')),
store: 'lucid',
stores: {
memory: stores.memory(),
lucid: stores.lucid(),
},
// Identity. Omit it and the endpoints are public — every browser is its own anonymous actor
// (an HttpOnly cookie). This line requires login instead (reads ctx.auth.user; 401 without).
actorResolver: new AuthActorResolver(),
// The implicit single agent. Omit for a bare assistant.
defaultAgent: {
systemPrompt: 'You are a helpful assistant for our app.',
},
})No identity is ever fabricated
The actor resolver defaults to one that throws on every request. The agent will not invent a caller. In development you can use HeaderActorResolver (trusts x-actor-id / x-actor-role headers) behind a trusted gateway, but production should read a verified principal. See Authorization.
aiSdkModel adapts any Vercel AI SDK v7 LanguageModel to the agent's ModelProvider — pass openai(...), anthropic(...), a gateway model, etc. Prefer a different LLM library? Implement the tiny ModelProvider SPI (runTurn) yourself.
3. Run the migration
The lucid store persists to nine tables (threads, messages, tool calls, token usage, model pricing, the run lifecycle, the chat message queue, spent confirm tokens, and the frame buffer the Lucid token sink uses). Apply the migration:
node ace migration:runYou can also skip this step: the Lucid store creates (and, across upgrades, repairs) its own tables as the app starts, which is why a fresh app works before you have run anything and an upgrade needs no migration of yours. See Auto-created tables for when to turn that off.
Omitting store entirely uses the in-memory store — single-process, no migration — which is perfect for a first spin or for tests. See State stores.
4. Define a tool
Tools live under app/agent_tools and are discovered at boot. A read tool auto-executes; an action tool requires human approval. Declare an @AiTool class implementing execute:
import { AiTool } from '@adonis-agora/agent'
import type { AiToolCtx, ToolHandler } from '@adonis-agora/agent'
import { z } from 'zod'
@AiTool({
name: 'getWeather',
kind: 'read',
description: 'Current weather for a city.',
input: z.object({ city: z.string() }),
roles: ['MEMBER'],
})
export default class GetWeatherTool implements ToolHandler<{ city: string }> {
async execute(input: { city: string }, ctx: AiToolCtx) {
// ctx.actor, ctx.threadId, ctx.runId are all available here.
return { city: input.city, tempC: 21 }
}
}Tools are open unless restricted
A tool that declares no roles inherits defaultRoles — [], no restriction, by default: anyone the actor resolver resolved can call it (an anonymous visitor included). Give it a roles list to restrict it, or set defaultRoles: ['ADMIN'] to make that the default. action tools still wait for approval. A tool the actor can't invoke is never even shown to the model. See Authorization.
Prefer a function to a class? Use defineTool:
import { defineTool } from '@adonis-agora/agent'
import { z } from 'zod'
export const purgeCache = defineTool(
{
name: 'purgeCache',
kind: 'action', // requires HITL approval before it runs
description: 'Purge a cache key.',
input: z.object({ key: z.string() }),
roles: ['ADMIN'],
},
async ({ key }, ctx) => {
// ... perform the purge
return { purged: key }
},
)5. Chat
The provider mounts the /agent routes at boot. Start a run:
curl -N http://localhost:3333/agent/chat \
-H 'content-type: application/json' \
-d '{"message":"What is the weather in Lisbon?"}'The reply streams back as Server-Sent Events:
event: meta
data: {"runId":"...","threadId":"..."}
id: 1
data: {"kind":"step-start"}
id: 2
data: {"kind":"text","text":"The "}
id: 3
data: {"kind":"text","text":"weather "}
...
event: done
data: {}The X-Agent-Run-Id and X-Agent-Thread-Id response headers carry the same ids, so a client can re-attach to a run's stream after a dropped connection. That's it — a governed agent that streams tokens, runs a role-gated tool, and records the whole exchange.
Next steps
- The agent loop — the turn state machine and the runner seam.
- Authoring tools — kinds, schemas, discovery, and roles.
- Personas & agents — personas and multi-agent delegation.
- Streaming & HTTP — every route, the SSE envelope, and HITL.
- React —
<AgentProvider>+useAgentChat(): a Hello chat for Adonis + Inertia. - Browser client — the framework-free SSE client, for everything that is not React.
- Configuration reference — every
AgentConfigfield.
Agent
A governed, durable-ready AI agent for AdonisJS — streaming chat, tool-calling, fail-closed governance, human-in-the-loop approvals, and multi-agent delegation. One agent loop, two runners.
The agent loop
The provider-agnostic agent turn — the model→tools→approval→model state machine, the hooks seam that makes it replay-safe, and the inline vs durable runners that drive it.