feat(heartbeat): add directPolicy and restore default direct delivery

This commit is contained in:
Peter Steinberger
2026-02-26 03:56:40 +01:00
parent ee594e2fdb
commit 8a006a3260
14 changed files with 149 additions and 16 deletions

View File

@@ -800,6 +800,7 @@ Periodic heartbeat runs.
includeReasoning: false,
session: "main",
to: "+15555550123",
directPolicy: "allow", // allow (default) | block
target: "none", // default: none | options: last | whatsapp | telegram | discord | ...
prompt: "Read HEARTBEAT.md if it exists...",
ackMaxChars: 300,
@@ -812,7 +813,7 @@ Periodic heartbeat runs.
- `every`: duration string (ms/s/m/h). Default: `30m`.
- `suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs.
- Heartbeats never deliver to direct/DM chat targets when the destination can be classified as direct (for example `user:<id>`, Telegram user chat IDs, or WhatsApp direct numbers/JIDs); those runs still execute, but outbound delivery is skipped.
- `directPolicy`: direct/DM delivery policy. `allow` (default) permits direct-target delivery. `block` suppresses direct-target delivery and emits `reason=dm-blocked`.
- Per-agent: set `agents.list[].heartbeat`. When any agent defines `heartbeat`, **only those agents** run heartbeats.
- Heartbeats run full agent turns — shorter intervals burn more tokens.

View File

@@ -239,7 +239,8 @@ When validation fails:
```
- `every`: duration string (`30m`, `2h`). Set `0m` to disable.
- `target`: `last` | `whatsapp` | `telegram` | `discord` | `none` (DM-style `user:<id>` heartbeat delivery is blocked)
- `target`: `last` | `whatsapp` | `telegram` | `discord` | `none`
- `directPolicy`: `allow` (default) or `block` for DM-style heartbeat targets
- See [Heartbeat](/gateway/heartbeat) for the full guide.
</Accordion>

View File

@@ -215,7 +215,9 @@ Use `accountId` to target a specific account on multi-account channels like Tele
- `last`: deliver to the last used external channel.
- explicit channel: `whatsapp` / `telegram` / `discord` / `googlechat` / `slack` / `msteams` / `signal` / `imessage`.
- `none` (default): run the heartbeat but **do not deliver** externally.
- Direct/DM heartbeat destinations are blocked when target parsing identifies a direct chat (for example `user:<id>`, Telegram user chat IDs, or WhatsApp direct numbers/JIDs).
- `directPolicy`: controls direct/DM delivery behavior:
- `allow` (default): allow direct/DM heartbeat delivery.
- `block`: suppress direct/DM delivery (`reason=dm-blocked`).
- `to`: optional recipient override (channel-specific id, e.g. E.164 for WhatsApp or a Telegram chat id). For Telegram topics/threads, use `<chatId>:topic:<messageThreadId>`.
- `accountId`: optional account id for multi-account channels. When `target: "last"`, the account id applies to the resolved last channel if it supports accounts; otherwise it is ignored. If the account id does not match a configured account for the resolved channel, delivery is skipped.
- `prompt`: overrides the default prompt body (not merged).
@@ -236,7 +238,7 @@ Use `accountId` to target a specific account on multi-account channels like Tele
- `session` only affects the run context; delivery is controlled by `target` and `to`.
- To deliver to a specific channel/recipient, set `target` + `to`. With
`target: "last"`, delivery uses the last external channel for that session.
- Heartbeat deliveries never send to direct/DM targets when the destination is identified as direct; those runs still execute, but outbound delivery is skipped.
- Heartbeat deliveries allow direct/DM targets by default. Set `directPolicy: "block"` to suppress direct-target sends while still running the heartbeat turn.
- If the main queue is busy, the heartbeat is skipped and retried later.
- If `target` resolves to no external destination, the run still happens but no
outbound message is sent.

View File

@@ -174,7 +174,7 @@ Common signatures:
- `cron: timer tick failed` → scheduler tick failed; check file/log/runtime errors.
- `heartbeat skipped` with `reason=quiet-hours` → outside active hours window.
- `heartbeat: unknown accountId` → invalid account id for heartbeat delivery target.
- `heartbeat skipped` with `reason=dm-blocked` → heartbeat target resolved to a DM-style `user:<id>` destination (blocked by design).
- `heartbeat skipped` with `reason=dm-blocked` → heartbeat target resolved to a DM-style destination while `agents.defaults.heartbeat.directPolicy` (or per-agent override) is set to `block`.
Related: