Skip to content

fix: issue #1 review findings - #3

Merged
joe-broadhead merged 11 commits into
masterfrom
fix/issue-1
Nov 12, 2026
Merged

joe-broadhead merged 11 commits into
masterfrom
fix/issue-1

Conversation

@joe-broadhead

Copy link
Copy Markdown
Owner

Refs #1. Each commit closes the issue #1 items named here; the items that need an owner decision are left open.

  • fix(fleet): keep the whole ledger record when ensure runs again — found while working on Agent feedback from 17 concurrent works: fleet status in one call, guest health, credentials, and a helper lost on reboot #1: boxd_ensure on an open work dropped the moved files and the last run's outcome.
  • fix(guest): read the newest idle message for a run's outcome — found while working on Agent feedback from 17 concurrent works: fleet status in one call, guest health, credentials, and a helper lost on reboot #1: a failed run after a completed one reported no error.
  • fix(tools): keep what happened in a coded error's result — bug 2, "the tool result drops the message": the result is now { error, message, next }, and a timeout names its step and machine.
  • fix(guest): copy the helper again when a reboot cleared it — bug 1: the helper is copied again, and the call run once, only when /tmp clearly lost its folder. A boxd CLI failure keeps its own error.
  • feat(guest): say why a guest that times out cannot answer — bug 2, the probe: GUEST_UNRESPONSIVE with free memory, load and the largest processes, and GUEST_SERVER_DOWN when the port is closed.
  • feat(job): say what the guest session last did, and when a prompt waits — boxd_job gets lastText, lastActivityAt and pending; boxd_submit gets queued.
  • feat(job): say who put a machine to sleep — "say why a machine hibernated": asleepBy, and sleptAt for the plugin's own sleeps. When boxd or the idle bound hibernated it, the time is boxd's to report.
  • docs(skill): correct directory and the job states, and cover many works and credentials — "explain proof: "missing"", "batch submit" (a Code Mode loop), the credentials and 401 guidance, and the read-only diagnosis commands.
  • feat(boxd): refuse the in-VM boxd CLI instead of ending as AWAKE_UNBOUND — found while working on Agent feedback from 17 concurrent works: fleet status in one call, guest health, credentials, and a helper lost on reboot #1: the in-VM CLI takes --vm, so the plugin now answers BOXD_MISSING on it.
  • fix(boxd): refuse the in-VM CLI only when its help carries both phrases — narrows the check above, so one phrase in the laptop CLI's help cannot block every tool.

Not in this PR, left for the owner: live fields per work in boxd_status, tail: n, one result shape for errors, model on submit, a size on boxd_ensure, a bounded wait, a credential view in boxd_status, and renaming proof. The boxd env set --secret stdin, token refresh and update-banner asks are for boxd.

boxd_ensure rebuilt an open work's record from a short list of fields, so a
coordinator that ensures each work at the start of a turn, such as after a resize,
lost the files moved in and out and the last run's outcome. A drop then refused
an output a harvest had already brought back, and status no longer showed a
failed run.

Ensure now starts from the record it loaded and clears only the create intent.

Refs #1
GET /api/session/<id>/message lists newest first, 50 a page, on 2.2.3 and 2.2.6.
The helper walked that page oldest first and stopped at the first idle message, so
a session with several runs reported an earlier run's error, or none: a credential
failure after a completed run read as GUEST_RUN_FAILED with no error.

The helper now asks for ?type=idle&order=desc&limit=1, one message, the last run's.
The fake guest server lists messages the way the real one does, and each finished
run ends with its own idle message.

Refs #1
A coded error's tool result was { error, next }: the sentence that said what
happened went only to the model's text, so a Code Mode program or a script saw a
bare TIMEOUT with no step and no cause. The result is now { error, message, next }.

A timeout names its command and machine, such as the guest's phase on
oc-<work id>, instead of "boxd machine". A GUEST_FAILED message carries the
helper's error and its detail, redacted, or the last line the machine wrote when
the helper answered nothing.

docs/scripting.md said a plugin code arrives as the RPC error failed; it arrives
as the method's output, and only the four RPC codes are errors.

Refs #1
The helper lives in the machine's /tmp, which a reboot or a resize clears, and the
plugin only remembered that it had copied it once in this process. Every guest call
then failed with GUEST_FAILED until OpenCode restarted.

A call that gets no JSON back now lists /tmp with ls -1a. Only when the listing
answered, includes ".", and lacks the helper's folder is the helper copied again
and the call run once more: Python never started the action, so nothing runs
twice. A crash, or a boxd CLI that failed (auth, network, a 503), keeps the call's
own error with no copy and no retry. The probe is plain words, so it reads the same
whether machine exec runs it directly or through a shell.

The fake CLI now runs the copy the plugin uploaded and answers like Python when it
is missing, so the host tests can clear /tmp as a reboot does.

Refs #1
A guest call that times out is followed by one probe of the machine, which reads
/proc and the server's port and never talks to OpenCode. A machine under a tenth
of its memory or over four load per CPU answers GUEST_UNRESPONSIVE with the
facts, and its one next step is to tell the user. A machine with nothing on the
OpenCode port answers GUEST_SERVER_DOWN, whose next step is boxd_job. A machine
that looks well keeps TIMEOUT, with the facts; a probe that does not answer, or
answers with a line that is not JSON, keeps the plain timeout.

Refs #1
boxd_job gave busy or idle and an outcome, so a session that stopped on a 401 and
one that finished its task both read as idle and completed, and a coordinator had
to read a report file on every machine by hand.

On a running machine boxd_job now adds lastText, the newest assistant message's
text parts, redacted and cut to 300 characters on the laptop; lastActivityAt, the
latest of the session's and that message's times; and pending, the user items in
the session's inbox. Each is read from the guest's own session with no model call,
and left out when the guest does not answer it. A message of another kind than the
one asked for is never read as an answer.

boxd_submit reads /api/session/active before the prompt and answers queued: true
when the session was running, so the prompt waits in its inbox for that run. That
read is best-effort: a server that refuses it still takes the prompt.

Refs #1
Ten machines were found hibernated with nothing to say whether boxd_sleep or the
idle bound had done it. boxd reports no hibernation time or reason, so the plugin
says what it knows.

boxd_sleep records sleptAt in the ledger, and every wake or create the plugin makes
clears it. boxd_job's asleep answer and each work in status carry asleepBy:
boxd_sleep with sleptAt, or idle_or_cli for the 30-minute idle bound or the boxd
CLI. The status text says the same beside the machine's state.

Refs #1
…ks and credentials

The skill said directory moves the guest's folder, which is always the plugin's own
work folder on the machine, and that boxd_job answers busy, idle or missing, when
it also answers empty, starting, asleep and gone. It now says directory is the
laptop folder, names every state, and says what proof fresh, stale and missing
mean.

It adds what a coordinator needed: drive many works from one Code Mode loop, since
there is no batch tool; where a guest's credentials come from, that a short-lived
token stops every guest once nothing renews it, and that a prompt should tell the
session to stop and report a 401 rather than start an interactive sign-in; and how
to look at a slow guest with the boxd CLI, read-only. The README's known limits and
the golden-image page say the same about short-lived tokens.

Refs #1
The boxd CLI that runs inside a boxd machine takes --vm <name> where the laptop CLI
takes the machine as an argument, so the plugin's commands failed one by one and the
awake bound ended as AWAKE_UNBOUND. Every machine call now first reads boxd --help
once per binary, which changes nothing, and answers BOXD_MISSING when it is the in-VM
CLI: this plugin drives remote machines from the laptop's CLI. A CLI that cannot
answer --help is not refused; its next command reports its own failure.

Refs #1
The laptop CLI's help has not been read, so one phrase alone could be its own
wording, and a false match would block every tool with BOXD_MISSING. The check now
requires both "boxd inside a machine" and "from inside the VM".

Refs #1
The helper runs on the Linux guest and reads /proc, which macOS has not, so the
test that runs it on the runner failed on both macOS jobs. It now runs on Linux
only.

Refs #1
@joe-broadhead
joe-broadhead merged commit 64a590c into master Nov 12, 2026
5 checks passed
@joe-broadhead
joe-broadhead deleted the fix/issue-1 branch November 12, 2026 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant