Vendors: engaging another agent harness
Delegate work to Codex, DSH, Hermes, OpenClaw or any command you can name — and what that does not promise.
Beyond model APIs, the office can engage other agent harnesses — Codex, DeepSeek Harness, Hermes, OpenClaw, or anything you can name a command for — and let an employee hand one of them a piece of work. One line of configuration gets you started. The interesting part is not the wiring; it is the three different things "read-only" can mean here, and why the office refuses to draw them the same colour.
Vendor delegation is on by default (DEV3D_VENDOR_DELEGATION is true) and inert until a vendor is configured. A vendor is not a model provider. Your provider keys do nothing for it, and its spend is not yours to see.
One line, four presets
DEV3D_VENDORS="codex;dsh;hermes;openclaw"
Each entry is either a bare preset name or <id>=<preset>, which is how you run the same harness twice under different names — fast=codex;cheap=dsh. The shipped presets are codex, dsh, hermes and openclaw. Anything needing a custom command line or a different transport goes in vendors.json, named by DEV3D_VENDORS_CONFIG (default ./vendors.json). As with MCP, entries are semicolon-separated because command lines contain spaces.
Each configured vendor becomes a tool published as agent__<id>__delegate. An employee that has been granted the tool can hand over one self-contained task and get back what the harness produced.
A rented terminal, not an employee
A vendor is docked in the 3D office as a rented terminal — a plinth and a screen at the edge of the floor, in a colour of its own. The shipped presets declare no colour, so the bay gives each vendor a distinct default; a vendors.json entry may set one, and only a plain hex triple like #4f8cff is accepted. It is not a desk and it is not a person.
The distinction is not decoration. A Role in dev3d means staff: a desk with a seat anchor, a manager, a place in the pipeline, a skill list, a model policy, and work that goes through code review. A harness somebody else operates is none of those things, and modelling it as an employee would make every count in the office wrong — thirteen employees, thirteen desks, thirteen seats. So vendors have their own status vocabulary and their own colour, they appear in OfficeState.vendorBay, and they never appear in EmployeeState.
VendorStatus is five values: offsite, unreachable, docked, engaged, errored. Offsite is "switched off by the operator"; unreachable is what a startup probe found; docked is a harness that answered; engaged is one currently working, and only one delegation per vendor can be engaged at a time. The console's Vendors tab — one of the twelve top-level tabs — shows the roster, a re-check button, who may engage them, and the read-only table described below.
Two transports
| Transport | How it runs | Presets |
|---|---|---|
command | Run the command once with a prompt, read stdout, map the output to a result. | codex, dsh, hermes |
acp | Mediation: the harness speaks the Agent Client Protocol and its tool calls are visible as they happen. | openclaw, and anything else in the ACP registry |
command is the simple case and the honest one: the office hands over a prompt, waits for the process to finish, and reads what it printed. Nothing in between is observable, so there is nothing in between to enforce.
acp is a different arrangement. The harness asks the client — dev3d — to do its filesystem work, which means the office sees each action as it is reported and gets a say in it. Reads are resolved through resolveInWorkspace, the same choke point every built-in file tool uses, so an ACP delegation's reads are confined to the run's workspace root by the same function that confines read_file. Writes are refused outright, and refused in words — the agent is told dev3d runs this delegation read-only and does not permit writing files. — rather than being dropped silently. And the capability the client advertises is explicit: writeTextFile: false, so a harness that inspects the client's capabilities is told up front that the write path does not exist.
Read-only is three-valued, and this is the important part
A guarantee and a request are not the same thing, and rendering both as a green "read-only" badge would be lying by omission. Three different mechanisms are in play, in three different places, with three different strengths. The field is readOnlyEnforcement and it takes three values. dev3d's own docs/external-agents.md calls this field enforcesReadOnly, which does not exist.
| Value | Who enforces it | What that means | Presets |
|---|---|---|---|
sandbox | The harness itself | An OS-level sandbox dev3d asked the harness for, on its own command line. Codex takes -s read-only, and that flag is what makes the claim true. | codex |
client | dev3d | Over ACP: writes refused in words, reads confined to the run's workspace through resolveInWorkspace, every reported action approved by a human one by one. | openclaw |
requested | Nobody | The task text asks the harness to stay read-only and to report what it reads. Nothing on the path checks anything, so a person approves the delegation itself before it starts. | dsh, hermes |
The third row is the one to read twice. requested is not a weaker sandbox; it is not a sandbox at all. Its whole content is that the office prepends an instruction — "You are acting as a read-only contractor for this task" — and then trusts the harness to comply.
Approval follows from that, and the shape of the approval differs per level, which is the detail that makes the three values real rather than labels. requested is the only level the office cannot bound at all, so it is the only one that gets the blanket up-front approval, in the same callout a shell command uses, with a detail panel that says in words that the harness is asked to work read-only and may still write to the workspace. client needs no blanket gate because dev3d is the one answering the filesystem requests — an ACP agent asks permission per action, and those questions go to the same human. sandbox is the harness confining itself.
Two rules keep the three values honest:
- An unrecognised value falls back to the preset's own value, and never to
sandbox. A typo cannot upgrade a claim. - A preset's
sandboxclaim is demoted torequestedif the configuration entry overrides the command. The claim comes from the command line the preset uses, so replacing that command line invalidates it — the office logs the demotion, and it stays demoted unless you declarereadOnlyEnforcementexplicitly for that entry. Otherwise an arbitrary program would be presented to the model as "pinned to a read-only sandbox".
The asymmetry is the point of the whole section. Codex can be held to read-only by its own sandbox flags. OpenClaw can be held to it by dev3d, because it asked dev3d to do the filesystem work. DSH and Hermes expose no sandbox flag and speak no client protocol, so for them read-only is a sentence in a prompt, and the office says exactly that rather than showing a badge.
Who may delegate
Two gates, and both have to pass:
| Gate | Default | What it does |
|---|---|---|
DEV3D_VENDOR_GRANT_ROLES | delegate-roles | Which roles may hand work to a vendor at all — role ids, or *. |
DEV3D_VENDOR_REQUIRE_CAN_DELEGATE | true | Also requires Role.canDelegate on the org chart. Only the literal false disables it. |
The second gate is the interesting one, because it means delegation is a property of the seat rather than of the person. canDelegate is a field on Role, seeded per role in the default company, so the org chart decides which employees are trusted to spend somebody else's subscription. A role can hold the tool and still be refused.
The limits, stated rather than hidden
A vendor reports no cost dev3d can see. Every shipped preset declares reportsCost: false, because the harness bills its own subscription — codex login, hermes config set, a running OpenClaw gateway — and the office has no visibility into it. The consequence is direct: the run's spend ceiling cannot bound a delegation. A run can stay under its budget and still cause an expensive thing to happen, because the money is being spent somewhere dev3d cannot read. The per-vendor timeoutMs is the real limit, and it is a time limit, not a money limit.
Timeouts: the per-vendor ceiling defaults to 300 000 ms (five minutes) and is clamped to 5 000…3 600 000 ms. A preset may set its own — Codex ships at 300 000, DSH, Hermes and OpenClaw at 600 000. A delegation may lower the ceiling for one call and can never raise it, because a model choosing its own timeout would be able to extend what an operator decided the harness may consume.
Two delegations to the same vendor are serialised. A second call while one is in flight is refused rather than queued. That is deliberate: a harness is one machine with one session store and one quota behind it, and two concurrent delegations would share both. The refusal says so — <label> is already working on something. Wait for it to finish, or use a different vendor. — and the vendor's status is engaged for the duration.
Auth is the vendor's own business, and each preset carries its own note about it. Codex owns its own auth and billing, and codex login in a terminal is the fix if it reports an auth failure. DSH reads its own profile and credentials under the DSH home. Hermes reads ~/.hermes/config.yaml. OpenClaw goes through its own Gateway, which has to be running before a delegation will work, because openclaw acp is a bridge to it rather than a standalone agent. A dev3d provider key does nothing for any of them, and every one of those panels says so rather than leaving you to discover it.
The literal refusals
Three things stop a delegation before the harness is reached, and each says something a model can act on:
| Situation | What the model gets back |
|---|---|
| The vendor is switched off | <label> is switched off, so it was not engaged. Do the work yourself, or ask the operator to enable it. |
| The vendor is already working | <label> is already working on something. Wait for it to finish, or use a different vendor. |
| A human declined the delegation | The human declined to engage <label>. Do not retry the same delegation; do the work yourself, or propose an approach that does not need an external harness. |
The wording is consistent on purpose: every one of them tells the employee not to retry and to proceed without the harness. A refusal that read as a transient error would produce an agent that loops on it, spending a run's budget asking a question whose answer is no.
One related behaviour is worth knowing because it looks inconsistent: a vendor whose startup probe failed is not refused. unreachable is a status from a probe taken at boot, and the most likely reason it is wrong is that the harness was installed since — so the delegation is attempted, and the delegation's own outcome is the authoritative answer. The only status that refuses outright is offsite, which means an operator switched the vendor off.
What is not shipped
Writing delegations are not implemented. The shipped path is read-only delegation, and mode: "write" is not a user-facing feature no matter how docs/external-agents.md reads — that document is a design document describing something that did not ship. An ACP delegation refuses writes on the wire, a command delegation asks the harness in the prompt, and the approval callout is explicit that a harness asked to work read-only may still write to the workspace, because on two of the four presets nothing stops it.
That is the honest framing of the whole capability: the office can hand a contained question to a capable external agent, watch what it does if the harness lets it, and give a person the decision on every engagement. It cannot tell you what that engagement cost, it cannot stop two employees from queueing behind one harness, and on two of its four presets "read-only" is something it asked for rather than something it enforces. Known gaps keeps the list; Environment variables has all four switches with their defaults.
Did this page answer your question?