Loops
You already run things on a clock. Cron lines and systemd timers drive your maintenance, CI fires on a schedule, and lately a while true; do claude -p "fix the next…
You already run things on a clock. Cron lines and systemd timers drive your maintenance, CI fires on a schedule, and lately a while true; do claude -p "fix the next failing test"; sleep 900; done is holding a terminal somewhere. The instinct behind that loop is right: state the goal once and let the loop iterate instead of you re-prompting by hand.
But cron and a hand-rolled while loop only know one move: start a fresh process and walk away. That scheduled claude -p is invisible, so it hangs in silence the first time it stops for a permission prompt. It fires whether or not there is anything to do, so a whole agent turn goes on re-checking a world that has not changed. And it cannot hand work to an agent you already have running; every run starts from an empty context.
rimz loop is the same clock with the room behind it. A task is a name, a trigger, and one action:
--agent <kind>starts a fresh agent that runs the prompt once and cleans up.--wait @handledelivers the prompt to an agent you already have running, so the work resumes in that conversation with all of its context.--check <cmd>runs a shell command. Alone it is the whole task, a scheduled command with a run history. In front of--agentor--waitit is a gate: the command runs first, and a turn is spent only on its result, so a scheduled agent never starts just to find nothing to do.
--verify <cmd> is the gate on the other side of an --agent turn: it runs afterwards and re-prompts the same session until the command passes.
When a task starts or wakes an agent, that turn runs in the room like any agent you started by hand: a live card in the sidebar, a permission question that routes to you instead of hanging the job, and a line in the run log that rimz loop show summarizes and rimz loop logs prints in full. A check-only task is a plain shell command, so it gets the run-log line and nothing else.
The trigger is a clock or a signal. A clock repeats only when --every or --cron says so; a bare time (--at 07:00 or --in 30m) fires once and the task removes itself. A signal subscription keeps listening until --once retires it.
Schedule a fresh turn
--agent starts a new agent for the turn. On the schedule you set, it opens a fresh pane, runs the prompt once, and cleans up, the same run rimz agents <kind> "<prompt>" -p makes when you type it yourself. Reach for it whenever the work should begin from a clean context: a nightly repository sweep, a Monday dependency check, a scheduled security audit, a recurring status report. Each fire is a new agent with no memory of the last one, which is the point when the job is "do this again against today's tree."
One prerequisite: an agent that a task starts or wakes reports its completion through that agent's own reporting hooks, so rimz loop add refuses a kind whose hooks are not installed, and names the fix:
$ rimz loop add deps --agent codex --every mon --at 09:00 --prompt "Check for outdated dependencies"
error: codex hooks are not installed, so a scheduled turn cannot report completion
install them with `rimz hooks install codex`rimz hooks install claude is enough for Claude. Codex also has to trust the installed hooks once, from inside Codex (/hooks), and the same error names that step when it is the one missing; there is no non-interactive way to grant it. Set up your machine covers hooks for every agent. The examples on this page use Claude and Codex interchangeably; swap in the kind you have set up.
Install the hooks, then run the add from the project the task belongs to:
rimz hooks install claude
cd ~/code/app
rimz loop add deps --agent claude --worktree deps --every mon --at 09:00 \
--prompt "Check for outdated dependencies. Open a PR for the safe minor and patch bumps, and list any majors that need review."added loop task `deps`
action: launches a fresh claude pane in /home/you/code/app
trigger: fires every Mon at 09:00
next fire: 2026-09-07 09:00 (in 2d)
live while a room for /home/you/code/app is open
no room is open there; start one with `rimz start`, or use `rimz loop timer install` to fire without oneThe receipt reads back the action, the schedule in plain words, and, for a clock, the concrete next fire. Two facts in it matter before your first task. A task is bound to the project you ran the command in (--root <path> picks another), and it fires only while a room for that project is open, or while the loop timer keeps time for it. Without a room, rimz loop list says waits for room beside the clock's trigger and tells you whether the timer is active.
That one command replaces a cron entry, the guard script around it, and the terminal you would have left open to watch it. It runs Claude in an isolated worktree every Monday at 09:00, and if a bump breaks the build the agent stops and asks instead of forcing it through: the question reaches you like any other waiting card.
A scheduled turn takes the launch flags you already use with rimz agents, so --worktree, --effort, --system-prompt-file, and a permission mode shape it the same way; --timeout caps the turn and its verify commands. Unsupported --effort or --fast is refused before saving the task, rather than failing on every fire. The --agent value is a kind, a profile, or a kind with its permission-mode suffix, like codex-yolo. What the agent does with the turn is bounded only by the prompt: a one-line check, or the whole fleet that works nights.
The prompt only has to say what the job is. Every agent a rule launches is told the rest alongside it: which rule started it and on what trigger, whether it is one turn in a pane that closes or an agent that stays on, whether you fired it by hand or nobody is watching, and the verify command, turn cap, and timeout it runs under. An unattended run therefore knows to put its result in its final message and to ask only when it cannot go on.
Who keeps time
There is no RimZ scheduler daemon. The clock is the sidebar, the background process that draws the room's column: while a room for the task's project is open, attached or not, it fires due tasks on its regular tick, each through a detached rimz loop run <name>. With no room open, clock tasks wait. To keep time without an open room, opt in once:
rimz loop timer installThat installs one systemd user timer on Linux or launchd agent on macOS. Once a minute it runs a one-off RimZ tick, re-reads every task, and fires only projects without an open room; an open room still wins for its own tasks. On Linux each fire launches through systemd-run --user --scope, in its own process group, so the work outlives the tick that started it. A tick that cannot reach your systemd user session refuses rather than firing, and tells you how to fix it; nothing is lost, and the due tasks fire on the next tick after that.
Know what a timer fire does before you install it. An --agent fire starts a room for that project and runs the turn in it, and a scheduled check-only fire opens the same room before running its command. Both leave the room open, so a 02:00 task means a multiplexer session running on the machine by morning, and that room takes over later occurrences. The timer cannot rescue a task that wakes a live agent (below): that target lives in the room, so a closed room means the session is gone and the task is removed with it. rimz loop timer status shows whether the timer is installed; rimz loop timer remove stops future ticks but does not close rooms already opened.
Only clocks need a timekeeper. A signal task and a rimz wait watcher are fired by the process that emits the signal or runs the command, whether or not a room is open.
Missed time is never made up in bulk. A task a clock has not seen before is armed rather than fired, so adding one sets nothing off; a task that was already armed and came due while nothing kept time fires once when a clock next sees it, then resumes its schedule. A week of missed 02:00 occurrences does not arrive as a week of fires.
Leave an agent in each ready worktree
When a team finishes a lane, you may still want someone watching its PR: a fixer to handle later CI failures or conflicts, then a cleanup agent after merge. Opening those panes by hand for every lane repeats the same handoff. A resident loop makes that one standing rule instead.
With your fixer and yagni agent profiles and reflect-notes command cell configured, these two tasks cover every RimZ-owned worktree:
rimz loop add fixer --when 'team.stage=Done && ci=passed && pr=open' --each-worktree \
--agent 'reflect-notes,fixer' --stay --takeover \
--subscribe ci.failed --subscribe pr.conflicted --prompt 'Watch this PR and repair CI failures or conflicts.'
rimz loop add yagni --when 'pr=merged' --each-worktree \
--agent yagni --stay --prompt 'Review the merged work for unnecessary complexity and propose focused cleanup.'The fixer rule waits until the board is Done and CI passes on an open PR. It takes over the checkout, stopping the agents resting there and waiting while any of them is still busy, then opens the layout in a background tab without moving your focus, and gives its prompt leader standing subscriptions scoped to that checkout. The yagni rule opens an agent after merge. Both leave ordinary agents running on the lane, rather than closing a supervised pane after one turn. A loop's agent joins its checkout's channel, not the channel of whoever fires the rule.
Keep a room running for CI and PR conditions to be observed. Lanes already meeting a rule qualify too. GitHub sees PRs already merged or closed; Gitea needs the room to have observed the checkout's PR while it was open, and room teardown or reset forgets that observation. Each task launches only once per checkout, even if the condition turns false and true again, the room restarts, or you edit the task under the same name. The project root and worktrees RimZ does not own are not included.
Use rimz loop show fixer to see why each checkout is blocked, ready, holding, waiting, or already launched, with current condition readings, live leader status, and armed subscription counts. Checkouts waiting on several terms and launches whose leaders ended fold into counts; rimz loop show fixer --all shows every checkout. Use rimz loop logs fixer for each fire and rimz stats --assists for the launch history. From an owned worktree, rimz loop fire fixer explicitly launches there again; removing the task clears its launch memory. A leader's subscriptions retire when its session ends.
Wake a running agent
An until nc -z localhost 3000; do sleep 1; done loop waits for a service, and tail -F build.log | grep -m1 READY waits for a log line. Both observe the right thing, but run in the agent's shell tool and hold its turn open while nothing needs its attention. A deploy is baking, the agent has nothing to do until it is ready, and the follow-up otherwise falls to you.
A wait inverts that. The agent arms its own alarm, ends its turn, and receives the result in the same conversation.
rimz wait --in 30m
rimz wait --pid 16776
rimz wait --check 'nc -z localhost 3000'
rimz wait --file build.log --grep 'READY'
rimz wait --run 'gh run watch --exit-status'
rimz wait --on fail --run 'cargo test'A timer has to be shorter than 24 hours. Everything else runs in a detached watcher with stdin closed, at the root of the checkout the wait was armed from, linked worktrees included; a polled --check or a --file watch samples once a second. When the trigger fires, the wake message names the command, its exit status, and the elapsed time, then points at a file holding the complete output rather than inlining it: ~/.rimz/ws/<workspace-dir>/out/<you>/<name>.output, the same host path in a sandboxed pane and outside one. rimz gc removes it 7 days after its last write once no watcher runs, and rimz loop logs <name> keeps the last 4 KiB in durable history.
A long command can check in rather than disappearing. Set --check-in to receive one still-running notice with the output file's line count and estimated tokens so far; the command keeps running and its exit verdict follows later. Without an explicit check-in, agents with cache keepalive enabled and a known provider TTL receive keepalive pings instead. Other agents still check in after 30 minutes. rimz wait list shows what is pending, rimz wait cancel <name> drops one row, and rimz wait cancel --all drops the caller's whole list and stops its watched commands with them. The wait reference owns every flag and the exact message each trigger delivers.
Long waits can also outlive the provider's prompt cache, making the eventual wake pay for a fresh cache write. While the agent sleeps, including a parent waiting on its subagents or a team it launched, RimZ sends a facts-only keepalive shortly before the configured cache expiry: “Cache keepalive, no action needed. Waiting on:” followed by a line per wait. Each completed ping turn starts another cache window, until the next ping would start at or beyond harness.cache_keepalive_max (default 6h) after the agent's last request of its own: that last ping ends with Keepalive limit 6h reached: this is the last ping until your next turn., and the agent's next turn of any other kind restarts the clock. On Claude's 1h cache the default allows six pings, on Codex's 30m cache twelve. An idle agent with nothing pending gets the same ping, with the bare first sentence, while a keep-warm horizon set on its profile or role is still running, under the same maximum; that ping leaves the agent's status and quiet time alone. This is best-effort and needs a running sidebar. The configuration guide covers the provider TTL overrides and the switch to disable it.
Only an agent RimZ can identify may arm or cancel its own waits, and a self wait steers that agent the moment it fires unless the agent has a native prompt open. In that case, the wake parks until the next turn boundary after the prompt is answered. A check-in parks the same way and is canceled when the wait delivers its final outcome. For a teammate, a recurring reminder, or a signal, use rimz loop add --wait, whose deliveries land at the target's next turn boundary instead:
rimz loop add check-ci --wait @planner --prompt "Check the run and merge if green" --in 30m
rimz loop add standup --wait @planner --prompt "Post today's plan to #dev" --every weekday --at 09:00
rimz loop add ci-red --wait @me --signal ci.failed@me, or a bare --wait, pins the calling agent; a user shell names a live target. Every delivery pins one exact session and never outlives it: a stop, a lost pane, a closed agent, or a reaped one retires that session's deliveries, and a chain of self waits ends when the agent no longer arms another.
rimz agents restart is the case worth knowing. A restart that launches fresh leaves the old session behind and retires its rows whole, while a restart that resumes the same conversation keeps that session, and a team's declared bindings with it. Either way a wait the agent armed for itself is gone, and the restart line says how many it dropped.
Signals: the room's event bus
A wait on a timer is a guess about when something will happen. A signal is the thing itself. Most of them need no wiring: RimZ emits from what the room already watches. The sidebar polls the forge for every worktree branch's pull request and CI verdict (the #91 badge and the check glyph in each group header), and the reporting hooks already know when an agent or a team changes state. Each of those transitions is a signal you can wait on by name.
| Signal | Fires when | Payload carries |
|---|---|---|
ci.passed, ci.failed | the checks on a commit on a watched checkout settle on success or failure, with or without a pull request | path, branch, repo, plus head and checks_url when the commit is known, and number and url when a pull request exists |
pr.merged, pr.closed | the pull request on one of the room's watched checkouts leaves the open state | path, branch, repo, state, plus number, url, head, and checks_url when known |
pr.opened | a successful probe finds a new open PR after monitoring has already observed the branch successfully | the PR payload above with state: "open" |
trunk.moved | the same trunk ref moves to a different commit | trunk, from, to, repo |
worktree.created, worktree.removed | a managed worktree is successfully created or removed, not reused or rolled back after a failed creation hook | name, branch, path, repo, plus from_pr when present; creation adds base when known, removal adds branch_deleted |
pr.behind, pr.conflicted (GitHub only) | an open PR first falls behind its base or has confirmed conflicts, including when first seen; fires again on a changed PR head while still in that state, not on repeated readings of the same head | the PR payload above with state: "open", plus pr_head, base when known, and behind_by for pr.behind; head remains the local worktree HEAD |
pr.queued, pr.dequeued (GitHub only) | an open PR enters the merge queue, or leaves it while still open (failed queue checks, a manual removal), including the first reading on a worktree the room was already watching; a room that opens on a PR already queued or dequeued gets no signal; fires again only for a later entry or removal, and a merge out of the queue fires pr.merged alone | the PR payload above with state: "open", plus pr_head and base when known; pr.queued adds queued_at, and pr.dequeued adds dequeued_at, GitHub's reason when supplied (failed_checks, manual), and queue_checks_url when GitHub names the queue commit |
agent.started, agent.idle, agent.waiting, agent.failed, agent.ended | one agent's own lifecycle transitions | kind, session, status, errored, plus handle when the card has a name |
team.idle, team.waiting, team.failed, team.ended | a team cohort settles: every member at rest with nothing queued and no wait armed, one member waiting on input, one member's turn failing, or the last member ending | team, instance (forge#feat-x), member, and members with each handle and status |
team.stage | a team hands a stage off, and again when a resumed owner takes its stage back | team, instance, to, owner, by, note, and board, plus from on every flip that did not open the board |
Those six families (ci, pr, trunk, worktree, agent, and team), plus wait for a watched command's own completion, belong to RimZ. rimz events emit refuses every name inside them, so your own signals can never collide with RimZ's.
Use pr.behind to react when the base moves ahead of your PR, and pr.conflicted when a push needs conflict resolution. GitHub may take time to calculate conflicts after a push; RimZ waits for a settled answer before treating that push as a new conflict. Both signals accept --match branch=feature or --match path=/your/worktree, just like pr.merged.
Use pr.dequeued to learn that the merge queue rejected your PR. The queue tests your change on a commit of its own, so a failure there never turns the PR's own checks red and ci.failed stays silent; queue_checks_url, when supplied, opens that queue commit's checks. A dequeued PR stays dequeued until it is queued again, merged, or closed, so an agent that was away can still read it with --when pr.queue=dequeued. A newly opened room receives no signal for a removal that predates its first poll.
Subscribe to one name or to a whole family: --signal ci.failed for the red verdict, --signal 'ci.*' for any verdict. Either subscription observes every name in the family, and what it does with the ones it did not ask for is the useful part. A subscription to ci.failed sees the green build too, records it as a skip, and stays quiet; a branch that goes green never wakes an agent that only asked for red. A signal that fails a --match, from another branch or another cohort, is ignored outright.
rimz loop add ci-red --signal ci.failed --wait, run from an agent pane, keeps listening until you remove it; --once retires it after one delivery, and a subscription has no deadline. A matching signal arrives from @rimz with Type: SIGNAL, a one-line subject such as ci.failed on check-multi #729 @88824c2, and an optional loop prompt; the loop reference lists the subject each signal prints and how to read the retained payload. If the agent is working, delivery parks for its next turn boundary.
RimZ fills in the scope so the ordinary subscription needs no flags: a CI or PR subscription watches the caller's worktree, a team subscription its own cohort, and a lifecycle subscription has to name another agent. A CI or PR subscription armed from the root checkout requires an explicit scope; supply a branch or path, use --match branch='*' for every checkout RimZ watches, work in a linked worktree, or watch gh run watch --exit-status as a command instead. The loop reference owns these defaults.
Adding the same live target, selector, matches, and root twice reports already subscribed as <name> without changing the existing subscription. For team-owned routing, declare a signal binding: forge sends failed CI directly to its coder, whoever pushed.
A fresh-agent subscription such as rimz loop add ci-fix --signal ci.failed --agent codex --prompt "Fix the failing checks" is not pinned to a session and can outlive the agent that armed it.
Your own signals
Anything that can run a command can put an event into the room:
rimz events emit deploy.finished --json '{"env":"prod","version":"1.4.2"}'emitted deploy.finished (evt_01a07076a71c72e09f0461533909ceb1) · fired 1 task
smokeA CI job's last step, a git hook, a deploy script, a cron line, another agent: each one emits, and every wait and task subscribed to that family fires. Names are lowercase dot-separated words outside the seven reserved families, and the payload is one JSON object whose top-level fields --match filters on and the woken agent reads, one key: value line each.
Your own families observe and skip like the built-in ones, so it pays to name them by outcome. An agent waiting with rimz loop add deploy-red --signal deploy.failed --wait --once is woken by deploy.failed, left armed and quiet by deploy.finished, and untouched by anything outside the deploy family.
The emitting process fires the subscribers itself, so this works with no daemon or running agent, but needs an existing room. Nothing is queued or replayed either: a signal reaches the subscriptions armed at that instant, which is why an agent arms its wait before starting the work that will emit it.
Wait for Done and green
A CI event can arrive before your team finishes review. Subscribing only to that event misses the later moment when both are ready. A condition reads their current state in either order and can require it to stay true before opening a PR.
From the team's worktree, with its room open:
rimz loop add open-pr --when 'team.stage=Done && ci=passed' --for 30m --once --agent claude --prompt 'Open the pull request for this worktree.'
rimz loop show open-prThe receipt names the checkout being observed. The sidebar checks its board and CI each tick: waiting becomes holding, and either reading turning false restarts the 30 minutes. Once ready, RimZ opens a fresh supervised agent pane with the prompt and removes the one-shot task; rimz loop list and rimz loop watch keep showing it as ▸ running until that run ends. The room's sidebar probes that checkout while the task is live, even after its agent panes close; removing the task releases that probe. Without a room sidebar, CI is unknown. Cancel before it fires with rimz loop remove open-pr; conditions lists the expression rules and unknown readings.
Guard a turn with a check
Most recurring automation should stay a plain script. A cron job that runs the test suite, syncs a mirror, or checks a certificate's expiry is deterministic, instant, and free; spending an agent turn to re-check a world that has not changed is burning tokens on true. What a script cannot do is recover. When the command that was supposed to pass fails at 2 a.m., the script's whole error path is "page a human."
--check runs a script before any agent action and spends a turn only on its result. That makes the script the loop's body and the agent its recovery path:
# Watchdog: run the suite every 15m; Codex wakes only when it fails
rimz loop add watchdog --check "cargo test" --on fail \
--agent codex --prompt "fix the failing test" --every 15m
# Trigger-when-green: poll CI until it passes, then hand the merge to the planner
rimz loop add ci-green --check "gh run watch --exit-status" --on success \
--until 30m --every 2m --wait @planner --prompt "CI is green; merge"Every fire runs the check first, at the root of the checkout the task was added from, linked worktrees included; project tasks always run at the canonical project root. --on fail (the default) fires the agent action on a non-zero exit or a timeout, --on success on a zero exit, and a check that spends no tokens is the whole point: the agent's turn is the exception, not the schedule.
When the guard fires, RimZ appends the command, its exit status, and its output tail to the prompt, so the agent wakes already reading the evidence. That adds a rung to the escalation ladder: the script handles the routine, the agent handles the failure, and you hear about it only when the agent itself gets stuck. The turn runs like any other, so a stuck fix goes ? waiting and a notification reaches you.
--until 30m is the poll-until deadline: the task retires when the check trips or the deadline passes, whichever comes first. A one-shot guarded by a check retires only when the guard fires, so a skipped check leaves a bare --at task armed for the same time next day.
A check is killed after five minutes unless --check-timeout says otherwise, falling back to the task's --timeout when set. A killed check counts as a failure. gh run watch on a long pipeline is the case to watch: give the check a cap longer than the pipeline, or poll on --every with a command that returns at once. Pipe nothing: plain sh takes a pipeline's last exit status, so cargo test | tail can hide a failure; RimZ trims the captured output itself.
Ctrl-C during a manual fire cancels the check and the fire, rather than treating the interruption as a failed check and launching the task's agent.
A --check with no agent action is still worth having. It is a scheduled command that logs completed, failed, or timed out, each with the exit code and output tail, into the run history, and it keeps recurring. It needs no prompt. No agent launch opens a room for it either, so RimZ opens the task root's room before the check runs, without resuming your closed agents; a fire that a budget, an overlap lock, or a deadline turned away opens nothing.
--check gates firing; --verify gates completion. Add --verify "cargo test" to a scheduled --agent task when the command is the definition of done: a red result returns its evidence to the same live session, up to --max-attempts total turns (default 3), before the fire records verify failed. It behaves exactly as it does on a hand-run -p, described under verify and retry.
When a failing test might be a flake or already fixed on main, ask a small model before spending a full agent turn. The shell runs first; only a failure reaches the judge, and only a negative answer to this question reaches Codex:
rimz loop add watchdog --every 15m --check "cargo test" --check-timeout 10m --judge haiku --judge-prompt "Are these failures flakes or already fixed on main?" --on fail --agent codex --prompt "fix the failing test"The judge runs without a pane, using the room-default account and the profile's isolation, and is told to ask no questions and change nothing. It sees the shell output, returns pass and a short reason, and the action receives both stages' evidence. loop logs watchdog shows each verdict, reason, cost, tokens, and duration. The judge has its own five-minute cap, adjustable with --judge-timeout, independent of the shell cap. A missing verdict passes the turn to the next judge or action with the error in history, so an outage alone cannot silently prevent recovery. --on is required with a judge: fail needs negative verdicts, success positive verdicts, and any accepts either.
The same task in loop.toml gives its judge a two-minute cap:
[tasks.watchdog]
every = "15m"
check = "cargo test"
check-timeout = "10m"
on = "fail"
agent = "codex"
prompt = "fix the failing test"
[tasks.watchdog.judge]
agent = "haiku"
prompt = "Are these failures flakes or already fixed on main?"
timeout = "2m"Shell checks and judges also guard --stay launches. A resident judge's decline is remembered, but the shell still runs on every eligible fire. While the hold stands, no judge is asked again; history marks the held verdict reused with zero spend. loop show names the declined checkout and reason, and stats --assists records fresh declines, not replays. Fire by hand with loop fire watchdog to ask again, or set --judge-recheck for periodic reconsideration. The guard reference lists reset, polarity, and prompt-file rules.
A check script that sometimes needs judgment can call an agent itself: rimz agents codex "inspect the failed deployment". RimZ supplies RIMZ_LOOP_TASK with the task name and keeps nested commands in the task's room; the launched agent uses the check's checkout unless it selects another worktree. Inside a check, a prompted launch implies -p: it waits for the run, captures the answer in the check output, and returns the run's exit code. The run opens its pane beside the room's loop dashboard and closes it on success or failure, unless --keep holds it open; its timeout is the launch's own --timeout or loop.default-timeout (two hours by default). A launch with no prompt, or with --bg, is refused, because the check has to wait for the run to end, and the check's own timeout still bounds how long it can wait.
Gate a task on surplus
Recurring background work is the natural fill for a loop: refactor the next rough module, close test gaps, triage dependencies. None of it is urgent, which is exactly why it must never crowd out the work that is. A background task draws on the same subscription window as your own sessions, and a schedule cannot tell a light week from a heavy one: fired blindly, the 03:00 task spends the budget your Tuesday afternoon needed.
--surplus puts that judgment in the schedule. Before each fire, RimZ reads the provider's usage window (the weekly bar on today's Claude and Codex plans) and divides the share of budget left by the share of time left. That ratio is the headroom: 1.0x means the current pace lands exactly on the reset, and 1.5x means half again as much budget remains as the remaining time needs.
rimz loop add refactor --agent claude --prompt "Refactor the next rough module and leave the branch green" \
--every 4h --surplus 1.5x --surplus-after 3d--surplus 1.5x opens the gate only at that headroom or above. --surplus-after 3d keeps the task quiet until three days of the window have elapsed, so an untouched early week is not spent before your own heavy days land (used alone, it still requires 1.0x). The gate guards --agent and --wait actions alike and runs before any --check, so a closed gate runs nothing and costs nothing: the fire records surplus skipped without adding a strike, and the schedule keeps polling until real slack appears. An account without a window reading, such as an API key or a window that has not started, keeps the gate closed. Which window counts per provider, the headroom model with a worked example, and the fail-closed rules are in budgets → the surplus gate.
Wait for room in a usage window
The surplus gate decides whether a fire may spend; it never picks the moment. Some work only pays off once the window has room: a long refactor that starts in a nearly spent 5-hour window stalls halfway, and what you want is to start it when the window has refilled.
window.5h.left and window.7d.left are condition keys for that. Each reads the percent left in the task provider's 5-hour or 7-day window, from the provider usage cache the room's sidebar keeps, and compares it with a whole percent:
rimz loop add big-refactor --when 'window.5h.left>=60' --once --agent claude --prompt 'Refactor the storage layer and leave the branch green.'Each tick projects the cached reading to now, so a window whose reset has passed reads 100 before a new reading lands; that is what lets the loop timer fire the task at the reset with no room open. Without a room nothing refreshes the cache, so the reading is only that projection: it rises at the reset but misses usage since the last refresh. --for 30m requires the window to stay at that level for half an hour, so it measures a window that stays quiet and full: any use that drops it below the bound restarts the hold.
The provider is fixed at add: the kind --agent or --wait resolves to, recorded on the task. Adding reads the current window first and refuses what it cannot read: a check-only task, which has no provider; Qwen, whose account is chosen per launch; and a missing or already-reset reading, which rimz providers --refresh or an open sidebar replaces. A window that has not started still reports about 1% used, so it reads 99 and >=100 rarely holds; ask for >=95 instead. Window conditions are not available to --project tasks. The expression rules are under conditions.
When the moment you want is the reset itself, --after-reset 5h (or 7d) fires the task once when that window next resets, then removes it. Run from an agent pane, this wakes that agent; from a terminal, name the agent (--wait @planner):
rimz loop add after-limit --after-reset 5h --wait --prompt 'The 5h window has reset; pick the refactor back up.'Add records the reset instant on the task, and the receipt shows the window's current percent left and when it resets. A window that has not started has no reset to wait for yet, so the task fires on the next tick. If the reset passes while no clock is running, or while the task is paused, it fires as soon as one sees it. It gets one attempt: whether the fire runs, is skipped by a gate, or fails, the task is gone afterwards. Add refuses what a window condition refuses, plus a window the provider is not enforcing and a --surplus gate on the provider's longest window, which is always closed right after that window resets. The rules are under after a reset.
Pace agent starts
A burst of due tasks would otherwise start every agent in the same second, and each new agent loads a model session, a language server, and a build cache at once. RimZ starts them one at a time: every fire that starts an agent takes a turn in one queue for the whole machine, and the turn passes on when the new agent reports in or after ten seconds, whichever comes first. Wakes of running agents, check-only tasks, and fires their check skipped never queue.
The [throttle] table in loop.toml tunes it, and can also hold starts back while the machine is busy:
[throttle]
pace = "10s" # "0s" turns pacing off
max-wait = "30m" # a fire held this long is skipped
max-active = 6 # agents mid-turn, machine-wide
max-active-per-task = 2 # agents mid-turn that one task started
cpu-pressure = 60 # percent, Linux pressure-stall
min-memory = "8GB"
min-disk = "20GB"Pressure numbers differ from one machine to the next, and inside a container they may be the host's. Read what rimz loop show <task> reports under a normal load before you pick a limit.
A held fire shows held: <reason> in rimz loop list and rimz loop show, Ctrl-C ends a held rimz loop fire without starting anything, and show ends with what the machine reads now beside each limit. A fire still held at max-wait records throttle skipped, which is never a strike. --throttle off on rimz loop add exempts one task. Every key, and what counts as an active agent, is in the loop reference.
Budgets and strikes
Two brakes bound a task that runs without you.
Dollar budgets cap what a task spends. --budget 5 caps each fired run, and --budget-per-day 20 sums that task's run costs in the configured local day and skips a fire it cannot fund, recording budget skipped. A room-wide or per-account daily cap gates the same fires the same way, before the task's check or pane exists. rimz loop list shows each task's spend against its daily cap and rimz loop show breaks the costs down per run; the caps themselves are the budgets guide.
A task spends on the room's current account for its provider unless you pin it: --account work beside --agent holds every fire on the named account work, with that account's caps and windows, whatever the room switches to. Its window.* conditions read that account's last stored window. A pinned fire never borrows another account. When work is removed, loses its home, or has a stored logged-out status, the fire records account skipped with the fix and adds no strike; an idle account RimZ holds no status for launches, and a login the provider rejects then fails that run (the account pin).
Repeated failures disable the task. Three consecutive failed fires auto-disable it, show disabled after 3 strikes under NEEDS YOU in rimz loop list, and fire notification handlers with kind loop_disabled. The subtle case is a watchdog. A turn fired by a failing check counts as a strike even when the turn itself completed, because the check on record is the one that ran before the turn, so a watchdog that finds the world broken three fires in a row is disabled whatever the agent did in between; the counter resets only when a later fire's check passes. A fire that a gate turned away, and a fire that left no run behind at all, are both neutral, so a machine that cannot start runners never disables your tasks. rimz loop enable <name> clears the strikes and re-arms the schedule, --max-strikes <N> changes the threshold per task and --max-strikes 0 removes it, and rimz loop fire still runs a disabled task for a manual test. Which run results strike, reset, and count for nothing is a table in the loop reference.
What a task does on your machine
rimz loop add writes a task definition and starts no process.
A standing task appends a [tasks.<name>] entry to ~/.rimz/loop.toml: per-machine automation, like your crontab, never inherited by a cloned repository. The file is plain TOML you can edit by hand, and the deps task above lands as:
[tasks.deps]
agent = "claude"
prompt = "Check for outdated dependencies. Open a PR for the safe minor and patch bumps, and list any majors that need review."
root = "/home/you/code/app"
worktree = "deps"
at = "09:00"
every = "mon"Every session delivery, including recurring clocks and standing signals, lives in ~/.rimz/ws/<workspace-dir>/records/loop-instances.json, alongside generated one-shots and poll-until tasks. Start a room with rimz start before arming these room-local tasks; standing machine tasks need no room at add time. One-shots retire after their terminal outcome, and one that launched an agent stays in rimz loop list and rimz loop watch as a ▸ running row while its run is active, so rimz loop stop <name> has a name to take; standing deliveries last until removed or their pinned session retires. A watched-command check-in leaves its row intact, and names and enable state are isolated per room. rimz loop list puts deliveries under WORKTREES beside their checkout; generated subscriptions name the team or loop that owns them instead of repeating generated IDs.
--project writes the entry to <root>/.rimz/config.toml: shared automation that travels with the repo, so it has to be a standing task (--every, --cron, or --signal), cannot use --wait (a session pinned on your machine means nothing on someone else's), and always runs commands at the project root. A committed task runs commands on whoever pulls it, so it enters the project trust hash and stays inert until each user approves it. Trust and enablement answer different questions: trust says the project config contains commands you accept as yours to run, and rimz loop enable <name> says this particular task may run unattended on this machine. A project task pulled from a repo starts disabled even after trust is granted; a task you create with rimz loop add --project starts enabled on your machine. A trusted project task wins over a same-named machine task without double-firing.
Where a scheduled turn runs
A scheduled --agent fire opens an unfocused tab named loop <task> with its own sidebar, so it never splits the tab you are working in. When the agent needs you, its tab shows the waiting glyph and its sidebar can ring the bell. An agent launched by a check gets the same tab, even under a manual fire. A manual rimz loop fire of an --agent task opens the pane beside your shell instead, or in a tab when called outside a pane, so its output stays in front of you.
Every scheduled --agent turn gets a timeout: the task's own --timeout, or the machine's loop.default-timeout (two hours by default). A manual rimz loop fire of an --agent task runs unbounded unless the task sets its own.
Loop-owned agent panes close on every terminal outcome, including failure and timeout, unless explicitly kept. The pane's wrapper handles cleanup even if the waiting check process has died; run records and rimz loop logs retain the evidence.
What a fire leaves behind
A fire leaves two things: whatever the task did (one transient pane for --agent, one delivered message for --wait), and one line of run history in ~/.rimz/logs/loop-runs.log.jsonl. rimz loop show <name> gives that history a health verdict; rimz loop logs <name> prints the complete stored forensics. Take a check-only task that runs cargo test hourly, fired once by hand in a repository with no Cargo.toml:
$ rimz loop fire suite
suite — check
check: cargo test
│ error: could not find `Cargo.toml` in `/home/me/code/app` or any parent directory
✗ check failed (exit 101) in 173ms
$ rimz loop show suite
suite — every 1h
✗ failing · last run 0s ago, failed (exit 101) in 173ms
LAST RUN — ✗ failed (exit 101) · 0s ago · 173ms · manual
│ error: could not find `Cargo.toml` in `/home/me/code/app` or any parent directory
RECENT RUNS (newest first · 1 of 1)
WHEN STATUS TOOK NOTE
0s ago ✗ failed (exit 101) 173ms error: could not find `Cargo.toml` in `/home/me/code/app`...
action: run check · cargo test
root: ~/code/app · no room
source: machine — ~/.rimz/loop.toml
strikes: 1/3The screen answers first and shows its evidence after. The verdict line reads the latest conclusive run: how long ago it ran, how it ended, and, once there is a streak, how many runs in a row ended that way and when the streak began. LAST RUN is that run's output (a passing check prints its last five lines and the rimz loop logs command for the rest), RECENT RUNS reads newest first, and the task's facts close the screen, where strikes counts consecutive failures against the threshold. manual marks a run you fired by hand; a clock's fire carries no mark.
A subscription can hear another signal in its family without acting on it. Those signals do not replace LAST RUN or inflate the run counts: a muted heard line counts them since the last acting run and names the latest. rimz loop logs <name> --heard includes that listening history when you need it. Refused fires and guard declines still appear in recent runs. A wake that has not fired describes what it will wake instead of suggesting a manual fire, which would spend a one-shot wait.
Teardown is one command. rimz loop remove <name> deletes the entry from whichever file owns it; the run history stays. rimz loop timer remove takes the timer out, and rimz uninstall removes it too.
Every schedule shape
Each task names one action (--agent, --wait, or a bare --check), carries a --prompt or --prompt-file unless it is check-only or a wait, and picks one firing shape. A clock recurs only with --every or --cron; a signal subscription keeps listening unless --once retires it.
| Shape | Flags | Repeats? | Example |
|---|---|---|---|
| One-shot | a bare --at HH:MM, or --in <delay> | fires once, then the task removes itself | --in 30m |
| Interval | --every <duration>, measured from the last fire | yes | --every 15m |
| Calendar | --every <days> --at HH:MM, where days is day, weekday, weekend, a range mon-fri, or a list mon,wed,fri | on each matching day | --every weekday --at 07:00 |
| Raw cron | --cron, a five-field expression | per the expression | --cron "*/15 * * * *" |
| Poll-until | --every <duration> plus --check, --on, --until, and an agent action (above) | until the check trips or the deadline passes | --check "gh run watch --exit-status" --on success --until 30m --every 2m |
| Signal | --signal <name-or-family>, narrowed with --match key=value (above) | on every delivering emit, or once with --once | --signal ci.failed, --signal 'ci.*' |
One pair is worth a second look. --every 1d is an interval: it fires a day after the last fire and drifts with it. --every day --at 07:00 is the calendar's 07:00 sharp. Calendar times, cron, --in, and --until resolve in the top-level timezone, falling back to the system zone when unset.
Inspect, test, and manage tasks with the rest of the surface:
rimz loop list # this room's tasks, grouped by attention and checkout
rimz loop list --all # every known room
rimz loop list --json # real task names and structured, uncollapsed rows
rimz loop watch # this room's live dashboard, including every worktree
rimz loop show pr-watch # health, next fire, agent-run rollup, and recent runs
rimz loop logs pr-watch # full forensics for recent runs
rimz loop fire pr-watch # fire now in the foreground for testing; the schedule stays put
rimz loop fire pr-watch --keep # leave the transient pane open to inspect
rimz loop enable pr-watch # arm locally and clear any pause or strike disable
rimz loop disable pr-watch # hold until the next explicit enable
rimz loop pause pr-watch --for 2h
rimz loop stop pr-watch # cancel a stuck run and release its overlap lock
rimz loop rename pr-watch ci-watch
rimz loop remove pr-watchrimz loop fire runs every gate a scheduled fire would (budget, surplus, overlap, check), streams the agent's messages as they land, then links the completed run and its transcript. When a fire reports previous run still active, rimz loop show <name> and rimz loop list show the task as ▸ running with how long it has run, and rimz loop stop <name> cancels it; stop asks the run to cancel first and sends SIGTERM only as a backstop, and if the run still holds its lock it prints the PID and lock path instead of escalating to SIGKILL.
The list starts with NEEDS YOU: failing tasks, throttle holds, trust blocks, invalid triggers, and checkouts whose directories are gone. Each reason points to the command that addresses it. ROOM shows room-wide tasks, with running ones first. WORKTREES groups deliveries and checkout-bound tasks, with your checkout first and marked (here); from an agent pane, your own wake targets are marked (you). A missing checkout is only flagged, not removed, so restoring its directory restores its row. Healthy tasks in other rooms stay out of the way; their attention tasks appear on an elsewhere: line. Use --all to inspect those rooms, or --json for scripts that need the real task names behind collapsed subscriptions.
LAST answers whether a task acted, not whether it merely heard an unrelated signal. A good run stays ✓ after sibling signals; a subscription that has only heard siblings says heard ci.passed 7m ago. Watched commands show the label you supplied, and watching with elapsed time or lost when their watcher is gone. Spend appears beside the result and in the room heading, rather than in a separate mostly-empty column.
Enablement and pauses belong to one machine and never edit the task definition. disable holds a task indefinitely, pause --for <duration> is a bounded hold that lifts itself, and enable clears either hold and any strike counter. --all applies enable or disable to all machine tasks plus this project's instance and project tasks, independently of the list's room scope. A lifted hold starts the schedule fresh from that moment, so fires missed while held never replay.
Every flag is in the loop CLI reference; the run mechanics an --agent task rides on (exit codes, output formats, wait --stream) are in scripting.
Keep the fleet moving
An unattended agent stops for reasons that need no judgment from you. The provider's usage window empties mid-turn. The API sheds load and drops the stream. The context window fills one step short of the finish. A spent Codex account sits on reset credits that expire unused. Each stop has a known fix: wait for the reset, retry in a few minutes, compact, redeem a credit. A stock CLI leaves every one of them to whoever is watching, and overnight that is nobody.
RimZ ships each of those fixes as a configurable reflex. The room recognizes the stop from the provider's own evidence, applies the fix at the moment it can work, and the agent carries on in the same session, as if you had typed the resume yourself. Idle team compaction and smart compaction are on by default; auto-continue and auto-redeem are opt-in, with one exception stated under auto-redeem: a Codex credit about to expire is redeemed whether or not you opted in. Because these reflexes let RimZ type into your panes and spend on your account, each section below states the rules it follows.
rimz config set resume.auto_continue true # resume rate-limit and API-error parks
rimz config set resume.auto_redeem true # spend Codex reset credits when they buy real time
rimz config set harness.idle_compact off # opt out of idle team compaction
rimz config set harness.smart_compact 200k # compact before a message once context passes 200k tokens (or "70%")Auto-continue
A turn that dies on a rate limit or an API failure parks its agent: the card shows ⏸, and the work stops until someone types continue. At your desk that is one keystroke. Away from it, the fleet sits idle from 1 a.m. until you notice. Auto-continue types that keystroke for you, and its policy is three decisions: what counts as evidence, which clock schedules the resume, and when to stop trying.
The evidence is the structured per-turn failure record the agent's own CLI writes when a turn dies, naming the cause. That record is the one provider input the decision reads, so every automatic resume traces back to the provider's own account of why the agent stopped, and every other kind of stop stays parked for your judgment. Because the record comes from each agent's adapter, auto-continue is a per-agent capability, and the per-agent notes say where it never arms. The one park that needs no provider record is the one RimZ imposed itself, a dollar-cap park, because RimZ already knows why it stopped.
The cause picks the clock:
- A rate-limit or spend-limit park waits for the latest applicable account or model window to reset, including a Fable model limit. If the provider still reports a limit afterward, nudges stay at least two minutes apart; those limit replies do not restart the attempt cap.
- A daily dollar-cap park resumes at the next local midnight, when the cap reopens.
- A transient overload or API-error park has no reset clock, so it retries on a lengthening ramp: the first attempt three minutes after the failure, then every five (
resume.auto_continue_backoff_secs, default[180, 300], last value repeating). Overload replies do not restart the attempt cap either.
Each attempt is a keystroke you could have typed: when the clock fires and the agent is still parked, RimZ sends the configured nudge (continue by default, resume.auto_continue_text) into the agent's pane through the same path as rimz message --steer. All causes share one attempt cap (resume.auto_continue_max_retries, default 12, just under an hour on the default ramp); when it runs out, the card goes failed and routes to you like any other actionable card. Every attempt appends the park time, delivery verdict, and message id to the assist log that rimz stats --assists prints.
The keys are in configuration → Resume.
Auto-redeem
A Codex plan grants reset credits: redeem one and a spent usage window refills on the spot, and its natural reset starts over a full window from then. Managed by hand they waste value at both ends. A credit you forget expires unspent; a credit you hold while a spent window parks the fleet is a night of work standing still. And the timing is a real judgment call: redeemed just before the window's natural reset, a credit buys minutes; redeemed the moment a spent window blocks a night of work, it buys hours. Auto-redeem makes that call by three rules, and each redemption's record (rimz stats --assists) names the rule that fired:
- Expiry rescue. A credit within thirty minutes of expiring is spent rather than lost. This rule always runs, even with
auto_redeemoff: the capacity is already paid for. It is also the one rule that reaches past this room: while a room is open, every Codex account you declared is watched, including one no agent here runs on. On such an account RimZ reads usage at most once every three hours and skips the rescue when the window shows no usage at all, since a refill there would buy nothing. - Blocked gain. A window is spent and its natural reset is at least
resume.auto_redeem_min_gainaway (twelve hours by default). A credit now recovers those hours, so it redeems immediately; a nearer reset means waiting is cheaper than spending. - Scheduled redeem. Several credits approach expiry together and a spent window resets sooner than
resume.auto_redeem_min_gain. RimZ measures from your recent usage how long a fresh window takes to fill and spaces the redemptions that far apart, working back from each credit's expiry, so each one lands on a window with room to absorb it.
Blocked gain and scheduled redeem fire when an in-use Codex account has spent its subscription window and a reset is the next source of capacity. They do not wait for an agent to park. By default, resume.spend_order = "credits_first" uses available paid credit before banked resets; choose resets_first to spend a useful reset before paid credit. Neither rule fires on an account this room is not using.
The reflex fails closed and paces itself. Every rule starts from a credit in hand; blocked gain and scheduled redeem also require a spent window with a readable reset time. Each login's attempts are throttled across every room on the machine: ten minutes between attempts, thirty after a success. Logins sharing a provider account serialize their refresh and redemption, so a second helper sees the first refill before deciding. A successful redemption refreshes the account reading at once, and because the refilled window is exactly the reset auto-continue waits for, it wakes the turns parked on it.
The Codex block in the sidebar shows what auto-redeem would do if the longest usage window ran dry right now. ⟳ 2 means armed: a credit would be spent, or no natural reset is known to wait for. ‖ 2 means holding: the fleet would use paid credit or wait for the free reset instead. When that reset is closer than resume.auto_redeem_min_gain and the nearest credit outlives it by a day, a hold stays a hold until the reset. ↻ 2 means auto_redeem is off and redeeming is up to you, using rimz accounts redeem codex, though expiry rescue still spends a credit in its last thirty minutes. Within a week of the nearest expiry, the time left follows the count, as in ⟳ 2 · 3h.
Idle compaction
An idle team member can outlive its provider's warm prompt cache, so its next stage pays to cache the whole accumulated conversation again. Idle compaction submits the member's native compact command with the team brief, with no new prompt behind it. The member summarizes its conversation and carries on from that smaller context; details not preserved in the summary are lost.
The configuration rules define the provider timing, eligible team members, and role overrides. Each attempt leaves a durable command for boundary delivery and an assist record visible in rimz stats --assists, including the resolved idle threshold. Set harness.idle_compact to off to disable the machine default; explicit role overrides still apply.
Stop when idle
An agent that has finished still holds its pane, its process, and a place in the roster. rimz agents stop @coder --when-idle hands the cleanup to the room: the agent stops once it has rested three minutes with nothing owed to it, and an agent can ask for its own exit with @me as its last act. A pending wait, an unreported subagent, a queued message, an open run, or a team board short of Done all hold the stop, as does a launched child that is still working or owed something, and a new turn restarts the clock. Pass a duration to change the rest, or off to withdraw. The stop reference has the exact rule; each stop shows in rimz stats --assists.
Smart compaction
Every agent CLI already compacts. /compact is the manual command: summarize the conversation and carry on against a fresh window. Auto-compaction is its fallback, firing when the context hits the ceiling, wherever the work happens to stand. Driving one agent by hand, you preempt the fallback without thinking about it: a task wraps up, you type /compact at the clean boundary, and the summary hands a finished state to whatever comes next.
In a fleet, most prompts arrive with no human there to make that call. A reviewer sends comments back to a coder sitting at 90% context; the coder takes the message, edits two files, hits the ceiling, and the automatic summary captures a half-changed tree mid-fix. Smart compaction takes that decision at the same boundary for you. When a rimz message or a scheduled wait is about to land and the receiver's context has passed your threshold, RimZ submits the agent's compact command first, then delivers the text against the fresh window. A message boundary is the strongest checkpoint available: the previous task has ended and the next has not begun, so the summary is a handover.
Every message send and scheduled wait inherits harness.smart_compact, which defaults to 258k. Set another occupied-token count like 200k or a percentage like 70% to change it, or "off" to disable it. --smart-compact overrides the machine setting per message. harness.compact_instruction changes the summary brief. The threshold grammar and delivery mechanics are in messaging → compact before the message lands.
Permissions for an unattended run
An unattended run has to answer permission prompts without you. Two patterns cover it, and they compose.
Answer in the agent's own UI to keep the full record. A handler that answers sends the answer with rimz pane send, leaving the prompt, the answer, and the tool run all in the agent's transcript, exactly as if you had typed it. Prefer this path when handled decisions belong on the record.
Use the agent's bypass flag when the run cannot afford to stop. --mode yolo on the task passes the adapter's bypass flag to the scheduled turn (claude --dangerously-skip-permissions, codex --dangerously-bypass-approvals-and-sandbox), while --mode ask keeps the provider's prompts in place; the modes are the same ones rimz agents takes. RimZ still observes sessions, completions, and failures through the reporting hooks, but the agent skips permission events at the source, so the durable record has no per-decision audit trail, only what the other hooks report. Reserve the flag for runs where you accept that missing trail.
The guardrails around either choice stay visible: trust grants, notification handlers, and the permission mode itself are product behavior, covered in security.
A fleet that works nights
A hands-off room composes four layers, from least to most involved: the recovery reflexes keep one agent alive through rate limits with no schedule at all; a scheduled turn puts work on a clock; a check guard fires that turn only when a condition trips; and a notification handler catches what none of them can decide alone. Reach for the lowest layer that solves the problem, and stack them when the job needs it.
Because a scheduled turn runs inside the room, its prompt can use every other RimZ command:
# 02:00 every night: a triage that fans out and opens PRs
rimz loop add nightly --agent claude --worktree nightly --timeout 4h --budget 5 --budget-per-day 20 --every day --at 02:00 \
--prompt "Scan the repository for bugs and cheap improvements. For each one worth fixing, \
run a codex -p subagent in its own worktree, review its diff, and open a PR."That is one scheduled turn, and its prompt hands the agent the room's own tools: it fans work out with subagents, isolates each fix in a worktree, and could as easily launch a team and brief it over messages.
Leave the room open, detached on your workstation or on a server you reach with rimz remote, and the night runs on the pieces above: auto-continue carries recoverable runs over rate limits, a question or failure trips a notification handler that reaches your phone, and the permission choice stays per task. By morning rimz loop list and the PR queue show what the night produced.
See also
- Scripting agents: the run mechanics every scheduled
--agenttask rides on, including exit codes,--output-format, andwait --stream. - Budgets: the dollar caps that bound hands-off work, and the surplus gate's headroom model.
- Notifications: the push routes and acting handlers that catch what a loop cannot handle alone.
- Messaging: the delivery path
--waituses,--schedulefor one-off reminders, and smart compaction in full. - Loop CLI, Wait CLI, and Events CLI: every flag, the watcher and
--wait, and the signal grammar. - Configuration: the
[resume]and[harness]keys, and theloop.tomlshape. - Security and trust: the safety story for bypass flags and project trust.
- Loop internals: the clock, the state files, and the run log underneath, for reading the code.
Subagents
Your agents already delegate. Claude hands a search to its Agent tool, Codex spawns a helper, and the parent gets back a summary instead of a window full of grep output.
Notifications
You already wire your own alerts: claude -p "..." ; notify-send done at the end of a command, a bell on the last line of a script, a curl to a Slack webhook when the…