DATABASE_URL=postgres://openbot:openbot@localhost:5432/openbot # Credential vault encryption key. The example value is public and only suitable for local # development. Production refuses to start with this key. # openssl rand -base64 32 KEY_ENCRYPTION_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= PORT=3001 TENANT_PACKAGE_DIR=../examples/fintech # What this deployment calls itself, when more than one shares an Intelligence project. A copy of a # deployment made for development uses the same project key, and threads are listed per Bot with # nothing to say which deployment a conversation came from. The name goes into every thread id this # deployment mints, so its own conversations stay identifiable. Unset, the tenant package's id is # used, which tells two packages apart but not two copies of one. # DEPLOYMENT_ID= # Google sign-in. Leave commented for local development with OPENBOT_DEV_NO_AUTH; uncomment all five # settings together so authentication is either fully configured or absent. # # BETTER_AUTH_SECRET must be a high-entropy secret of at least 32 characters. Generate one: # openssl rand -base64 32 # TRUSTED_ORIGINS is where the app is served from, which is port 3010 locally. # BETTER_AUTH_URL=http://localhost:3001 # BETTER_AUTH_SECRET= # GOOGLE_OAUTH_CLIENT_ID= # GOOGLE_OAUTH_CLIENT_SECRET= # INITIAL_ADMIN_EMAILS=admin@example.com # Local development only. This admits every request as one local administrator. Keep it explicit and # never expose a deployment to the internet in this state; production refuses to start with it. OPENBOT_DEV_NO_AUTH=true TRUSTED_ORIGINS=http://localhost:3010 # CopilotKit Intelligence. Required: the server refuses to start without all four because # Intelligence owns durable threads and memory and a deployment without it forgets every # conversation. There is no degraded mode. # # Do not run Intelligence yourself. These two point at the managed service and should be left # unchanged; the CLI below provisions a free licence for it. Running Intelligence on your own # infrastructure is an Enterprise Intelligence Platform feature deployed by Helm chart, and is not # self-serve: https://docs.showcase.copilotkit.ai/premium/self-hosting INTELLIGENCE_API_URL=https://api.intelligence.copilotkit.ai INTELLIGENCE_GATEWAY_WS_URL=wss://realtime.intelligence.copilotkit.ai # # Get both of the next two from the CopilotKit CLI: # # npx --yes copilotkit@latest login # browser sign-in # npx --yes copilotkit@latest project select # prints the cpk-... runtime key -> INTELLIGENCE_API_KEY # npx --yes copilotkit@latest license --write # writes COPILOTKIT_LICENSE_TOKEN into this file # # The runtime key is also under "API Keys" in your project at https://intelligence.copilotkit.ai INTELLIGENCE_API_KEY= COPILOTKIT_LICENSE_TOKEN= # How long a Bot's stream may say nothing before this deployment gives up on the turn, in # milliseconds. A Bot is any AG-UI endpoint, which means it will be redeployed mid-answer, its own # upstream will time out, and it will sometimes accept a connection and then write nothing at all. # Without this the channel stays busy, the composer stays locked, and the only way out is a reload. # # Silence, not duration. A turn may legitimately run for an hour while events keep arriving; what is # measured here is the gap between them. Nothing about how long a Bot is allowed to work changes. # # A minute, because this deadline is in a race it has to win. Every Bot in this repository serves on # Bun with `idleTimeout: 120`, and Bun tears a wedged streaming response down at roughly a second # past that. A watchdog set to the same two minutes lands within a second of the socket dying, and # whichever gets there first decides what the person sees: this deployment's sentence naming the Bot # and saying the turn was ended, or "The socket connection was closed unexpectedly" and no audit row # at all. Half the Bot's own idle timeout is far enough clear that the answer is always the first. # # A minute is still far longer than any real silence inside a run. The longest legitimate one is the # wait for a model's first token, which is seconds. Browser tool calls do not need allowing for: # they run between turns, not during one, because the run ends before the browser executes the tool # and a second run carries the result back. # # 0, or leaving this unset, switches the watchdog off. Nothing is watched and no turn is ever ended. AGENT_STALL_TIMEOUT_MS=60000 # Model key. Required by the proof-of-concept Bot, which speaks OpenAI's API directly, and by the # framework Bot unless you point it at another provider below. OPENAI_API_KEY= # Where that key is spent. Unset, it is OpenAI. Set, it is any endpoint speaking the same # `/v1/chat/completions` API: a gateway in front of several providers, a proxy, or a model on # hardware you control. The API server reads it for the built-in agents and both shipped Bots read # it too, so one line moves the whole deployment rather than one Bot. # # Model names travel verbatim, so use whatever the endpoint publishes. An endpoint that namespaces # its catalogue wants both halves of the name, in `BOT_MODEL` and in the tenant package's # `default_model` alike: # # OPENAI_BASE_URL=https://gateway.internal/v1 # OPENAI_API_KEY=... # BOT_MODEL=openai/gpt-4o # # OPENAI_BASE_URL= # The same for the other two providers, under the names the API server already reads. They are # different APIs rather than different URLs for this one, so each has its own. # ANTHROPIC_BASE_URL= # GOOGLE_GENERATIVE_AI_BASE_URL= # Framework Bot provider: openai, anthropic or google. It reads that provider's own # key and refuses to start without it, so a deployment on Anthropic never needs an OpenAI key for it. # The proof-of-concept Bot is OpenAI only by construction: it speaks that API directly. # BOT_PROVIDER=openai # ANTHROPIC_API_KEY= # GOOGLE_API_KEY= # Which model. Defaults per provider: gpt-5.5, claude-sonnet-4-5, gemini-2.5-flash. # BOT_MODEL=gpt-5.5 # OpenAI only. Its newer models require the Responses API, which the framework Bot handles and the # proof-of-concept one cannot. # BOT_RESPONSES_API=false # The Bot computer. Absent means the feature is off and its routes are not mounted. AGENT_COMPUTER_URL=http://localhost:4100 # Secret every computer requires from its caller. `agent-computer` drives a browser holding real # logins, while policy, audit and sign-in live in the API server. The computer refuses to start # without this value and refuses every request that does not present it. Use a long random value; # `scripts/start.sh` sets a development one for you. COMPUTER_TOKEN= # Local only. Lets a Bot browse this machine's own services; never set this in a deployment. AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS=true # # What a Bot may do on its computer, as one JSON object. Absent uses the built-in default, which # permits the acting tools and forbids nothing, and records every action either way. # # `deny` is evaluated first and beats `allow`. An empty `allow` permits nothing, a missing policy # permits nothing, and a rule that fails to parse denies rather than letting the action through. The # server refuses to start if this is set and malformed, so an invalid restriction never falls back to # permissive behavior. # # Workspace and browser profile per Bot. Each Bot's computer is its own container with its own # volumes, so one Bot cannot read another's files or use another's logins, and every action records # which Bot took it. A rule can still restrict a single Bot with `bot.id`. # # Attributes: tool.name, bot.id, actor.id, page.url, page.host, element.ref/role/name/type, # key, file.path, file.name, file.extension. # # Name every route to the same effect. A form submits from a keypress in any of its fields, so a rule # that only blocks a Submit button does not block Enter from another field. The example below refuses # Enter outright for that reason. # Functions: contains(haystack, needle) and matches(value, pattern), both case-insensitive. # `enforce` blocks; `dry-run` decides and records but lets everything through, so a new rule can be # tried against real traffic before it starts refusing anybody's work. # # AGENT_COMPUTER_POLICY={"mode":"enforce","deny":["(intent == \"activate\" && contains(element.name, \"submit\")) || (tool.name == \"computer_key\" && key == \"Enter\")"],"allow":["true"]} # How long one action waits for its element, in ms. Read by agent-computer, not the server. # ACTION_TIMEOUT_MS=10000 # --------------------------------------------------------------------------- # The computer's profile and where its traffic leaves from # --------------------------------------------------------------------------- # Chromium profile directory inside agent-computer, mounted from the `agent-profiles` volume in # docker-compose.yml. Pointing it somewhere unmounted disables login persistence. # PROFILES_DIR=/profiles # Which Bot this computer belongs to, and therefore which profile directory it uses. # COMPUTER_BOT_ID=shared # Per-Bot egress identity. A proxy the Bot's browser sends everything through, so the far side sees a # stable address it can allow-list or attribute to that Bot. `EGRESS_PROXY_` names one Bot, # `EGRESS_PROXY_DEFAULT` covers the rest, and absent means the browser goes out directly. # # Credentials may be in the URL and are split out before Playwright sees them. Only the HOST is ever # reported back on the admin page or the API, so the password does not end up on a screen. # # This is attribution, not anonymity, and it is not a boundary by itself: it gives a security team a # per-Bot address for network rules alongside AGENT_COMPUTER_POLICY. # EGRESS_PROXY_DEFAULT=http://user:password@proxy.internal:8080 # EGRESS_PROXY_SALES_BOT=http://sales.proxy.internal:8080 # The managed coworker AG-UI endpoint. Required: use an HTTP(S) URL. MANAGED_AGENT_AG_UI_URL=http://localhost:4200/ag-ui # The second Bot in the box runs on http://localhost:4201/ag-ui, on a framework rather than # proof of concept, and is reached the same way: point MANAGED_AGENT_AG_UI_URL at it, or add it as a # Bot of its own in the tenant package or at /agents. # Which model the Bots use. agent-bot speaks /v1/chat/completions and cannot use gpt-5.6-*, which # require the Responses API. agent-langgraph can: set BOT_RESPONSES_API=true and give it a 5.6 model. # BOT_RESPONSES_API=false # One computer per Bot. Unset, every Bot shares the computer at AGENT_COMPUTER_URL, suitable on a # laptop and explicit about being one shared machine. Set, the supervisor gives each Bot a container # of its own, with its own /workspace and its own browser profile. COMPUTER_SUPERVISOR_URL= # Shared with the supervisor. Not the security boundary, its four verbs are, but it keeps anything # else that can reach the port from cycling a Bot's computer. SUPERVISOR_TOKEN= # Set to runsc to run every computer under gVisor, if the host has it. Unset, a computer is an # ordinary container and shares the host kernel, which is worth knowing when the Bot is not ours. COMPUTER_RUNTIME=