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.
The smallest program
Section titled “The smallest program”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.
What the package exports
Section titled “What the package exports”| 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.
Options
Section titled “Options”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.
Instance options
Section titled “Instance options”| 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. |
Sign-in
Section titled “Sign-in”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
/healthanswers503withSetupRequired. - 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.
App options
Section titled “App options”| 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. |
Services that OpenBot needs
Section titled “Services that OpenBot needs”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.
Add a tool
Section titled “Add a tool”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.
Your own routes
Section titled “Your own routes”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.
Use your own HTTP server
Section titled “Use your own HTTP server”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.
Migrate the database
Section titled “Migrate the database”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.
pnpm --filter @openbot-build/openbot migrateRun the development server
Section titled “Run the development server”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.
Send error logs to your own service
Section titled “Send error logs to your own service”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.