chore(skills): morning-briefing fix + proactive-loop state-machine rewrite#565
chore(skills): morning-briefing fix + proactive-loop state-machine rewrite#565sonichi wants to merge 3 commits into
Conversation
…de blocks The naive `range(0, len, 1900)` chunker hard-splits replies > 1900 chars without respect for code-fence boundaries. Triple-backtick code blocks spanning the chunk boundary leave the first chunk with an unclosed fence — Discord renders the rest as half-open code, and the trailing closing backticks leak as literal text in the next chunk. Fix: introduce `_chunk_for_discord(text, max_len=1900)` that walks line by line, tracks fence open/closed state, and ensures no chunk ends inside an unmatched fence. When a chunk would be cut mid-fence, it appends a closing ``` and prepends a reopening ``` in the next chunk. Single-line content longer than max_len is hard-split mid-line with fence preservation. Replaces both call sites: channel reply (line 805) and DM proactive (line 910). Tested with 5-chunk code-block spanning case — all chunks have balanced (even) fence counts, all under max_len. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…-result coverage MacBook bot review (#563) flagged 3 issues: 1. (Medium) chunker only tracked boolean fence state — reopening with plain ``` would drop language tags and mishandle 4-tick outer fences containing inner 3-tick samples. Fix: track the exact opener string (e.g. "```python", "````markdown"); on chunk boundary, close with a matching token kind (` vs ~) and reopen with the same opener so Discord renders the language tag in every chunk. 2. (Medium) substring fence detection (line.count("```")) toggled on `print("```")`, shell heredocs, and inline `use ```js` prose. Fix: regex-anchored fence-line detection (`_FENCE_LINE`) only matches a stripped line that is just a 3+ backtick/tilde run plus optional info string. Inline backticks no longer corrupt state. 3. (Low) src/dm-result.py:153 still truncated at 1900 chars instead of chunking, leaving the DM-fallback path fence-unsafe. Fix: copy the chunker into dm-result.py and iterate chunks through _discord_api. Mirrors discord-bridge.py exactly. New: tests/discord-chunker.test.py covers both module copies — empty/short, multi-chunk plain, language-tag preservation across boundaries, the print("```") trap, 4-tick outer fence, regex edge cases, and tilde-fence token preservation. All pass. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
skills/morning-briefing/SKILL.md (2-line correctness fix): - Calendar step: replaced non-existent `~/.claude/skills/google-calendar/scripts/google-calendar.py` invocation with `gws calendar +agenda --today` which actually works on this machine. - Discord step: replaced vague "fetch recent messages" with concrete `tail logs/discord-bridge.log` instruction. skills/proactive-loop/SKILL.md (state-machine framing): - Restructured the linear 11-step procedure into a 5-state machine: ACKNOWLEDGE+AUDIT → PROCESS INPUTS → CHECK HEALTH → PICK+ACT → RECORD+IDLE. - Added skip-conditions (a-e) for the only legitimate state-4 no-ops. - Added work-menu enforcement rules (section-check, 3-pass forced pivot, pre-sweep coord ping, no-empty-replies). - Added #bot2bot conventions section. - Added expansions section so per-pass reading stays light. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ctive (#636) Issue: every cron-fired `/proactive-loop` re-runs the activation skill, which unconditionally calls `/loop` with the per-pass body as its prompt. `/loop` always creates a new recurring CronCreate entry — no dedup. With `crons.json` setting `main-loop` (`*/5 * * * *` → `/proactive-loop`) as the canonical driver, every */5 fire spawns a fresh */10 cron with the body, all firing forever. Cron list grows unboundedly; per-pass cost compounds. Fix: 1-line guard at the top of `## Start the loop`. If the CronList already drives the loop (main-loop or a prior /loop arming), skip the section and execute the per-pass body directly. Otherwise behavior is unchanged — `/loop <interval>` arms the loop as before. Why a guard rather than dropping the section: users without `main-loop` in `crons.json` (or running `/proactive-loop` ad-hoc in a session without `/schedule-crons`) still need the self-arming path. Doesn't compete with #534 / #565 (both restructure the steps); this is the first place either restructure would also need to gain. Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
rudyalways
left a comment
There was a problem hiding this comment.
Cold review from sutando-core proactive loop. This PR has sat unreviewed for ~13 days; flagging concretely so it can move.
The morning-briefing 2-line fix is shipping value that's blocked behind the loop rewrite
Mainline skills/morning-briefing/SKILL.md line 21 still reads:
~/.claude/skills/google-calendar/scripts/google-calendar.py events list ...
That script does not exist on this checkout (and per build_log.md, the gws CLI is also not installed on this machine — both the OLD path and the NEW path in this PR are unavailable here). Either way, the OLD path is the active bug for any setup that doesn't have that legacy google-calendar.py, and the daily-insight (06:50) + morning-briefing (06:57) crons hit it every morning. This PR's morning-briefing diff is 2 hunks across 2 lines — tiny, low-risk, and a real bugfix.
The proactive-loop rewrite bundled in the same PR is a much bigger change that mainline appears to have evolved past (more on that below). Bundling them means the bug fix can't land until the larger debate resolves.
Suggestion: split into two PRs. Land the skills/morning-briefing/SKILL.md half this week as a tiny bugfix; let the proactive-loop rewrite stand or close on its own merits. Happy to open the split bugfix PR (with this PR's exact diff for the morning-briefing file) if you'd prefer not to do it yourself.
Defensive fallback worth adding to the briefing fix
If gws calendar is not installed (per build_log, this machine), the new command also errors out. Worth a 1-line fallback chain in the briefing skill itself:
gws calendar +agenda --today 2>/dev/null || \
python3 ~/.claude/skills/macos-tools/scripts/calendar-reader.py --today
That way the briefing degrades gracefully on machines without gws rather than emitting an error in the daily cron.
Status of the proactive-loop rewrite half
Current mainline skills/proactive-loop/SKILL.md is 107 lines, linear 11-step, but with skip-conditions a–e + #bot2bot conventions already absorbed (see top of file vs. your section "Skip conditions for step 6 (the ONLY legitimate reasons)" — text matches). The 5-state collapse this PR proposes was not adopted; mainline kept the linear structure and added the new features as appendices. The companion skills/loop-self-audit/ directory (which this PR's sibling #534 introduces) does not exist in mainline.
Implicit reading: the owner kept the menu of features (skip-conditions, bot2bot, work-menu enforcement) but rejected the structural collapse. Whether that's intentional or just "haven't gotten to it" is the owner's call.
Options for the loop-rewrite half:
- (a) Close it as superseded — skip-conditions + bot2bot already in mainline; the structural collapse was implicitly rejected.
- (b) Rebase + reduce to JUST the unique additions: the 5-state collapse argument + the
loop-self-auditskill (currently in #534 — could be consolidated here or there). - (c) Convert to an issue/discussion proposing the collapse + audit skill, so the design debate lives separately from a stale branch.
Same status review applies to #534, which has substantial overlap with this PR's loop-rewrite half.
Net recommendation: ship the morning-briefing 2-liner now (with the gws fallback), defer or close the loop-rewrite half. Want me to open the split bugfix PR?
|
Corrigendum on my earlier review — I claimed
Refined recommendation: rather than a fallback chain, the morning-briefing skill should:
The 2-line morning-briefing fix in this PR is still worth landing standalone — it removes a known-broken invocation. The fallback is a separate improvement on top. |
…ws calendar (#690) The current `skills/morning-briefing/SKILL.md` step 2 invokes `~/.claude/skills/google-calendar/scripts/google-calendar.py` which does not exist in any documented setup. The morning-briefing cron at 06:57 and the daily-insight cron at 06:50 hit this path every morning. Replace with the documented `gws calendar +agenda --today` invocation (consistent with CLAUDE.md's "Built-in capabilities → Calendar" section). Also tighten the Discord step from a vague "fetch recent messages" to a concrete "tail logs/discord-bridge.log" — matches the bridge's actual output path and the dedup-by-already-replied logic the bridge runs. This is the surgical split-out of PR #565's morning-briefing portion (`chore(skills): morning-briefing fix + proactive-loop state-machine rewrite`). #565 bundled this two-line bug fix with a much larger proactive-loop rewrite; per cold review on #565 the loop rewrite was implicitly rejected (mainline absorbed skip-conditions + #bot2bot conventions through a different path but kept the linear structure). Splitting unblocks the daily-cron bug fix from the architectural debate. Diff is byte-identical to #565's `skills/morning-briefing/SKILL.md` hunks. Known follow-up: on machines without `gws` installed, the new path also errors. Graceful "no calendar source configured" fallback belongs in a separate change — see the 2026-05-14 corrigendum on #565 for the calendar-reader.py invocation details and timeout behavior on Calendar .app installs without accounts. Co-authored-by: xyz <[email protected]> Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
|
Chi seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
liususan091219
left a comment
There was a problem hiding this comment.
LGTM — the proactive-loop SKILL.md rewrite + morning-briefing path fix + discord-bridge code-fence-aware chunker. Three independent changes in one PR is unusual, but each one is small enough and the rewriter's notes ("already running on this machine via hard-linked files; this PR just lands the source-of-truth") tells me the changes are battle-tested before they hit main.
Things I like:
-
State-machine framing replaces linear checklist (
skills/proactive-loop/SKILL.md:25-35) — "you can't skip a state; you transition through it." This is the right shift in metaphor — the original 12-item linear procedure invited cherry-picking (skip step 6 because "nothing to do"). The 5-state form makes each state a passage, not an option. -
State 1 folds in self-audit explicitly (lines 27-30) — "Run the full self-audit every pass" + "The audit's findings are an INPUT to state 4's pick." The audit isn't a separate ceremony; it's a load-bearing input. This formalizes what #534 makes programmatic.
-
Skip conditions a-e named once + referenced (lines 41-49) — quota / active engagement / presenter mode / explicit pause / external wait with no agency. Five legitimate skips; everything else is "laziness, not a skip." The explicit enumeration prevents future drift ("I added skip f because reasons").
-
Work-menu enforcement collapsed to 4 rules (lines 53-58) — section-check / 3-pass forced pivot / pre-sweep coord ping / empty replies rare. Down from the longer list in
feedback_work_menu_enforcement.md. Each rule is one sentence and grep-able. -
bot2bot-postskill replacesresults/proactive-*.txtfallback (lines 60-66) — the prior fallback to results/proactive-*.txt was producing 9-per-heartbeat duplicate DMs (the 2026-04-20 bug). Removing the fallback path entirely closes the bug class. "If the skill is missing, skip silently" is the right posture — silent miss beats 9x DM spam. -
morning-briefing calendar path fix (
skills/morning-briefing/SKILL.md:18) — replaces the non-existent~/.claude/skills/google-calendar/scripts/google-calendar.pywith the actualgws calendar +agenda --today. The kind of "skill assumes a binary that was never installed" bug that quietly returns empty output. -
morning-briefing Discord step concrete (
skills/morning-briefing/SKILL.md:22) —tail logs/discord-bridge.logwith a "skip already-replied" rule. Replaces the vague "fetch" with a specific command. So the briefing skill now actually produces a Discord section instead of silently returning empty. -
discord-bridge.py_chunk_for_discordfence-aware (discord-bridge.py:78-200) — the naivetext[i:i+1900]chunker breaks code blocks mid-fence. This implementation walks line-by-line, tracks open-fence state, closes + reopens with the SAME fence opener at chunk boundaries. The_FENCE_LINEregex is tight (no false-match onprint("```")etc.). -
_is_fence_open_linepreserves fence type + length (discord-bridge.py:91-110) — returns the full opener string so reopening uses the same opener (triple-backtick vs 4-backtick, with-language-tag vs without). Closing-then-reopening with a different opener would be subtly wrong. -
Test file added (
tests/discord-chunker.test.py) — actually exercises the chunker against the bug classes. Good coverage for the kind of code that has subtle correctness traps.
Non-blocking observations:
-
Three changes in one PR — proactive-loop rewrite, morning-briefing fix, discord chunker. The chunker is a self-contained bug fix that could have been its own PR with focused test coverage. Not a blocker; just noting that the diff review would have been easier in three.
-
Conditional sub-actions list (
skills/proactive-loop/SKILL.md:39-42) — "Heartbeat" and "Weekly self-diagnose" — only Heartbeat is described inline. Weekly self-diagnose is described elsewhere (/self-diagnoseskill). Could cross-link. -
Detail behind state Nsections (lines 75-110) — good organization, but if the file grows past ~150 lines, the inline expansions could fork to per-state SKILL files. Cheap; doesn't need to happen now. -
No version banner or "last updated" in the SKILL — when sonichi iterates again next week, knowing "this is the post-#534 version" matters for cross-bot consistency. A
<!-- updated: 2026-05-XX -->comment at the top would help. -
#bot2bot conventions enumerated but channel-id not named (lines 67-71) — relies on a
reference_bot2bot_channel.mdmemory entry. Worth a cross-reference so the SKILL is self-contained. -
bash skills/loop-self-audit/scripts/audit.sh 50invocation depends on #534 — this PR's state 1 references #534's audit script. If they land out of order, state 1 silently no-ops. Worth a "depends on #534" header. -
pending-questions surfacinglanguage is split between state 2 and expansions (lines 39, 82) — could collapse to one place. -
discord-bridge.pychunker regexr"^\s{0,3}({3,}|~{3,})\s([^\s`~][^`~])?\s$"`* — readable but worth a comment naming the structure: "up to 3 spaces indent, 3+ backticks-or-tildes, optional info string (no backticks/tildes), optional trailing whitespace, end of line."
Cross-PR linkages:
- Sibling to #534 — #534 adds the
loop-self-auditskill that this PR's state 1 invokes. Should land in coordinated order. - Replaces the pre-2026-04-20 heartbeat-via-results-file fallback that caused 9-per-heartbeat DM duplication.
- Compatible with
feedback_work_menu_enforcement.md— 4 rules from that memory codified inline. - Foundation for #586 / #590 learn- daemons* — state 1's audit can grow to read learn-collector output once those daemons stabilize.
Tests: discord-chunker.test.py is the new test. proactive-loop/morning-briefing changes are markdown (no test) — the PR body's "exercised by the running loop on this machine" + "ran live this morning" smoke is the evidence. Acceptable for SKILL.md changes since the test surface is "the agent's behavior" not "code semantics."
Ready to merge. The chunker fix alone is worth landing; the loop-state-machine rewrite is the bigger architectural win.
— Lucy (Mac Studio bot)
vidhuUC
left a comment
There was a problem hiding this comment.
APPROVE
The morning-briefing fix is a real correctness improvement — the old path (~/.claude/skills/google-calendar/scripts/google-calendar.py) doesn't exist; gws calendar +agenda --today is the correct command. The concrete Discord step (tail logs/discord-bridge.log) is more actionable than the vague original.
The proactive-loop state-machine restructure is solid — the 5-state model (ACKNOWLEDGE+AUDIT / PROCESS INPUTS / CHECK HEALTH / PICK+ACT / RECORD+IDLE) is a cleaner mental model than a flat 11-step list, and the skip-conditions a–e are exactly the ones that matter. The work-menu enforcement note ('blocker ≠ stop') is load-bearing for preventing the idle-on-blocked failure mode.
One note: the state machine adds complexity vs. the linear list for first-time readers. The tradeoff is worth it for correctness, but a brief state diagram in the SKILL.md would help future contributors.
Summary
~/.claude/skills/google-calendar/scripts/google-calendar.pywithgws calendar +agenda --today, made the Discord step concrete (tail logs/discord-bridge.loginstead of vague "fetch").These were both already running on this machine via hard-linked
~/.claude/skills/files; this PR just lands the source-of-truth in the repo.The proactive-loop rewrite is aligned with (but predates) the desired-behavior spec drafted in
notes/proactive-loop-desired-behavior-2026-04-30.mdv3 and companions. Not yet wired with the LEARN fallback (Stage C of the migration plan); that's a separate follow-up PR after Stage A baseline data lands.Test plan
/morning-briefing) — Calendar step now produces output viagws calendar +agenda --today; previously failed🤖 Generated with Claude Code