Skip to content

Run from code

For developers. Start with the developer manual to choose API work, App publishing, or project changes.

The openbot program covers most needs. When you need more, you can start OpenBot from your own program. Then you can add your own models, tools, routes, and App Index sources.

OpenBot is built with Effect. You build it from Layers. A Layer is an Effect recipe that builds services. Making a Layer does not open the network or the database. That happens when you launch it.


This program does the same as openbot:

import { Launch, Server } from "@openbot-build/openbot"
Launch.main(Server.layerLive)

Launch.main first reads the command line. With --help it shows the help and stops, and it needs no settings for that. Then it reads the variables in Configuration, from the environment or from secret files. It blocks outbound connections to private addresses, and logs a failed start. Then it runs the Layer that you give it until the process stops. It gives that Layer the Node.js services and the fetch HTTP client. Launch.server is the same program as an Effect, for when you run it yourself. Give it NodeServices.layer.

EnvironmentConfiguration.load gives the same settings: host, port, databaseUrl, secretKeys, clusterSecret, clusterHost, and outboundAllow. You can also write these values in code.

The program still needs a platform. Run the setup program once before people use it. See Platform.

Export What it is
Server.layerLive(options) OpenBot with its own Node.js HTTP server and the real services from outside. openbot-server runs it.
Server.layer(options) The same server, without the services that it needs. See Services that OpenBot needs.
Server.layerFixed(options) Like Server.layer, but it first writes one sign-in configuration to the platform settings. For tests and development.
Server.layerApp(options) The OpenBot routes and services, for an HTTP server that you already have.
Server.layerApi Only the HTTP routes.
Launch.main(make), Launch.server(make) Runs the Layer from make as openbot-server does.
OpenBot.layerShared(options) The services that live as long as the instance: the database pool, the secret keys, and the shared settings.
OpenBot.layer(options) The services behind the routes: sign-in, tokens, permissions, storage, Chats, and Apps.
OpenBot.layerOutside The real services from outside of OpenBot, such as the network of Apps.
OpenBot.layerLive(options) OpenBot.layer with the real services from outside, and no tools of the host.
HostTools The tools that your program gives to every Chat. See Add a tool.
DatabasePostgres The database Layer and the migrate program.
EnvironmentConfiguration Reads the instance settings from environment variables.
Auth, Chats, AppHost, AppTools, Configuration Service types for advanced setups.

The subpath @openbot-build/openbot/Development exports the development server. See Run the development server.


Server.layerLive takes instance options and app options. Server.layer and Server.layerFixed take the same options, but not secretKeys, because your program gives them the shared services.

Option Type What it is
host string Required. The address to listen on, such as 127.0.0.1.
port number Required. The port to listen on, such as 3000.
databaseUrl Redacted<string> Required. The PostgreSQL URL.
secretKeys Redacted<string> The secret key list. See Secret keys.
clusterSecret Redacted<string> Required. At least 32 characters. All nodes use the same secret for private RPC.
clusterHost string Optional. The address other nodes can reach. It defaults to the bound HTTP address.

The server takes no sign-in options. It reads the public origin, the platform provider, and the sign-in secret from the platform settings.

  • Before setup, every route except /health answers 503 with SetupRequired.
  • OpenBot reads these settings each time it uses them. When one of them changes, the next request uses the new value. The server is built only once, so requests and Turns that are running go on.

Server.layerFixed takes one auth configuration. When it starts, it writes the public origin, the sign-in secret, and the provider of auth to the platform settings. Then it works like Server.layer, so a later change of the platform settings also works at once. Tests and the development server use it. Each server gives the app services to your program, so tests can use them.

Option What it does
builtinModels Models that every organization can use, given in code. See Models.
builtinModels.defaultModel The model of a new Chat when its Bot names none. It is one of builtinModels. Only a simulated platform sets it.
routes A Layer with your own HTTP routes.
indexSources More sources for the App Index search, next to the OpenBot Index.
indexWork Timing of the App Index worker: { intervalMs, retryMs }.

Some values were options before. They are settings in the database now, so every instance shares them:

Old option Setting
appRestrictions appRestrictions in the platform settings.
appReadRules appReadRules in the organization settings.
bots.helperModel rulesModel and botMemoryModel in the organization settings.
bots.requestTimeout requestTimeoutMs in the organization settings.
bots.memory botMemory in the organization settings.
appEncryptionKey The secretKeys instance option.

OpenBot.layer, Server.layer, and Server.layerFixed do not build the services that come from outside of OpenBot. They need them, and your program gives them as Layers. So a test can give fakes, and your program can give its own.

Services What they are Layer for a real server
Shared services The database pool, the secret keys, and the shared settings. OpenBot.layerShared(options)
Services from outside (OpenBot.Outside) The drivers of the Computer providers, the Live backends, the readers of password managers, the network and the check of Apps, the DNS check of domains, the public network of the App Index, and the discovery of Apps. OpenBot.layerOutside
Tools of the host (HostTools) The tools that your program gives to every Chat. HostTools.none
Platform services Files, paths, random numbers, and the HTTP client. NodeServices.layer and FetchHttpClient.layer

Server.layerLive gives all of them but the platform services. Launch.main gives the platform services. This program does the same as Launch.main(Server.layerLive), with each part in view:

import { HostTools, Launch, OpenBot, Server } from "@openbot-build/openbot"
import { Layer } from "effect"
Launch.main((settings) =>
Server.layer(settings).pipe(
Layer.provide(Layer.merge(OpenBot.layerOutside, HostTools.none)),
Layer.provideMerge(OpenBot.layerShared(settings)),
),
)

The options chats, discovery, computerDrivers, liveBackends, vaultReaders, appProbe, appNetwork, domainControl, and indexPublicNetwork are gone. Give the services in their place.


Models come from the model providers in the database and from builtinModels. See Models. Bot memory follows botMemory in the organization settings.

The server keeps application services and active executions. Each Turn builds its model and tools in its own scope. A saved execution identity carries the member, original token, Bot, and delegation limits. Requests do not keep a member runtime open. One Chat admits one advancing Turn at a time. Its commands, checkpoints, events, and results stay in PostgreSQL across nodes and restarts. Closing an HTTP stream closes observation; cancellation is a separate command.

A tool is a function that the model can call. Make it with Effect AI. Then give the toolkit and its handlers to OpenBot with HostTools.layer. Every Turn of every Chat can call the tool. handlers gets the ID of the Chat and gives the handlers for its Turns. Each Turn builds them when it starts, and closes them when it ends.

import {
type Configuration,
HostTools,
Launch,
OpenBot,
Server,
} from "@openbot-build/openbot"
import { DateTime, Effect, Layer, Schema } from "effect"
import { Tool, Toolkit } from "effect/ai"
const CurrentTime = Tool.make("CurrentTime", {
description: "Returns the current time on the server.",
parameters: Schema.Struct({}),
success: Schema.String,
})
const toolkit = Toolkit.make(CurrentTime)
const handlers = toolkit.toLayer({
CurrentTime: () => Effect.map(DateTime.now, DateTime.formatIso),
})
const server = (settings: Configuration.Settings) =>
Server.layer(settings).pipe(
Layer.provide(
Layer.merge(
OpenBot.layerOutside,
HostTools.layer({ toolkit, handlers: () => handlers }),
),
),
Layer.provideMerge(OpenBot.layerShared(settings)),
)
Launch.main(server)

Add needsApproval: true to Tool.make when each call needs a decision. OpenBot then checks the capability tools.call for it. When the call needs a decider, the Bot gets a pending result, and the call runs later when it is allowed. See Requests.


routes adds HTTP routes to the same server. It is a Layer. The Layer can use the Auth service to check the sign-in cookie.

import { HttpRouter, HttpServerResponse } from "effect/http"
const routes = HttpRouter.add(
"GET",
"/status",
HttpServerResponse.text("ready"),
)
Server.layerLive({ ...settings, routes })

Do not use paths under /auth/ or /v1/. OpenBot owns them.

Server.layerApp(options) gives the OpenBot routes and services. It needs the same services as Server.layer. Server.layerApi gives only the OpenBot routes. It also adds error logging for failed requests.

Server.layerLive, Server.layer, and Server.layerFixed start their own Node.js server. To put OpenBot inside a server that you already have, build the same parts yourself. Read packages/openbot/src/app/Server.ts for how Server.layer serves the app once, with a check that answers SetupRequired before setup. Keep the sign-in secret in the platform settings, not in your own environment variable. Every instance must use the same one.


DatabasePostgres.migrate prepares the schema and the initial data gate. It can mark only an empty database ready. Use the maintenance command for a full upgrade. It needs OPENBOT_DATABASE_URL and OPENBOT_SECRET_KEYS. Stop all instances before running it. OpenBot.layerShared checks both the schema and the completed data version before it starts services. Its shared lock stays open for the server lifetime. See Deploy for the backup and recovery steps.

Terminal window
pnpm --filter @openbot-build/openbot migrate

pnpm dev runs the development server. It has a local test account, simulated models, and a simulated App. A project that builds on OpenBot can run the same server from its own program:

import { NodeRuntime } from "@effect/platform-node"
import * as Development from "@openbot-build/openbot/Development"
NodeRuntime.runMain(Development.launch)

Development.launch reads the same settings as Launch.main, and OPENBOT_DEV_PUBLIC_ORIGIN. When the OPENBOT_DEV_S3_* variables are set, it uses that local object storage for files, and makes its bucket. Development.layer is the server without these steps. It needs the platform services, such as NodeServices.layer and FetchHttpClient.layer, and Development.launch gives them. It writes the platform settings itself, so it needs no setup program. Development.developmentSecret makes the sign-in secret from the first secret key, so it stays the same after a restart. Never use the development server in production. Everyone who can reach it can sign in.

OpenBot writes failures as error logs. Add your own Effect logger to send them to another service. See Error logs for what each log contains and a full example.