Getting started
Requirements
- Bun ≥ 1.1, or Node ≥ 22, which AI SDK 7 needs (contributing to the repository itself needs Bun ≥ 1.4 — see its readme)
- A model provider through the AI SDK (v7).
aiis a peer dependency: install it next toagentenkit, as below.
Install
bun add agentenkit ai zodThe React hook is separate, and only needed if you want it:
bun add use-agentenkitReference adapters lean on optional peers. Install only the ones you use:
bun add @prisma/client redis @upstash/qstash @upstash/redis pgA runtime you can run right now
There is no createDevRuntime. Assembling the platform is five adapters and one wire, and seeing them is the point — swapping any one of them for its durable equivalent later is then obvious rather than magic.
// lib/runtime.ts
import { Database } from 'bun:sqlite';
import { setupAgentCore } from 'agentenkit';
import { SqliteRunStreams, SqliteStorage } from 'agentenkit/adapters/sqlite';
import { InlineQueue } from 'agentenkit/adapters/inline';
import { MemoryBus, MemoryKv } from 'agentenkit/adapters/memory';
import { SqliteAdminStore } from 'agentenkit/admin/sqlite';
import { openai } from '@ai-sdk/openai';
// One file holds both your tables and — under an `agentic_` prefix — the
// platform's own operational history.
const db = new Database('agentic-kit.sqlite');
const queue = new InlineQueue();
export const runtime = await setupAgentCore({
storage: new SqliteStorage(db),
streams: new SqliteRunStreams(db), // each run's live output
admin: SqliteAdminStore.open(db),
bus: new MemoryBus(),
kv: new MemoryKv(),
queue,
resolveModel: (name) => ({
instance: () => openai(name),
contextWindow: 128_000,
}),
});
// The queue and the worker each need the other, so the queue is wired once the
// core exists. Nothing dispatches until this line runs.
queue.bind((job) => runtime.worker.handleJob(job));
export const chat = runtime.createStreamTextAgent({ name: 'chat', model: 'gpt-4o' });Then:
const { accepted, threadId, runId } = await chat.run({ prompt: 'hi' });That call persists the message, marks the thread RUNNING, and enqueues a job. It does not wait for the model. The run continues after the call returns.
Why await setupAgentCore
The operational store is opened there. A store that cannot be opened is a startup error rather than a surprise on the first run — falling back to memory would silently lose every record, and a dashboard showing nothing looks exactly like no traffic.
What InlineQueue is and is not
It dispatches on a later tick, so enqueue returns immediately and a run really does outlive the request that started it. It honours delaySeconds, so parked approvals expire and blocked jobs redrive exactly as they do against a durable queue. What it does not do is survive a restart. Use it in development.
Choosing a SQLite driver
openSqlite(file) picks one at runtime — bun:sqlite where it exists, node:sqlite otherwise — if you would rather not import one yourself. Pass ':memory:' for a database that dies with the process.
Watching a run
Nothing above prints anything, because a run's output goes to its run stream, not to the caller. The shortest way to see it:
for await (const frame of runtime.events.follow(threadId)) {
if (frame.kind !== 'stream') continue;
if (frame.item.type === 'TEXT_MESSAGE_CONTENT') process.stdout.write(frame.item.delta);
if (frame.item.type === 'RUN_FINISHED' || frame.item.type === 'RUN_ERROR') break;
}follow reads the thread record and the run streams as one. Breaking out of the loop stops it. See Run streams.
For a browser, expose the HTTP endpoints and use use-agentenkit, which does the hydrate-then-tail dance for you.
Next
- Core concepts — what a thread, a run and a step actually are.
- HTTP API — the endpoints to expose.
- Production — swapping the adapters for durable ones.