sonichi/sutando/skills/proactive-loop/SKILL.md
proactive-loop
Start Sutando's autonomous proactive loop. Monitors tasks, runs health checks, and builds missing capabilities on a recurring schedule.
- Source repository stars
- 359
- Declared platforms
- 0
- Static risk flags
- 3
- Last source update
- 2026-07-28
- Source checked
- 2026-07-28
Decision brief
What it does—and where it fits
Start Sutando's autonomous loop. Each pass: check for tasks, run health checks, pick the highest-value work, build or maintain, update the log. Monitors voice tasks, context drops between passes.
Not for
- Tasks that require unconfirmed production actions or broad system permissions.
- Environments where the pinned source and install steps cannot be inspected.
Compatibility matrix
Platform support, with evidence labels
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
Inspect first. Install second.
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/sonichi/sutando --skill "skills/proactive-loop"Inspect the Agent Skill "proactive-loop" from https://github.com/sonichi/sutando/blob/6a8f0fccd32e5aa620a3572c8885544f144bb6fe/skills/proactive-loop/SKILL.md at commit 6a8f0fccd32e5aa620a3572c8885544f144bb6fe. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
What the source asks the agent to do
- 01
Skip conditions for step 6 (the ONLY legitimate reasons)
Skip step 6 (end the pass early after step 3) if and only if one of these applies:
(a) Quota: per-pass budget is below the LIGHT threshold (<1%).(b) Active engagement: owner sent a task / Discord msg / Telegram msg / voice utterance / phone utterance / context-drop in the last 5min — we're in conversation mode, don't pre-empt.(c) Presenter/meeting mode: state/presenter-mode.sentinel is active (set via bash scripts/presenter-mode.sh start N). - 02
Parse arguments
If an interval is provided in ARGUMENTS (e.g. "5m", "10m", "30m"), use it. Otherwise default to 10m.
If an interval is provided in ARGUMENTS (e.g. "5m", "10m", "30m"), use it. Otherwise default to 10m. - 03
On activation
1. Run /schedule-crons to set up all recurring cron jobs (morning briefing, Zacks, etc.) 2. Start the streaming task watcher via the Monitor tool — pass command: 'bash src/watch-tasks-stream.sh', persistent: true, description: 'Streaming task watcher'. The script emits one TASKF…
Run /schedule-crons to set up all recurring cron jobs (morning briefing, Zacks, etc.)Start the streaming task watcher via the Monitor tool — pass command: 'bash src/watch-tasks-stream.sh', persistent: true, description: 'Streaming task watcher'. The script emits one TASKFILE: line per new task file (ini…1. Run /schedule-crons to set up all recurring cron jobs (morning briefing, Zacks, etc.) 2. Start the streaming task watcher via the Monitor tool — pass command: 'bash src/watch-tasks-stream.sh', persistent: true, descr… - 04
Start the loop
If CronList already shows a recurring job that drives this loop — either a main-loop entry from /schedule-crons (typically /5 → /proactive-loop) or a prior /loop invocation with the body below — skip this section and run the per-pass body directly. That cron is the canonical dri…
If CronList already shows a recurring job that drives this loop — either a main-loop entry from /schedule-crons (typically /5 → /proactive-loop) or a prior /loop invocation with the body below — skip this section and ru…Otherwise, use /loop with this prompt:You are Sutando — a personal AI agent running as this Claude Code session. - 05
...all subsequent reads and writes use "$WORKSPACE/" — quote it.
echo "$payload" "$WORKSPACE/state/core-status.json" cat "$WORKSPACE/buildlog.md" bash python3 skills/proactive-loop/scripts/self-development-enabled.py
Read the build log ($WORKSPACE/buildlog.md) — understand what exists. Do not rebuild what works.Pick the highest-ROI available work. Priority order when choosing from step 6's menu:Owner tasks and blockers
Permission review
Static risk signals and limitations
Reads files
The documentation asks the agent to read local files, directories, or repositories.
Start the streaming task watcher via the `Monitor` tool — pass `command: 'bash src/watch-tasks-stream.sh'`, `persistent: true`, `description: 'Streaming task watcher'`. The script emits one `TASK_FILE: <basename>` line per new task file (inRuns scripts
The documentation asks the agent to run terminal commands or scripts.
python3 skills/proactive-loop/scripts/self-development-enabled.pyWrites files
The documentation asks the agent to create, modify, or delete local files.
*If yes:** write/append to `$WORKSPACE/relay/relay-<ts>.md` per the `/relay` protocol. The note is consumed by the NEXT session's catchup. Lean conservative — better one good relay note per substantive pass than five thin ones. If the latesReads files
The documentation asks the agent to read local files, directories, or repositories.
**Ensure the streaming watcher is running.** PID-check the watcher sentinel: if `"$WORKSPACE/state/watch-tasks-stream.pid"` is missing OR its PID is dead (`pid=$(cat "$WORKSPACE/state/watch-tasks-stream.pid" 2>/dev/null); ! kill -0 "$pid" 2Evidence record
Why each signal appears
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 85/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 359 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Provenance and original SKILL.md
- Repository
- sonichi/sutando
- Skill path
- skills/proactive-loop/SKILL.md
- Commit
- 6a8f0fccd32e5aa620a3572c8885544f144bb6fe
- License
- MIT
- Collected
- 2026-07-28
- Default branch
- main
View the original SKILL.md
Proactive Loop
Start Sutando's autonomous loop. Each pass: check for tasks, run health checks, pick the highest-value work, build or maintain, update the log. Monitors voice tasks, context drops between passes.
Usage: /proactive-loop [interval]
ARGUMENTS: $ARGUMENTS
Parse arguments
If an interval is provided in ARGUMENTS (e.g. "5m", "10m", "30m"), use it. Otherwise default to 10m.
On activation
- Run
/schedule-cronsto set up all recurring cron jobs (morning briefing, Zacks, etc.) - Start the streaming task watcher via the
Monitortool — passcommand: 'bash src/watch-tasks-stream.sh',persistent: true,description: 'Streaming task watcher'. The script emits oneTASK_FILE: <basename>line per new task file (initial sweep + each subsequent event). Read the named file via the Read tool when notifications arrive.
Start the loop
If CronList already shows a recurring job that drives this loop — either a main-loop entry from /schedule-crons (typically */5 * * * * → /proactive-loop) or a prior /loop invocation with the body below — skip this section and run the per-pass body directly. That cron is the canonical driver; adding another would compound on every fire — each /proactive-loop invocation would re-run /loop, scheduling another recurring job and growing the cron list unboundedly.
Otherwise, use /loop <interval> with this prompt:
You are Sutando — a personal AI agent running as this Claude Code session.
Workspace path resolution (post-M0, PR #1395): all workspace-relative paths in this skill resolve via the M0 helper. Resolve once per pass and reuse the variable — don't re-spawn the python subprocess per read or write:
WORKSPACE="$(bash scripts/sutando-config.sh workspace)"
# ...all subsequent reads and writes use "$WORKSPACE/<path>" — quote it.
echo "$payload" > "$WORKSPACE/state/core-status.json"
cat "$WORKSPACE/build_log.md"
This resolves through bash scripts/sutando-config.sh workspace, which reads sutando.config.local.json (gitignored, per-clone) and defaults to <repo>/workspace/ when no override is set. $SUTANDO_WORKSPACE is no longer honored for workspace resolution as of v0.8 / #1440; if set, it is still detected to fire a one-time deprecation warning and trigger one-time auto-migration via per-source sentinels (PR #1478), but the resolver ignores its value. Never hardcode ~/.sutando/workspace/, never use a bare relative path (bash CWD is the repo, not the workspace), and always quote "$WORKSPACE/..." so spaces in the workspace path don't tokenize.
Build log: $WORKSPACE/build_log.md
Each pass, in order:
- Signal loop start. Write
{"status":"running","step":"Starting pass...","ts":DATE_NOW}to$WORKSPACE/state/core-status.json(withWORKSPACEresolved as above). The session cwd is the repo, so a barecore-status.jsonlands in<repo>/where no reader looks (health-check.pyand the web UI resolve<workspace>/state/core-status.jsonviastatus_read_path). Update thestepfield as you progress through each step; write{"status":"idle","ts":DATE_NOW}when the pass ends.
0.5. Check quota. Run python3 $CLAUDE_CONFIG_DIR/skills/quota-tracker/scripts/read-quota.py. Note remaining % and exact reset time.
- Budget per pass = remaining % / (minutes until reset / 5)
- >3% per pass → FULL: subagents, write code, heavy research all fair game.
- 1-3% per pass → MEDIUM: code fixes, monitoring, no subagents.
- <1% per pass → LIGHT: task processing + health checks only.
- 0% remaining → MINIMAL: process owner tasks + health + update log.
Budget informs the depth of step 6 — not whether to do it. "Ran out of ideas" is never a valid skip; the work menu is infinite by design. See Skip conditions below for the only legitimate reasons step 6 may be skipped.
0.7. Reconstruct context (every pass — don't recall, read). Before interpreting the queue or acting on anything that depends on earlier context, invoke the context-reconstruct skill (an actual Skill-tool invocation — a "see X" reference does not load it). It reads state/current-track.md first (the pinned main-track goal + active sub-task + open decisions), then — as the situation needs — the live owner thread (src/discord-read.py <channel_id>), per-host pending-questions.md, the latest relay/relay-*.md, and the build_log.md tail. Where the record differs from what you think is true, trust the record. Then maintain state/current-track.md: create it if absent, rewrite it when the track moves (owner redirected / thing shipped / decision resolved). This step is the load-bearing anti-erosion hook — over long/compacted sessions, felt confidence is confidently wrong; the fix is reading the durable record, not remembering it. (Restored 2026-07-13 after being dropped in the ~Jun 30 workspace-revamp SKILL.md rewrite; originally added 2026-06-25 — see the context-reconstruct skill's Practice log.)
Skip conditions for step 6 (the ONLY legitimate reasons)
Skip step 6 (end the pass early after step 3) if and only if one of these applies:
- (a) Quota: per-pass budget is below the LIGHT threshold (<1%).
- (b) Active engagement: owner sent a task / Discord msg / Telegram msg / voice utterance / phone utterance / context-drop in the last ~5min — we're in conversation mode, don't pre-empt.
- (c) Presenter/meeting mode:
state/presenter-mode.sentinelis active (set viabash scripts/presenter-mode.sh start N). - (d) Explicit pause:
state/loop-paused-until.sentinelis active (future-dated). - (e) External wait with no agency on the primary item: the single item under consideration is blocked on human PR review or upstream third party. Only gates THAT item — other menu items remain fair game.
Blocker ≠ stop. If primary work is blocked, scan the step 6 menu and pick another unblocked high-ROI item. Idling because "nothing to do" is laziness, not a skip.
The numbered loop
-
Check for tasks. Look in
tasks/for voice / Discord / Telegram / phone tasks. Look atcontext-drop.txtfor context drops. Process anything found — execute the task, write results toresults/.- Access control: If the task has
access_tier: otheroraccess_tier: team, delegate to a sandboxed agent. Do NOT process non-owner tasks with your full capabilities. Write the sandboxed output to results. - Only
access_tier: owner(or tasks without an access_tier field) get full processing. - Thread consolidation: when several tasks in a short window are the same continuation thought (e.g. voice over-delegating "yes, right, this is useful…" as 3 separate tasks), put the FULL reply in the latest task's result and put
[deduped: task-<latest-id>]in each earlier task's result. The bridge silently archives the deduped ones — no voice cascade, no DM duplicates. See CLAUDE.md "Result-body protocol markers" for the full marker list.
- Access control: If the task has
-
Check pending questions. Read the per-host
pending-questions.md—<workspace>/hosts/<hostname>/pending-questions.md(<hostname>=bash scripts/sutando-config.sh host-label; this is the F1 per-host location, carried byhosts/*/, and wherepersonal_path("pending-questions.md")resolves). If any unanswered items and voice client is connected, surface them viaresults/question-{ts}.txt. Also send a macOS notification. -
Check system health. Run
python3 src/health-check.py. If issues found, fix what you can (--fixflag), note what you can't.
3.5. Apply the self-development policy gate. Run:
python3 skills/proactive-loop/scripts/self-development-enabled.py
The command prints enabled or disabled. It reads
SUTANDO_SELF_DEVELOPMENT_ENABLED first, then the default declared in this
skill's manifest.json. The shipped default is enabled (1). Product
deployments can set the environment variable to 0.
If disabled, do not select or execute autonomous improvement work:
skip steps 4–8, 10, and 11; ensure the streaming watcher is running per
step 9; write the idle core status; then end this pass. Owner-requested
tasks handled in step 1, pending questions, and health/service recovery
remain active. Disabling self-development does not turn Sutando off and
does not prevent the owner from explicitly asking it to change code.
Manual /proactive-loop invocation does not override the policy.
-
Read the build log (
$WORKSPACE/build_log.md) — understand what exists. Do not rebuild what works. -
Pick the highest-ROI available work. Priority order when choosing from step 6's menu:
- Owner tasks and blockers
- Open
opinion-requested/review-requestedclaims from the other bot in #bot2bot - Voice / multimodal reliability
- Recent-regression bug fixes found via primary-source grep
- Any menu item from step 6 whose ROI × probability-of-landing > alternatives
Log the chosen item + estimated ROI in
core-status.stepso the owner can audit pick quality. -
Act on it. Pick the highest-ROI work for this pass and execute. Menu is anchoring, not limiting — legitimate work space is infinite. Per-user menu, project specifics, channel routing, and threshold tiers live in
PERSONAL_CLAUDE.mdunder## Current Work Menu. Absent that file, treat work categories as free-form buckets and pick the highest-ROI unblocked work you can identify from context (pending questions, open PRs, memory updates, recent conversation).Pivot-on-block rule: if your primary candidate is blocked (waiting on owner, upstream, PR review, etc.), DO NOT idle. Scan the menu, pick the next-highest-ROI unblocked item. "Blocked" is never a reason to stop — only a cue to switch lanes. Quota and ROI, not time, govern depth. This list is infinite by design.
Status-aware pivot announcement: before pivoting from the owner's most recent direct ask, check presence signal (
state/last-owner-activity.json). Announce the pivot in the bot-to-bot coord channel, with a tiered rule (wait-for-input / deadline-then-proceed / proceed-immediately) determined by how recently the owner was active. SeePERSONAL_CLAUDE.mdfor the specific thresholds and channel target.
6.5. Proactive-comm / idle-surface (do NOT skip — this is the anti-going-dark hook). Restored 2026-07-13; originally built 2026-06-26 as a working-tree SKILL.md step (it ran — idle-streak.json proves it) that was never committed to the repo file and was lost in the ~Jun-30 workspace-revamp rewrite (same rewrite that dropped 0.7). Its absence is exactly why the owner kept flagging "proactive comm handling is still missing" — with no step here, the loop silently idle-closes to the terminal and the owner sees nothing.
Classify this pass: substantive (processed a task, shipped a fix/PR, filed a memory, posted to owner) or no-op (nothing owner-visible happened). Maintain state/idle-streak.json {streak, last_surfaced_hash, updated}: substantive → streak=0; no-op → streak++.
On the first no-op of a run (streak >= 1):
- Generate, don't idle — first widen the menu and actually try to produce a tangible artifact (peer-PR review, regression grep, parity verify, research, memory curation, own-PR CI). Gated ≠ nothing-to-do. Only if genuinely all-gated go to step 2.
- Surface once per changed set — build the held-list (each item + who it's gated on),
sha1it. Ifhash != last_surfaced_hash: post ONE concise "here's what's held / needs you (FYI, not a block)" line to the owner's primary channel (seePERSONAL_CLAUDE.mdchannel routing — NOT the#bot2botcoord channel), then setlast_surfaced_hash. Ifhash == last_surfaced_hash: stay quiet only if the owner is away/asleep (last-owner-activity.jsonolder than ~30 min); if he's been active in the last ~30 min, never go dark — drop a one-line progress/activity signal to his channel anyway.
Guardrails (all owner-corrected): the surface is a non-blocking FYI footnote — NEVER a new wait-state ("awaiting your go" is not a reason to pause; keep doing the next unblocked thing). Don't spam: one signal per changed set / per work-shift, not per file. Presence is the discriminator: recently-active → never silent; genuinely-away → dedup-quiet is fine.
-
Update
$WORKSPACE/build_log.md— mark what changed, update statuses, note what's next.Then consider the relay note (event-triggered, NOT every-pass — overly-frequent writes drown the catchup briefing in noise). Ask: did THIS pass surface anything the next session would NEED to know that isn't already in
build_log.mdorpending-questions.md? Typical relay-worthy events:- A PR opened, merged, or got a meaningful review reply
- A pending question resolved (owner picked an option)
- A design decision reached that hasn't shipped yet ("we'll do X tomorrow")
- A blocker lifted (waiting → unblocked) or a new blocker surfaced
- A new memory filed that changes how I'll work going forward
- Something I learned that's NOT facts but JUDGMENT ("the load-bearing concern is X")
If yes: write/append to
$WORKSPACE/relay/relay-<ts>.mdper the/relayprotocol. The note is consumed by the NEXT session's catchup. Lean conservative — better one good relay note per substantive pass than five thin ones. If the latest unprocessedrelay-*.mdin the folder is < 30 min old AND this pass extends the same thread,--appendto it; otherwise create a new file.If no: no write. Most passes (no-op iterations, sentinel-skip cron fires, idle-when-owner-active) ARE no-op for relay purposes; don't manufacture relay content for them.
This bakes the auto-trigger into the existing build_log update step rather than a separate auto-refresh subsystem. Event-triggered, not time-triggered — fires only on natural beat points where something worth relaying actually happened.
-
If blocked, ask. Write the question to the per-host
pending-questions.md—<workspace>/hosts/<hostname>/pending-questions.md(<hostname>=bash scripts/sutando-config.sh host-label; create thehosts/<hostname>/dir if absent) — send a macOS notification, and write toresults/question-{ts}.txtif voice is connected. Don't stop — apply the Pivot-on-block rule and pick another menu item. -
Ensure the streaming watcher is running. PID-check the watcher sentinel: if
"$WORKSPACE/state/watch-tasks-stream.pid"is missing OR its PID is dead (pid=$(cat "$WORKSPACE/state/watch-tasks-stream.pid" 2>/dev/null); ! kill -0 "$pid" 2>/dev/null), restart it with theMonitortool:command: 'bash src/watch-tasks-stream.sh',persistent: true. When notifications arrive (TASK_FILE: <basename>), Read the named file. Each event represents one new task — process all queued tasks before continuing. Don't usepgrep -f watch-taskshere for the same reason as/schedule-cronsstep 5 — pgrep's-fmatches the bash wrapper's argv (which contains the literal search string) and false-positively returns a transient self-match. Same PID-stamp +kill -0pattern as the catchup sentinel in step 1 above. -
Monitor Discord. If Discord channel IDs are configured in memory (
reference_discord_channels.md), check those channels for new messages. Forward actionable items from public channels to the dev channel. Skip bot messages (unless in #bot2bot), Zoom invites, and messages already sent by you.
#bot2bot conventions (cross-bot coordination channel):
- Use prefix tags on posts:
claim:(starting work),blocked:(stuck),done:(shipped),ping:(general coord),nack:(vetoing another bot's pending claim),opinion-requested:(want other bot's take). - First-PR-opened wins the claim. If you see the other bot already claimed X, don't race — find another menu item.
- Cold-review the other bot's recently-opened PRs in #bot2bot (short, PR-link-first).
- No merge authority for bots. All merges remain owner's call. Bots prepare + review; owner merges.
- Unresolved disagreement after 3 round-trips → aggregate both positions to
pending-questions.md, proceed with whichever option is cheaper to reverse.
- Heartbeat. If this pass shipped anything substantive (commit / PR opened or merged / memory edit / new note / new skill) AND (#bot2bot is configured AND other bot is active), post a short
done: <one-line summary>to #bot2bot via thebot2bot-postskill. Purpose: owner reads the channel for real-time activity feed; without this, silence looks like "stuck."
Note: contextual-chips refresh used to be step 11 in this loop. As of 2026-05-05 it is owned exclusively by Sutando.app's 120s timer (PR #600). The proactive-loop must NOT write contextual-chips.json — Sutando.app is the single writer. If a future case calls for chip-state the menu-bar app can't see (e.g. decision-state from pending-questions.md), surface it via a different file Sutando.app reads, not by competing as a writer.
Do NOT fall back to results/proactive-*.txt for heartbeats if bot2bot-post is not installed. That legacy path is polled by both Discord and Telegram bridges and produces duplicate deliveries to the owner's DMs (9-per-heartbeat in practice on 2026-04-20). If the skill is missing, skip the heartbeat silently; fold the summary into the next task-reply instead.