| name | pulse |
| description | Run a report-only triage tick over a repo/task and write a prioritized DIGEST of items needing human review. Modes - `run` (the tick), `queue` (consume the review queue), `propose` (attended L2 propose-only acting - human-invoked, never pushes/deploys/merges, refuses unattended), `arm` (GATED - refuses until the containment spike passes). All fail-closed logic lives in scripts/review_digest.py; this skill only invokes it. The automated tick NEVER acts (no push/deploy/merge/outbound); `propose` is attended, propose-only, and still never pushes/deploys/merges. Human-invoked. Aliases: review-digest, flywheel. |
pulse — report-only triage tick (L1) + attended propose (L2)
Status (2026-07-02): pulse is an OPTIONAL hand-run triage tool — it is NOT the meta-harness /
The runner (scripts/review_digest.py) is the RUNTIME; this file is the INVOKER. Every
safety-critical property — registry parse/validate, refusal gates, the 0-token early-exit,
atomic queue writes, and deterministic structural validation of every finding — lives in the
runner as code the LLM cannot skip. This skill's only judgment jobs are (a) classify survey
output into findings and (b) OPTIONALLY grade them with a verifier.
Let RD="python3 <this-skill-dir>/scripts/review_digest.py" and always pass --target <repo>
(never rely on cwd).
run — one report-only tick
- Precheck (runner):
$RD run --mission <id> --target <repo> --phase precheck
- exit 30 (kill-switch:
loop-pause present) or exit 20 (a refusal gate) → STOP and print the reason.
- exit 10 (early-exit: cursors unchanged) → DONE, zero fan-out.
- exit 0 → parse the emitted JSON (
decision, current_cursors, survey_plan).
- Survey (LLM, READ-ONLY): run each
survey_plan[].cmd. A source that errors or is unauthenticated
→ record source_status:"failed" (NEVER silently "nothing new").
- Classify (LLM): build a findings array. Each finding MUST carry
{source, id, finding, suggested_action, evidence, severity, source_status} where
id = <mission>:<source-type>:<natural-key> and severity ∈ {high,watch,noise},
source_status ∈ {ok,degraded,failed}. source must be one of the registry's survey sources.
- Verify (OPTIONAL, LLM judgment layer): spawn
Agent(subagent_type:"verifier"), default-REJECT any
finding whose evidence does not support it, and set verified on the survivors.
The runner's structural validation is the ALWAYS-ON floor; the verifier grades quality ON TOP of it.
Degraded mode: the skill→Agent spawn path is undocumented and often unavailable (headless/scheduler).
If the verifier cannot run, leave verified:false and DO NOT block — the runner still writes a
structurally-valid report and STATE.md shows a loud "UNVERIFIED — verifier unavailable" banner.
- Commit (runner):
$RD run --mission <id> --target <repo> --phase commit --findings findings.json
(or --findings - to pipe on stdin). The runner structural-validates every finding (exit 50 on a
malformed one — including duplicate ids in one batch or a non-boolean verified), computes
content_hash (severity is part of it, so a watch→high escalation re-surfaces + re-verifies),
upserts/dedupes/ages the queue atomically, then writes STATE.md + run-log.jsonl.
Pass --survey-ok ONLY when the survey actually ran and every required source succeeded. It is
the explicit success signal that lets the runner ADVANCE cursors.json. If any required source
FAILED (or the survey/classifier itself failed), OMIT --survey-ok: the runner still queues whatever
findings you did produce, but it will NOT advance the cursor — so the next precheck re-surveys instead
of early-exiting and masking the failure. Empty findings + --survey-ok == "clean tick, nothing new".
Non-operating styles at L1 (style-escape is closed by the runner)
The runner REFUSES any mission whose style != "operating" (exit 20), and any autonomy above L1.
On that refusal, the skill writes ONE hand-off PROPOSAL to the queue (e.g. "this mission wants a
completion/convergence style — hand off to ralph/autoresearch under human supervision"). It NEVER
auto-launches ralph, autoresearch, or any maker. At L1, completion/convergence intent is a proposal,
not an action.
queue — the human consumer
$RD queue list --target <repo> — deterministic render, grouped by severity then age; aged/escalated
items float to the top; header N pending (M aged) · K ack.
$RD queue ack|done|dismiss --id <id> --target <repo> — atomic status transition (pending → ack → done,
plus dismissed). These are HUMAN decisions: they are REFUSED under --scheduler (exit 20 — a
noninteractive/scheduled context is read-only) and while the kill-switch (loop-pause) is present
(exit 30). queue list stays available in both cases so a human can still inspect.
propose — L2 acting (attended, human-invoked, propose-only)
propose is the attended L2 command: a human runs pulse propose <item> and Claude makes the
change in the attended main session (not a spawned sub-agent), where git-safety.sh + the
permissions.deny push/deploy rules fire. The runner only gates + records + emits the plan — it
never spawns agents, creates worktrees, or edits code. It NEVER pushes/deploys/merges, refuses
unattended/scheduler use, and L3 (auto-apply on the clock) stays gated behind the containment spike
(design spec not included in this shared export).
Flow the Claude session follows when the user runs pulse propose <item>:
- Gate + plan (runner):
$RD propose --item <id> --target <repo> --phase plan.
- exit 20 (attended-only / L3-without-ARMED refusal), 30 (kill-switch:
loop-pause), 40 (bad
registry), or 50 (item missing or not pending/ack) → STOP and tell the user why. Do not proceed.
- exit 0 → parse the plan JSON (
item, worktree_suggestion, gate:"pass", steps). The item is
now marked proposal.state:"proposing" on the queue.
- Isolated worktree: create an ISOLATED git worktree off the target (prefer the harness worktree
mechanism —
EnterWorktree). The main checkout stays untouched; the change lives ONLY in the worktree.
- Make the change (attended session): make the MINIMAL change for that item (ponytail: smallest
diff), in the attended main session under the active git-safety hook +
permissions.deny rules.
- Verify: run the repo's real gate (
typecheck → lint → test) on the change. A failed gate →
do NOT propose; report the failure and STOP.
- Present + STOP: show the diff + evidence + verify results to the user and STOP. Never
push/deploy/merge. Only if the user passed
--apply do a LOCAL commit in the worktree
(reversible) — a local commit only, never an outbound action.
- Record (runner):
$RD propose --item <id> --target <repo> --phase record [--apply] --verify '<json>'
→ records the outcome: proposal.state:"proposed" (or "applied-local" with --apply) plus the
verify result. The runner fail-closes a --apply recorded against a failed verify gate (exit 20).
--apply means only a LOCAL git commit in the worktree (reversible) — it must NOT push/deploy.
propose writes ONLY to review-queue.jsonl (the single source of truth) via the same atomic
(flock + temp + fsync + os.replace) path; it does not touch STATE.md, cursors.json, or the run log.
This is the containment approach: main-session acting where the hooks fire, propose-only, review-gated —
L3 (unattended acting on the clock) is NOT built and stays fail-closed behind arm.
Invariants (enforced by the runner, not by this prose)
READ + LOCAL-WRITE only, under <target>/.omc/loop/ (git-excluded). No build/deploy/push/merge/outbound.
Everything actionable is a PROPOSAL for a human. review-queue.jsonl is the single source of truth for
item state; cursors.json holds only per-source cursors + last_run; STATE.md is derived (regenerated
each tick). A content_hash change on an existing id re-surfaces it and marks it for re-verification.
Setup (idempotent)
mkdir -p <target>/.omc/loop
- Copy
templates/{registry.json,cursors.json,STATE.md,loop-constraints.md} into <target>/.omc/loop/
and edit registry.json (target, survey, cursor_probes) for the repo.
⚠ registry.json is a HUMAN-ONLY trust boundary. Its cmd strings run under shell=True
(they carry pipes like … | wc -l), so whoever can write it can run arbitrary shell as the runner —
editing registry.json == editing the script; review it at that bar. The runner does defend the
lower-trust inputs around it: --target is validated + shlex.quote()d before {target}
substitution (no injection via a crafted target), probes run under a hard timeout, and LLM-authored
findings are validated data that is NEVER shelled out.
- Append
.omc/loop/ to <target>/.git/info/exclude if absent — this gives NO tracked working-tree
mutation (the artifacts still exist on disk; they just never dirty git status or get git add-ed).
Note: ~/.claude is not a git repo, so this step applies only to git-repo targets.
Scheduler (opt-in, read-only)
An OPT-IN, structurally read-only "always-on clock" so a human need not remember to run a
tick. It is DISARMED by default — nothing runs until you explicitly arm it. Full docs +
templates: scheduler/README.md. Two mechanisms, both unable to act:
Why it can't act: --scheduler refuses any mode but run/queue, refuses every queue
mutation, and refuses arm; --l1-only refuses a non-L1 registry (never downgrades one);
precheck writes nothing; the launchd path has no LLM. --survey-ok discipline: the launchd
detector never commits and never passes --survey-ok; the /loop full tick passes it ONLY when
every required survey source succeeded (otherwise the cursor must not advance — see run step 5).
arm — GATED-FUTURE (fail-closed)
$RD arm --mission <id> --target <repo> ALWAYS refuses in SLICE 1 (exit 20). Arming will require BOTH
<target>/.omc/loop/ARMED and ~/.claude/.omc/meta-harness-build/P4-PASS.json (matching cc_version),
earned only on a containment-spike PASS. (Design spec not included in this shared export.)
Tests
bash <this-skill-dir>/tests/run.sh is the committed, re-runnable safety suite. Re-run it (and update
tests/.last-cc-version) on any Claude Code version bump.