Harness spec 3.5.0: the watch command's hash puts the exit status after the bytes, so the output is hashed as it streams
ci / check (push) Waiting to run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 12:13:38 +00:00
1 parent e349d829a1
commit 788db0ff4d
3 files changed
+13 -12

No files matched your search

+1 -1
View File
@@ -93,7 +93,7 @@ round (`acp.md` *Scheduled work on a linked computer*, `loop.json` `scheduled`).
goes on. `ask_user` and `plan_submit` have nobody to answer; `show` is not offered. goes on. `ask_user` and `plan_submit` have nobody to answer; `show` is not offered.
- **Monitor mode, the device's half**: `_lembas/monitor` runs a schedule's watch command in its - **Monitor mode, the device's half**: `_lembas/monitor` runs a schedule's watch command in its
folder, with a timeout, under the mode's permission rules — **a command the mode would ask about folder, with a timeout, under the mode's permission rules — **a command the mode would ask about
is refused** — and answers the output's hash (`sha256:` over the exit status and the bytes) and a is refused** — and answers the output's hash (`sha256:` over the bytes and then the exit status) and a
short excerpt; the server runs the model only when the hash changed, and records `unchanged` short excerpt; the server runs the model only when the hash changed, and records `unchanged`
otherwise. otherwise.
- **Schedules over `/api/v2` with the device's token** (scope `link`): the CLI's `/schedule` lists, - **Schedules over `/api/v2` with the device's token** (scope `link`): the CLI's `/schedule` lists,
+7 -6
View File
@@ -435,10 +435,11 @@ default rules, the global rules and — in a trusted project — the project's),
`scheduled.texts.monitor_denied`, both `-32602` with `data: { reason: "permission" }` `scheduled.texts.monitor_denied`, both `-32602` with `data: { reason: "permission" }`
(`conformance/monitor.json`). (`conformance/monitor.json`).
**The hash** (`monitor.json` `hash` cases): `sha256:` and the lowercase hex SHA-256 of the exit **The hash** (`monitor.json` `hash` cases): `sha256:` and the lowercase hex SHA-256 of the
status as decimal digits, a newline, and then the output's bytes exactly as read — standard output output's bytes exactly as read — standard output and standard error as one stream, as the `bash`
and standard error as one stream, as the `bash` tool reads them. Nothing is normalised: a watch tool reads them — followed by a newline and the exit status as decimal digits (after the bytes, so
command that prints a time changes every time, and should not. the device hashes the output as it streams, however long). Nothing is normalised: a watch command
that prints a time changes every time, and should not.
## `_lembas/*` methods ## `_lembas/*` methods
@@ -499,8 +500,8 @@ with no input; at the timeout the group is stopped.
Result `{ hash, exit, timed_out, excerpt, truncated, bytes, duration_ms }`: `hash` as above (`null` Result `{ hash, exit, timed_out, excerpt, truncated, bytes, duration_ms }`: `hash` as above (`null`
when it timed out), `exit` the exit status (`null` on a signal or a timeout), `excerpt` the start of when it timed out), `exit` the exit status (`null` on a signal or a timeout), `excerpt` the start of
the output decoded as UTF-8 (invalid bytes replaced), at most `scheduled.monitor.excerpt_chars` the output decoded as UTF-8 (invalid bytes replaced), at most `scheduled.monitor.excerpt_chars`
characters, `truncated` whether there was more, `bytes` the output's length. The device keeps at characters, `truncated` whether there was more, `bytes` the output's length. The device hashes all
most `scheduled.monitor.output_max_bytes` of output and hashes all of it as it streams. of the output as it streams and keeps at most `scheduled.monitor.output_max_bytes` of it.
### `_lembas/directories` — client → agent → result ### `_lembas/directories` — client → agent → result
+5 -5
View File
@@ -6,35 +6,35 @@
"kind": "hash", "kind": "hash",
"exit": 0, "exit": 0,
"output": "ok\n", "output": "ok\n",
"expect": "sha256:9dbd5131695af2d8636ea64178344ffcb5d682333c2ac8342153b5f8a999037a" "expect": "sha256:9427ae13c91f98a3af2213b34fb59e4bb5aa9e6101a1f7407f526dde3571e6aa"
}, },
{ {
"about": "The same output with another exit status is a change.", "about": "The same output with another exit status is a change.",
"kind": "hash", "kind": "hash",
"exit": 1, "exit": 1,
"output": "ok\n", "output": "ok\n",
"expect": "sha256:262115bdbc989849fb45967adac594dde8d45deb291f3a26b38fb2bf4e10512b" "expect": "sha256:21e0bbd37d04094ef29f22d0212bb0f4bedda1b1fab38d51f9b8670b5cf07df4"
}, },
{ {
"about": "No output.", "about": "No output.",
"kind": "hash", "kind": "hash",
"exit": 0, "exit": 0,
"output": "", "output": "",
"expect": "sha256:9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa" "expect": "sha256:c7757c0896cbfe6182d8ea2bda4a8bf94addc428980eedab8609c57ca7ff1763"
}, },
{ {
"about": "A trailing newline counts: nothing is normalised.", "about": "A trailing newline counts: nothing is normalised.",
"kind": "hash", "kind": "hash",
"exit": 0, "exit": 0,
"output": "ok", "output": "ok",
"expect": "sha256:05df918976ca296956abf9b5fb424bb9bef7c32bb471b1005b417abc696e25a9" "expect": "sha256:b9b8913ecae1ba8190ef1a22cc57afef08ad3933ab4b13a32f8cdccb07bb527b"
}, },
{ {
"about": "UTF-8 output, hashed as its bytes.", "about": "UTF-8 output, hashed as its bytes.",
"kind": "hash", "kind": "hash",
"exit": 0, "exit": 0,
"output": "lembas · ✓\n", "output": "lembas · ✓\n",
"expect": "sha256:cb00179ad9e36546b1b973ded0903592a6b975552b5596feac573426c67e24e4" "expect": "sha256:3d1c38d3b96ee10d71faac35eb2933ba13b68d6325872fdda3e459c9fbd17d98"
}, },
{ {
"about": "A read-only command in Edit runs.", "about": "A read-only command in Edit runs.",