diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index eebd74f0f8e6..74e4dfbc0346 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -359,7 +359,6 @@ jobs:
cat "${temp_root}/out.log"
exit 1
fi
- test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"
if [ "${status}" -ne 0 ]; then
cat "${temp_root}/out.log"
fi
@@ -568,7 +567,6 @@ jobs:
cat "${temp_root}/out.log"
exit 1
fi
- test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"
if [ "${status}" -ne 0 ]; then
cat "${temp_root}/out.log"
fi
diff --git a/docs/.i18n/glossary.zh-CN.json b/docs/.i18n/glossary.zh-CN.json
index 76b6f498ce82..168e678a19bc 100644
--- a/docs/.i18n/glossary.zh-CN.json
+++ b/docs/.i18n/glossary.zh-CN.json
@@ -1634,5 +1634,13 @@
{
"source": "Gateway security",
"target": "Gateway 安全"
+ },
+ {
+ "source": "Retired HEARTBEAT.md workspace file",
+ "target": "已弃用的 HEARTBEAT.md 工作区文件"
+ },
+ {
+ "source": "Cron CLI",
+ "target": "Cron CLI"
}
]
diff --git a/docs/automation/hooks.md b/docs/automation/hooks.md
index fe0cd0cea285..f31846e023c4 100644
--- a/docs/automation/hooks.md
+++ b/docs/automation/hooks.md
@@ -259,7 +259,7 @@ Extracts the last user/assistant messages (default 15, configurable with `hooks.
}
```
-`patterns` and `files` are accepted as aliases of `paths`. Paths resolve relative to the workspace and must stay inside it. Only recognized bootstrap basenames are loaded (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `MEMORY.md`).
+`patterns` and `files` are accepted as aliases of `paths`. Paths resolve relative to the workspace and must stay inside it. Only recognized bootstrap basenames are loaded (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `BOOTSTRAP.md`, `MEMORY.md`).
diff --git a/docs/automation/standing-orders.md b/docs/automation/standing-orders.md
index ac4e3616945a..3deeb04a6921 100644
--- a/docs/automation/standing-orders.md
+++ b/docs/automation/standing-orders.md
@@ -29,7 +29,7 @@ Each program specifies:
The agent loads these instructions every session via the workspace bootstrap files (see [Agent Workspace](/concepts/agent-workspace) for the full list of auto-injected files) and executes against them, combined with [cron jobs](/automation/cron-jobs) for time-based enforcement.
-Put standing orders in `AGENTS.md` to guarantee they're loaded every session. The workspace bootstrap automatically injects `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, and `MEMORY.md` - but not arbitrary files in subdirectories.
+Put standing orders in `AGENTS.md` to guarantee they're loaded every session. The workspace bootstrap automatically injects `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `BOOTSTRAP.md`, and `MEMORY.md` - but not arbitrary files in subdirectories.
## Anatomy of a standing order
diff --git a/docs/cli/onboard.md b/docs/cli/onboard.md
index 7aced66ad33f..b43de7ec6d18 100644
--- a/docs/cli/onboard.md
+++ b/docs/cli/onboard.md
@@ -291,7 +291,7 @@ openclaw onboard --non-interactive \
- Unless you pass `--skip-health`, onboarding waits for a reachable local gateway before exiting successfully.
- `--install-daemon` starts the managed gateway install path first. Without it, a local gateway must already be running (for example `openclaw gateway run`).
- `--skip-health` skips the wait if you only want config/workspace/bootstrap writes in automation.
-- `--skip-bootstrap` sets `agents.defaults.skipBootstrap: true` and skips creating `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, and `BOOTSTRAP.md`.
+- `--skip-bootstrap` sets `agents.defaults.skipBootstrap: true` and skips creating `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md`.
- On native Windows, `--install-daemon` tries Scheduled Tasks first and falls back to a per-user Startup-folder login item if task creation is denied.
### Interactive ref mode
diff --git a/docs/concepts/agent-workspace.md b/docs/concepts/agent-workspace.md
index 5cd185ebfe05..dae040fd0332 100644
--- a/docs/concepts/agent-workspace.md
+++ b/docs/concepts/agent-workspace.md
@@ -79,9 +79,6 @@ Standard files OpenClaw expects inside the workspace:
Notes about your local tools and conventions. Does not control tool availability; it is only guidance.
-
- Optional tiny checklist for heartbeat runs. Keep it short to avoid token burn.
-
Optional startup checklist run automatically on gateway restart (when [internal hooks](/automation/hooks) are enabled). Keep it short; use the message tool for outbound sends.
@@ -140,7 +137,7 @@ Run these steps on the machine where the Gateway runs (that is where the workspa
```bash
cd ~/.openclaw/workspace
git init
- git add AGENTS.md SOUL.md TOOLS.md IDENTITY.md USER.md HEARTBEAT.md memory/
+ git add AGENTS.md SOUL.md TOOLS.md IDENTITY.md USER.md memory/
git commit -m "Add agent workspace"
```
@@ -238,7 +235,7 @@ Suggested `.gitignore` starter:
## Related
-- [Heartbeat](/gateway/heartbeat) - HEARTBEAT.md workspace file
+- [Heartbeat](/gateway/heartbeat) - heartbeat monitors and cron scratch
- [Sandboxing](/gateway/sandboxing) - workspace access in sandboxed environments
- [Session](/concepts/session) - session storage paths
- [Standing orders](/automation/standing-orders) - persistent instructions in workspace files
diff --git a/docs/concepts/agent.md b/docs/concepts/agent.md
index d22436c79874..d0415c465274 100644
--- a/docs/concepts/agent.md
+++ b/docs/concepts/agent.md
@@ -37,7 +37,6 @@ Inside the workspace, OpenClaw expects these user-editable files:
| `TOOLS.md` | User-maintained tool notes and conventions |
| `IDENTITY.md` | Agent name/vibe/emoji |
| `USER.md` | User profile + preferred address |
-| `HEARTBEAT.md` | Heartbeat-specific instructions |
| `BOOTSTRAP.md` | One-time first-run ritual (deleted after completion) |
| `MEMORY.md` | Root long-term memory file, if present |
diff --git a/docs/concepts/context.md b/docs/concepts/context.md
index f641ccce1be5..9ff58f407a08 100644
--- a/docs/concepts/context.md
+++ b/docs/concepts/context.md
@@ -47,7 +47,6 @@ Injected workspace files:
- TOOLS.md: TRUNCATED | raw 54,210 chars (~13,553 tok) | injected 20,962 chars (~5,241 tok)
- IDENTITY.md: OK | raw 211 chars (~53 tok) | injected 211 chars (~53 tok)
- USER.md: OK | raw 388 chars (~97 tok) | injected 388 chars (~97 tok)
-- HEARTBEAT.md: MISSING | raw 0 | injected 0
- BOOTSTRAP.md: OK | raw 0 chars (~0 tok) | injected 0 chars (~0 tok)
Skills list (system prompt text): 2,184 chars (~546 tok) (12 skills)
@@ -122,7 +121,6 @@ By default, OpenClaw injects a fixed set of workspace files (if present):
- `TOOLS.md`
- `IDENTITY.md`
- `USER.md`
-- `HEARTBEAT.md`
- `BOOTSTRAP.md` (first-run only)
Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (default `20000` chars). OpenClaw also enforces a total bootstrap injection cap across files with `agents.defaults.bootstrapTotalMaxChars` (default `60000` chars). `/context` shows **raw vs injected** sizes and whether truncation happened.
diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md
index edbe2d01e5d8..ea8b4f8469ce 100644
--- a/docs/concepts/system-prompt.md
+++ b/docs/concepts/system-prompt.md
@@ -94,13 +94,12 @@ Bootstrap files are resolved from the active workspace and routed to the prompt
- `TOOLS.md`
- `IDENTITY.md`
- `USER.md`
-- `HEARTBEAT.md`
- `BOOTSTRAP.md` (only on brand-new workspaces)
- `MEMORY.md` when present
-On the native Codex harness, OpenClaw avoids repeating stable workspace files in every user turn. Codex loads `AGENTS.md` through its own project-doc discovery. `TOOLS.md` is forwarded as inherited Codex developer instructions. `SOUL.md`, `IDENTITY.md`, and `USER.md` are forwarded as turn-scoped collaboration developer instructions so native Codex sub-agents do not inherit them. `HEARTBEAT.md` content is not injected directly; heartbeat turns get a collaboration-mode note pointing to the file when it exists and is non-empty. `MEMORY.md` content is not pasted into every native Codex turn either: when memory tools are available for the workspace, Codex turns get a small workspace-memory note directing the model to `memory_search` or `memory_get`. If tools are disabled, memory search is unavailable, or the active workspace differs from the agent memory workspace, `MEMORY.md` falls back to the normal bounded turn-context path. `BOOTSTRAP.md` keeps the normal turn-context role.
+On the native Codex harness, OpenClaw avoids repeating stable workspace files in every user turn. Codex loads `AGENTS.md` through its own project-doc discovery. `TOOLS.md` is forwarded as inherited Codex developer instructions. `SOUL.md`, `IDENTITY.md`, and `USER.md` are forwarded as turn-scoped collaboration developer instructions so native Codex sub-agents do not inherit them. `MEMORY.md` content is not pasted into every native Codex turn either: when memory tools are available for the workspace, Codex turns get a small workspace-memory note directing the model to `memory_search` or `memory_get`. If tools are disabled, memory search is unavailable, or the active workspace differs from the agent memory workspace, `MEMORY.md` falls back to the normal bounded turn-context path. `BOOTSTRAP.md` keeps the normal turn-context role.
-On non-Codex harnesses, bootstrap files compose into the OpenClaw prompt per their existing gates. `HEARTBEAT.md` is omitted on normal runs when heartbeats are disabled for the default agent or `agents.defaults.heartbeat.includeSystemPromptSection` is false. Keep injected files concise, especially non-Codex `MEMORY.md`: it should stay a curated long-term summary, with detailed daily notes in `memory/*.md` retrievable on demand via `memory_search` / `memory_get`. Oversized non-Codex `MEMORY.md` files increase prompt usage and can be partially injected under the bootstrap file limits below.
+On non-Codex harnesses, bootstrap files compose into the OpenClaw prompt per their existing gates. Keep injected files concise, especially non-Codex `MEMORY.md`: it should stay a curated long-term summary, with detailed daily notes in `memory/*.md` retrievable on demand via `memory_search` / `memory_get`. Oversized non-Codex `MEMORY.md` files increase prompt usage and can be partially injected under the bootstrap file limits below.
`memory/*.md` daily files are **not** part of the normal bootstrap Project Context. On ordinary turns they are accessed on demand via `memory_search` / `memory_get`, so they do not count against the context window unless the model explicitly reads them. Bare `/new` and `/reset` turns are the exception: the runtime can prepend recent daily memory as a one-shot startup-context block for that first turn.
diff --git a/docs/docs_map.md b/docs/docs_map.md
index dc4f8fc158dc..6334d985d6a1 100644
--- a/docs/docs_map.md
+++ b/docs/docs_map.md
@@ -9057,7 +9057,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
- Route: /reference/templates/HEARTBEAT
- Headings:
- - H1: HEARTBEAT.md template
+ - H1: HEARTBEAT.md is retired
- H2: Related
## reference/templates/IDENTITY.dev.md
diff --git a/docs/help/faq.md b/docs/help/faq.md
index cda33ddbd3f6..fcefe79b3663 100644
--- a/docs/help/faq.md
+++ b/docs/help/faq.md
@@ -477,7 +477,7 @@ First-run Q&A - install, onboard, auth routes, subscriptions, initial failures -
These live in the **agent workspace**, not `~/.openclaw`.
- - **Workspace (per agent)**: `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `MEMORY.md`, `memory/YYYY-MM-DD.md`, optional `HEARTBEAT.md`. Lowercase root `memory.md` is legacy repair input only; `openclaw doctor --fix` can merge it into `MEMORY.md` when both exist.
+ - **Workspace (per agent)**: `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `MEMORY.md`, `memory/YYYY-MM-DD.md`. Lowercase root `memory.md` is legacy repair input only; `openclaw doctor --fix` can merge it into `MEMORY.md` when both exist.
- **State dir (`~/.openclaw`)**: config, channel/provider state, auth profiles, sessions, logs, shared skills (`~/.openclaw/skills`).
Default workspace is `~/.openclaw/workspace`, configurable:
@@ -1036,7 +1036,7 @@ First-run Q&A - install, onboard, auth routes, subscriptions, initial failures -
}
```
- If `HEARTBEAT.md` exists but is effectively empty (only blank lines, Markdown/HTML comments, ATX headings, fence markers, or empty list-item stubs), OpenClaw skips the heartbeat run to save API calls. If the file is missing, the heartbeat still runs and the model decides what to do.
+ Heartbeat instructions live in the monitor's cron scratch. Effectively empty scratch skips the heartbeat run to save API calls; without scratch, the heartbeat still runs and the model decides what to do.
Per-agent overrides use `agents.entries.*.heartbeat`. Docs: [Heartbeat](/gateway/heartbeat).
diff --git a/docs/plugins/codex-harness-reference.md b/docs/plugins/codex-harness-reference.md
index aa75f473f2e9..a6642e66ab1b 100644
--- a/docs/plugins/codex-harness-reference.md
+++ b/docs/plugins/codex-harness-reference.md
@@ -685,9 +685,9 @@ bootstrap files as developer instructions, but not identically:
- The compact loaded OpenClaw skills list is also forwarded as turn-scoped
collaboration developer instructions, so native Codex subagents do not
inherit it either.
-- `HEARTBEAT.md` content is not injected; heartbeat turns get a
- collaboration-mode pointer to read the file when it exists and is
- non-empty.
+- Heartbeat turns receive generic initiative guidance through collaboration
+ mode. Monitor cron scratch is appended to the heartbeat prompt instead of
+ injected as workspace context.
- `MEMORY.md` content from the configured agent workspace is not pasted into
native Codex turn input when memory tools are available for that
workspace; when it exists, the harness adds a small workspace-memory
diff --git a/docs/plugins/codex-harness-runtime.md b/docs/plugins/codex-harness-runtime.md
index 8ddc0f5162c3..3198415734bb 100644
--- a/docs/plugins/codex-harness-runtime.md
+++ b/docs/plugins/codex-harness-runtime.md
@@ -124,8 +124,8 @@ Codex heartbeat turns get `heartbeat_respond` in the searchable OpenClaw tool
catalog by default so the agent can record whether the wake should stay quiet
or notify. Heartbeat initiative guidance is sent as a Codex collaboration-mode
developer instruction scoped to the heartbeat turn; ordinary chat turns stay
-in Codex Default mode. When `HEARTBEAT.md` is non-empty, the heartbeat
-instructions point Codex at the file instead of inlining its contents.
+in Codex Default mode. The heartbeat monitor's cron scratch is appended to the
+heartbeat prompt when present.
## Hook boundaries
diff --git a/docs/reference/prompt-caching.md b/docs/reference/prompt-caching.md
index 1943dfd9a610..045640d99dbc 100644
--- a/docs/reference/prompt-caching.md
+++ b/docs/reference/prompt-caching.md
@@ -129,11 +129,11 @@ If a provider does not support any of the above cache modes, `cacheRetention` ha
## System-prompt cache boundary
-OpenClaw splits the system prompt into a **stable prefix** and a **volatile suffix** at an internal cache-prefix boundary. Content above the boundary (tool definitions, skills metadata, workspace files) is ordered to stay byte-identical across turns. Content below the boundary (for example `HEARTBEAT.md`, runtime timestamps, other per-turn metadata) can change without invalidating the cached prefix.
+OpenClaw splits the system prompt into a **stable prefix** and a **volatile suffix** at an internal cache-prefix boundary. Content above the boundary (tool definitions, skills metadata, workspace files) is ordered to stay byte-identical across turns. Content below the boundary (for example runtime timestamps and other per-turn metadata) can change without invalidating the cached prefix.
Key design choices:
-- Stable workspace project-context files are ordered before `HEARTBEAT.md` so heartbeat churn does not bust the stable prefix.
+- Stable workspace project-context files are ordered before volatile per-turn metadata so routine churn does not bust the stable prefix.
- The boundary applies across Anthropic-family, OpenAI-family, Google, and CLI transport shaping, so all supported providers benefit from the same prefix stability.
- Codex Responses and Anthropic Vertex requests are routed through boundary-aware cache shaping so cache reuse stays aligned with what providers actually receive.
- System-prompt fingerprints are normalized (whitespace, line endings, hook-added context, runtime capability ordering) so semantically unchanged prompts share cache across turns.
diff --git a/docs/reference/templates/AGENTS.dev.md b/docs/reference/templates/AGENTS.dev.md
index 04f5248da257..a56192116921 100644
--- a/docs/reference/templates/AGENTS.dev.md
+++ b/docs/reference/templates/AGENTS.dev.md
@@ -51,7 +51,7 @@ Before proposing or building a custom system, feature, workflow, tool, integrati
## Heartbeats (optional)
-- HEARTBEAT.md can hold a tiny checklist for heartbeat runs; keep it small.
+- The heartbeat monitor's cron scratch can hold a tiny checklist for heartbeat runs; keep it small.
## Customize
diff --git a/docs/reference/templates/AGENTS.md b/docs/reference/templates/AGENTS.md
index 183028982a14..115220cbc0b6 100644
--- a/docs/reference/templates/AGENTS.md
+++ b/docs/reference/templates/AGENTS.md
@@ -97,7 +97,7 @@ Skills provide your tools. When you need one, check its `SKILL.md`. Keep local n
## Heartbeats - Be Proactive
-When you receive a heartbeat poll (message matches the configured heartbeat prompt), don't just reply `HEARTBEAT_OK` every time. You're free to edit `HEARTBEAT.md` with a short checklist or reminders - keep it small to limit token burn.
+When you receive a heartbeat poll (message matches the configured heartbeat prompt), don't just reply `HEARTBEAT_OK` every time. Keep a short checklist or reminders in the heartbeat monitor's cron scratch; use `openclaw cron list --all` to find the monitor job, then `openclaw cron scratch --set "..."` to update it. Keep it small to limit token burn.
See [Scheduled Tasks (Cron) vs Heartbeat](/automation#scheduled-tasks-cron-vs-heartbeat) for the full decision table. Short version: heartbeat batches periodic checks with full session context on approximate timing (default every 30 minutes); cron is for exact timing, isolated runs, a different model, or one-shot reminders.
diff --git a/docs/reference/templates/BOOTSTRAP.md b/docs/reference/templates/BOOTSTRAP.md
index 343d718844e5..ba60f7434b58 100644
--- a/docs/reference/templates/BOOTSTRAP.md
+++ b/docs/reference/templates/BOOTSTRAP.md
@@ -9,7 +9,7 @@ read_when:
_You just woke up. Keep this first conversation short and make it yours._
-OpenClaw only seeds this file into a brand-new workspace, alongside `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, and `HEARTBEAT.md`. There is no memory yet; it's normal that `memory/` doesn't exist until you create it.
+OpenClaw only seeds this file into a brand-new workspace, alongside `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, and `USER.md`. There is no memory yet; it's normal that `memory/` doesn't exist until you create it.
Complete these three beats. Do not turn them into a questionnaire or a long
biography.
diff --git a/docs/reference/templates/HEARTBEAT.md b/docs/reference/templates/HEARTBEAT.md
index 0a39ecedca0e..825a45658485 100644
--- a/docs/reference/templates/HEARTBEAT.md
+++ b/docs/reference/templates/HEARTBEAT.md
@@ -1,29 +1,28 @@
---
-summary: "Workspace template for HEARTBEAT.md"
-title: "HEARTBEAT.md template"
+summary: "Migration guide for the retired HEARTBEAT.md workspace file"
+title: "Retired HEARTBEAT.md workspace file"
read_when:
- - Bootstrapping a workspace manually
+ - Migrating an older workspace that still has HEARTBEAT.md
---
-# HEARTBEAT.md template
+# HEARTBEAT.md is retired
-`HEARTBEAT.md` lives in the agent workspace and holds the periodic heartbeat checklist. Keep it empty, or with only whitespace, Markdown comments, ATX headings, empty list stubs (`- `, `* [ ]`), or fence markers, to make OpenClaw skip the heartbeat model call entirely (`reason=empty-heartbeat-file`).
+OpenClaw no longer creates `HEARTBEAT.md` in new workspaces or reads it at runtime. Heartbeat instructions now live in the system-owned monitor's cron scratch in the shared state database.
-Shipped default content:
+Manage the current scratch with the monitor job id from `openclaw cron list --all`:
-```markdown
-
-
-# Keep this file empty (or with only comments) to skip heartbeat API calls.
-
-# Add a short checklist below when the heartbeat should inspect shared context.
+```bash
+openclaw cron scratch
+openclaw cron scratch --set "..."
+openclaw cron scratch --file notes.md
+openclaw cron scratch --unset
```
-Add a short checklist below the comment lines only when one heartbeat turn should inspect the items together. Keep it small: heartbeat runs read this file every tick (default every 30 minutes), so bloated instructions burn tokens on every wake.
-
-For independently scheduled or due-only checks, create [cron jobs](/automation/cron-jobs). Heartbeat scratch no longer supports scheduler syntax. Run `openclaw doctor --fix` to convert older `tasks:` blocks.
+If an older workspace still contains `HEARTBEAT.md`, run `openclaw doctor --fix`. Doctor imports its instructions into monitor scratch, converts valid legacy `tasks:` entries into cron jobs, archives the original under the state directory, and removes the workspace file.
## Related
- [Heartbeat](/gateway/heartbeat)
+- [Cron CLI](/cli/cron)
+- [Doctor](/cli/doctor)
- [Heartbeat config](/gateway/config-agents)
diff --git a/docs/reference/token-use.md b/docs/reference/token-use.md
index d8892f3065fc..74e59ab86c82 100644
--- a/docs/reference/token-use.md
+++ b/docs/reference/token-use.md
@@ -21,7 +21,7 @@ OpenClaw assembles its own system prompt on every run. It includes:
override at `agents.entries.*.skillsLimits.maxSkillsPromptChars`.
- Self-update instructions
- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`,
- `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus
+ `IDENTITY.md`, `USER.md`, `BOOTSTRAP.md` when new, plus
`MEMORY.md` when present). Large injected files are truncated by
`agents.defaults.bootstrapMaxChars` (default: `20000`); total bootstrap
injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default:
diff --git a/docs/start/bootstrapping.md b/docs/start/bootstrapping.md
index f6fbe53068d9..d7c081d316f2 100644
--- a/docs/start/bootstrapping.md
+++ b/docs/start/bootstrapping.md
@@ -17,7 +17,7 @@ onboarding, on the agent's first real turn.
On the first run against a brand-new workspace (default `~/.openclaw/workspace`),
OpenClaw:
-- Seeds `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, and `BOOTSTRAP.md`.
+- Seeds `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md`.
- Has the agent follow a capped three-beat birth sequence: it asks what you want
to call it, shares one short soul/vibe line, and asks whether you want the
minimal recommended plugin set or maximum convenience.
diff --git a/docs/start/hubs.md b/docs/start/hubs.md
index e55d4e2f9eea..6db79bc55aa3 100644
--- a/docs/start/hubs.md
+++ b/docs/start/hubs.md
@@ -180,7 +180,7 @@ Use these hubs to discover every page, including deep dives and reference docs t
- [Default AGENTS](/reference/AGENTS.default)
- [Templates: AGENTS](/reference/templates/AGENTS)
- [Templates: BOOTSTRAP](/reference/templates/BOOTSTRAP)
-- [Templates: HEARTBEAT](/reference/templates/HEARTBEAT)
+- [Retired HEARTBEAT.md workspace file](/reference/templates/HEARTBEAT)
- [Templates: IDENTITY](/reference/templates/IDENTITY)
- [Templates: SOUL](/reference/templates/SOUL)
- [Templates: TOOLS](/reference/templates/TOOLS)
diff --git a/docs/tools/subagents.md b/docs/tools/subagents.md
index 75ff4aff50c5..2cb686ced56a 100644
--- a/docs/tools/subagents.md
+++ b/docs/tools/subagents.md
@@ -674,7 +674,7 @@ still need normal device approval for scope upgrades.
- Sub-agent announce is **best-effort**. If the gateway restarts, pending "announce back" work is lost.
- Sub-agents still share the same gateway process resources; treat `maxConcurrent` as a safety valve.
- `sessions_spawn` is always non-blocking: it returns `{ status: "accepted", runId, childSessionKey }` immediately.
-- Sub-agent context only injects `AGENTS.md` and `TOOLS.md` (no `SOUL.md`, `IDENTITY.md`, `USER.md`, `MEMORY.md`, `HEARTBEAT.md`, or `BOOTSTRAP.md`). Codex-native subagents follow the same boundary: `TOOLS.md` stays in inherited Codex thread instructions, while parent-only persona, identity, and user files are injected as turn-scoped collaboration instructions so children do not clone them.
+- Sub-agent context only injects `AGENTS.md` and `TOOLS.md` (no `SOUL.md`, `IDENTITY.md`, `USER.md`, `MEMORY.md`, or `BOOTSTRAP.md`). Codex-native subagents follow the same boundary: `TOOLS.md` stays in inherited Codex thread instructions, while parent-only persona, identity, and user files are injected as turn-scoped collaboration instructions so children do not clone them.
- Maximum nesting depth is 5 (`maxSpawnDepth` range: 1-5). Depth 2 is recommended for most use cases.
- `maxChildrenPerAgent` caps active children per session (default `5`, range `1-20`).
diff --git a/qa/scenarios/agents/subagent-fanout-synthesis.yaml b/qa/scenarios/agents/subagent-fanout-synthesis.yaml
index cef593585103..a80047eb92cb 100644
--- a/qa/scenarios/agents/subagent-fanout-synthesis.yaml
+++ b/qa/scenarios/agents/subagent-fanout-synthesis.yaml
@@ -28,7 +28,7 @@ scenario:
config:
prompt: |-
Subagent fanout synthesis check: delegate exactly two bounded subagents sequentially using sessions_spawn, not ACP.
- First spawn exactly one child with label qa-fanout-alpha and task: verify that `HEARTBEAT.md` exists and reply exactly `ok` if it does. Wait for that child to finish.
+ First spawn exactly one child with label qa-fanout-alpha and task: verify that `AGENTS.md` exists and reply exactly `ok` if it does. Wait for that child to finish.
Then spawn exactly one child with label qa-fanout-beta and task: verify that `repo/qa/scenarios/agents/subagent-fanout-synthesis.yaml` exists and reply exactly `ok` if it does. Wait for that child to finish.
Do not spawn any more children after qa-fanout-beta finishes.
Then reply with exactly these two lines and nothing else:
@@ -89,7 +89,7 @@ flow:
expr: "env.providerMode === 'mock-openai' ? config.expectedChildLabels[1] : `${config.expectedChildLabels[1]}-${attempt}`"
- set: prompt
value:
- expr: "`Subagent fanout synthesis check: delegate exactly two bounded subagents sequentially using sessions_spawn, not ACP.\nFirst spawn exactly one child with label ${alphaLabel} and task: verify that \\`HEARTBEAT.md\\` exists and reply exactly \\`ok\\` if it does. Wait for that child to finish.\nThen spawn exactly one child with label ${betaLabel} and task: verify that \\`repo/qa/scenarios/agents/subagent-fanout-synthesis.yaml\\` exists and reply exactly \\`ok\\` if it does. Wait for that child to finish.\nDo not spawn any more children after ${betaLabel} finishes.\nThen reply with exactly these two lines and nothing else:\nsubagent-1: ok\nsubagent-2: ok`"
+ expr: "`Subagent fanout synthesis check: delegate exactly two bounded subagents sequentially using sessions_spawn, not ACP.\nFirst spawn exactly one child with label ${alphaLabel} and task: verify that \\`AGENTS.md\\` exists and reply exactly \\`ok\\` if it does. Wait for that child to finish.\nThen spawn exactly one child with label ${betaLabel} and task: verify that \\`repo/qa/scenarios/agents/subagent-fanout-synthesis.yaml\\` exists and reply exactly \\`ok\\` if it does. Wait for that child to finish.\nDo not spawn any more children after ${betaLabel} finishes.\nThen reply with exactly these two lines and nothing else:\nsubagent-1: ok\nsubagent-2: ok`"
- set: sessionKey
value:
expr: "`agent:qa:fanout:${attempt}:${randomUUID().slice(0, 8)}`"
diff --git a/src/agents/sandbox/workspace.ts b/src/agents/sandbox/workspace.ts
index b81f9f354591..03f5417826c1 100644
--- a/src/agents/sandbox/workspace.ts
+++ b/src/agents/sandbox/workspace.ts
@@ -17,7 +17,6 @@ import {
import {
DEFAULT_AGENTS_FILENAME,
DEFAULT_BOOTSTRAP_FILENAME,
- DEFAULT_HEARTBEAT_FILENAME,
DEFAULT_IDENTITY_FILENAME,
DEFAULT_SOUL_FILENAME,
DEFAULT_TOOLS_FILENAME,
@@ -43,7 +42,6 @@ export async function ensureSandboxWorkspace(
DEFAULT_IDENTITY_FILENAME,
DEFAULT_USER_FILENAME,
DEFAULT_BOOTSTRAP_FILENAME,
- DEFAULT_HEARTBEAT_FILENAME,
];
for (const name of files) {
const src = path.join(seed, name);
diff --git a/src/agents/workspace.ts b/src/agents/workspace.ts
index c5d049e00295..aa7d672fe418 100644
--- a/src/agents/workspace.ts
+++ b/src/agents/workspace.ts
@@ -84,7 +84,7 @@ async function readWorkspaceFileWithGuards(params: {
try {
// A transient FS race (EAGAIN/EWOULDBLOCK/EINTR under load) on the open or
// read must not drop the agent's bootstrap file for the turn — this reader
- // runs every turn for AGENTS/SOUL/HEARTBEAT/etc. Retry the whole open+read so
+ // runs every turn for AGENTS/SOUL/TOOLS/etc. Retry the whole open+read so
// each attempt uses a fresh fd (retrying readFileSync on the same fd could
// return truncated content after a partial read); the inode-identity guard
// in openRootFile still protects against a swapped file between attempts.
diff --git a/src/dockerfile.test.ts b/src/dockerfile.test.ts
index dfcc89c73fa5..49c835ff339c 100644
--- a/src/dockerfile.test.ts
+++ b/src/dockerfile.test.ts
@@ -513,7 +513,7 @@ describe("Dockerfile", () => {
expect(workflow).toContain("Smoke test arm64 runtime workspace templates");
expect(workflow).toContain("test -f /app/src/agents/templates/HEARTBEAT.md");
expect(workflow).toContain('grep -F "Missing workspace template:"');
- expect(workflow).toContain('test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"');
+ expect(workflow).not.toContain('test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"');
});
it("keeps only the runtime-assets prune proof in full release validation", async () => {
diff --git a/src/gateway/server-methods/agents-mutate.test.ts b/src/gateway/server-methods/agents-mutate.test.ts
index 41be2c8a702f..2ea6a309b2fa 100644
--- a/src/gateway/server-methods/agents-mutate.test.ts
+++ b/src/gateway/server-methods/agents-mutate.test.ts
@@ -2935,6 +2935,27 @@ describe("agents.files.list", () => {
expect(names).toContain("BOOTSTRAP.md");
});
+ it("does not expose retired HEARTBEAT.md workspace files", async () => {
+ const names = await listAgentFileNames();
+ expect(names).not.toContain("HEARTBEAT.md");
+ });
+
+ it("rejects writes to retired HEARTBEAT.md workspace files", async () => {
+ const { respond, promise } = makeCall("agents.files.set", {
+ agentId: "main",
+ name: "HEARTBEAT.md",
+ content: "legacy checklist",
+ });
+ await promise;
+
+ expectRecordFields(expectRespondErrorContaining(respond, "unsupported file"), {
+ code: "INVALID_REQUEST",
+ message: 'unsupported file "HEARTBEAT.md"',
+ });
+ expect(mocks.fsMkdir).not.toHaveBeenCalled();
+ expect(mocks.rootWrite).not.toHaveBeenCalled();
+ });
+
it("hides BOOTSTRAP.md when workspace setup is complete", async () => {
mockWorkspaceStateRead({ setupCompletedAt: "2026-02-15T14:00:00.000Z" });
diff --git a/src/gateway/server-methods/agents.ts b/src/gateway/server-methods/agents.ts
index 335fa3db817a..9053a100e67f 100644
--- a/src/gateway/server-methods/agents.ts
+++ b/src/gateway/server-methods/agents.ts
@@ -58,7 +58,6 @@ import {
import {
DEFAULT_AGENTS_FILENAME,
DEFAULT_BOOTSTRAP_FILENAME,
- DEFAULT_HEARTBEAT_FILENAME,
DEFAULT_IDENTITY_FILENAME,
DEFAULT_MEMORY_FILENAME,
DEFAULT_SOUL_FILENAME,
@@ -110,7 +109,6 @@ const BOOTSTRAP_FILE_NAMES = [
DEFAULT_TOOLS_FILENAME,
DEFAULT_IDENTITY_FILENAME,
DEFAULT_USER_FILENAME,
- DEFAULT_HEARTBEAT_FILENAME,
DEFAULT_BOOTSTRAP_FILENAME,
] as const;
const BOOTSTRAP_FILE_NAMES_POST_ONBOARDING = BOOTSTRAP_FILE_NAMES.filter(
diff --git a/src/hooks/bundled/bootstrap-extra-files/HOOK.md b/src/hooks/bundled/bootstrap-extra-files/HOOK.md
index 37ba56caa0b3..5f56baf923a0 100644
--- a/src/hooks/bundled/bootstrap-extra-files/HOOK.md
+++ b/src/hooks/bundled/bootstrap-extra-files/HOOK.md
@@ -50,4 +50,4 @@ workspace root.
All paths are resolved from the workspace and must stay inside it (including realpath checks).
Only recognized bootstrap basenames are loaded (`AGENTS.md`, `SOUL.md`, `TOOLS.md`,
-`IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `MEMORY.md`).
+`IDENTITY.md`, `USER.md`, `BOOTSTRAP.md`, `MEMORY.md`).
diff --git a/ui/src/pages/agents/view.test.ts b/ui/src/pages/agents/view.test.ts
index 510406ee8977..2ab21074bb0b 100644
--- a/ui/src/pages/agents/view.test.ts
+++ b/ui/src/pages/agents/view.test.ts
@@ -480,8 +480,8 @@ describe("renderAgentFiles", () => {
missing: false,
},
{
- name: "HEARTBEAT.md",
- path: "/tmp/workspace/HEARTBEAT.md",
+ name: "SOUL.md",
+ path: "/tmp/workspace/SOUL.md",
missing: false,
},
],
@@ -501,9 +501,9 @@ describe("renderAgentFiles", () => {
container,
);
- const heartbeatTab = expectAgentTab(container, "HEARTBEAT");
- expect(heartbeatTab.disabled).toBe(true);
- heartbeatTab.click();
+ const soulTab = expectAgentTab(container, "SOUL");
+ expect(soulTab.disabled).toBe(true);
+ soulTab.click();
expect(onSelectFile).not.toHaveBeenCalled();
});