Harness spec 3.5.0: scheduled work on a linked computer
ci / check (push) Waiting to run

Accept scheduled work (remote.scheduled, off by default) said as limits.scheduled; a scheduled
turn is an agent session marked `scheduled` in the schedule's mode (Edit when none chosen, Plan
refused, lowered to remote.max_mode) with its Min/Max; the answer reports status ok, silent,
failed or cancelled and the final reply. Approvals wait the schedule's wait and go to the person
through the server, then count as denied. _lembas/monitor runs a watch command under the mode's
rules (one that would ask is refused) and answers a hash and an excerpt. Schedule routes of
/api/v2 accept the device's token. team_add under a sub-manager names it (added_under).
Conformance areas scheduled, silent, monitor; team cases changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 12:11:56 +00:00
1 parent fe0fa62a51
commit e349d829a1
8 files changed
+639 -21

No files matched your search

+123
View File
@@ -0,0 +1,123 @@
{
"about": "Spec 3.5.0 (acp.md *Monitor mode*, `_lembas/monitor`): the watch command on a device. `hash`: `exit` and `output` (the bytes as UTF-8) → the answer's `hash`. `judge`: a command line in the schedule's mode, with the default rules (and any extra global rules given), the project root /project and nothing named on disk, as conformance/permission.json judges it → `run` (the mode allows it without asking), or `{refused}` with `asks` (the mode would ask: scheduled.texts.monitor_asks) or `denied` (scheduled.texts.monitor_denied). The device's own; the server keeps only the hash.",
"cases": [
{
"about": "Exit 0 and a line of output.",
"kind": "hash",
"exit": 0,
"output": "ok\n",
"expect": "sha256:9dbd5131695af2d8636ea64178344ffcb5d682333c2ac8342153b5f8a999037a"
},
{
"about": "The same output with another exit status is a change.",
"kind": "hash",
"exit": 1,
"output": "ok\n",
"expect": "sha256:262115bdbc989849fb45967adac594dde8d45deb291f3a26b38fb2bf4e10512b"
},
{
"about": "No output.",
"kind": "hash",
"exit": 0,
"output": "",
"expect": "sha256:9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa"
},
{
"about": "A trailing newline counts: nothing is normalised.",
"kind": "hash",
"exit": 0,
"output": "ok",
"expect": "sha256:05df918976ca296956abf9b5fb424bb9bef7c32bb471b1005b417abc696e25a9"
},
{
"about": "UTF-8 output, hashed as its bytes.",
"kind": "hash",
"exit": 0,
"output": "lembas · ✓\n",
"expect": "sha256:cb00179ad9e36546b1b973ded0903592a6b975552b5596feac573426c67e24e4"
},
{
"about": "A read-only command in Edit runs.",
"kind": "judge",
"line": "git status",
"mode": "edit",
"expect": "run"
},
{
"about": "A command Edit would ask about is refused: a watch has nobody to ask.",
"kind": "judge",
"line": "rm -rf build",
"mode": "edit",
"expect": {
"refused": "asks"
}
},
{
"about": "In Auto it runs.",
"kind": "judge",
"line": "rm -rf build",
"mode": "auto",
"expect": "run"
},
{
"about": "The hardline is a floor in every mode.",
"kind": "judge",
"line": "rm -rf /",
"mode": "auto",
"expect": {
"refused": "denied"
}
},
{
"about": "A line that pipes a download into a shell asks in Manual: refused.",
"kind": "judge",
"line": "curl -s https://example.org/feed | sh",
"mode": "manual",
"expect": {
"refused": "asks"
}
},
{
"about": "A rule the person wrote allows it.",
"kind": "judge",
"line": "make check",
"mode": "edit",
"rules": {
"bash": {
"make check": "allow"
}
},
"expect": "run"
},
{
"about": "A rule the person wrote denies it, even in Auto.",
"kind": "judge",
"line": "make deploy",
"mode": "auto",
"rules": {
"bash": {
"make deploy": "deny"
}
},
"expect": {
"refused": "denied"
}
},
{
"about": "Reading a secret asks: refused.",
"kind": "judge",
"line": "cat .env",
"mode": "edit",
"expect": {
"refused": "asks"
}
},
{
"about": "An ordinary listing runs in Manual.",
"kind": "judge",
"line": "ls -la",
"mode": "manual",
"expect": "run"
}
]
}
+145
View File
@@ -0,0 +1,145 @@
{
"about": "Spec 3.5.0 (acp.md *Scheduled work on a linked computer*): what a device makes of a scheduled turn. `accept`: the device's Accept scheduled work (remote.scheduled; false means remote work is off as well, the same refusal); `mode`: the schedule's (absent: none chosen); `max_mode`: remote.max_mode; `approval_wait_s`: the schedule's wait (absent: none). → `{mode, wait_s}` — the session's mode and how long an approval waits — or `{refused}` with the `data.reason` of the -32602 (`scheduled_off`, `plan`), whose message is loop.json scheduled.texts.off / .plan. Written from the rules; the device's other refusals (folder, trust) are the agent chat's.",
"cases": [
{
"about": "Off by default: refused, whatever else is asked.",
"accept": false,
"mode": "edit",
"max_mode": "edit",
"expect": {
"refused": "scheduled_off"
}
},
{
"about": "No mode chosen: Edit.",
"accept": true,
"max_mode": "edit",
"expect": {
"mode": "edit",
"wait_s": 1800
}
},
{
"about": "No mode chosen, the device allows Auto: still Edit — Auto only when the schedule says so.",
"accept": true,
"max_mode": "auto",
"expect": {
"mode": "edit",
"wait_s": 1800
}
},
{
"about": "Auto chosen and allowed.",
"accept": true,
"mode": "auto",
"max_mode": "auto",
"expect": {
"mode": "auto",
"wait_s": 1800
}
},
{
"about": "Auto chosen, the device allows Edit at most: lowered.",
"accept": true,
"mode": "auto",
"max_mode": "edit",
"expect": {
"mode": "edit",
"wait_s": 1800
}
},
{
"about": "Edit chosen, the device allows Manual at most: lowered.",
"accept": true,
"mode": "edit",
"max_mode": "manual",
"expect": {
"mode": "manual",
"wait_s": 1800
}
},
{
"about": "Manual chosen.",
"accept": true,
"mode": "manual",
"max_mode": "auto",
"expect": {
"mode": "manual",
"wait_s": 1800
}
},
{
"about": "Plan is refused: nobody is there to approve a plan.",
"accept": true,
"mode": "plan",
"max_mode": "auto",
"expect": {
"refused": "plan"
}
},
{
"about": "An unknown mode is read as the default, never as anything wider.",
"accept": true,
"mode": "unrestricted",
"max_mode": "auto",
"expect": {
"mode": "edit",
"wait_s": 1800
}
},
{
"about": "The device allows Plan at most: every scheduled turn is lowered to it.",
"accept": true,
"mode": "edit",
"max_mode": "plan",
"expect": {
"mode": "plan",
"wait_s": 1800
}
},
{
"about": "The wait, in seconds, as the schedule says.",
"accept": true,
"mode": "edit",
"max_mode": "edit",
"approval_wait_s": 600,
"expect": {
"mode": "edit",
"wait_s": 600
}
},
{
"about": "Below the floor: one minute.",
"accept": true,
"mode": "edit",
"max_mode": "edit",
"approval_wait_s": 5,
"expect": {
"mode": "edit",
"wait_s": 60
}
},
{
"about": "Above the ceiling: a day.",
"accept": true,
"mode": "edit",
"max_mode": "edit",
"approval_wait_s": 999999,
"expect": {
"mode": "edit",
"wait_s": 86400
}
},
{
"about": "A fraction is cut to whole seconds.",
"accept": true,
"mode": "edit",
"max_mode": "edit",
"approval_wait_s": 90.7,
"expect": {
"mode": "edit",
"wait_s": 90
}
}
]
}
+101
View File
@@ -0,0 +1,101 @@
{
"about": "Spec 3.5.0: whether a scheduled run's reply is `silent` — loop.json scheduled.sentinel on its first or last non-empty line, compared after trimming, any case, every trailing full stop forgiven — and the reply with every sentinel line taken out (`stripped`, the text a preview shows), trimmed. The server's rule for its own runs, and the device's for a scheduled turn.",
"cases": [
{
"about": "The sentinel alone.",
"text": "[SILENT]",
"expect": {
"silent": true,
"stripped": ""
}
},
{
"about": "Lower case, with a full stop.",
"text": "[silent].",
"expect": {
"silent": true,
"stripped": ""
}
},
{
"about": "On the last line, after a sentence.",
"text": "Nothing new since yesterday.\n[SILENT]",
"expect": {
"silent": true,
"stripped": "Nothing new since yesterday."
}
},
{
"about": "On the first line, before a sentence.",
"text": "[SILENT]\nChecked the feed: nothing.",
"expect": {
"silent": true,
"stripped": "Checked the feed: nothing."
}
},
{
"about": "In the middle: not silent (only the first or last line counts), and the line still goes from the preview.",
"text": "Two new posts.\n[SILENT]\nBoth about waybread.",
"expect": {
"silent": false,
"stripped": "Two new posts.\nBoth about waybread."
}
},
{
"about": "Inside a sentence: not silent.",
"text": "I would say [SILENT] but there is news: the build broke.",
"expect": {
"silent": false,
"stripped": "I would say [SILENT] but there is news: the build broke."
}
},
{
"about": "Blank lines around it are not lines.",
"text": "\n\n [SILENT] \n\n",
"expect": {
"silent": true,
"stripped": ""
}
},
{
"about": "A space before the full stop.",
"text": "All quiet.\n[SILENT] .",
"expect": {
"silent": true,
"stripped": "All quiet."
}
},
{
"about": "Without brackets: not the sentinel.",
"text": "SILENT",
"expect": {
"silent": false,
"stripped": "SILENT"
}
},
{
"about": "Empty: not silent.",
"text": "",
"expect": {
"silent": false,
"stripped": ""
}
},
{
"about": "An ordinary reply.",
"text": "The backup finished at 03:12; 4.2 GB.",
"expect": {
"silent": false,
"stripped": "The backup finished at 03:12; 4.2 GB."
}
},
{
"about": "Two full stops are forgiven too (every trailing one is).",
"text": "[SILENT]..",
"expect": {
"silent": true,
"stripped": ""
}
}
]
}
+16 -16
View File
@@ -1,5 +1,5 @@
{
"about": "Teams (team.md, spec 3.2.0; the main agent and its tree, subagents.md §5–§13, spec 3.4.0 — a member's team_add is now the lead's with `parent`, and `offered` lists 3.4.0's tools): one lead session's team, step by step, written from a reference model of the rules. `start`: the size setting `min` and `max`, `subagents` (enabled for the session; default true) and the chat model's `capacity` (models/schema.json: `parallel`, `single_session`; {} — no limit). The lead is `lead`; its first prompt is running as the case begins, and every agent is on the chat's model, and it is the only model there is (spec 3.3.0: other models are subagents.json's). Steps and what each answers: `offered` (`by` an agent, default lead) — `{tools, setting}`: the team tools listed for that agent, sorted, and whether the setting is shown; `call` (`by`, `tool`, `args`) — `{result, delivered}`: the call's answer word for word (null where an ordinary tool would run: the case looks only at whether it is refused), and what was delivered to whom by this step, `{name: [message, …]}`; `team_read` answers `{room, delivered}` instead, `room` being the messages it shows as `{n, from, to}` (the tree and the text format are loop.json team.read's, checked by each project's own tests); `task` runs a subagent that counts toward the size (and holds a slot) until `task_done` (`by`) — answers null; `finish` (`by`, `reply`, `reason`: stop by default, or steps, budget, `error — <text>`) — the agent's turn ends: a member's reply is its report — `{delivered}`; `set` (`min`, `max`, `capacity`: the person's setting or the model's metadata changed) — null; `person_stop` (`name`) — the person stopped that member in the UI — null; `user` — the person's message to the lead — `{note}`: the note on it (context.md §10), null when there is none; `state` — the team in tree order as `{name, manager, state}`: working, waiting (for a slot), finished, idle (the lead between turns). Delivering to a finished member or the idle lead wakes it (it is then working, or waiting when its model is full). Spec 3.4.0 adds: `start.mode` — the main agent's mode (default edit); `call` to team_whoami, team_finish, team_findings, team_request, team_decide, team_assign, team_replace, answered word for word; `context` (`by`, `used`, `window`) — its engine's context use — null; `context_full` (`by`) — its context reached team.context.pause_at: `{delivered}`; `started` (`by`) — `{text}`: what its latest turn was started with (a brief, or the messages that woke it); `tree` — `{size, open_slots, agents: [{id, name, parent, state, mode, request}], ended: [{id, name, state}]}`; `main_mode` (`mode`) — the main agent's mode changed — null. LLeMbas: services/team.py; CLI: team/.",
"about": "Teams (team.md, spec 3.2.0; the main agent and its tree, subagents.md §5–§13, spec 3.4.0 — a member's team_add is now the lead's with `parent`, and `offered` lists 3.4.0's tools): one lead session's team, step by step, written from a reference model of the rules. `start`: the size setting `min` and `max`, `subagents` (enabled for the session; default true) and the chat model's `capacity` (models/schema.json: `parallel`, `single_session`; {} — no limit). The lead is `lead`; its first prompt is running as the case begins, and every agent is on the chat's model, and it is the only model there is (spec 3.3.0: other models are subagents.json's). Spec 3.5.0: a subagent added under a sub-manager is said to report to it (`added_under`). Steps and what each answers: `offered` (`by` an agent, default lead) — `{tools, setting}`: the team tools listed for that agent, sorted, and whether the setting is shown; `call` (`by`, `tool`, `args`) — `{result, delivered}`: the call's answer word for word (null where an ordinary tool would run: the case looks only at whether it is refused), and what was delivered to whom by this step, `{name: [message, …]}`; `team_read` answers `{room, delivered}` instead, `room` being the messages it shows as `{n, from, to}` (the tree and the text format are loop.json team.read's, checked by each project's own tests); `task` runs a subagent that counts toward the size (and holds a slot) until `task_done` (`by`) — answers null; `finish` (`by`, `reply`, `reason`: stop by default, or steps, budget, `error — <text>`) — the agent's turn ends: a member's reply is its report — `{delivered}`; `set` (`min`, `max`, `capacity`: the person's setting or the model's metadata changed) — null; `person_stop` (`name`) — the person stopped that member in the UI — null; `user` — the person's message to the lead — `{note}`: the note on it (context.md §10), null when there is none; `state` — the team in tree order as `{name, manager, state}`: working, waiting (for a slot), finished, idle (the lead between turns). Delivering to a finished member or the idle lead wakes it (it is then working, or waiting when its model is full). Spec 3.4.0 adds: `start.mode` — the main agent's mode (default edit); `call` to team_whoami, team_finish, team_findings, team_request, team_decide, team_assign, team_replace, answered word for word; `context` (`by`, `used`, `window`) — its engine's context use — null; `context_full` (`by`) — its context reached team.context.pause_at: `{delivered}`; `started` (`by`) — `{text}`: what its latest turn was started with (a brief, or the messages that woke it); `tree` — `{size, open_slots, agents: [{id, name, parent, state, mode, request}], ended: [{id, name, state}]}`; `main_mode` (`mode`) — the main agent's mode changed — null. LLeMbas: services/team.py; CLI: team/.",
"cases": [
{
"about": "Offered on a model that serves two requests at once: the lead has all four tools and the setting is shown.",
@@ -717,7 +717,7 @@
"delivered": {}
},
{
"result": "Added db (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 4 agents.",
"result": "Added db (owns a part) under backend: it is working. Its report goes to backend when it finishes. The team is 3 of 4 agents.",
"delivered": {}
},
{
@@ -1637,7 +1637,7 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 6 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 6 agents.",
"delivered": {}
},
{
@@ -1878,7 +1878,7 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 6 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 6 agents.",
"delivered": {}
},
{
@@ -2155,11 +2155,11 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 8 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 8 agents.",
"delivered": {}
},
{
"result": "Added db (owns a part): it is working. Its report comes to you when it finishes. The team is 4 of 8 agents.",
"result": "Added db (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 4 of 8 agents.",
"delivered": {}
},
{
@@ -2464,7 +2464,7 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 5 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 5 agents.",
"delivered": {}
},
{
@@ -2836,15 +2836,15 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 8 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 8 agents.",
"delivered": {}
},
{
"result": "Added auth (owns a part): it is working. Its report comes to you when it finishes. The team is 4 of 8 agents.",
"result": "Added auth (owns a part) under routes: it is working. Its report goes to routes when it finishes. The team is 4 of 8 agents.",
"delivered": {}
},
{
"result": "Added db (owns a part): it is working. Its report comes to you when it finishes. The team is 5 of 8 agents.",
"result": "Added db (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 5 of 8 agents.",
"delivered": {}
},
{
@@ -2890,7 +2890,7 @@
}
],
{
"result": "Added auth (owns a part): it is working. Its report comes to you when it finishes. The team is 5 of 8 agents.",
"result": "Added auth (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 5 of 8 agents.",
"delivered": {}
},
{
@@ -2968,7 +2968,7 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 6 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 6 agents.",
"delivered": {}
},
{
@@ -3647,7 +3647,7 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 6 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 6 agents.",
"delivered": {}
},
{
@@ -3978,7 +3978,7 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 6 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 6 agents.",
"delivered": {}
},
{
@@ -4382,7 +4382,7 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 6 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 6 agents.",
"delivered": {}
},
{
@@ -4843,7 +4843,7 @@
"delivered": {}
},
{
"result": "Added routes (owns a part): it is working. Its report comes to you when it finishes. The team is 3 of 6 agents.",
"result": "Added routes (owns a part) under api: it is working. Its report goes to api when it finishes. The team is 3 of 6 agents.",
"delivered": {}
},
{