You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Merging this publishes the image, tags the commit and creates the GitHub Release.
Before merging:
The notes below describe what a deployment does differently
The smoke journey passed against a licensed deployment, and the result is
pasted in a comment: bash scripts/start.sh && bun run test:smoke
Merging runs the full suite against this commit before it builds, so there is
nothing to check about CI here. The journey is the part CI cannot do: it needs a
licence, and a licence belongs to the machine it was issued for.
### The published service images are zstd rather than gzip
An image pull was already a compressed transfer, so this is not compression where there was none; it
is a better algorithm for the same job. `agent-computer` goes from 962 MB to 886 MB on the wire, and
zstd inflates several times faster, which on 2 GB of Chromium is worth more than the 8%. The saving
comes from recompressing the layers that arrived from somebody else's registry, where nearly all of
the bytes are, so the images no longer share layers with a gzip pull of the same base.
This applies to the five `ghcr.io/copilotkit/openbot-<service>` images and not to
`ghcr.io/copilotkit/openbot`. Reading a zstd layer needs a client that supports it, which Podman and
current containerd do; the single image is pulled by deployments running whatever they have, so it
stays gzip.
### A release publishes every service's image, not just the one
`ghcr.io/copilotkit/openbot` was the only image a release produced, so anything running the Compose
stack built `agent-computer`, the supervisor, both Bots and the migration image from source on
every machine. That needs a toolchain and it needs several minutes, most of them Chromium, and it
is the difference between a deployment and a laptop being able to start at all.
Each of those is now published beside it, at `ghcr.io/copilotkit/openbot-<service>`, carrying both
`linux/amd64` and `linux/arm64` so one reference works on a server and on an arm64 laptop. Every
image gets its own build provenance attestation, and `container-images.json` on the release pins a
digest for all of them rather than for one.
Nothing changes for a checkout: unset, `COMPUTER_IMAGE`, `SUPERVISOR_IMAGE`, `BOT_IMAGE`,
`LANGGRAPH_IMAGE` and `SERVER_IMAGE` name local tags and Compose builds them as before. Point them
at published digests and set `IMAGE_PULL_POLICY=missing` to pull instead. `docs/releasing.md` shows
reading the references out of `container-images.json`; `docs/configuration.md` lists the settings.
CI now builds those five Dockerfiles too. Nothing did before, because they are built by
`docker compose up --build`, which only the smoke journey runs and which cannot run in CI, so a
broken one surfaced during a release after the first image had already been pushed.
### A skill can be written in the conversation instead of retyped into a form
A skill is four fields and an instruction a Bot follows, and the instruction is the one that decides
whether the skill works. The only place to write it was a textarea on `/skills`, which meant having
the conversation, getting a good draft in the transcript, and then copying it out and retyping it.
Mostly nobody bothered, which is how a deployment runs for months with no skills in it.
The example package now ships a `skill-creator` skill, granted to `general-assistant`. A Bot holding
it is offered four tools for listing, reading and saving skills; every other Bot is offered none of
them. It interviews you about the skill you want, looks at what already exists before naming it,
rehearses it against one realistic request, and then saves it.
The save is a card, not something that happens quietly. It draws the command, the title, the declared
tools and the whole instruction, and writes nothing until a button is pressed. A skill appears in
everybody's `/` menu with somebody's name on it, and saving is also how an edit is spelled, so an
unattended save could replace a skill somebody is already using.
The tools run in the browser as the signed-in person, over the same `POST /api/plugins/skills` a
person uses, so who may take a slug is answered the same way and the `configuration.changed` audit
row is written the same way.
### A person can set standing instructions that every coworker follows
Settings now has a box for standing instructions: one piece of text per person, saved once and
spliced into every built-in coworker's prompt, in every channel, on every run, including the runs a
routine starts overnight. It is the place for what is true of every task rather than of any one of
them, such as how somebody wants to be written to or what their company is and is not to be called.
A coworker's role still decides what it does; these decide how it does it, and the prompt says so,
so an instruction cannot quietly redefine what a coworker is for.
Instructions belong to the person who wrote them. Nobody, administrators included, can read or set
somebody else's, and they are deleted with the account. A coworker running at a remote AG-UI
endpoint is not sent them, since this deployment does not compose that prompt. Nothing is added to
any prompt until somebody writes something, so a deployment where nobody uses this behaves exactly
as before.
This adds migration `0026_user_instructions`, which creates one table.
### A coworker can be made in the conversation, and it starts able to reach nothing
A coworker without an endpoint runs on its role description, which becomes the standing instruction
handed to a model on every turn in every channel it is in. It is the hardest thing anybody is asked
to write cold, so people write a sentence, get a coworker that answers vaguely, and never go back to
the field that decided everything.
The example package now ships a `bot-creator` skill, granted to `general-assistant`, with tools for
listing, reading and saving coworkers. It asks the follow-up your last answer calls for, reads the
roster to say when something already does the job, and can be told to make one like an existing
coworker but for a different job, then go and read what that coworker actually runs on.
The card shows the name, the job, the skills and the entire role description, scrolled rather than
clamped, because that text runs on somebody's behalf. What is made is granted nothing: it can reach
no connector, no tool and no browser until somebody grants it, and a conversation with it says so.
Like the skill tools above, these run in the browser as the signed-in person over the endpoints a
person uses, so who may create a coworker is answered the same way and `bot.created` carries the
actor.
### A conversation has a name of its own
A channel's name was only the names of the Bots in it, so asking one Bot about six unrelated things
gave six rows reading the same thing, told apart by a preview of whatever was said last, which is
usually the tail of an answer and says nothing about the question. A conversation is now named from
its opening exchange, and the roster's second line holds that name instead of the preview, falling
back to the preview until a name exists. A row is never blank and never worse off than before.
Two things a deployment should know. The opening exchange, up to 600 code points, is sent to whatever
`tenantPackage.model` names, which is the same provider the Bots already use, so it is not new egress
but it is sent as housekeeping rather than because somebody asked for it. And the second line now
says what the conversation is about instead of what was last said.
It runs on the work queue rather than as a headless turn, so naming a conversation never takes the
Intelligence thread lock and cannot refuse somebody's own next message with a 409. A deployment with
no model key names nothing and carries on.
### The trail says who a coworker was opened to
Making a coworker public admits every signed-in person to it, and being admitted to a coworker is
being allowed to act as it — with the connectors, the tools and the browser it was granted. It is one
click in the coworker dialog. The `bot.created` and `bot.updated` rows recorded the name, the
endpoint and whether a key was set, and said nothing about this, so an edit that opened a coworker to
the whole deployment was byte-identical on the audit page to one that corrected its title. Both rows
now carry the visibility, on every edit rather than only the edit that moved it, so reading the trail
forward says who could reach each coworker at any point.
### Duplicating a Bot in the box keeps its instructions
A coworker that runs on this deployment's own Bot has no endpoint — it has a prompt, which is the
whole of what makes it that coworker. Duplicate rebuilt every copy from the endpoint alone, found
none, and fell back to the managed Bot with the prompt dropped, so the copy carried the name, the
title, the role and the avatar and none of the instructions. Its entire instruction became the one
sentence of role description, which is the shape behind the compliance answer this repository
already has a note about. The two coworkers the default package ships are both of this kind, and one
of them is a careful do-not-fabricate instruction. A copy now keeps the prompt and stays a Bot in the
box, which also means it can still be granted the right to hand work on — written as a hosted
coworker it could never hold that grant, however the original was set up — and copying one no longer
needs a managed Bot to fall back to.
### Hiding a coworker no longer hides the grants pointing at it
Hiding a coworker is a preference about your own roster — one row per person — and the grants saying
which Bots may hand work to it are a deployment-wide fact an administrator set. The Handoff section
joined the two, so hiding a coworker from your roster took every grant aimed at it off the screen:
the switch was gone, no note said why, and the count above the list quietly dropped by one. Those
grants were still in force, because a hop is decided by the grant and not by anybody's roster, so
the coworker went on being asked while the only screen that could stop it had stopped listing it.
A coworker you have hidden now appears in that list when a grant already points at it, marked as
hidden from your roster, so it can be switched off. One you have hidden with nothing granted to it
stays hidden.
### A tool call that failed no longer reads as one that worked
The audit page draws a row it does not recognise as `Allowed`, which is right for the many rows that
are neither a refusal nor a failure. Two rows that are failures were falling through to it: a
connector tool call this deployment permitted and the vendor did not complete, and a component's
data read that was granted and then broke. Both were drawn in the same muted colour as a call that
went through, and neither appeared under `Did not happen`, so the view an administrator opens to ask
what did not work here was short by exactly the rows they came for. A per-person connector fails on
this path every time somebody's token expires, so this was the most common failure the product has
and the one the trail was quietest about. Both now read as `Did not happen`, and both are in that
saved view. Neither is filed as a refusal: nothing was forbidden on either row.
### A blank agentId on a channel activity says which field was wrong
`POST /api/channels/:id/activity` accepted an `agentId` of only spaces, trimmed it to nothing, then
looked that up and answered `404 Agent not found`. The field was malformed rather than the agent
missing, so the answer sent whoever was integrating to look for a coworker that was never named. It
is now a `400` naming the field, which is what the same endpoint already did for malformed text.
### Audit payloads are redacted by the store as well as by its caller
Redaction of secrets out of audit payloads happened in `recordAuditEvent`, and every caller in the
tree goes through it. The store underneath it is exported, though, and its `insert` wrote whatever it
was handed, so a future direct caller would have written secrets to the audit table in cleartext.
`insert` now redacts too. Redaction is idempotent, so nothing about the existing path changes; this
is the floor under it rather than a fix to it.
### A stray space in NODE_ENV no longer lets the public example key through
A deployment that never changed `KEY_ENCRYPTION_KEY` encrypts its credential vault with the key
printed in `.env.example`, so the server refuses to start with it under `NODE_ENV=production`. That
refusal compared the variable exactly as written, while the other production refusal beside it —
private-host browsing — trimmed first. Both read the same env file, and a trailing space there is
invisible: Docker's `env_file` preserves it and so does every hosting dashboard with a text box. So
`NODE_ENV=production ` tripped one refusal, slipped past the other, and started the deployment on the
public key with only a warning at boot. Both gates now ask the same question the same way.
### A tool result from an MCP server with an empty part no longer crashes the turn
Reading a tool result cast every part to an object and asked for its type. A `null` or missing entry,
which a vendor's MCP server is free to send, threw instead, and the turn that had just called the
tool failed. Such a part is now named `[unknown]`, which is the same naming-rather-than-dropping the
surrounding code already does for parts it does not recognise, so the rest of the result still
reaches the Bot.
Run against 31e7fb9, the commit this release publishes, on a machine with real Intelligence credentials.
$ OPENBOT_API_URL=http://localhost:3101 bun run test:smoke
5 pass
0 fail
13 expect() calls
Ran 5 tests across 1 file.
licenseStatus was valid and /api/capabilities reported {"mode":"intelligence","durableHistory":true,...}.
The wiring the journey exists to prove did happen rather than being skipped: the supervisor created openbot-relsmoke-computer-risk-analyst, healthy, during the run.
How it was run
An isolated stack, so the deployment already running on this machine and its database were untouched: its own Compose project and COMPUTER_NAMESPACE (openbot-relsmoke), its own DEPLOYMENT_ID, its own Postgres volume, and shifted ports (server 3101, app 3110, Postgres 5442, computer 4110, Bots 4210/4211, supervisor 4510).
Auth was off for the run, which is what the journey requires and how ci.yml runs it: the test sends no session. Two things had to come off for that, both of them the code refusing correctly rather than a defect:
OPENBOT_SINGLE_USER is ignored while an identity provider is configured, so GOOGLE_OAUTH_* had to be unset in the isolated copy.
loadConfig then refused to start with BETTER_AUTH_SECRET/BETTER_AUTH_URL set and no provider, naming both variables. Unset, it started and reported authProviders: [].
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v0.0.7
Merging this publishes the image, tags the commit and creates the GitHub Release.
Before merging:
pasted in a comment:
bash scripts/start.sh && bun run test:smokeMerging runs the full suite against this commit before it builds, so there is
nothing to check about CI here. The journey is the part CI cannot do: it needs a
licence, and a licence belongs to the machine it was issued for.