From f43fac21c7641b4b6faa2105fb15e1e81c177dd0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 25 Jul 2026 04:58:31 -0700 Subject: [PATCH] fix(gateway): stop showing retired HEARTBEAT.md in the agent files editor (#113621) * fix(gateway): drop retired HEARTBEAT.md from agent core files * fix(ci): stop asserting retired HEARTBEAT.md in docker release smoke * docs: align HEARTBEAT.md references with cron scratch * docs(agents): drop retired HEARTBEAT.md from workspace read comment --- .github/workflows/docker-release.yml | 2 -- docs/.i18n/glossary.zh-CN.json | 8 +++++ docs/automation/hooks.md | 2 +- docs/automation/standing-orders.md | 2 +- docs/cli/onboard.md | 2 +- docs/concepts/agent-workspace.md | 7 ++--- docs/concepts/agent.md | 1 - docs/concepts/context.md | 2 -- docs/concepts/system-prompt.md | 5 ++-- docs/docs_map.md | 2 +- docs/help/faq.md | 4 +-- docs/plugins/codex-harness-reference.md | 6 ++-- docs/plugins/codex-harness-runtime.md | 4 +-- docs/reference/prompt-caching.md | 4 +-- docs/reference/templates/AGENTS.dev.md | 2 +- docs/reference/templates/AGENTS.md | 2 +- docs/reference/templates/BOOTSTRAP.md | 2 +- docs/reference/templates/HEARTBEAT.md | 29 +++++++++---------- docs/reference/token-use.md | 2 +- docs/start/bootstrapping.md | 2 +- docs/start/hubs.md | 2 +- docs/tools/subagents.md | 2 +- .../agents/subagent-fanout-synthesis.yaml | 4 +-- src/agents/sandbox/workspace.ts | 2 -- src/agents/workspace.ts | 2 +- src/dockerfile.test.ts | 2 +- .../server-methods/agents-mutate.test.ts | 21 ++++++++++++++ src/gateway/server-methods/agents.ts | 2 -- .../bundled/bootstrap-extra-files/HOOK.md | 2 +- ui/src/pages/agents/view.test.ts | 10 +++---- 30 files changed, 77 insertions(+), 62 deletions(-) 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(); });