openclaw - ✅(Solved) Fix Feature: Selective/lazy injection for workspace context files [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#61347Fetched 2026-04-08 02:59:40
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Participants
Timeline (top)
cross-referenced ×1

Fix Action

Fix / Workaround

Current Workaround

PR fix notes

PR #63434: fix(heartbeat): widen empty-detection to skip API calls for comment-only HEARTBEAT.md (#61690)

Description (problem / solution / changelog)

Summary

  • Problem: A default-installed HEARTBEAT.md could fire a full heartbeat LLM call every interval even when the file was effectively just scaffold text.
  • Root cause: docs/reference/templates/HEARTBEAT.md was changed in 198de10523 to wrap the scaffold body in a fenced ```markdown block so Mintlify would render the # lines as code instead of page headings. Runtime loadTemplate() strips frontmatter but not fenced markers, so those ``` lines are written verbatim into the user workspace. isHeartbeatContentEffectivelyEmpty() treated those marker lines as actionable content, so the preflight skip path never fired.
  • What changed: isHeartbeatContentEffectivelyEmpty() now treats Markdown fence marker lines such as ``` and ```markdown as format-only noise, while still treating real content inside the fence as actionable. The PR also adds regression coverage for the current fenced template shape, the pre-fence legacy comment-only template shape, and runner-level skip behavior.
  • What did NOT change: This PR does not add HTML comment, YAML frontmatter, or bare tasks: handling. Those patterns are not part of the shipped heartbeat template path that caused the regression.

Change Type

  • Bug fix

Scope

  • Gateway / orchestration

Linked Issue/PR

  • Closes #61690
  • Related #61347
  • This PR fixes a bug or regression

Root Cause

  • Root cause: the runtime heartbeat scaffold and the docs template share the same source file. After 198de10523, the shipped HEARTBEAT.md scaffold started including fenced Markdown markers for docs rendering, but runtime template loading still wrote those markers into workspaces.
  • Missing detection / guardrail: isHeartbeatContentEffectivelyEmpty() skipped headers and blank lines but not fence marker lines, so a comment-only scaffold created from the current template looked non-empty.
  • Why old installs also matter: pre-198de10523 installs may still have the old comment-only scaffold shape, so the regression tests keep both shapes covered.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
    • Integration / runner test
  • Target tests or files:
    • src/auto-reply/heartbeat.test.ts
    • src/agents/workspace.test.ts
    • src/infra/heartbeat-runner.returns-default-unset.test.ts
  • Scenarios the tests lock in:
    • the current fenced default template body is treated as effectively empty
    • the legacy pre-fence comment-only template body is still treated as effectively empty
    • fenced content with a real task still runs and does not skip
    • new workspaces really do seed the fenced HEARTBEAT.md template body from docs/reference/templates/HEARTBEAT.md
  • Why this is the smallest reliable guardrail: it covers the actual docs-template-to-workspace path plus the runner preflight gate, not just the helper in isolation.

User-visible / Behavior Changes

For users whose HEARTBEAT.md still contains only the shipped scaffold text, heartbeat ticks now return {status: "skipped", reason: "empty-heartbeat-file"} again instead of making an unnecessary LLM call. Fenced templates with real task content still run normally.

Diagram

Before (default shipped scaffold after 198de10523):
[interval tick] -> workspace HEARTBEAT.md contains ```markdown / ``` lines
                -> isHeartbeatContentEffectivelyEmpty: false
                -> full LLM call

After:
[interval tick] -> fence marker lines ignored as formatting noise
                -> scaffold-only HEARTBEAT.md classified empty
                -> {status: "skipped"}

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: local repo test environment
  • Runtime: Node 22+, pnpm 10.x
  • Model/provider: provider-agnostic; the fix is in heartbeat preflight gating before any LLM call

Steps

  1. Inspect docs/reference/templates/HEARTBEAT.md and confirm the scaffold body is fenced with ```markdown.
  2. Confirm src/agents/workspace.ts strips frontmatter but not fence markers when seeding the workspace template.
  3. Run:
    • pnpm test src/auto-reply/heartbeat.test.ts
    • pnpm test src/agents/workspace.test.ts
    • pnpm test src/infra/heartbeat-runner.returns-default-unset.test.ts

Expected

  • Current fenced scaffold-only HEARTBEAT.md skips heartbeat runs.
  • Legacy comment-only scaffold still skips.
  • Fenced heartbeat files with real tasks still run.

Actual

Matches expected.

Evidence

  • Failing test/log before + passing after
pnpm test src/auto-reply/heartbeat.test.ts
pnpm test src/agents/workspace.test.ts
pnpm test src/infra/heartbeat-runner.returns-default-unset.test.ts

All passed locally on the final PR head.

Human Verification

  • Verified scenarios:
    • current fenced template scaffold is classified empty
    • legacy pre-fence comment-only scaffold is classified empty
    • fenced scaffold with real tasks is not classified empty
    • workspace seeding still writes the fenced heartbeat template body from the docs template source
  • What I did NOT verify: live end-to-end billing/provider behavior against a real model API. The change is confined to local preflight gating, and runner-level tests cover the skip path.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: fence marker lines are ignored even outside the shipped scaffold.
    • Mitigation: only the marker lines themselves are ignored; content inside the fence is still evaluated normally, and runner tests cover fenced actionable content.
  • Risk: future template formatting changes could drift from runtime assumptions again.
    • Mitigation: workspace seeding and runner gating are now both covered by regression tests.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/workspace.test.ts (modified, +16/-0)
  • src/auto-reply/heartbeat.test.ts (modified, +36/-3)
  • src/auto-reply/heartbeat.ts (modified, +9/-3)
  • src/infra/heartbeat-runner.returns-default-unset.test.ts (modified, +66/-1)

Code Example

{
  "hooks.internal.entries.boot-md.files": {
    "HEARTBEAT.md": { "inject": "heartbeat-only" },
    "MEMORY.md": { "inject": "main-session-only" }
  }
}

---

---
inject: heartbeat-only
---
RAW_BUFFERClick to expand / collapse

Problem

OpenClaw's boot-md hook injects all workspace context files (AGENTS.md, SOUL.md, IDENTITY.md, USER.md, TOOLS.md, HEARTBEAT.md, MEMORY.md) into every single turn — regardless of whether they're needed. This burns tokens on every message, and the cost compounds on expensive models like Opus.

After manually optimizing all workspace files (~61% token reduction), the remaining overhead is still ~2,700+ tokens per turn with no way to reduce further from the user side.

Proposed Features

1. Selective injection (conditional loading)

Allow workspace files to declare when they should be injected. For example, HEARTBEAT.md is only relevant during heartbeat polls but currently loads on every turn.

Possible config:

{
  "hooks.internal.entries.boot-md.files": {
    "HEARTBEAT.md": { "inject": "heartbeat-only" },
    "MEMORY.md": { "inject": "main-session-only" }
  }
}

Or frontmatter in the .md files themselves:

---
inject: heartbeat-only
---

2. Lazy loading (TOC mode)

Instead of injecting full file contents, inject a table of contents (filenames + first-line summaries) and let the agent read files on demand. This would be especially valuable as workspace files grow over time.

3. Token-budget-aware summarization

Set a max token budget for workspace context injection. If files exceed it, auto-summarize or truncate least-important files. Could use the agent's own summarization or a cheaper model.

Impact

  • Selective injection alone would save ~150-400 tokens/turn (HEARTBEAT.md doesn't need to load on every chat message)
  • Lazy loading could cut workspace injection by 50-80% for users with many workspace files
  • Token budgets would prevent runaway costs as users add more context files over time

Current Workaround

Manual optimization of file contents + moving data to non-injected files (e.g. secrets.md). Works but has limits — the hook itself has no configurability beyond enabled/disabled.

Environment

  • OpenClaw v2026.4.1
  • Windows 11

extent analysis

TL;DR

Implement selective injection of workspace context files to reduce unnecessary token usage.

Guidance

  • Consider adding a configuration option to declare when each workspace file should be injected, such as the proposed inject property in the boot-md hook configuration or frontmatter in the .md files.
  • Evaluate the potential benefits of lazy loading, which could reduce workspace injection by 50-80% for users with many workspace files.
  • Investigate implementing token-budget-aware summarization to prevent excessive token usage when injecting workspace context files.
  • Review the current workaround of manual optimization and moving data to non-injected files, and consider its limitations.

Example

{
  "hooks.internal.entries.boot-md.files": {
    "HEARTBEAT.md": { "inject": "heartbeat-only" },
    "MEMORY.md": { "inject": "main-session-only" }
  }
}

or

---
inject: heartbeat-only
---

Notes

The proposed features require changes to the boot-md hook and may involve updates to the OpenClaw configuration or the workspace files themselves. The effectiveness of these changes depends on the specific use case and the number of workspace files.

Recommendation

Apply a workaround by implementing selective injection of workspace context files, as this could save ~150-400 tokens/turn and is a more targeted solution than the current manual optimization workaround.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING