원클릭으로
residual
Inspect and drain the proactive-residual queue at .dynos/proactive-findings.json. Subcommands: list, run-next.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Inspect and drain the proactive-residual queue at .dynos/proactive-findings.json. Subcommands: list, run-next.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Run checkpoint audit, repair any findings, then reach DONE — all in one shot. Use after /dynos-work:execute.
Execute the approved plan. Orchestrates execution graph segments through specialized executor agents, including dependency management and error recovery.
Internal: Data Executor. Implements ETL, analytics, backfills, data quality, reconciliation, and data pipeline changes. Write evidence on completion.
Internal: Infrastructure Executor. Implements deployment, CI/CD, container, IaC, and environment configuration changes. Write evidence on completion.
Internal: Observability Executor. Implements logs, metrics, traces, alerts, dashboards, and reliability instrumentation. Write evidence on completion.
Internal: Release Executor. Implements version/changelog updates, rollout flags, rollback plans, release notes, and migration sequencing. Write evidence on completion.
| name | residual |
| description | Inspect and drain the proactive-residual queue at .dynos/proactive-findings.json. Subcommands: list, run-next. |
Inspect and drain the proactive-residual queue maintained at
{root}/.dynos/proactive-findings.json (where {root} is the current
project root — the directory that contains .dynos/). Two subcommands
are supported: list and run-next.
proactive-findings.json
on every invocation.run-next MUST NOT spawn auditor or executor agents directly. The
only execution path is /dynos-work:start. Any other route bypasses
the lifecycle, the audit gate, and the round-trip update in
cmd_run_audit_finish.hooks/lib_residuals.update_row_status (or
an equivalent LOCK_EX atomic write). Never edit the JSON file by hand
or with a partial overwrite — concurrent producers (other audits
finishing) can clobber a non-atomic write./dynos-work:start task is not done
the moment it returns control. Only the manifest.json
stage field tells you when it is finished.listInvoked in two forms:
/dynos-work:residual list — default view, excludes done rows./dynos-work:residual list --all — includes all rows (including done).Resolve {root} — the current project root (the directory containing
.dynos/). If running from a repo subdirectory, walk upward until
.dynos/ is found.
Run the following Python snippet. To include done rows, pass --all
as a positional argument to the heredoc process — bash makes it visible
to the snippet via sys.argv:
# Default (excludes done rows):
python3 - <<'PY'
...
PY
# Include all rows (including done):
python3 - --all <<'PY'
...
PY
The shared snippet body:
python3 - <<'PY'
from pathlib import Path
import sys
sys.path.insert(0, str(Path("hooks").resolve()))
import lib_residuals
root = Path(".").resolve()
queue = lib_residuals.queue_path(root)
data = lib_residuals.load_queue(queue)
findings = data.get("findings", [])
raw_count = len(findings)
include_done = "--all" in sys.argv
findings = [r for r in findings if include_done or r.get("status") != "done"]
if not findings:
if raw_count == 0:
print("dynos-work: no residuals queued")
else:
print("dynos-work: no active residuals (use --all to show done rows)")
sys.exit(0)
# Sort oldest first by created_at
findings_sorted = sorted(findings, key=lambda r: r.get("created_at", ""))
for r in findings_sorted:
title = r.get("title", "") or ""
if len(title) > 60:
title = title[:60]
print(
f"{r.get('id','')}\t"
f"{r.get('status','')}\t"
f"attempts={r.get('attempts',0)}\t"
f"{r.get('source_auditor','')}\t"
f"{title}\t"
f"{r.get('created_at','')}"
)
PY
Output rules — these are CONTRACTUAL:
proactive-findings.json does not exist, print exactly
dynos-work: no residuals queued and exit 0.findings is [], print exactly
dynos-work: no residuals queued and exit 0.done and --all was not
passed, print exactly
dynos-work: no active residuals (use --all to show done rows)
and exit 0.id, status, attempts, source_auditor,
title (truncated to 60 chars), created_at. All six fields must
appear on every row.done rows. Pass --all to include them.
pending, in_progress, wontfix, and failed rows always appear
regardless of --all.proactive-findings.json: lib_residuals.load_queue
treats this as an empty queue and returns {"findings": []}. The
command therefore prints dynos-work: no residuals queued rather
than raising. If you want to surface the corruption to the user,
print a separate stderr warning (do not change the stdout shape).run-nextInvoked as /dynos-work:residual run-next.
Before picking a new row, scan the queue for any row whose status is
"in_progress". If at least one such row exists, another run-next
invocation (or a previously-spawned residual task that has not yet
completed its round-trip) is already in flight. In that case, print
exactly:
dynos-work: residual in_progress — run-next already active
and exit 0. Do NOT select another row, do NOT mutate the queue, do NOT
spawn /dynos-work:start. This guard prevents two concurrent
run-next fires from double-selecting and lets a stuck round-trip be
resolved (manually or by cmd_run_audit_finish) before another
attempt is consumed.
Use lib_residuals.load_queue(lib_residuals.queue_path(root)). If the
result has an empty findings list (file missing or empty), print
exactly dynos-work: residual queue empty and exit 0.
Use lib_residuals.select_next_pending(root). This returns the oldest
row (by created_at ascending) whose status == "pending" AND
attempts < 3. A row whose status == "in_progress" is NEVER eligible
(it is already in flight). If the function returns None, print
exactly dynos-work: residual queue empty and exit 0.
Call lib_residuals.update_row_status(root, row["id"], "in_progress").
This is a LOCK_EX read-modify-write that:
status = "in_progress".attempts by 1.last_attempt_at to the current ISO 8601 UTC timestamp.If the call raises an exception (filesystem error, race with another
writer, etc.), print the error to stderr and exit non-zero. DO NOT
proceed to step 4 or step 5 — the queue is in an unknown state and a
spawned task without a corresponding in_progress row would lose its
round-trip update.
NOTE: update_row_status from the lib only changes status (and sets
last_attempt_at on the in_progress transition); it does NOT increment
attempts. Increment attempts in the same critical section by
either calling a helper that wraps both, or by reading the row,
mutating both fields, and writing back via the same LOCK_EX path. The
key invariant is: when control returns from step 3, the row on disk
has status == "in_progress" AND attempts incremented AND
last_attempt_at updated, all visible to subsequent readers.
Recommended Python snippet for step 3:
python3 - <<'PY'
from pathlib import Path
import sys, json
sys.path.insert(0, str(Path("hooks").resolve()))
import lib_residuals
root = Path(".").resolve()
row = lib_residuals.select_next_pending(root)
if row is None:
print("dynos-work: residual queue empty")
sys.exit(0)
# Guard: any in_progress already?
data = lib_residuals.load_queue(lib_residuals.queue_path(root))
if any(r.get("status") == "in_progress" for r in data.get("findings", [])):
print("dynos-work: residual in_progress — run-next already active")
sys.exit(0)
lib_residuals.update_row_status(root, row["id"], "in_progress")
# Emit the row payload so the orchestrator can build the start input.
print(json.dumps({"id": row["id"], "description": row.get("description","")}))
PY
Before invoking /dynos-work:start, the new task directory does not
yet exist. The auto-approve-gates ctl command requires the new task's
manifest.json to be on disk because it is the sole writer of
manifest.auto_approve_gates. There is therefore a sequencing
constraint: set-auto-approve-gates MUST be called AFTER
/dynos-work:start Step 0 has created .dynos/task-{new_id}/manifest.json
but BEFORE classification has been settled (the gate refuses
false → true once manifest.classification is non-null — this is
the AC-4 enforcement in cmd_set_auto_approve_gates).
In practice, call set-auto-approve-gates immediately after
/dynos-work:start returns control with the new task_id. If the
spawned start skill has already advanced past classification by the
time you make the call, the gate will refuse with a non-zero exit; in
that case fall through to the normal human-approval path (the gates
were not enabled, but the residual still runs).
"${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-}}/bin/dynos" ctl set-auto-approve-gates \
--task-dir .dynos/task-{new_id} \
--from-residual-id {row["id"]}
Fall-through behavior:
manifest.auto_approve_gates was set to true on the
new task. Subsequent SPEC_REVIEW / PLAN_REVIEW / TDD_REVIEW gates in
/dynos-work:start will auto-approve via the receipt path. Continue
polling in Step 5.run-next flow — the residual must still run; it just runs with a
human in the loop.Recommended logging:
{timestamp} [GATE] set-auto-approve-gates residual={row.id} task={new_id} result={ok|refused: <reason>}
The auto-approval pathway is an opportunistic acceleration of operator-trusted residuals. A refusal is a normal outcome (e.g. for high-risk security findings) and never blocks the residual from running.
/dynos-work:start and POLL until terminal stageBuild the input text for /dynos-work:start. The first line MUST be
the residual-id sentinel comment, followed by a blank line, followed
by the row's description:
<!-- residual-id: {row.id} -->
{row.description}
This sentinel is what cmd_run_audit_finish greps for via
lib_residuals.extract_residual_id to perform the round-trip status
update when the spawned task hits a terminal stage.
Invoke /dynos-work:start with that text. The spawned task is a NEW
dynos-work task with its own task-{id} directory under .dynos/ and
its own manifest.json.
WAIT — do NOT continue to step 6 until you have read the spawned
task's manifest.json and confirmed stage is exactly "DONE" or
"FAILED". Reading the manifest once and assuming the task completed
is INCORRECT. The /dynos-work:start skill returns control to you
long before the task is finished — it only kicks off the lifecycle.
The actual work proceeds through PLANNING, EXECUTION, AUDIT, and
REPAIR stages, which can take many minutes.
.dynos/task-{id}/ from the
/dynos-work:start output. (If the start skill does not echo the
id, inspect .dynos/ for the most recent task-* directory whose
manifest.json references this residual via the sentinel — but
prefer reading the id from the start skill's output to avoid
ambiguity.).dynos/task-{id}/manifest.json. Inspect the stage field.stage is "DONE" or "FAILED", polling is over — proceed to
step 6.pending.The skill prose deliberately uses 30-second polling rather than busy waiting. The lifecycle stages take seconds to minutes; polling tighter than every few seconds wastes CPU and risks rate-limiting the filesystem. Polling looser than a minute slows feedback for the operator. 30 seconds is the recommended cadence.
Once the spawned task is at a terminal stage (or polling has been abandoned because the manifest is permanently missing), re-read the queue:
data = lib_residuals.load_queue(lib_residuals.queue_path(root))
row = next((r for r in data["findings"] if r["id"] == residual_id), None)
Then dispatch on row["status"]:
| Observed status | Meaning | Output |
|---|---|---|
"done" | cmd_run_audit_finish saw the sentinel and applied the round-trip update on a successful audit. | residual {id} → done |
"failed" | cmd_run_audit_finish recorded a third FAILED attempt (attempts >= 3 after the third failure). | residual {id} → failed |
"pending" and attempts < 3 | The spawned task FAILED but more attempts are allowed; cmd_run_audit_finish set status back to "pending" for the next run-next to retry. | residual {id} → pending |
"in_progress" | The round-trip update did NOT happen. The orchestrator crashed, the manifest never reached DONE/FAILED, or cmd_run_audit_finish did not see the sentinel. The attempt is sacrificed. | Revert the row to "pending" WITHOUT incrementing attempts (call update_row_status(root, id, "pending"); do NOT touch attempts). Print residual {id} → pending (round-trip missing, reverted). |
Exit 0 in all four cases. The queue is now in a consistent state for
the next run-next invocation.
The "still in_progress at terminal" case represents a real failure mode: a spawned residual task whose lifecycle finished but whose queue row was never updated. Possible causes include:
manifest stage = DONE and
the cmd_run_audit_finish round-trip write.cmd_run_audit_finish did not
recognise the sentinel (e.g. malformed first line — but
extract_residual_id is anchored, so this is rare).In all these cases, the attempt is sacrificed — we do NOT increment
attempts further on revert, because the original update_row_status
in step 3 already incremented it once. (Steps 4 and 5 do not touch
attempts.) Reverting status to "pending" lets the next
run-next retry. After three sacrificed
attempts the row will not be eligible for selection (attempts < 3
guard in select_next_pending), and an operator must inspect it
manually.
To revert without touching attempts, call:
lib_residuals.update_row_status(root, residual_id, "pending")
update_row_status is documented to change ONLY status (and
last_attempt_at on the in_progress transition). It does not modify
attempts. The increment that already happened in step 3 stays.
| Condition | Output (exact) | Exit |
|---|---|---|
list: queue file missing or findings == [] | dynos-work: no residuals queued | 0 |
list: queue file has rows but all are done (default view) | dynos-work: no active residuals (use --all to show done rows) | 0 |
list: rows present | one tab-separated line per row (id, status, attempts, source_auditor, title[60], created_at) | 0 |
run-next: queue file missing or findings == [] or no eligible row | dynos-work: residual queue empty | 0 |
run-next: another row already in_progress | dynos-work: residual in_progress — run-next already active | 0 |
run-next: spawned task → row.status == "done" | residual {id} → done | 0 |
run-next: spawned task → row.status == "failed" | residual {id} → failed | 0 |
run-next: spawned task → row.status == "pending" and attempts < 3 | residual {id} → pending | 0 |
run-next: spawned task → row.status still "in_progress" | revert to "pending", print residual {id} → pending (round-trip missing, reverted) | 0 |
run-next: step-3 atomic write failed | error message to stderr | non-zero |
Plain text only. No JSON wrapping. No ANSI colour. The list output
must be parseable by awk '{print $1}' style scripts (tabs separate
fields).
/dynos-work:audit run you want to drain newly-surfaced
proactive residual findings.run-next until the
output is dynos-work: residual queue empty.list)./dynos-work:start is the execution path; it
enforces the full lifecycle and the audit gate.manifest.json
once and assuming completion is INCORRECT.proactive-findings.json outside of
lib_residuals.update_row_status (or an equivalent LOCK_EX atomic
write). Concurrent audits writing to the queue will clobber a
non-atomic write.