Skip to main content

Configuration

Embedded profile

createAgent is the batteries-included entry point. It defaults every port to a reference in-memory adapter; override only what you need:

import { createAgent } from "@forge/agentkit/providers";

const agent = createAgent({
manifest: {
id: "assistant",
name: "Assistant",
instructions: "Be concise.",
modelPolicy: { role: "smart" },
},
tenantId: "acme",
providerCredentials: { openai: { apiKey: process.env.OPENAI_API_KEY ?? "" } },
roleAssignments: { smart: ["gpt-4o"], fast: ["gpt-4o-mini"] },
});

Everything else has a default that works:

OptionDefaultOverride when
modelsa small built-in catalogYou have your own model list, pricing or residency constraints
roleAssignmentsmaps smart/fast to the built-in catalogYou changed models
providerCredentialsread from the environment by the provider factoryYou hold keys somewhere else
toolsnoneAlways, eventually — see your first tool
contextProvidersnoneYou want memory or retrieval in the prompt
authorizationallow-allMore than one kind of user
guardrailsnoneYou need input or output checks — see Guardrails
toolSearch / catalogBudget / toolsetsoffYour catalogue is large; read the measurement first
tenantId"default"Always, in anything multi-tenant

Server profile ("AgentOS")

The server profile composes the same pieces explicitly — the durable worker driven by a real queue, production adapters, and the HITL/usage services — so many runs execute concurrently with recovery and a live UI:

import { asId, defineAgent } from "@forge/agentkit";
import type { AgentId, ResolvedModel, Run } from "@forge/agentkit";
import { createDefaultEngine, createDurableWorker, createMemoryEventBus } from "@forge/agentkit/runtime";
import {
createMemoryCheckpointStore,
createMemoryRunEventLog,
createMemoryRunStore,
} from "@forge/agentkit/persistence";

const bus = createMemoryEventBus();

const worker = createDurableWorker({
// The in-memory adapters here so this compiles as written. A deployment swaps in the Postgres ones from
// `@forge/agentkit/adapters/postgres` — same ports, no change to any agent or tool.
runs: createMemoryRunStore(),
checkpoints: createMemoryCheckpointStore(),
eventLog: createMemoryRunEventLog(),
publisher: bus.publisher,
engine: createDefaultEngine({
// `defineAgent` fills in the manifest's defaults — response format, tool policy, limits. A hand-written
// manifest has to supply every field, which is a lot of typing to say "the usual".
loadManifest: async () =>
defineAgent({
// Branded ids: `asId<AgentId>` rather than a bare string, so a conversation id cannot be passed where an
// agent id belongs. The compiler is the only thing that catches that swap.
id: asId<AgentId>("assistant"),
name: "Assistant",
instructions: "Be concise.",
modelPolicy: { role: "smart" },
}),
resolveModel: () => ({ model: {} as ResolvedModel, modelId: "gpt-4o" }),
loadHistory: async () => [],
}),
buildContext: (run: Run) => ({
tenantId: run.tenantId,
principalId: asId("system"),
roleIds: [],
locale: "en",
timezone: "UTC",
requestId: asId(`req-${run.id}`),
conversationId: run.conversationId,
runId: run.id,
}),
workerId: process.env.HOSTNAME ?? "worker-1",
});

// A BullMQ processor calls worker.process({ tenantId, runId }) for each queued run.

The GraphQL schema + thin resolvers (typeDefs, createResolvers) and the SSE transport (openRunEventSse) sit on top; the frontend package consumes them. See the Durable runtime concept and the API reference.

Adapters

Every capability is a port; you choose the adapter. Start in-memory, move to production adapters without changing agent or tool code.

PortDevelopmentProduction
Storesin-memoryPostgreSQL / Supabase (RLS)
Job dispatcherinlineBullMQ + Redis
Realtimein-memory event busSupabase Realtime / Redis pub-sub
Blob storein-memoryS3-compatible

The forge command

Installing the package puts a forge binary on your path. Nothing here needs you to write an entrypoint first.

npx forge doctor
CommandDoes
forge migrateApplies pending migrations. Idempotent, and safe to run from several pods at once — it takes a Postgres advisory lock, so concurrent runs serialise instead of racing on DDL.
forge migrate --statusReports applied and pending migrations. Changes nothing.
forge migrate --dry-runPrints the statements that would run. Changes nothing — not even the ledger table.
forge serveStarts the API host. Needs FORGE_APP_MODULE.
forge workerStarts a run worker. Needs FORGE_APP_MODULE.
forge doctorChecks configuration, the database, the schema version and Redis.

migrate and doctor deliberately need no app module: a database is provisioned before an application exists, and a diagnostic you cannot run until everything else is configured is a diagnostic nobody can use.

doctor reports every failure, not the first

✓ configuration: schema mode off, port 4000
✗ postgres: postgres://db.internal:5432/app: connect ECONNREFUSED
→ Check the database is running and FORGE_DATABASE_URL points at it.
– schema: not checked — Postgres is unreachable, so this would fail for the same reason
✗ redis: redis://cache.internal:6379/0: connection refused
→ Check Redis is running and FORGE_REDIS_URL points at it.

✗ 2 of 5 check(s) failed

Three things it will not do. It does not stop at the first problem — otherwise it adds nothing over starting the server and reading the error. It does not report a check it could not run as a pass: a means skipped, and a check downstream of a failure is skipped rather than blamed. And it never prints a credential — a connection string is reduced to host and database, and if the value is ambiguous (an unescaped @ in a password, say) it prints nothing at all rather than guessing which part was the secret.

Automatic schema

Development adapters can provision their own schema on startup (auto mode), so a fresh database is usable with no manual migration step. Production defaults to managed migrations (off) — run forge migrate as a deploy step.

Environment

ANTHROPIC_API_KEY=... # or OPENAI_API_KEY, etc.
DATABASE_URL=postgres://... # server profile
REDIS_URL=redis://... # server profile

Model IDs are never hardcoded into agents — an agent asks for a fast or smart role and the registry resolves it by capability, cost ceiling, and data residency.