All plugins

heartbeat2

Your agent follows up on its own: timed check-ins, recurring checks and watchers that wake it in the same chat when something changes. You (with /pulse) or your agent (with the pulse tool) arm follow-ups at a time, recurring checks, a check-in after a long silence, or deterministic watchers on a file, process, URL, read-only command or Hermes event (kanban, subagent, API error, reactions). A wake is a turn in the same messaging DM that armed it, on that chat's own model: no side model, cron session or subagent. Watchers decide without a model call; the agent answers each wake with a structured verdict (notify or stay silent, never a typed sentinel); a no-op governor slows, then pauses pointless wakes and asks once. Every guardrail is a user setting (quiet hours, daily caps, unanswered mute, floors, lifetimes) that the agent works inside and can never widen. A one-time reminder you ask for in chat bypasses the unanswered mute and daily caps; HeartBeat 2.0 grants that only in a turn that carries your message. Arming returns a one-line receipt saying when the wake will really fire, and a wake that could never fire before it expires is refused. It delivers to messaging-gateway DMs only and needs plugins.entries.heartbeat2.allow_gateway_injection: true. Ships the plugin skill `heartbeat2:heartbeat2-report-problem`, which drafts a bug report and files it with the GitHub CLI only after the user approves the exact text. Disclosure — wakes and watchers run full turns on the conversation's own model, billed like any message, at your conversation's context size, with the conversation's normal tools and approval prompts; the agent can arm wakes and watchers itself unless you set agent_can_create to ask_first or no; command watchers run allowlisted read-only commands (default gh pr view/checks, gh run view/list, git ls-remote/status/log) without an approval prompt, with no shell, a sanitized environment (no provider keys, GH_TOKEN or GITHUB_TOKEN) and the process group killed after 30 s, can use your stored git/gh credentials, and `git ls-remote <url>` / `gh -R` can contact any host your machine can reach; URL watchers make the GET requests you or the agent configured, without URL credentials, cookies, auth headers or proxies, always refuse cloud-metadata addresses and refuse private or local addresses unless url_allow_private is on; file watchers refuse paths Hermes blocks from reading; notes, summaries and event payloads are stored in plaintext in the profile's plugin-data; no telemetry.

100yenadmin10Automationv0.2.2Updated 2 days ago

About

Your agent follows up on its own: timed check-ins, recurring checks and watchers that wake it in the same chat when something changes. You (with /pulse) or your agent (with the pulse tool) arm follow-ups at a time, recurring checks, a check-in after a long silence, or deterministic watchers on a file, process, URL, read-only command or Hermes event (kanban, subagent, API error, reactions). A wake is a turn in the same messaging DM that armed it, on that chat's own model: no side model, cron session or subagent. Watchers decide without a model call; the agent answers each wake with a structured verdict (notify or stay silent, never a typed sentinel); a no-op governor slows, then pauses pointless wakes and asks once. Every guardrail is a user setting (quiet hours, daily caps, unanswered mute, floors, lifetimes) that the agent works inside and can never widen. A one-time reminder you ask for in chat bypasses the unanswered mute and daily caps; HeartBeat 2.0 grants that only in a turn that carries your message. Arming returns a one-line receipt saying when the wake will really fire, and a wake that could never fire before it expires is refused. It delivers to messaging-gateway DMs only and needs plugins.entries.heartbeat2.allow_gateway_injection: true. Ships the plugin skill `heartbeat2:heartbeat2-report-problem`, which drafts a bug report and files it with the GitHub CLI only after the user approves the exact text. Disclosure — wakes and watchers run full turns on the conversation's own model, billed like any message, at your conversation's context size, with the conversation's normal tools and approval prompts; the agent can arm wakes and watchers itself unless you set agent_can_create to ask_first or no; command watchers run allowlisted read-only commands (default gh pr view/checks, gh run view/list, git ls-remote/status/log) without an approval prompt, with no shell, a sanitized environment (no provider keys, GH_TOKEN or GITHUB_TOKEN) and the process group killed after 30 s, can use your stored git/gh credentials, and `git ls-remote <url>` / `gh -R` can contact any host your machine can reach; URL watchers make the GET requests you or the agent configured, without URL credentials, cookies, auth headers or proxies, always refuse cloud-metadata addresses and refuse private or local addresses unless url_allow_private is on; file watchers refuse paths Hermes blocks from reading; notes, summaries and event payloads are stored in plaintext in the profile's plugin-data; no telemetry.

HeartBeat 2.0

Your Hermes agent can say "I'll check back at four", and then actually do it: in the same chat, on the same model, with everything it already knows. It can watch a build, a log file, a process or a URL and speak up only when something changed. When there is nothing worth saying, it stays quiet.

  • You or your agent create the wakes and watchers. A follow-up at a set time, a recurring check, a check-in after a long silence, or a deterministic watcher on a file, process, URL, read-only command or Hermes event. You arm them with /pulse; the agent arms them with its pulse tool.
  • Same conversation, same model. A wake is a turn in the chat that armed it, on that chat's own model. There is no side model, separate cron session or subagent.
  • It catches its own pointless wakes. A watcher costs no model call until something changes. A wake that keeps finding nothing slows down, suggests a watcher, then pauses and asks you once.
  • You own every guardrail. Quiet hours, daily caps, the unanswered mute, intervals and lifetimes are plugin settings. The agent works inside them: it can only narrow its own wakes (shorter lifetime, fewer fires, slower pacing) or switch itself off, never widen a setting.

What it feels like

A follow-up that reads the room.

You: Remind me at 4 to send the Acme invoice.
Agent: Will do. I'll check in at 16:00.

16:00. Agent: It's 4. Did the Acme invoice go out? I can draft the email if that helps.

The wake runs inside your conversation. If you had written "sent it" at 15:10, the 16:00 turn would see that and stay silent. If the agent armed it with "cancel when I reply", any message from you cancels it outright.

A watcher that speaks only when something changed.

You: Keep an eye on the nightly build and tell me if it breaks.
Agent: Watching the latest nightly run with a read-only gh run list check every 10 minutes. I'll only look when the result changes, and only message you if it failed.

02:10, after 14 unchanged checks that cost no model turns: Agent: The nightly build failed in tests/test_api.py (3 failures, all timeouts). Want me to dig in this morning?

During quiet hours that message waits until they end, unless you marked the watcher urgent and allowed urgent wakes through.

A pointless wake that catches itself. Say the agent set up an hourly "check the inbox" wake. After three empty checks it starts stretching the interval, and after six it pauses itself. The next time you talk:

Agent: I paused "check the inbox": six checks in a row found nothing. Keep, slow, stop, or turn it into a watcher? (/pulse keep|slow|stop|watch w_3f2a)

A check-in after a long silence (off by default). When it is on, the agent checks in once after a silence (24 h by default) and says how to turn check-ins off. It doesn't check in again until you've talked.

Why "2.0"

Heartbeats, as OpenClaw popularised them, made agents feel alive. They also produced a well-documented list of failures. We turned each one into a deterministic scenario in the benchmark suite:

| Heartbeat-1.0 failure | HeartBeat 2.0 |
|---|---|
| Stops silently after compaction, sleep or restart | The schedule and claims live in SQLite on wall-clock time. After a restart or sleep, missed fires collapse into one. |
| Duplicate or replayed deliveries | Each fire is a durable compare-and-set claim. Only one wake can be in flight per chat, and a wake is never injected into a busy chat. |
| Message floods (one loop resent 189×) | Daily caps on model turns and messages, spacing between wakes, a mute after unanswered messages, and an error ladder that ends in a pause. |
| "HEARTBEAT_OK" leaking into the chat | The model never types a sentinel. It calls pulse respond notify=false, and the plugin makes the turn silent. |
| Messages to the wrong chat or person | There is no destination parameter at all. A wake can only return to the chat that armed it, and only in DMs by default. |
| Most spend going to no-op ticks | Watchers decide without a model call. The no-op governor backs off, then pauses. |

Totals over 34 deterministic scenarios (B1 simulation driving the real engine; python -m bench.run --baseline --gate, source ca12767, v0.1.14 engine; the gate report records content digests). The comparison arm is a naive heartbeat-1.0 emulator built from documented failure reports. It is not a measurement of OpenClaw itself.

| Across the suite | HeartBeat 2.0 | Naive heartbeat |
|---|---:|---:|
| Duplicate deliveries | 0 | 1 |
| Required fires missed | 0 | 27 |
| Sentinel text leaked into chat | 0 | 2 |
| Messages to the wrong chat | 0 | 6 |
| Most messages in one minute | 1 | 100 |
| Model turns | 110 | 588 |
| Model turns that found nothing | 15 | 82 |
| Estimated input tokens (total; calls × context) | 33,000,000 | 176,400,000 |
| Estimated cache read tokens | 25,950,000 | 116,550,000 |
| Estimated cache write tokens | 7,050,000 | 59,850,000 |
| Estimated output tokens | 33,000 | 176,400 |

Every scenario and its hard bars are in bench/README.md; the full per-scenario table is regenerated per commit.

How it works

  you (/pulse) or the agent (pulse tool): what, when or what to watch, bounds
                                   │
                    durable schedule (SQLite in the profile)
                                   │
        time · silence · file · process · URL · command · Hermes event
                                   │
              nothing changed? ──► no model call, nothing sent
                                   │ due / changed / deadline
     gates: enabled · estop · paused · provider health · quiet hours ·
            unanswered mute · daily caps · busy · spacing · one in flight
                                   │
           one turn in the same chat, on the chat's own model
                                   │
                 pulse respond {outcome, notify, summary}
           notify=false ──► silent          notify=true ──► one short message
                                   │
          ledger + no-op governor (slow down → suggest a watcher → pause → ask once)

One poller per profile runs inside the messaging gateway that serves that profile and ticks every 5 seconds. Hermes loads every profile's plugins when a gateway starts; a gateway that does not serve a profile leaves that profile's schedule alone and delivers nothing for it. A file lock guarantees only one poller even when several Hermes processes share the profile. Every fire is a durable claim with an attempt marker. An accepted turn that never starts is tried three times, and a refused injection backs off and is tried five times, before it is marked dead. A turn that started but never finished (a crash or restart) is recorded as interrupted. Hermes may auto-resume that wake turn, sometimes before heartbeat2's restart recovery has run; heartbeat2 keeps the replay silent either way, never counts it as a fire or delivery, and counts it as a model turn toward the daily cap.

Install

Requires Hermes ≥ 0.21.6 and Python ≥ 3.11 on macOS or Linux. HeartBeat 2.0 delivers into messaging-gateway DMs (and group or topic chats when dm_only is off): Telegram, Discord, Slack, WhatsApp and the other gateway platforms.

hermes plugins install electricsheephq/heartbeat2 --enable

Then allow the plugin to start turns in your conversations. This is a Hermes core key, deliberately kept outside the plugin's own settings:

plugins:
  entries:
    heartbeat2:
      allow_gateway_injection: true

Restart the messaging gateway and send /pulse in your DM. It shows:

  • whether the scheduler is running;
  • whether injection is allowed, with the exact YAML if it isn't;
  • your local time and quiet hours;
  • today's wakes, messages, cache counts and estimated cost (or token splits).

All other settings are in Settings ▸ Plugins in Hermes Desktop or in config.yaml.
At start a rejected setting falls back to its default; on a live change it keeps
its last good value, and your other changes still apply. /pulse status and
pulse tool results name the ignored setting.

Using it

You, in the chat:

| Command | What it does |
|---|---|
| /pulse | Status: scheduler, consent, local time, quiet hours, today's counts, next due |
| /pulse add in 2h check the deploy | A one-off wake |
| /pulse add every 1d at 09:00 plan my day | A recurring wake at a fixed time |
| /pulse add when silent 24h ask how the trip went | Once per silence of that length |
| /pulse list · /pulse why <id> | Everything armed in this chat; why lists the last 10 trigger keys, states, reasons, matched/created and held-since times, and delivered yes/no |
| /pulse ledger [7d] | Totals over the range: fires, checks, model turns, messages, replies, cache/cost and token splits |
| /pulse pause <id\|all> [for 3d] · resume · cancel | Control one item or all of them |
| /pulse quiet 22:00-07:30 · quiet for 3h · quiet off · quiet default | Quiet hours for this chat; default goes back to your settings |
| /pulse ok <id> | Approve a wake the agent proposed (when Agent can arm wakes is ask_first) |
| /pulse keep\|slow\|stop\|watch <id> | Answer the governor's card for a paused wake (watch pauses it and asks your agent to turn it into a watcher; the next watcher-only trigger or schedule conversion keeps it yours). Any other agent edit of the trigger, timing or note makes it the agent's: it gets the agent's interval floor and cache snap, and the unanswered mute applies. |
| /pulse off · /pulse on | Turn wakes off or back on for this chat. Only you can turn them back on. |

/pulse is a control panel, not conversation. Using it never counts as replying to the agent. Anything else you send counts as a reply, including a reaction or an edit on platforms that report them: it resets the unanswered mute and cancels wakes armed to stop when you reply. A reaction or edit counts only when its chat maps to one HeartBeat session; in a chat shared by several sessions (for example DM threads or topics) it is ignored. The other exceptions are listed under known issues: a message queued while the agent is busy on Signal or SimpleX, and a Codex app-server steer.

Your agent gets one tool, pulse (create · list · update · pause · resume · cancel · respond · off), and the bundled heartbeat2-usage skill. The skill teaches it to:

  • arm a wake only for a named open loop, never just to stay in touch;
  • prefer a watcher to a timer whenever a deterministic check can tell "changed";
  • answer every wake with respond: outcome (no_change · progress · done · blocked · needs_attention), notify, a one-line summary, and optionally next_check and scratch notes for its next wake. done ends the wake. A wake that stays silent (notify=false) counts as a no-op for backoff, whatever its outcome.

A wake turn is a normal turn in your chat: the agent has the same tools as in any other turn there. HeartBeat 2.0 does not restrict them, so arm wakes in conversations whose tools you are happy to have run while you are away.

Every arm returns a receipt with timing, any quiet-hour hold or unanswered mute, and expiry. A granted owner-requested agent reminder adds: "Counts as your own reminder: not muted or capped." The agent relays that receipt instead of promising exact timing; a proposed wake is awaiting your OK.

Watchers available to the agent:

| Watcher | Fires when |
|---|---|
| file | The file changes, or an appended line matches a pattern |
| process | A process exits (PID reuse is detected) |
| url | A GET response changes, or a pattern appears (10 s hard deadline, 256 KB cap) |
| command | A read-only allowlisted command's output changes, it succeeds or fails, or it prints {"fire": true, …} |
| Hermes events | A kanban task completes or blocks, a worker exits, a subagent finishes, the API errors, or someone reacts to or edits a message |

Hermes events are selected by trigger.kind: kanban_completed, kanban_blocked, kanban_worker_exited, subagent_done, api_error, reaction (with an optional emojis filter) and message_edited.

Only probe and event watchers can carry a deadline: it is a one-time "nothing happened" fallback, and the watcher keeps going until it expires. Quiet hours can hold that fallback wake.

Settings

These live under plugins.entries.heartbeat2.settings and appear in Settings ▸ Plugins. The agent's tool can't change them. When a request goes past a bound, the tool tells the agent exactly what it clamped.

| Setting | Key | Default | What it does |
|---|---|---|---|
| Enabled | enabled | on | Master switch for wakes and watchers in this profile. |
| Agent can arm wakes | agent_can_create | yes | yes · ask_first (the agent's wakes wait for /pulse ok <id>) · no |
| Direct messages only | dm_only | on | Refuse group chats. Unknown chat types are refused too. When off, group and topic wakes are recognised behind the gateway's sender prefix. |
| Time zone | timezone | (blank) | IANA name such as Europe/Paris. Blank uses the profile's time zone. |
| Quiet hours | quiet_hours | 22:00-08:00 | HH:MM-HH:MM, may cross midnight; blank for none. Due one-shots wait until the end; recurring ticks inside are skipped. |
| Urgent wakes ignore quiet hours | quiet_hours_urgent | off | Let wakes marked urgent through quiet hours. |
| Wake turns per day | max_model_wakes_per_day | 24 | Per chat, local day. Each wake turn is a full model turn. |
| Wake messages per day | max_messages_per_day | 6 | Per chat, local day. |
| Unanswered messages before mute | max_unanswered | 2 | Wakes you armed, and one-time reminders you ask for in chat, always fire; agent watchers wait for your reply; other agent reminders are dropped. An agent edit revokes the exemption (for a chat reminder, so does an agent resume), except a watcher-only conversion you requested with /pulse watch. |
| Shortest repeat (minutes) | min_interval_minutes | 15 | Floor for every recurring time wake. |
| Shortest agent timer without a watcher (minutes) | agent_ungated_floor_minutes | 55 | Floor for recurring timers the agent arms without a watcher. |
| Minutes between wake turns | wake_spacing_minutes | 10 | Per chat. |
| Fastest watcher check (seconds) | probe_floor_seconds | 60 | Watchers never check more often than this. |
| Active wakes per chat | max_active | 10 | Also capped at 50 across the profile. |
| Longest lifetime (days) | max_lifetime_days | 14 | Recurring wakes, watchers and agent follow-ups expire by then; agent recurring wakes default to 7 days. |
| Longest owner one-time reminder (days) | max_reminder_days | 365 | Owner-requested one-time reminders, including /pulse add, can be due up to this many days out, with max_lifetime_days of grace after a long reminder's due time. The hard end is the sum of both settings. If max_lifetime_days is larger, its existing bound applies instead; maximum 3650 days. |
| Check in after a long silence | owner_silent_checkin | off | Off by default. One check-in per silence, with how to turn it off. |
| Silence before a check-in (hours) | checkin_after_hours | 24 | Doubles for the next silence if a check-in is ignored. |
| Wake-from-wake depth | max_rearm_chain | 2 | How many wakes deep a chain armed inside wake turns may go; a message from you resets it. |
| Empty fires before slowing down | noop_slowdown_after | 3 | A recurring wake that keeps finding nothing doubles its interval after this many. |
| Slow-down multiplier | noop_backoff_factor | 2 | How much each slow-down stretches the interval. |
| Longest slowed interval (hours) | noop_max_backoff_hours | 24 | Slowing down stops at this interval. |
| Empty fires before suggesting a watcher | suggest_gate_after | 4 | The wake text suggests turning the timer into a watcher. |
| Empty fires before pausing | noop_pause_after | 6 | Pauses the wake and asks you once: keep, slow, stop or watch. |
| Ignored messages before pausing | ignored_pause_after | 3 | Delivered messages with no reply in the engagement window. |
| Errors in a row before pausing | error_pause_after | 5 | Errors in a row before pausing. |
| Reply window (hours) | engagement_window_hours | 6 | A reply within this window counts a delivered message as useful. |
| Catch-up window (hours) | catch_up_hours | 6 | A one-shot more than this late after downtime is reported as missed instead of firing. |
| Allowed watcher commands | probe_commands | gh pr view, gh pr checks, gh run view, gh run list, git ls-remote, git status, git log | Comma-separated argv prefixes for command watchers. Blank disables command watchers. |
| Silence check-ins | source_silence | on | Silence check-ins. |
| Process watchers | source_process | on | Process watchers. |
| File watchers | source_file | on | File watchers. |
| URL watchers | source_url | on | URL watchers. |
| Let URL watchers reach private and local addresses | url_allow_private | off | Allow private and local URL targets (loopback, private ranges). Cloud metadata and the whole IPv4 link-local range 169.254.0.0/16 stay blocked, as Hermes treats that range as metadata. |
| Command watchers | source_command | on | Command watchers. |
| Hermes event watchers | source_events | on | Kanban, subagent, API error and reaction events. |
| Tell the agent what's armed | presence_line | on | One short context line on your turns while something is armed. |
| Ledger history (days) | ledger_days | 30 | Ledger history (days). |

Guardrails, briefly

Every guardrail is configurable:

  • Recurring timers repeat at most every 15 minutes, or every 55 minutes when the agent arms one without a watcher. Watchers check at most once a minute.
  • Every wake expires after 14 days at most, except a one-time reminder you ask for, which can be due up to 365 days out (max_reminder_days, never below max_lifetime_days). A reminder due later than 14 days stays deliverable for 14 days after its due time, so downtime at the due time follows the catch-up rule instead of expiring silently.
  • A wake that could not fire before it expires is refused, including an owner-silence wait or a probe's next check without a deadline. On create, an implicit lifetime stretches to fit, up to the maximum. Existing wakes keep their expiry; an edit past it is refused with a hint to set until.expires when extending can help, otherwise it names the maximum lifetime. Quiet hours count at the real dispatch time for one-shot times and watcher deadlines. A timed quiet override chains into the default quiet hours if they still apply when it ends. A one-shot outside the catch-up window is refused; one due now fires on the next check. A timed resume that cannot fit ends the wake with a notice. Receipts name the silence duration and are at most 240 characters; long ones end with an ellipsis.
  • Daily caps hold watchers until capacity frees, including on a new local day; your unedited one-shot reminders, including one-time reminders you ask for in chat, bypass both caps.
  • After 2 unanswered messages, agent watchers hold until you write; other agent timers and built-in check-ins drop their occurrence. Wakes you armed, and one-time reminders you ask for in chat (the agent marks them owner_asked), still fire unless the agent edited their trigger, timing or note; a watcher-only conversion you requested with /pulse watch keeps owner status. Held matches merge, keeping the latest payload; pauses preserve them and expiry is visible in /pulse why.
  • A wake turn that carries untrusted data, such as a log line or a web page, cannot arm new recurring work.
  • Hermes' emergency stop halts every wake.
  • /pulse off stops everything in that chat, and only you can turn it back on.

Cost

A wake turn is an ordinary model turn in that conversation. It is billed like a message, and its size is your conversation's context size: on a long-running agent that can be 100–200k input tokens per wake. Watchers, gates and skipped wakes cost nothing. /pulse status and /pulse ledger show warm/cold wake counts and estimated cost when priced, otherwise the in/cached/written/out split. The ledger also counts no_verdict turns. Start with watchers and long intervals.

Prompt cache

Claude marker routes use Hermes' 5m/1h/auto setting; GPT-5.6+ and GPT-6.x on OpenAI API routes use 30m.
The Codex OAuth route is reported as unknown until learned.
Other routes learn a conservative TTL only after both warm and cold gaps are observed.
Agent recurring intervals near the TTL snap below it with downward jitter, respecting floors.
Owner wakes, cron and anchored repeats retain their timing. Cache lines show the effect and warm interval.

Security and privacy

Disclosure — wakes and watchers run full turns on the conversation's own model, billed like any message; command watchers run allowlisted read-only commands without an approval prompt; URL watchers make the GET requests you configured; state stays in the profile's plugin-data; no telemetry. Command environments use Hermes' sanitizer on an allowlisted base and carry no provider, bot or GitHub tokens; gh uses its own hosts.yml or keyring. URL watchers always block cloud metadata, and block private/local addresses unless url_allow_private is enabled, checking every hop at connect time. File watchers honor Hermes read blocks at arm/update and every check, including rotation. Helpers run in the watcher's profile scope.

Command watchers:

  • use an allowlisted environment that does not pass proxy or custom-CA variables yet;
  • run only argv prefixes from your allowlist. The default is gh pr view/checks, gh run view/list, git ls-remote/status/log;
  • run with no shell and no terminal;
  • refuse output, config, editor, browser and mutating flags and verbs, including gh --jq/-q and --template/-t;
  • have the whole process group killed after 30 seconds.

The argv guard is not a sandbox: only allow programs you trust to be read-only. URL watchers refuse credentials in the URL and send no cookies, auth headers or proxy settings. Wake turns keep the conversation's normal tools and permissions. Full threat model and limits: SECURITY.md.

The default command allowlist still permits gh -R HOST/... and git ls-remote <url> to reach any host the machine can reach; v0.1.1 does not change this exception.

Roadmap

Compatibility and known issues

  • Delivery is to messaging-gateway chats: DMs by default, plus group and topic chats when dm_only is off. Group and topic delivery is not yet tested live (#98). It refuses to arm wakes from the CLI, TUI, Desktop, the API server or cron: a CLI wake would interrupt your turn, and TUI/Desktop have no silence filter for injected turns yet. Desktop and TUI wakes are planned for v0.3 (#8), once Hermes core supports them.
  • A wake turn that fails at the provider (quota, auth, outage) shows Hermes's own failure notice in the chat. The plugin cannot suppress it (NousResearch/hermes-agent#136749). HeartBeat 2.0 settles that wake once Hermes ends the turn (some provider errors leave it open until the agent timeout, #27), then holds every pending wake back (5 min → 15 min → 1 h → 4 h) until a model call on the profile succeeds again. A wake already in flight when the error hits can still show one more notice. A long outage therefore costs about one notice per backoff step, not one per wake.
  • Silence covers the final reply. notify=false keeps the agent's reply out of the chat. On platforms that show tool progress by default (Discord; WhatsApp and Matrix show new tools), each wake turn still posts a progress bubble. Telegram and Slack hide tool progress by default. To keep wakes fully silent there, set display.platforms.<platform>.tool_progress: off. A quiet-injection option is requested upstream (NousResearch/hermes-agent#136825).
  • Messages sent while the agent is busy. On Signal/SimpleX, a message that waits in Hermes' queue while a turn is running (a photo, or any message when busy_input_mode is queue) reaches the agent without a message id, so HeartBeat 2.0 does not count it as your reply. The unanswered mute and the silence check-in treat you as silent until your next message. Text you send mid-turn in the default mode is counted. With the Codex app-server runtime, its native steer (text you send into a running wake turn) is not detected, so a wake that decides to stay silent can leave that steer unanswered.
  • Memory providers see wake text as a user turn (NousResearch/hermes-agent#130622). An external memory provider may store a wake's instructions as if you had written them.
  • Kanban watchers only see tasks whose worker runs in this profile. A task assigned to another profile completes in that profile's worker, out of sight.
  • codex_app_server turns drop per-turn context, so the presence line and governor card don't reach the model on that provider path. Wakes still fire, and silence still works.
  • File watchers poll. A truncate-and-regrow or a replacement that happens entirely between two checks can be missed. Reading catches up within 4 MB per check, and lines over 256 KB are skipped.
  • Proposals (agent_can_create: ask_first). A reminder the agent arms stays a proposal until you send /pulse ok <id>, and its receipt says "Not set yet". The reminder keeps the time you asked for: confirming late does not restart the delay. In the live benchmark, the model sometimes describes the delay as starting at confirmation (1 of 6 replies on GLM 5.3). The receipt is the source of truth.
  • Notes, scratch, summaries and event payloads are stored in plaintext in <profile>/plugin-data/heartbeat2/pulse.db. Don't watch credential files.
  • Live proof so far: one profile (Telegram DM, GLM model) covered a delivered follow-up, a silent verdict and survival across a gateway restart. A live-model journey smoke in an isolated rig (round 6: GLM 5.3, pinned Hermes, loopback Bot API, 3 tries per journey) armed reminders from the owner's own words and delivered them on time in 3 of 3 tries for relative and clock-time reminders. In that run, no wake was armed on a reply or stop turn, and nothing was delivered after a stop. It measures one model on a stand-in Bot API, not a reliability rate. The deterministic benchmark suite simulates the engine; it does not measure live delivery.

Reporting a bug

In the affected messaging-gateway chat, run /pulse status (or /pulse) for scheduler state, consent, counts and next due.
Read recent logs with hermes logs -n 300 | grep -i heartbeat; share only short excerpts with secrets, identifiers and private text removed.
Do not fire a wake, send a DM or change consent just to reproduce a problem.
Ask your Hermes agent to report a problem with HeartBeat 2.0; review its draft before approving publication.
Use the bug report form for plugin bugs.
For unauthorized delivery, cross-profile data exposure or exposed credentials, use a private security advisory.

How it compares

A workflow comparison, not a measured parity claim.

| Option | What it is | When to pick it instead |
|---|---|---|
| HeartBeat 2.0 | Wakes and watchers that you or the agent create. They run in the live DM on its own model, with deterministic watchers, owner-set bounds, structured silence and a no-op governor. | |
| Hermes /heartbeat (core) | One recurring instruction for the current session, with core persistence and idle admission. Silence is a bare marker. | You want one periodic nudge and nothing else. |
| Hermes /loop | Repeats an instruction in the active interactive session. | You are working at the keyboard, not waiting on something. |
| Hermes cron | Scheduled jobs, each in a fresh session with no conversation history, delivering a report. A no_agent job's script can print {"wakeAgent": false} to stay silent. HeartBeat 2.0 borrows that idea for command watchers but fires inside the live conversation. | Independent scheduled tasks that don't need the conversation. |
| hermes-cron | An operating skill and doctor for core cron. | Complementary: it makes cron easier, it isn't a scheduler. |
| jev-cron-gate | A pre-run gate for core cron: a side model answers yes or no on whether a run is pointless, then the run happens in a fresh cron session. HeartBeat 2.0's gates are deterministic, with no side model. | You already run cron jobs and want to skip pointless ones. |
| Alan's Way | An idle check-in for one bound Telegram DM after 2 h of quiet (08:00–22:00), which may take one safe step, bundled with a desktop workspace app and services. It uses the same injection seam. | You want that whole workspace and a single fixed check-in rhythm. |
| OpenClaw heartbeat | A periodic heartbeat inside the main conversation. HeartBeat 2.0 was designed against its documented failure reports; it is not a port, and it makes no claim about OpenClaw's current behaviour. | You run OpenClaw. |

Development

python -m pytest -q                                   # unit tests (network denied)
HERMES_HOME=$(mktemp -d) HERMES_DISABLE_LAZY_INSTALLS=1 HEARTBEAT2_REQUIRE_HERMES=1 \
  PYTHONPATH=/path/to/hermes-agent python -m pytest tests/integration -q   # real Hermes loader
python -m bench.run --baseline --gate --out bench-out  # simulation benchmark + gate

CI runs the unit tests on Ubuntu and macOS with Python 3.11, 3.13 and 3.14. It also runs the real-loader integration test against Hermes v0.21.6 and a pinned main commit. A mutant test applies known-bad changes to a temporary copy of the engine (replayed slots, claim CAS off, flock off, estop off, raw transform, cancel-on-reply off and more), and checks that the benchmark gate fails for each one. That keeps the gate from becoming a tautology.

Credits: claim, refund and coalescing semantics follow Hermes core /heartbeat. Fingerprinting follows Hermes' cron monitor, and estop and busy ordering follow the gateway. OpenClaw's public issue reports shaped the benchmark scenarios.

Built for Hermes Agent by Electric Sheep. Apache-2.0.

Known issues: provider-exhaustion settlement (#27) remains blocked by NousResearch/hermes-agent#137357.

README from the reviewed commit 8a1fc6e; it updates when the author re-pins.

More by 100yenadmin

8 plugins by 100yenadmin in the catalog.

100yenadminhermes-lcm-xLossless memory for long sessions: LCM-X keeps every turn in a local store instead of compacting it away, and the agent can search and expand it. The store is local SQLite (lcm.db) with a recoverable DAG of summaries over it and 16 lcm_* tools for search, recall and expansion (lcm_memory only with LCM_ENTITIES_ENABLED=1). Select it with context.engine: lcm-x. Upgrading from hermes-lcm (breaking in v0.24.0): stop Hermes, then in plugins.enabled replace hermes-lcm with hermes-lcm-x and run exactly one LCM copy (the install script prints the steps; the old engine name lcm still resolves with a deprecation warning); lcm.db and the lcm: config block are unchanged. Summaries use the model Hermes is configured with. Disclosure — local semantic recall is on by default once its dependency companion is installed (hermes plugins install electricsheephq/lcm-x/companions/hermes-lcm-x-local-embeddings --ref <the LCM-X commit>; it carries fastembed): summaries and message chunks are embedded in-process with BAAI/bge-small-en-v1.5 after a one-time model download, in the background, and stay on the machine. Without the companion, recall answers from full text and lcm_doctor prints the install command; on hosts with 2 or fewer effective CPUs the default answers from full text unless LCM_EMBEDDINGS_ENABLED=true; LCM_EMBEDDINGS_ENABLED=false turns semantic recall off. Cloud embedding providers (Voyage via VOYAGE_API_KEY, an OpenAI-compatible endpoint, or a local Ollama) send message text to that provider and run only with LCM_EMBEDDINGS_ENABLED=true and an explicit model. For known cloud providers the provider-bound copy is privacy-transformed by default (LCM_EMBEDDING_PRIVACY_ENABLED=false opts out), while lcm.db keeps the raw text. Every turn (user, assistant and tool output) is stored in plaintext in lcm.db (secret redaction at rest is opt-in); on gateway platforms the sender's platform user ID is recorded with each user message by default (LCM_SENDER_CAPTURE=0 turns it off), and oversized tool outputs are by default written as plaintext files under HERMES_HOME/lcm-large-outputs and replaced in the model's context by recoverable references. The engine also reads Hermes' state.db read-only for session lineage, reads private AIAgent attributes via the call stack on Hermes builds without subagent hooks, and runs git on its own install directory for lcm_status/lcm_doctor; tiktoken may fetch its BPE file on first use.Communitymemory114100yenadminfish-audioLifelike voices for your agent: Fish Audio's expressive text-to-speech in 80+ languages, plus transcription, voice cloning and a voice library. The official Fish Audio plugin: Fish's Drama 3 model with word-level direction, speech-to-text, Fish Audio's community voice library, voice cloning and voice design, with a Voices page in Hermes Desktop. Fish Audio becomes a Hermes speech and transcription provider for voice replies, read-aloud, voice notes and Desktop dictation, with sentence-by-sentence streaming in CLI voice mode and Desktop, and word-by-word live dictation captions on Hermes 0.21.6+ (stt.streaming: true). Adds the fish_speak, fish_voices and fish_transcribe tools, /fish and hermes fish commands, and four skills. Disclosure — sends your Fish API key and the text, audio (with file names) and voice titles, tags, descriptions, cover images and design instructions you ask it to process to api.fish.audio, or to the base_url you configure, and reads your Fish wallet for balances and model choice; with live dictation on, microphone audio streams to Fish's native realtime speech-to-text at api.fish.audio (transcribe-stream-1, Preview) while you speak, billed $0.36 per hour of open session, per second, including silence; a session opens with the first audio and closes once Fish confirms the last words (the plugin stops waiting after 15 seconds), or 30 seconds after the last audio of a cancelled recording; the live_transcription: compat setting uses Fish's older realtime endpoint instead, billed per committed phrase rounded up to a second; synthesis, transcription, cloning and voice design are paid requests billed to your Fish account, except the free s2.1-pro-free model (free through 30 November 2026), whose requests Fish may use to improve its models. The key comes from the profile's secret scope (Hermes stores it in the profile .env; a single-profile install may also read FISH_API_KEY from the environment). Generated audio and subtitles are written to Hermes's audio cache; clone samples and recordings it uploads must be regular audio files, and Hermes config, secret and credential paths are refused. Cloning and deleting voices from chat or the model tools go through Hermes's approval gate and follow your approval settings. The Desktop Voices page and a Fish Audio settings page add gateway routes under /api/plugins/fish-audio/, behind the Hermes dashboard's existing authentication. On the Voices page, previews, clones and voice designs are billed; cloning a voice or saving a designed one creates a voice in your Fish account; cloning and deleting are confirmed on the page (a speaker-consent box, a typed voice name) instead of through Hermes's approval gate; and Use sets the profile's Fish voice, and its speech provider when none is set. The settings page and /fish alias can set or reset the profile's Fish model and voice and save up to 50 voice names in the profile's config. Preview and design audio files are deleted from the gateway once returned; a design's audio is held in gateway memory so you can save it, until it is saved, a later design or save finds it over an hour old, or the gateway stops; clone uploads are deleted after the clone, or by the next upload once idle for 15 minutes. With a key set, Desktop reads your Fish wallet when an agent is selected and about every five minutes, for the status-bar credit; favourites stay in Desktop's plugin storage on that computer, and fetched previews stay in the window's memory until it closes. Includes a report-a-problem skill: the agent drafts a bug report and files it to this plugin's repository as a public GitHub issue or comment with your gh login only after you approve the exact text; reports leave out keys, credit amounts and your own voice ids, and security problems go to the repository's private advisory form instead. No telemetry.Communityvoice70100yenadminhermes-teammatesGive your agent a team: named subagents with their own instructions, tools and model, which it assigns work to, checks on and steers mid-task. The operator defines the roster; the agent follows up with a digest of earlier results and can work a Kanban teammates lane with a review handoff. Launches subagents only through the public ctx.subagent_lifecycle API. Stock Hermes; no patched core. Disclosure — adds six tools (teammates_roster, teammate_assign, teammate_check, teammate_wait, teammate_message, teammate_stop) and a /teammates command; the model picks only a teammate name, and the operator's config decides what it means. Teammates run as in-process Hermes subagents using credentials selected by Hermes from the parent, delegation settings or an operator-configured teammate route where supported. Toolsets narrow the parent's tools at toolset granularity only (the file toolset includes write_file and patch); Hermes can add inherited MCP toolsets back unless delegation.inherit_mcp_toolsets is false. A teammate with invalid settings refuses to launch. A delegated child cannot start teammates. Steering uses the lifecycle steer where Hermes provides it, otherwise delegate_task's steer action with the turn-bound parent. Under plugins.isolation: host every tool except the roster refuses. Optionally the roster is read from an operator-named YAML file (roster_file, relative to the profile home or absolute, so several profiles can share one); the plugin never writes it. It keeps a per-profile SQLite run ledger in its plugin-data folder (goals, statuses, clipped summaries, role hashes and available token receipts; no full transcripts). It does not read credential stores. Where supported in-process, subagent_start and post_api_request hooks collect token receipts for mapped child sessions. For a card assigned to its non-profile lane, it claims the card through the Kanban DB, heartbeats the claim and its worker, then moves the card to review without a reviewer, or blocks it on failure, a stop or an unavailable result; it never completes a card. On Hermes builds with plugin prompt sections it adds one short paragraph (under 600 characters) about the Kanban lane to new top-level session prompts. The paragraph is rendered from its settings, carries no conversation content, and is left out without a usable teammate, for delegated children and under host isolation. It runs one in-process monitor thread per run to retain finished results and, for lane runs, keep the claim alive; no network calls, child processes or self-updates of its own. Completion-notice code exists but never injects on current Hermes because the host cannot bind notices to the receiving session; use teammate_wait instead. Includes a report-a-problem skill: the agent drafts a bug report and files it to this repository with gh only after the user approves the draft.Communityautomation62100yenadminplan-modeClaude Code-style plan mode for Hermes: the agent explores and writes a plan, and edits nothing else until you approve it. It works on every interactive Hermes surface: /plan or /planmode on blocks mutating Hermes tools dispatched through pre_tool_call (writes outside .hermes/plans, terminal, code execution, delegation, MCP) while the agent explores, asks clarifying questions and writes a plan. The agent shows the complete plan, submits it with plan_mode(submit) and one approval card asks: approve and auto-accept edits, approve and review each file edit (when Hermes approvals are on, every write_file and patch then gets Hermes' approval prompt with its path and a preview), or keep planning; Other sends feedback, and the revised plan says what changed since the last revision. Once approved, it implements in the same turn and tracks todo_list progress. To change the plan yourself, keep planning, run /planmode edit, save, then /planmode show and /planmode approve; the text you were shown is saved as an approved snapshot, so later edits to the file do not change the approval. The classic profile (plan_profile: classic), approval_card: false and surfaces without a card use Hermes' own approval prompt (gateway buttons, the TUI/Desktop card, the CLI panel or /approve). When yolo or approvals.mode: off would auto-approve, it asks through clarify instead, so a person still decides. By default plans are compact and decision-complete, the lean profile asks at most three targeted questions, and the agent does not commit unless asked (plan_style, plan_profile, allow_commits and plan_skill settings). Classic CLI enforced on every supported Hermes; gateway, TUI and Desktop from Hermes v2026.9.24 (0.21.5), and refused on older builds; 0.21.6 recommended. On the interactive classic CLI, Hermes' inline diff shows a new plan file in full, so the agent does not retype it (write_once). A report-a-problem skill drafts a bug report and files it with gh only after you approve the draft. Disclosure — plan-mode does not enforce Codex app-server exec/applyPatch or TUI /background and btw side agents. A plan is approved only on its approval card (a clarify question only the plugin issues and checks), through Hermes' own approval prompt, or with /planmode approve. It reads nine groups of internal Hermes seams for session identity, workspace, profile, skills.inline_shell and the classic CLI's inline diff, each guarded with a documented fallback. It creates .hermes/plans under the session working directory, reads plan files there, saves the approved text of an edited plan as a new file under .hermes/plans/approved, and keeps per-session state in plugin state. It adds a turn note while planning or executing, a ≤200-char system-prompt hint, and a one-line footer to short replies on chat platforms. When a turn ends while a submitted plan still awaits approval and no approval card was answered, it adds one line to that reply on any surface, whatever its length, saying how to approve (unless footer is off). When you pick "Always" on a plan approval or a per-edit review, Hermes core writes a plugin_rule:plan-mode entry to command_allowlist in config.yaml; each entry is keyed to one plan revision, so it cannot approve another plan. With allow_gateway_injection: true it queues one message to start work after a typed /planmode approve. On TUI, Desktop and hermes serve sessions (lean profile, plan_once) it reads the visible reply text from post_api_request while planning and saves its <proposed_plan> block as the plan file. On the interactive classic CLI (lean, write_once) it checks that Hermes printed a newly written plan file in full, then tells the agent not to retype it. Its report-a-problem skill has the agent file to the plugin repository with gh only after the user approves the exact saved draft. The plugin itself makes no network calls, launches no subprocesses and has no self-updater.Communitytools51