feat(supervision): add choice watcher daemon, HTTPS spec docs, and test suites
- bin/muse_choice_watcher.py + systemd/muse-choices-reconcile.*: automatic choice answering and timer reconciliation - bin/digest.py: fleet log and health summarization - docs/BOX-*-HTTPS.md: comprehensive HTTPS execution contracts and API documentation - docs/MUSE-CHOICES-POLICY.md & docs/SUPERVISION-SPEC.md: autonomous execution specs - tests/test_*.py: unit test suites for HTTPS API, choice watcher, fleet heal, and swarm pruning
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# Box Job Lifecycle over HTTPS (No SSH)
|
||||
|
||||
> **Box is the main surface.** All operator work goes through Box (box.muse-dev.online). The web UI, `box` CLI, and agents share the same API endpoints. No UI-only powers.
|
||||
|
||||
**Date:** 2026-10-06
|
||||
**Status:** implemented on bl (`exec-constrained.py` + `box-relay.sh`;
|
||||
`box-ctl.py` verbs pre-existed)
|
||||
**Scope:** safe job-lifecycle mutations. Deletes are deliberately NOT
|
||||
exposed (`job-delete`, `timer-delete` stay SSH/operator-only).
|
||||
|
||||
## 1. Why
|
||||
|
||||
Agents own scheduled automation but could only run jobs (`cron.run`) or read
|
||||
them (`box.exec`). Creating, updating, triggering, chaining, previewing, and
|
||||
pausing jobs needed SSH. All of this now rides the agent HTTPS path
|
||||
(`https://exec.muse-dev.online/exec`, signature or bearer auth, named-op
|
||||
allowlist, audit log).
|
||||
|
||||
## 2. New ops
|
||||
|
||||
| Op | Args | Backend | Write? | Who |
|
||||
|---|---|---|---|---|
|
||||
| `job.put` | `{name, definition}` | `box-ctl.py job-put` (definition on stdin) | yes (writes + commits) | known identities only |
|
||||
| `job.trigger` | `{name}` | `box-ctl.py job-trigger` | yes (dispatches now) | known identities only |
|
||||
| `job.chain` | `{from, to, on_failure?}` | `box-ctl.py job-chain` | yes (writes + commits) | known identities only |
|
||||
| `job.next` | `{job_id, success?}` | `box-ctl.py job-next` | no (dry-run) | any valid signer |
|
||||
| `cron.timer_stop` | `{name}` | `box-ctl.py timer-stop` | yes (systemd) | known identities only |
|
||||
| `cron.timer_disable` | `{name}` | `box-ctl.py timer-disable` | yes (systemd) | known identities only |
|
||||
|
||||
New `box-relay.sh` client commands:
|
||||
|
||||
```bash
|
||||
box cron put <name> '<json-definition>' # create or update (see §3)
|
||||
box cron trigger <name> # dispatch now (audited JSON)
|
||||
box cron chain <from> <to> [--on-failure] # wire chain_next
|
||||
box cron next <job-id> [--success|--fail] # dry-run: what dispatches next
|
||||
box timer stop <name> # pause schedule (keeps unit)
|
||||
box timer disable <name> # pause schedule (disables unit)
|
||||
```
|
||||
|
||||
Raw op call (signature auth, no token):
|
||||
|
||||
```bash
|
||||
exec-sign.sh job.next '{"job_id": "heartbeat-20200101-000000-deadbeef"}'
|
||||
exec-sign.sh job.chain '{"from": "ops-audit-step2", "to": "ops-audit-step3"}'
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `job.trigger` vs existing `job.run`: `job.run` shells straight to
|
||||
`job-dispatch.py` and relays raw output; `job.trigger` goes through
|
||||
`box-ctl.py` (existence check, 300s bound, audit trail, JSON contract).
|
||||
Prefer `job.trigger` for agent-driven dispatches.
|
||||
- `job.next` job ids look like `<name>-YYYYMMDD-HHMMSS-<8hex>`; with no
|
||||
`success` flag the box infers it from the last recorded result.
|
||||
|
||||
## 3. Job definition shape (`job.put`)
|
||||
|
||||
The full schema is enforced by `box-ctl.py validate_job` (single copy);
|
||||
required fields: `name` (must match the argv name), `schedule` (`manual`
|
||||
or convertible cron), `agent` (fleet member), `prompt_template` (1–4000
|
||||
chars, no protocol literals, known `{placeholders}` only). `timeout`
|
||||
60–3600s, `on_failure` policy, optional `chain_next` (must exist) and
|
||||
`sidechat` / `dm_target` routing. `box-ctl.py` writes `jobs/<name>.json`
|
||||
and commits (`Add/Update job <name> via box-ctl`).
|
||||
|
||||
## 4. Safety notes (same posture as existing ops)
|
||||
|
||||
- **Fixed argv, validated values.** Job names match
|
||||
`^[a-z0-9][a-z0-9-]{0,63}$`; trigger/chain/timer ops require the job
|
||||
file to exist; chain rejects self-links and cycles; timer ops require
|
||||
the unit to exist. All checks run before any side effect.
|
||||
- **Stdin plumbing.** `job.put` is the first op to pipe a request body to
|
||||
`box-ctl.py` stdin (the raw definition, not the envelope); the routing
|
||||
lives in one helper (`_stdin_body`) covered by unit tests.
|
||||
- **No deletes, no kills.** `job-delete` / `timer-delete` are reachable
|
||||
only over the operator SSH path, by explicit scope decision.
|
||||
- **Audited.** Every execution records identity + op + args hash;
|
||||
`box-ctl.py` additionally audits each mutation with its target.
|
||||
|
||||
## 5. Rollout notes
|
||||
|
||||
- Restart `exec-constrained.py` after deploy for the new ops to appear in
|
||||
`GET /ops`.
|
||||
- `box-relay.sh` is served from bl (`GET /box`); agents re-fetch to get
|
||||
the `cron put|trigger|chain|next` and `timer stop|disable` commands.
|
||||
- Non-goals: deletes, `job-result` ingestion, `strat`/`loop` writes,
|
||||
approvals — future expansions, same pattern.
|
||||
Reference in New Issue
Block a user