Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.

### Upgrading

`AGENT_TOOL_TOKEN` is generated for you on a laptop. `scripts/start.sh` mints one and writes it to
`.env`, the way it already did for `MANAGED_AGENT_TOKEN`. Without it no Bot could call a tool back
through the deployment, which is the correct default for a deployment and made every MCP tool dead
on arrival on a fresh clone. A value already set is kept, and `.env.example` still ships it empty,
so a deployment not using `start.sh` is unchanged and still fails closed.

`start.sh` also stops skipping work for services that are already answering. A Bot container is now
handed to `docker compose` on every run and the server is restarted when this run minted a secret,
because answering says a process is alive and not that it still agrees with the deployment. The cost
is that a run which rebuilds an image recreates the Bot containers, about five seconds; `supervisor`
already behaved this way.

Two configurations now refuse to start:

- A provider configured with no `INITIAL_ADMIN_EMAILS`. Set it to at least one address.
Expand Down Expand Up @@ -189,6 +201,42 @@ Sessions survive and nobody signs in again.
is unavailable never blocks a sign-in.

### Fixed
- **A Bot browsed to a vendor it already had tools for.** Granted Google Drive, asked what was in a
document, it opened `drive.google.com` in its own browser, met a sign-in page that browser can
never satisfy, and asked the person to sign in to an account they had already connected. A tool
array says a tool exists; it does not say the tool is the way to reach that system, and it was
competing with a page of prose about the browser that mentions connectors nowhere. A Bot is now
told which systems it holds tools for, generated from its grants and placed before that prose, so
enabling a connector changes what the Bot is told on its next run.
- **A question went to a coworker that had no way to answer it.** Routing read the sentence somebody
wrote about what a coworker is for, which is not the same as what it can reach, so a question about
a Drive document went to the one whose description says "company knowledge" and which held no Drive
grants. Candidates now carry the systems they hold tools for. Purpose still decides first: a
specialist with no connectors is still right for a question about its specialism.
- **A deny rule about submitting a form was walked around by typing.** `computer_type` takes a
`submit` flag that presses Enter once the text is in, and the policy never saw a key, so a rule
refused at the button and at the keypress let the third route through. Both shipped copies of that
rule name both tools now, the key reaches the policy, and the audit row carries it — without it a
row said a field was filled in rather than that a form was sent.
- **A Bot refused at the door left no trace.** A callback that could not prove which Bot it was
returned 401 and wrote nothing, so a Bot holding a token the deployment no longer accepted had
every call refused, returned nothing to its own model, and the model told the person there were no
results. A false negative delivered as an answer, with the audit trail agreeing nothing had
happened. Recorded now as `mcp.callback_refused`, naming the tool and the reason but no Bot or
actor, since both arrive in the credential that just failed to verify.
- **An unanswered request for the wheel followed a Bot around.** Control belongs to a Bot's computer
rather than to a conversation, so a request nobody took sat there indefinitely and every later
conversation with that Bot showed a live prompt for work it was not doing, captioned with a reason
written for somebody else. An unanswered ask now stops being shown after ten minutes and its reason
goes with it. A person actually holding the wheel is never timed out.
- **`/admin/computers` listed nothing, ever.** Admin addressed the fleet through a per-Bot route with
a placeholder id, which stopped working when that route began checking whether the caller may act
as the Bot in the path. The screen renders nothing while the list is null, so a deployment with two
running computers looked like one with none. The fleet has a route of its own, still
administrator-only.
- **Every shipped component was recorded twice on a first start.** Two browsers announcing at once is
ordinary and the insert was already safe for it; the answer was not, so the loser of that race
named every component anyway and the caller wrote an audit row per name.
- **A Bot could reach the deployment's own network by writing the address a different way.** The
guard refused `169.254.169.254` and the private ranges as usually written, but not the same
addresses spelled as an IPv6-mapped or NAT64 form, an integer, or with a trailing dot, so a Bot
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ Settings worth knowing:
| `ANTHROPIC_BASE_URL`, `GOOGLE_GENERATIVE_AI_BASE_URL` | The same, for those two APIs. |
| `COMPUTER_TOKEN` | Secret every Bot computer request must present. `start.sh` sets one. |
| `SUPERVISOR_TOKEN` | Secret the supervisor requires. `start.sh` sets one. |
| `AGENT_TOOL_TOKEN` | Secret a Bot presents to call a granted tool back. `start.sh` sets one. Without it no Bot may call tools. |
| `COMPUTER_SUPERVISOR_URL` | Gives each Bot a computer of its own instead of one shared computer. |
| `COMPUTER_RUNTIME` | Set to `runsc` to run computers under gVisor, where the host has it. |
| `COMPUTER_SANDBOX` | Set to `on` for Chromium's own sandbox, where the host permits it. |
Expand Down
13 changes: 12 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ at `agent-langgraph` on a laptop.
| `BOT_MODEL` | provider default from Bot code/env | Model used by the shipped Bots. |
| `BOT_RESPONSES_API` | `false` | Makes `agent-langgraph` use the OpenAI Responses API. |
| `AGENT_STALL_TIMEOUT_MS` | unset (off) | How long a Bot's stream may produce nothing before the turn is ended for it. |
| `AGENT_TOOL_TOKEN` | unset | The secret a framework Bot presents when it calls a granted tool back through this server. |
| `AGENT_TOOL_TOKEN` | unset; `start.sh` generates one | The secret a framework Bot presents when it calls a granted tool back through this server. |
| `APP_DIST_DIR` | unset | Where the built app is, when this process serves it. Set inside the container image; unset in development, where Vite serves the app. |
| `AUDIT_RETENTION_DAYS` | unset | Whole number of days to keep audit rows; older ones are removed. Unset keeps the trail forever. |

Expand All @@ -67,6 +67,17 @@ may not reach a vendor directly. It calls the deployment that granted the tool,
grant, the policy and the audit row live. Absent, no Bot may call tools back, and it is told so
rather than quietly allowed.

That default is right for a deployment and wrong for a laptop, where it meant every granted MCP tool
was refused before it reached the grant, the boundary or the trail — and a refusal at that point is
not visible in the transcript, so a Bot reported no results rather than an error. `scripts/start.sh`
therefore generates one and writes it to `.env`, as it already does for `MANAGED_AGENT_TOKEN`. A
value already set is kept.

It is one of a pair, and they are not interchangeable: `MANAGED_AGENT_TOKEN` is the server proving
itself to a Bot, this is a Bot proving itself to the server. Rotating either means the process
holding the old one refuses every call, which is why `start.sh` restarts the server and recreates the
Bot containers on a run that mints one.

## OpenAI-compatible endpoints

`OPENAI_BASE_URL` decides where an OpenAI-shaped request is answered. Unset, that is OpenAI. Set, it is any endpoint speaking the same API: a gateway in front of several providers, a proxy, or a model on hardware you control.
Expand Down
Empty file removed server/tests/routing.test.ts
Empty file.