Both You.com hosts join PROVIDER_HOSTS (keyed vs keyless) and the Sites catalogue; the YDC_API_KEY fallback moves to key resolution.
Every provider declares key_mode none/optional/required beside display_name and key_url.
Firecrawl's host joins PROVIDER_HOSTS and the Access ▸ Sites catalogue.
The class gets display_name and key_url and raises on HTTP errors like the others.
config.example.toml covered 7 of the 13 supported keys; a user reading it
could not discover how to switch web-search providers or point a dev/BYO
deployment at its own cloud instance. Adds web_search_provider (with the
three supported backends) and the cloud_* family (commented out — the
defaults are the production values, and an empty cloud_relay_ws_url
meaningfully disables the relay). Also notes the Windows global config
location and that allowed_commands/auto_allow are global-only until
workspace trust.
Verified: the example parses with tomllib and every key (including the
commented cloud_* names) matches coworker.config._FIELDS.
For sealed evaluation runs and air-gapped work. A network block only refuses the call: the model still sees both tools, can spend turns on them, and the record cannot say they were absent. The flag withholds them at registration (build_engine(web_tools=False)), and summary.json and the trajectory record web_tools: false. Without the flag nothing changes.
Tests: with the flag both calls get the engine's unknown-tool answer and the record says false; without it web_fetch is present, shown with a non-URL the tool rejects before any network I/O.
Each is a provider of its own with an address and an optional key; the served model, its window and tool support are read from the server (vLLM is probed for tool calling).
Providers carry a kind (local, subscription, api_key) for the settings page; docs/local-servers.md.
model_config.json holds context size, longest reply, thinking, sampling, auto-compact and the default model; the app's API and the CLI read it.
recommended_models.json carries the makers' own values per model, with sources; docs/model-settings.md explains both.
build_macos_cli.sh freezes the CLI, signs and notarizes it; the release attaches it for arm64 and x86_64.
install.sh downloads it on a Mac instead of the Python package.
With a person at the terminal the coworker asks, they allow the site from outside, and it tries again.
With auto-answer it reports the block in its answer, as before.
packaging/openshell/Dockerfile: the CLI plus common developer tools, about 1.3 GB.
Profiles for OpenAI and Anthropic name OpenWorker's own Python. docs/openshell-inside.md has the steps.
A failed model call prints what was underneath it, such as a proxy's 403.
SIGTERM during the wait before a retry now stops the run and leaves its record (exit 130).
--mode, --persona, --attendance and the unlisted mode names are no longer accepted.
Flags cannot be shortened, so --mode is refused instead of being read as --model.
Default mode is ask; approvals and questions are separate (--auto-answer); --isolated keeps the sealed benchmark run.
Without --out the record goes to sessions/<workspace path>/<session id> in the state folder; --allow-site adds sites for the run.
Exit code 0 only when the task finished; 3 when it stopped early with its record written.
The sandbox's own log is followed from outside; a refusal reaches us up to half a second late.
A failed command waits for it; one that arrives after its result is told with the next result.
Sites from Settings are listed plain; sites allowed for this session are marked "Session only".
The person can allow one more for the session or take one back; the running sandbox follows at once.
A new tool, request_network_access, raises a card only a person answers: this session, always, or deny.
A command's result names the sites the sandbox blocked while it ran, and the agent is told the live list each turn.
The header chip lists the sites allowed for this session apart from the machine's.
"This session" and "Always allow" now reach the session's commands at once.
OpenShell: the policy is rendered again and set on the sandbox; macOS and Windows: the session's own proxy takes the host.
"Always allow" now copies the list back as the store keeps it, so the chip matches Settings.
The three sandbox pages say web search and fetch follow the list; connectors and MCP do not.
"root" already means the superuser and the top of the filesystem; `--add-dir` says what
the flag does and is the name the best-known agent CLIs use for the same idea. Recorded
in summary.json as args.extra_dirs.
A harness can put its output contract outside the workspace: CyberGym-E2E runs the agent
in /src and grades /output/fix.patch and /output/poc.bin. OpenWorker's file tools only
write under a declared root, in every permission mode (the dangerous mode clears the
permission floor, but the tools still resolve paths against the session's roots by
construction), while the shell is not scoped. So without a way to declare /output, a
delivery depended on which tool the model happened to pick.
`--root DIR` (repeatable) adds writable roots after the workspace, which stays the folder
relative paths resolve against; the folders are announced to the model in the session's
directories block and recorded under args.roots in summary.json. The reference harness
does the same for Gemini CLI with --include-directories /src,/scripts,/output.
A headless one-shot command beside `up`: give it a task, a folder and a model; it builds the same engine the desktop app uses, runs to the end with attendance `auto` (coworker/unattended.py), and leaves a record: every event, the final conversation, a summary with tokens and cost, every answer the engine gave on the absent user's behalf, the audit log, a per-call model log, and a trajectory in the ATIF interchange format. The trajectory and the exit code are the stable contract; the other files are internal.
State and scratch folders are pointed under --out before the engine starts, so a run never touches the machine's own conversations, keys or settings. A transient provider failure or an empty reply is waited out with backoff and a nudge instead of ending the run; permanent errors are not retried. Tool results are bounded with the full text spilled beside the record. A small price table gives an estimated cost per call as a cross-check; an unpriced model gets counts but no cost.
The mode axis decides approvals (`dangerously-bypass-approvals` prints its warning line here); the runner adds no policy of its own.
The app moves to a free port only when 8765 is taken (after waiting 5 s for a previous copy to stop) and the Machines page then says so; OPENWORKER_PORT overrides for developers (OPE-212).
A throwaway container asks the kernel; without Landlock the Docker row says update Docker Desktop.
A sandbox that exits at start on such a Mac gets the same message instead of ContainerExited.
Network allow list holds only the machine's ticked sites, none by default; strict/standard removed.
The app no longer installs OpenShell; setup is two checks and one step with progress.
DMG build requires grpcio so the sidecar can use OpenShell.
Network: registries and search (strict folded in) with the machine's own hosts, or allow everything.
Credentials list only what was added; shipped entries become presets with the variable each tool reads.
A hung docker info no longer fails the OpenShell checklist.
The OpenShell checklist and setup job show under the OpenShell type; the Windows setup keeps its own routes.
Co-Authored-By: Devika Verma <devikaverma11@gmail.com>
Setup and remove routes prove the wall in a throwaway sandbox before the choice takes effect.
Credential entries are a file or a folder, labelled credential or configuration; collapsible cards.
The same-user fallback is gone: without setup the provider refuses and Settings say why (ruling 18).
A shell could not enter a granted folder under the profile; the parents now get a traverse-only entry.
The entry check matches by SID, so it works for a real account too.
From a fresh-user run on WSL (2026-09-27): "Set up sandbox" handed over two
commands, gave no install instructions, and put a Copy button on notes
("found none") and file paths.
- steps carry `command` (run in a terminal, gets Copy) apart from `hint`
(a note) and `docs` (a Guide link). `checks()` prints command or hint.
- `admin_prefix()`: `sudo -n` when this user may sudo without a password,
else `pkexec` (the desktop's own password prompt) when there is a display
and this is not WSL. The app never sees or holds a password.
- `install_openshell()`: NVIDIA's installer as the user on a Mac; on Linux
under the admin prefix with `loginctl enable-linger` FIRST in the same
run, because the installer starts the gateway through the user's systemd
manager, which on WSL does not run until linger is on (it otherwise
prints "could not reach the user systemd manager" and exits 0 without
registering the gateway). The job streams its output as progress and it
can be cancelled.
- No prefix (WSL): the openshell row hands over ONE command, linger + the
installer, and the job stops until "Check again".
- `enable_linger()` tries the user, then the admin prefix, then hands over.
- Terminal `setup`: linger (sudo) before the installer; Guide URL printed.
- Page: "Run this in a terminal on this machine:" above a command; notes
plain, no Copy; Guide links; install progress row; a fixable row shows
its command only once the app could not do it.
- Wording: switch-off text says what it means ("directly on this machine,
with the same access as your user account"); "stored in <path>" only
while the switch is on; the credential editor explains itself with an
npm example and placeholders; "needs download" hint points at the
button, not a terminal command.
The page listed "No sandbox", "macOS sandbox" and "OpenShell" as three equal radio
buttons (with "ready" next to no-sandbox, and "on this Mac" on every OS), and always
showed the network allow list and shared credentials even when they applied to nothing.
Choosing OpenShell did not say what the machine still needed; the user found out when a
session was refused.
Now:
- One switch, "Run agents in a sandbox". Off removes `sandbox_provider` from
config.toml (it used to write "direct", which also disabled a headless machine's
automatic-OpenShell rule); on picks Seatbelt on a Mac, OpenShell elsewhere, with a
dropdown when both exist. On Windows the switch is disabled with the WSL hint.
- Under the switch, for OpenShell: a readiness checklist, the same rows `openworker
machine sandbox status` prints, now with keys and whether the app may fix each one
itself, plus two new rows: free disk space for the image (about 10 GB) and Landlock in
the kernel (OpenShell refuses to start a sandbox without it). Each missing row shows
the command to run, with Copy.
- "Set up sandbox" runs `SetupJob` on the machine's server: it fixes what the app may
(bind-mount config and gateway restart, the config line, linger when no administrator
is needed, the image download with per-layer progress and Cancel) and stops at a step
that needs an administrator or an installer, showing the command; "Check again"
continues. It never runs anything as root and never turns the sandbox off. A desktop
notification says when the download is done.
- Network allow list and shared credentials are collapsible and shown only while the
switch is on.
`setup_cmd` is refactored into `steps()` (rows with keys and fixability) and one
function per fix, used by both the terminal `setup` and the job, so there is one list
of checks. New endpoints: GET /v1/settings/sandbox/readiness, GET/POST
/v1/settings/sandbox/setup, POST /v1/settings/sandbox/setup/cancel.
Copied credentials (and the policy file) went into tempfile.mkdtemp() under /tmp and
were bind-mounted into the sandbox. NVIDIA's installer runs the gateway as a systemd
user service with PrivateTmp=true, so the gateway has its own empty /tmp: the mount
source "does not exist" for it and every session with a shared credential failed to
create. Verified through /proc/<gateway pid>/mountinfo and /proc/<pid>/root/tmp.
The folder is now `<state dir>/sandbox-runtime/ow-openshell-<pid>-<random>/`: on the
real filesystem, owner-only, and a SIBLING of `sandbox/`, never a child, because
`sandbox/` is mounted read-only into every sandbox for the runner and copied keys must
not be readable from another session's sandbox. The server pid in the name lets the
registry's reap sweep the folders of a dead server, which /tmp used to do on its own.
On first use of the OpenShell sandbox, `openshell sandbox create` pulls the pinned base
image (about 5 GB). OpenWorker waited for that create with a 600 s timeout, inside the
session-start request; on a slow link the pull outlived it, the session failed with
`TimeoutExpired`, the UI showed "Starting OpenWorker..." and retried, and nothing told
the user a one-time download was the cause. Settings > Sandbox said "ready" throughout.
Now the image is part of the readiness check, keyed on the gateway's compute driver
(`openshell gateway info`), not the operating system, since the same image serves
Linux, WSL and macOS and only the Docker driver can be asked:
- `preflight()` raises `OpenShellImageMissing` (an `OpenShellUnavailable`) with the
`docker pull` command when the driver is Docker and the image is absent, so a session
is refused in a second with a message that says what to do.
- Settings > Sandbox shows OpenShell as "needs download" with a hint, and keeps it
selectable; `state` joins each provider row of /v1/settings/sandbox.
- `openworker machine sandbox status` gets a row for the image; `setup` offers the pull
last, with Docker's own progress and no time limit, on macOS too.
One list per platform (coworker/sandbox/toolchains.py), machine-level, editable by name; read entries on Windows,
the Seatbelt profile on the Mac. The Windows full mode passes its environment through a file (1024-character logon limit).