Skip to content

Configuration Reference

Authoritative reference for oma’s configuration shapes. The TypeScript source of truth lives in packages/api-types; the in-repo human-readable schema doc is AGENTS.md.

interface AgentConfig {
id: string; // assigned by the platform
name: string;
model: string; // e.g. "claude-sonnet-4-6"
system: string; // system prompt
tools: ToolDefinition[];
skills?: string[]; // skill ids
environment_id?: string;
mcp_servers?: McpServerConfig[];
memory_store_id?: string;
harness?: string; // defaults to "default"
archived?: boolean;
version: number; // platform-bumped on every update
created_at: string;
updated_at: string;
}
type ToolDefinition =
| { type: 'agent_toolset_20260401' } // built-in toolset
| {
type: 'custom';
name: string;
description: string;
input_schema: JsonSchema;
execution:
| { type: 'http'; endpoint: string; method?: string }
| { type: 'sandbox'; command: string };
};
interface McpServerConfig {
name: string; // becomes the prefix: mcp__<name>__<tool>
type: 'url' | 'stdio';
url?: string; // required for HTTP/SSE; derived for stdio
authorization_token?: string; // inline bearer (otherwise: matched vault credential)
stdio?: {
command: string;
args?: string[];
env?: Record<string, string>;
port: number; // 127.0.0.1:port the spawned process binds to
ready_timeout_ms?: number; // default 60_000
};
}

For type: 'stdio', oma spawns the process inside the sandbox container, waits for the port to bind, and proxies the existing HTTP-based MCP tool wiring at it. Tool discovery is bounded at 15 s per server; up to 20 servers per agent.

interface EnvironmentConfig {
id: string;
name: string;
base_image: string; // e.g. "openma/sandbox-base:python-3.12"
packages: {
pip?: string[];
npm?: string[];
apt?: string[];
cargo?: string[];
gem?: string[];
go?: string[];
};
network?: {
allowlist?: string[]; // hostnames the sandbox may reach
denylist?: string[];
};
env?: Record<string, string>;
}

A vault is a tenant-scoped credential bundle; credentials inside it are bound to MCP servers or sandbox CLIs.

interface Vault {
id: string;
tenant_id: string;
name: string;
created_at: string;
updated_at: string;
archived_at?: string;
}
// Credentials live in a separate table; each is one of three types
type CredentialAuth =
| { type: 'static_bearer'; token: string; mcp_server_url: string }
| { type: 'mcp_oauth'; access_token: string; refresh_token?: string;
token_endpoint?: string; expires_at?: string; mcp_server_url: string }
| { type: 'cap_cli'; cli_id: string; token: string }; // e.g. cli_id: "gh", "glab", "aws"

Binding to a host happens via the credential, not the vault: a static_bearer / mcp_oauth credential matches by parsing the request hostname against mcp_server_url; a cap_cli credential matches by cli_id lookup in the cap spec registry. Up to 20 credentials per vault. Tokens are AES-GCM-encrypted at rest under PLATFORM_ROOT_SECRET and never returned via the API once written.

interface SkillMetadata {
type: 'skill';
id: string;
display_title: string;
name: string; // SKILL.md frontmatter `name` — also the mount-folder name
description: string;
source: 'anthropic' | 'custom';
latest_version: string; // numeric epoch string
created_at: string;
updated_at: string;
}
// Per-version detail returned by GET /v1/skills/:id/versions/:version
interface SkillVersion {
version: string;
files: Array<{ filename: string }>; // R2 objects under t/{tenant}/skills/{id}/{version}/<filename>
}

The platform mounts skill files at /home/user/.skills/{name}/ (using the SKILL.md name, not id) and inlines the SKILL.md body directly into the system prompt at session start — no lazy read. The injected wrapping is:

<source name="skill:{id}">
<skill name="{name}">
{full SKILL.md body}
</skill>
</source>

Attach to an agent with the object form (not a bare string array):

{ "skills": [{ "skill_id": "skill_abc123", "type": "custom" }] }

Built-in skills (source: "anthropic"): xlsx, pdf, docx, pptx — four total, no upload needed.

interface MemoryStore {
id: string;
agent_id: string;
embedding_model: string; // defaults to platform setting
vector_index: string; // Vectorize index name
}
interface SessionMeta {
id: string;
agent_id: string;
agent_version: number; // pinned at creation
status: 'pending' | 'running' | 'idle' | 'done' | 'failed';
created_at: string;
updated_at: string;
}
interface SessionEvent {
id: string;
session_id: string;
type: string; // 'agent.message', 'agent.tool_use', 'agent.thinking', etc.
data: unknown;
created_at: string;
}

The full event type catalog is in packages/api-types/src/events.ts.

Required for self-host. Set as Worker secrets via npx wrangler secret put NAME.

VariableWorkerPurpose
PLATFORM_ROOT_SECRETmain, integrationsRoot secret for at-rest encryption (credentials, model card keys, integration tokens) and outbound MCP token signing. Workers refuse to start without it. Back it up — losing it makes every encrypted row unreadable.
BETTER_AUTH_SECRETmainbetter-auth session signing key
API_KEYmainBootstrap REST key for first-run. .env.example / .dev.vars.example ship dev-test-key-change-me.
INTEGRATIONS_INTERNAL_SECRETmain, integrationsShared secret between main and integrations workers
VariableWorkerPurpose
AUTH_DISABLEDmain (Node + CF wrangler local)Set to 1 for a single-user local trial. Every request becomes tenant_id="default" (no x-api-key), /auth-info returns providers: [], and the Console skips login. /auth/* returns 410 while auth is off. Do not use in production.
CONSOLE_DIRmain (self-host Node)Path to the built Console SPA (index.html). Docker images set this. Local pnpm --filter @duyet/oma-main-node start also auto-detects apps/console/dist. Unset + no dist → API-only on :8787.
VariableWorkerPurpose
ANTHROPIC_API_KEYmain, agentFallback LLM credential when a tenant has not added a Model Card. In production, prefer per-tenant Model Cards from the Console — they’re encrypted under PLATFORM_ROOT_SECRET and rotatable without redeploy. (Alternates: OPENAI_API_KEY, MINIMAX_API_KEY.)
ANYROUTER_API_KEYagentStatic fallback provider (routes through AnyRouter, an OpenAI-compatible gateway) used only when an agent’s model matches no Model Card and ANTHROPIC_API_KEY is unset. Bare claude-* handles are rewritten to anthropic/claude-*. AnyRouter aliases hyphenated anthropic/claude-sonnet-4-6 to dotted BYOK-only anthropic/claude-sonnet-4.6, so this path sends the catalog id anyrouter/free for those sonnet handles. Never send the dotted 4.6 slug.
CLAUDE_CODE_OAUTH_TOKENagent (self-host Node only)Non-interactive Claude Code auth (claude setup-token) for agents on the claude-agent-sdk harness — the CI/CD alternative to ANTHROPIC_API_KEY for that harness’s CLI subprocess. Not available on the Cloudflare Workers deploy (the SDK can’t spawn a subprocess in a Workers isolate).
LINEAR_CLIENT_IDintegrationsLinear OAuth
LINEAR_CLIENT_SECRETintegrationsLinear OAuth
LINEAR_WEBHOOK_SECRETintegrationsVerify inbound Linear webhooks
GITHUB_APP_IDintegrationsGitHub App ID
GITHUB_PRIVATE_KEYintegrationsGitHub App private key (.pem contents)
GITHUB_WEBHOOK_SECRETintegrationsVerify inbound GitHub webhooks
SLACK_CLIENT_IDintegrationsSlack OAuth
SLACK_CLIENT_SECRETintegrationsSlack OAuth
SLACK_SIGNING_SECRETintegrationsVerify inbound Slack events
SLACK_MANAGED_CLIENT_IDintegrationsShared managed Slack App for one-click “Add to Slack” — all three SLACK_MANAGED_* must be set together, else the wizard falls back to the bring-your-own-App manifest flow
SLACK_MANAGED_CLIENT_SECRETintegrationsManaged Slack App client secret
SLACK_MANAGED_SIGNING_SECRETintegrationsManaged Slack App signing secret
LINEAR_OAUTH_CLIENT_ID / LINEAR_OAUTH_CLIENT_SECRETintegrationsUnified OAuth connect flow (/oauth/linear/*) — provider is absent (501) unless both are set
GITHUB_OAUTH_CLIENT_ID / GITHUB_OAUTH_CLIENT_SECRETintegrationsUnified OAuth connect flow (/oauth/github/*)
SLACK_OAUTH_CLIENT_ID / SLACK_OAUTH_CLIENT_SECRETintegrationsUnified OAuth connect flow (/oauth/slack/*)
TELEGRAM_BOT_TOKENintegrationsBotFather token — enables the /telegram/webhook route. Alone it only gets past the “not configured” 503; a bound agent is also required
TELEGRAM_AGENT_IDintegrationsAgent that Telegram chats run against (required to actually create/resume sessions)
TELEGRAM_VAULT_IDSintegrationsComma-separated vault ids bound to Telegram sessions
TELEGRAM_ENVIRONMENT_IDintegrationsEnvironment for Telegram sessions
TELEGRAM_IDLE_TIMEOUT_MSintegrationsIdle window before a chat’s sandbox is auto-paused. Default 5 min (300000)
GOOGLE_CLIENT_IDmainGoogle sign-in for Console
GOOGLE_CLIENT_SECRETmainGoogle sign-in for Console
STRIPE_SECRET_KEYmainConsumer payments / paywall (@duyet/oma-payments). Unset → checkout returns 501
STRIPE_WEBHOOK_SECRETmainVerify POST /webhooks/stripe signatures
PAYMENTS_DISABLEDmainKill-switch — set truthy to make every publication turn free regardless of pricing
VariableWorkerPurpose
TAVILY_API_KEYmain, agentWeb search backend for web_search built-in
CLOUDFLARE_API_TOKENmainProgrammatic CF resource management (optional)
CLOUDFLARE_ACCOUNT_IDmainProgrammatic CF resource management (optional)
INTEGRATIONS_PUBLIC_URLmainOverride auto-detected integrations URL
PER_TENANT_DB_ENABLEDmainSet "true" to enable per-tenant D1 isolation
STORE_BACKENDSmainJSON config for storage backends (advanced)
DATABASE_URLmainExternal Postgres URL (advanced)
SCHEDULED_AGENT_RUNS_CRONmainOverride the agent-schedule tick cron (default * * * * *, per minute). Cloudflare only

What each Worker needs in its wrangler.jsonc:

BindingTypeName
MAIN_DBD1openma-auth
CONFIG_KVKV(your namespace)
FILES_BUCKETR2managed-agents-files
AIWorkers AI(built-in)
VECTORIZEVectorizeopenma-memory
SANDBOX_sandbox_defaultService→ agent worker
INTEGRATIONSService→ integrations worker
SEND_EMAILEmail(your sender)
ANALYTICSAnalytics Engineoma_events
BindingTypeName
SESSION_DODurable ObjectSessionDO
SANDBOXDurable ObjectSandbox (Container class)
CONFIG_KVKV(shared with main)
MAIN_DBD1(shared with main)
WORKSPACE_BUCKETR2managed-agents-workspace
FILES_BUCKETR2(shared with main)
AI, VECTORIZE, BROWSER, ANALYTICS(same as main)
BindingTypeName
MAIN_DBD1(shared with main)
MAINService→ main worker