| 267 |
267
|
|
3. Output logs, tokens, and push receipts on the server: **output logs
|
| 268 |
268
|
|
demonstrated live**; tokens not applicable (no model turn); **push
|
| 269 |
269
|
|
receipts blocked** by the credentialed-dispatch failure above.
|
|
270
|
+ |
|
|
271
|
+ |
## 8. Server-side Box fixes and live verification (2026-08-25)
|
|
272
|
+ |
|
|
273
|
+ |
Three defects named in §7 and in openagents#58 were fixed on the server. Boxes
|
|
274
|
+ |
`bx_732ts8jg` and `bx_se9xfq7q` on conversation `3dd6d813` carried the
|
|
275
|
+ |
verification; both were stopped afterwards, peak concurrency 2.
|
|
276
|
+ |
|
|
277
|
+ |
### Setup script: configuration first, pinned artifact, PATH the run sees
|
|
278
|
+ |
|
|
279
|
+ |
`OpenAgents.Box.setup_script/0` piped `https://opencode.ai/install` into bash.
|
|
280
|
+ |
That installer resolves its version through the unauthenticated GitHub API,
|
|
281
|
+ |
which answers 403 for the provider's shared egress IP, and the whole script
|
|
282
|
+ |
runs under `set -euo pipefail`, so one rate-limited lookup cost the box both
|
|
283
|
+ |
the binary and the `opencode.json` write that followed it.
|
|
284
|
+ |
|
|
285
|
+ |
The script now writes the configuration first, fetches a pinned release
|
|
286
|
+ |
tarball directly (`releases/download/v1.18.23/opencode-<target>.tar.gz`, arch
|
|
287
|
+ |
resolved from `uname -m`), retries a refused fetch twice before failing loudly,
|
|
288
|
+ |
and symlinks the binary into `$HOME/.local/bin` — already on the PATH a
|
|
289
|
+ |
non-interactive `sh -c` run gets, which the installer's
|
|
290
|
+ |
`$HOME/.opencode/bin` plus a shell-rc `export PATH` line never was.
|
|
291
|
+ |
|
|
292
|
+ |
Verified on `bx_se9xfq7q`: with `~/.opencode`, `~/.local/bin/opencode`, and
|
|
293
|
+ |
`~/.config/opencode` deleted, the rendered script exits 0 and
|
|
294
|
+ |
`sh -c 'command -v opencode && opencode --version'` answers
|
|
295
|
+ |
`/home/user/.local/bin/opencode` and `1.18.23`. An `opencode run` in that state
|
|
296
|
+ |
returns `> build · stealth/ox-alpha` and the model's reply, with
|
|
297
|
+ |
`OPENROUTER_API_KEY` supplied by the box environment as before.
|
|
298
|
+ |
|
|
299
|
+ |
Verified on `bx_732ts8jg`: the same script pointed at an unreachable release
|
|
300
|
+ |
tag retries twice, exits 1 — so `setup_status` still reports `failed` honestly
|
|
301
|
+ |
— and leaves `opencode.json` intact with no binary.
|
|
302
|
+ |
|
|
303
|
+ |
### Conversation bootstrap a box token can reach
|
|
304
|
+ |
|
|
305
|
+ |
`GET /api/v1/conversation` under the `box_control_api` pipeline answers
|
|
306
|
+ |
`conversation_id` for the calling account, creating the conversation when the
|
|
307
|
+ |
account has none. Confirmed against production that a `box:control`-only token
|
|
308
|
+ |
is refused `401` on `GET /api/v1/user`, which is why a route of its own exists
|
|
309
|
+ |
rather than a field added there.
|
|
310
|
+ |
|
|
311
|
+ |
The field is deliberately not on `/api/v1/user`. That response is
|
|
312
|
+ |
GitHub-shaped, and API-001 holds every OpenAgents field there to a namespaced
|
|
313
|
+ |
`openagents` object, which is not what `BoxClient.resolveConversationId` reads.
|
|
314
|
+ |
One canonical route carries it at the top level instead. The CLI change that
|
|
315
|
+ |
follows this is a one-line reader retarget in the `openagents` monorepo; the
|
|
316
|
+ |
body shape already matches what the probe expects.
|
|
317
|
+ |
|
|
318
|
+ |
### Credentialed dispatch: the provider command API has no `env`
|
|
319
|
+ |
|
|
320
|
+ |
The provider's published `CommandRequest` schema is `command`, `cwd`,
|
|
321
|
+ |
`timeoutSeconds`, and `detached`. There is no `env`, so
|
|
322
|
+ |
`OpenAgents.Box.Client.dispatch_run/5` sent a field that was accepted with the
|
|
323
|
+ |
request and dropped. The wrapper then read `$OPENAGENTS_FORGE_TOKEN` under
|
|
324
|
+ |
`set -u` and died on the unbound variable before printing a pid — the
|
|
325
|
+ |
`box_response_invalid` of §7. Reproduced exactly on `bx_732ts8jg`: the same
|
|
326
|
+ |
wrapper with the variable unset returns HTTP 2xx, `exit_code` 1, empty stdout,
|
|
327
|
+ |
and `bash: line 12: OPENAGENTS_FORGE_TOKEN: unbound variable`.
|
|
328
|
+ |
|
|
329
|
+ |
`env` on box *create* is a different endpoint and does work, which is how
|
|
330
|
+ |
`OPENROUTER_API_KEY` reaches a box. It is not available here: an assignment
|
|
331
|
+ |
credential is minted per attempt, long after its box exists, and
|
|
332
|
+ |
`PATCH /boxes/{id}` takes only `name`, `ttlSeconds`, and `subdomain`. So the
|
|
333
|
+ |
credential now travels inside the dispatch command, base64 only so a token
|
|
334
|
+ |
cannot break the surrounding shell quoting. The cost is explicit: the
|
|
335
|
+ |
credential is in the provider's request body and in whatever the provider logs
|
|
336
|
+ |
of it. It is not in `box_runs.command`, which holds the caller's script and
|
|
337
|
+ |
never sees this wrapper, and an assignment credential is short-lived and scoped
|
|
338
|
+ |
to one branch of one repository.
|
|
339
|
+ |
|
|
340
|
+ |
A 2xx that carries no pid now logs a bounded operational event — response keys,
|
|
341
|
+ |
exit code, output sizes, and one truncated stderr line with the credential
|
|
342
|
+ |
struck out by exact match — instead of a bare atom.
|
|
343
|
+ |
|
|
344
|
+ |
Verified on `bx_732ts8jg` with a stand-in credential: dispatch returns a
|
|
345
|
+ |
parseable pid, the run root is created, the detached child runs under
|
|
346
|
+ |
`GIT_CONFIG_GLOBAL` pointing at the run's own gitconfig, `git credential fill`
|
|
347
|
+ |
for `https://openagents.com` presents `username=x` and the password, the exit
|
|
348
|
+ |
sentinel is written, and `forge-credential` and `gitconfig` are removed when
|
|
349
|
+ |
the run ends.
|
|
350
|
+ |
|
|
351
|
+ |
A push receipt is still not demonstrated. It needs a real assignment
|
|
352
|
+ |
credential, which `Forge.Assignments.create/1` mints server-side and never
|
|
353
|
+ |
returns to an API caller, so it cannot be exercised until this change is
|
|
354
|
+ |
deployed. Every step before the authenticated git handshake is demonstrated
|
|
355
|
+ |
live above.
|