docs(cookbook): add truthful-agent-lights recipe - #462
Conversation
The Stop classifier and staleness sweeper from discussion umputun#456, in the shape the verdict asked for: a recipe, not an installer change. Replaces the Stop hook's unconditional completed --auto-reset with a classifier over the tool subtrees still alive under the agent, and adds a scheduled sweeper that enforces no-glyph-means-idle in both directions.
The ubuntu runner's shellcheck flags SC2015 on these two lines; the local 0.11.0 and px 0.9.0 do not flag the unrolled form on any version tried. Behavior identical: each condition already meant break/return on failure.
umputun
left a comment
There was a problem hiding this comment.
two worth fixing before this goes in, both in the sweeper. The rest are small. The #456 asks are all there, and the 0.17.0 floor holds against every flag and read-back field the scripts touch.
the row can go green over live work
status-sweep.sh:308-314 - the "work drained" branch never checks that the scan found nothing, it fires whenever work_state came back empty, which includes machinery alive but under the subtree_busy cpu gate. End a turn with rsync, pip install or docker pull in a background tool call and the next sweep posts completed --auto-reset over it. Fix: gate on [ "$total" -eq 0 ], and gate the idle branch at :315 the same way, otherwise the same case falls through and clears the row instead. README:181 needs rewording after that, it describes the outcome as leaving the row dark.
a split session is classified from one pane
status-sweep.sh:85-100 - both panes share AGTERM_SESSION_ID and both have a tty, so sort -n | awk '!seen[$1]++' keeps the lower pid and the whole row is judged from that pane's tree. With an agent in each half, an idle pane A can green pane B's live work. Limits:179 doesn't cover it, the "lower pid is the pane's own agent" reasoning is about a pty-wrapped worker and doesn't hold when both are real pane agents. Fix: keep every pane-attached pid per session and combine the counts before deciding, no state file has to change shape. annotate-claude-replies keys by session and pane for the same reason.
smaller
README.md:173 - "readable by you rather than by everyone on the machine" isn't what the code does, both creation sites use a bare mkdir -p, so 0755/0644 under the default umask. Fix: (umask 077; mkdir -p ...) at both, or drop that half of the sentence. park-and-resume/agt-park.sh:11 does the former.
README.md:68 - the socket wrapper has to append --socket after the subcommand. It's on BasicOptions, not the root command, so the obvious wrapper fails on every call and set_status swallows it with || true. new-session-in-workspace/agt-new-session.sh:32 has the working form.
README.md:144 - "every reason it did nothing at all" isn't true, the no-op branches at :294, :322 and :325 log nothing. The next sentence in the same paragraph states it correctly. Fix: drop the clause.
status-sweep.sh:222 - no pipefail, so the pipeline takes jq's status and jq exits 0 on empty input. A dead socket or a wrong AGTERMCTL wrapper gives an empty window list and a completely empty log, which is the failure a reader is most likely to hit. Fix: log when $windows is empty, ideally off the agtermctl call's own status so a closed agterm doesn't write a line every 120s.
work-scan.sh:10-11 - the legend puts a long-lived ssh in machinery, the classifier at :116-118 puts every ssh/scp in remote or waiting and never in realwork. Limits sends readers into this file to edit the lists. Fix: drop that clause, the remote line below already has it right.
status-sweep.sh:282-286 - the own_turn_live branch reposts active --blink whenever the row isn't blinking, which wipes what machinery-paint.sh just set, since that one posts no --blink. The PreToolUse glyph survives a single sweep. Fix: skip the repaint when the row already carries the work color, but keep the stuck-shape escape.
README.md:49-64 - AGT_LIGHTS_LOG is defined in status-sweep.sh:29, not in lights-common.sh as the table's intro says. Fix: annotate the row the way AGT_MACHINERY_PATTERN is.
README.md:3 and :173 - "clears every glyph nothing backs" is unqualified, but blocked on a session with no recorded agent is never touched. Code is right there, clearing a prompt-for-input it knows nothing about would be worse. Fix: narrow the two claims, the script header at :17-20 has the wording.
shellcheck clean, modes right, index row correct, nothing personal committed. Nothing was executed here per CONTRIBUTING, so the snapshot-wrapper shape is unverified against a live 2.1.235, which Limits already calls out.
your Cookbook checks job has been running about an hour, looks stuck on the zsh apt install rather than on anything in the diff.
The classifier from #456, in the shape the verdict asked for: a cookbook recipe, with the wrapper dependency pinned and stated in Limits, and the sub-state color/shape vocabulary as something the reader opts into.
What it does: replaces the Stop hook's unconditional
completed --auto-resetwith a classifier over the tool subtrees still alive under the agent — a dispatched worker keeps the pulse, machinery gets the work tint, a lock queue reads as queued rather than working — and adds a launchd sweeper that enforces "no glyph means idle" in both directions: it clears a claim no process backs and re-lights a row that is quietly working. Stuck detection reads transcript progress, never hook silence, per the discussion.Points from the discussion, so review can check them off:
rgover a big tree reads as waiting,tail -fon a build log as work), with the lists placed where a reader edits them.session status --shapeplus thestatusShaperead-back ontree(feat(status): add selectable agent status glyph shapes #292); everything else the recipe calls is older.AGTERM_*writes the spawner's state files and paints its row — Limits states exactly which files and which self-heal. That inheritance is the stock-package defect from the Truthful agent lights: classify live background work at turn end, and expire stale `active` glyphs #456 guard thread (fix proposed in fix(agent-status): guard the Claude hooks against a spawned agent repainting the spawner's row #461), not something a recipe can fix.$HOME/.local/state, andXDG_STATE_HOMEis deliberately ignored: the hooks see a shell-rc export, launchd does not, and honoring it would split the two halves into different directories on exactly the machines that set it. The reason is in the file where the variable is set.On the "workflow you actually run" bar: the scripts these were distilled from have been driving ~20 concurrent agent sessions on the machine this was written on since the #456 filing. The recipe copies themselves were exercised against stub suites for every turn-end decision path and sweeper branch, plus a live read-only pass — the classifier over 14 live sessions across ten process topologies, the sweeper parsing the real tree with its writes recorded instead of issued. What has not run end to end is the recipe copies wired in as a session's actual hooks; saying that here rather than having it discovered.