Skip to content

Quickstart

This quickstart follows RFC-002 §9.3.8: a developer with Postgres and Node 20+ should reach a streamed answer in under 10 minutes of wall-clock time (user experience expectation; CI uses a Puppeteer harness with a 10-minute test timeout).

  • Node.js 20+ and pnpm
  • Postgres 14+ (local or Docker)
  • Optional: GOOGLE_GENERATIVE_AI_API_KEY (Gemini is the documented default; without any model key the example uses a deterministic mock)

If you just want a working Arivie agent without scaffolding a Next.js app, the bare minimum is six commands:

Terminal window
mkdir my-arivie-app && cd my-arivie-app
pnpm init
pnpm add @arivie/core @arivie/db-postgres @arivie/workspace @ai-sdk/google
pnpm add -D @arivie/cli
pnpm dlx @arivie/cli add skill cohort-analysis
pnpm dlx @arivie/cli add skill funnel-conversion

That gives you the core framework plus two SOP skill playbooks dropped into ./skills/. The CLI ships with six skills bundled (cohort-analysis, funnel-conversion, revenue-attribution, dau-mau-ratio, churn-investigation, anomaly-detection) and eight UI scaffolds — no extra package installs needed; everything resolves from the CLI’s own tarball.

From there, jump to the Build your first BI/BA agent tutorial for the full schema → semantic-layer → agent → file-artifact walkthrough.

The Next.js flow below is the production-shaped scaffold (HTTP route + chat UI + middleware-friendly handler shape). Pick whichever matches what you’re building.

Terminal window
pnpm create next-app my-arivie-app --typescript --app --eslint
cd my-arivie-app
Terminal window
pnpm add @arivie/core @arivie/db-postgres @arivie/react @arivie/mcp
pnpm add -D @arivie/cli
Terminal window
pnpm dlx arivie init

This scaffolds arivie.config.ts, semantic directory stubs, and CLI wiring.

Copy the example env template and set DATABASE_URL:

.env.local
# Arivie with-nextjs example — copy to .env.local and fill in.
DATABASE_URL=postgresql://localhost:5432/arivie
ARIVIE_OWNER_ID=with-nextjs-owner
# Model selection: first non-empty key wins (Gemini → Anthropic → OpenAI → mock).
GOOGLE_GENERATIVE_AI_API_KEY=
GOOGLE_MODEL=gemini-2.5-flash
# ANTHROPIC_API_KEY=
# OPENAI_API_KEY=
# OPENAI_MODEL=gpt-5
# Mixpanel (optional — omit for mock-Plan-B cross-source demo per RFC-003 §13 D5)
# MIXPANEL_PROJECT_TOKEN=
# MIXPANEL_PROJECT_ID=
# MIXPANEL_REGION=mixpanel
Terminal window
pnpm dlx arivie setup

setup creates the read-only DB role, runs Mastra Memory migrations, and performs the owner-identity smoke check (RFC-002 §4.5).

Seed the dogfood schema (matches examples/with-nextjs/seed.sql):

Terminal window
psql "$DATABASE_URL" -f path/to/seed.sql

Add to .env.local:

Terminal window
GOOGLE_GENERATIVE_AI_API_KEY=your-key
GOOGLE_MODEL=gemini-2.5-flash

Preference order in the reference config: Gemini → Anthropic → OpenAI → mock.

Terminal window
pnpm dlx arivie add entity orders
pnpm dlx arivie add ui agent-chat

Create app/api/arivie/route.ts (canonical example):

app/api/arivie/route.ts
/* SPDX-License-Identifier: Apache-2.0 */
import { getArivieRuntime } from "../../../arivie.config";
export async function POST(req: Request): Promise<Response> {
const { arivie } = await getArivieRuntime();
return arivie.next.POST(req);
}

Your arivie.config.ts should match the reference resolver and defineArivie block:

arivie.config.ts
/* SPDX-License-Identifier: Apache-2.0 */
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { defineArivie, type ArivieInstance } from "@arivie/core";
import { postgresAdapter } from "@arivie/db-postgres";
import { resolveMixpanelSource } from "./lib/mixpanel-source.js";
import { makeMcpServer } from "@arivie/mcp";
import type { MCPServer } from "@mastra/mcp";
import { loadSemanticLayerSync, type SemanticLayer } from "@arivie/semantic";
import { anthropic } from "@ai-sdk/anthropic";
import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { createOpenAI } from "@ai-sdk/openai";
import type { LanguageModel } from "ai";
import { MockLanguageModelV3 } from "ai/test";
const __dirname = dirname(fileURLToPath(import.meta.url));
const semanticPath = join(__dirname, "semantic");
function requireDatabaseUrl(): string {
const url = process.env.DATABASE_URL;
if (url == null || url.length === 0) {
throw new Error(
"DATABASE_URL is required — copy .env.example to .env and set your Postgres URL",
);
}
return url;
}
function resolveModel(): LanguageModel {
// Preference order: Gemini (cheapest) → Anthropic → OpenAI → mock.
// Override the Gemini default via GOOGLE_MODEL (e.g. "gemini-2.5-flash").
const googleKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY;
if (googleKey != null && googleKey.length > 0) {
const google = createGoogleGenerativeAI({ apiKey: googleKey });
const modelId = process.env.GOOGLE_MODEL ?? "gemini-2.5-flash";
return google(modelId) as LanguageModel;
}
const anthropicKey = process.env.ANTHROPIC_API_KEY;
if (anthropicKey != null && anthropicKey.length > 0) {
return anthropic("claude-sonnet-4-20250514");
}
const openaiKey = process.env.OPENAI_API_KEY;
if (openaiKey != null && openaiKey.length > 0) {
const openai = createOpenAI({ apiKey: openaiKey });
return openai(process.env.OPENAI_MODEL ?? "gpt-5") as LanguageModel;
}
console.warn(
"[with-nextjs] No model key set (GOOGLE_GENERATIVE_AI_API_KEY|ANTHROPIC_API_KEY|OPENAI_API_KEY) — using deterministic mock model.",
);
return new MockLanguageModelV3({
provider: "mock",
modelId: "mock",
doGenerate: {
content: [
{
type: "text",
text: "Example mock response (set GOOGLE_GENERATIVE_AI_API_KEY for a live Gemini run).",
},
],
finishReason: "stop",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
warnings: [],
},
} as unknown as ConstructorParameters<typeof MockLanguageModelV3>[0]) as LanguageModel;
}
let cached: {
arivie: ArivieInstance;
mcp: MCPServer;
semantic: SemanticLayer;
} | undefined;
export async function getArivieRuntime(): Promise<{
arivie: ArivieInstance;
mcp: MCPServer;
}> {
if (cached == null) {
const postgres = postgresAdapter({
url: requireDatabaseUrl(),
readOnlyRole: "arivie_reader",
});
const { adapter: mixpanel, label: mixpanelLabel } = resolveMixpanelSource();
if (mixpanelLabel.includes("mock-plan-b")) {
console.warn(`[with-nextjs] ${mixpanelLabel}`);
}
const semantic = loadSemanticLayerSync(semanticPath);
const arivie = await defineArivie({
owner: {
id: process.env.ARIVIE_OWNER_ID ?? "with-nextjs-owner",
name: "With Next.js Example",
},
model: resolveModel(),
workspace: { rootDir: semanticPath },
sources: { postgres, mixpanel },
semantic: { path: semanticPath, mode: "preload" },
compileMetric: true, // JSON-IR routing for declared measures (arxiv 2502.00032)
resolveUser: async () => ({
userId: "demo-user",
permissions: ["analytics:read"],
dbRole: "arivie_reader",
}),
});
const mcp = makeMcpServer({
agent: arivie.agent,
semantic,
db: postgres,
ownerId: process.env.ARIVIE_OWNER_ID ?? "with-nextjs-owner",
ownerName: "With Next.js Example",
});
cached = { arivie, mcp, semantic };
}
return { arivie: cached.arivie, mcp: cached.mcp };
}
app/page.tsx
/* SPDX-License-Identifier: Apache-2.0 */
import { AgentChat } from "./agent-chat-client";
export default function HomePage() {
return (
<main className="page">
<h1>Arivie + Next.js</h1>
<p className="lede">
Chat surface powered by <code>useAgent</code> via the registry{" "}
<code>AgentChat</code> component.
</p>
<AgentChat endpoint="/api/arivie" title="Ask your data" />
</main>
);
}
app/agent-chat-client.tsx
/* SPDX-License-Identifier: Apache-2.0 */
"use client";
export { AgentChat } from "@arivie/registry/agent-chat";
Terminal window
pnpm dev

Open http://localhost:3000, type How many customers?, and submit. You should see a streamed assistant message. Without API keys the mock model returns text containing Example mock response.

The monorepo runs pnpm test:quickstart (Puppeteer + testcontainers Postgres) on every PR after examples boot. See arivie/tests/e2e/quickstart.test.ts.

  • Want a deeper walkthrough? Build your first BI/BA agent — 60-minute tutorial that adds a semantic layer, an SOP skill, and demonstrates writing a Markdown report directly from the agent. Each step has a checkpoint.
  • SQL is the calculator — research-backed convention that prevents LLM arithmetic errors. Adopt this before your skill count grows.
  • The boundaryowner.id and arivie_owner_identity
  • Auth integrations — Clerk, WorkOS, Better Auth, Auth.js, custom JWT
  • Full runnable project: arivie/examples/with-nextjs/ (basic) or arivie/examples/with-pos-fnb/ (full BI/BA example — multi-outlet F&B chain, 10 SOP skills, single agent producing Markdown / HTML artifacts via workspace tools)

The simplest path is the typed instance.ask() facade:

const result = await instance.ask({
prompt: "How many customers did we get last week?",
user: { userId: "u1", permissions: ["analytics:read"], dbRole: "arivie_reader" },
});
console.log(result.text); // string
console.log(result.toolCalls); // typed tool-call trace
console.log(result.sql); // string[] of execute_<source> statements
console.log(result.artifacts); // file paths written via mastra_workspace_write_file

ask() sets the AsyncLocalStorage user context the execute_<source> tool reads, manages the Mastra Memory { thread, resource } thread, and walks the response into a strict AskResult shape. For streaming or other Mastra-specific options, instance.agent.generate(...) remains the escape hatch.