Goal stewardship — operator guide
This document is for humans operating the OpenClaw hybrid-memory plugin with long-running goals. For design rationale and edge cases, see GOAL-STEWARDSHIP-DESIGN.md.
Enable and locate data
- Set
goalStewardship.enabled: truein the plugin memory config (same file as other hybrid-memory settings). - Goals are stored as JSON under the workspace, by default
state/goals/(relative toOPENCLAW_WORKSPACE, usually~/.openclaw/workspace). Override withgoalStewardship.goalsDirif needed. - Tactical tasks live in
ACTIVE-TASKS.md(seeactiveTask.*in config). Optional task hygiene on heartbeat — stale nudges, optional long-running hints, and toolactive_task_propose_goal— is documented in TASK-HYGIENE.md. - Optional toggles:
heartbeatStewardship— inject stewardship when the last user message matches a heartbeat pattern (built-in defaults include “heartbeat”, “scheduled ping”, “cron heartbeat”; override withgoalStewardship.heartbeatPatterns).watchdogHealthCheck— run deterministic checks (budget, staleness, mechanical verification) on the plugin’s five-minute timer (default: on).heartbeatRefreshActiveTask— on each heartbeat match, rewriteACTIVE-TASKS.mdwith an ## Active Goals mirror section (requiresactiveTask.enabled). Do not edit that section by hand.multiGoalMaxChars/multiGoalMaxGoals/attentionWeights— cap and prioritize multi-goal stewardship prepends (defaults: weights critical 4×, high 2×, medium 1×, low 0.5×).confirmationPolicy— priorities that requireconfirmed: trueongoal_registerafter user approval (default: critical and high).llmTriageOnHeartbeat— optional nano LLM triage for “needs heavy” hints (default: off; enable only when API keys and cost are acceptable).circuitBreaker— optional automatic stop and escalate whengoal_assessis called repeatedly with the same blockers (or when too many assessments occur without the blocker fingerprint changing). Defaultenabled: false. When enabled, set at least one ofsameBlockerRepeatLimit(≥1) ormaxAssessmentsWithoutProgress(≥1);0means off for that threshold. On trip: goal becomesblocked,humanEscalationSummaryholds a human-readable brief (blockers, criteria, linked tasks, recent history),appendMemoryEscalation(default on) appends the same tomemory/YYYY-MM-DD.md, and the event log may recordgoal.circuit_breaker. This is separate fromescalateAfterFailures(subagent / PID failures), which the watchdog already enforces.escalationPolicy.taskHygieneOnBlockedGoals— when task hygiene runs on a heartbeat match, also prepend a<goal-escalation>snippet if any goal isblockedorstalled(default: true). Set to false to disable the cross-layer nudge.
Heartbeat scheduling checklist
Goal stewardship injection runs on before_agent_start only when the last user message matches goalStewardship.heartbeatPatterns (or the built-in defaults: “heartbeat”, “scheduled ping”, “cron heartbeat”, case-insensitive). The plugin’s watchdog does not start LLM turns; it only updates deterministic goal state on a timer.
- Confirm patterns: List effective patterns with
openclaw hybrid-mem goals config(or your config file).openclaw hybrid-mem verifyprints how many matchers compiled and warns if~/.openclaw/cron/jobs.jsonhas no job message matching those patterns. - Cron / job message: Ensure the string OpenClaw delivers as the user message for your scheduled job matches a pattern. Examples that match defaults:
heartbeatscheduled pingcron heartbeat
- Troubleshooting — no stewardship prepend: Check gateway logs for
goal stewardship; confirm the actual last user text (not only the job title). AdjustheartbeatPatternsor the jobmessage/payload.messageso they align.
Host boundary (synthetic turns)
This plugin reacts to agent turns; it does not enqueue scheduled LLM runs. To get recurring stewardship prepends, OpenClaw (or your host) must deliver messages that match your heartbeat patterns — e.g. via jobs / cron in ~/.openclaw/cron/jobs.json. Upstream scheduling features belong in OpenClaw core; the hybrid-memory plugin stays local-first and does not replace the host scheduler.
User guide: scheduled stewardship, agents, and “how often?”
This section is for everyday operators who want recurring goal check-ins without reading plugin source code.
Two different things (do not confuse them)
| What | What it does | Uses the LLM? |
|---|---|---|
Watchdog (watchdogHealthCheck, ~every 5 minutes) | Updates goal JSON: budgets, stalled / blocked, mechanical checks, PID links, etc. | No |
Heartbeat stewardship (heartbeatStewardship) | When a new turn starts, if the last user message matches your heartbeat patterns, the plugin prepends goal context so the model can call goal_assess and related tools. | Yes (this turn) |
So: the watchdog can run while nothing “talks” to the model. If you want the agent to actively work goals on a schedule, you need scheduled agent turns (OpenClaw jobs) whose message text matches your heartbeat patterns — not a second timer inside the plugin.
How often should a “goal heartbeat” job run?
There is no single correct answer; it is a trade-off between responsiveness, cost, and noise.
| Cadence | Typical use |
|---|---|
| 1–2× per day | Long-running goals; light-touch reminders. |
| Every 4–6 hours | You want regular goal_assess / progress without spamming. |
| Every 30–60 minutes | Active goals where same-day follow-up matters. |
| Every 5–15 minutes | Rare; usually overkill unless goals are truly time-critical — high token cost and channel noise. |
Start conservative (few times per day or every 4–6 hours). Increase only if goals stall between other automation.
Can I use an agent other than main for goal heartbeats?
Yes. That is a normal pattern: keep interactive chat on main, and use a dedicated agentId (e.g. hearth, ops-goals, or another profile) for short scheduled “pulse” turns.
Requirements for that agent to see the same goals:
- Same workspace — The agent’s process must resolve the same
OPENCLAW_WORKSPACE(and thus the samestate/goals/tree) as the goals you care about. - Goal stewardship enabled — Hybrid-memory must load with
goalStewardship.enabled: truefor that agent’s plugin config (same as formainif you use one global plugin config). - Same
goalsDir— If you overridegoalStewardship.goalsDir, it must point to the same directory for every agent that should share those goals.
“Can every agent read goals via hybrid-memory?” — Recall of memories (memory_recall, etc.) is not the same as registered goals. Goals are goal_* tools and JSON under state/goals/. Any agent that should drive stewardship needs goal stewardship enabled and the goal tools available — not only generic memory recall.
Minimal OpenClaw job shape (another agent + heartbeat text)
OpenClaw stores scheduled work in ~/.openclaw/cron/jobs.json (exact path may match your install). A minimal pattern is:
agentId: the agent that should run the pulse (not necessarilymain).schedule: your cron expression and timezone.sessionTarget: oftenisolatedso the pulse does not hijack your interactive session.payload.kind:agentTurnpayload.message: must match a heartbeat pattern (see below).
Example (illustrative — adjust fields to your OpenClaw version):
{
"name": "goal-stewardship-pulse",
"agentId": "hearth",
"enabled": true,
"schedule": { "kind": "cron", "expr": "0 */6 * * *", "tz": "Europe/Stockholm" },
"sessionTarget": "isolated",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "cron heartbeat\n\nYou are running a scheduled goal-stewardship pulse. List active goals (via tools / workspace), call goal_assess where appropriate, and report only concise status and next actions.",
"timeoutSeconds": 600
},
"delivery": { "mode": "none" }
}
First line matters for matching: put a known trigger on line 1, e.g. cron heartbeat, then a blank line, then instructions. Default patterns match substrings like heartbeat, scheduled ping, and cron heartbeat (case-insensitive).
Avoid accidental matches: Long job prompts that mention unrelated paths (e.g. heartbeat-state.json) can contain the word heartbeat and make openclaw hybrid-mem verify think a job “matches” even when that job is not your goal pulse. Prefer a dedicated short job for stewardship, or a custom goalStewardship.heartbeatPatterns (e.g. a unique tag like GOAL_PULSE_V1) that only your pulse job uses.
Suggested minimal job text (copy-paste)
Option A — default pattern on first line:
cron heartbeat
Goal stewardship pulse: review active goals, run goal_assess for each that needs an update, keep the reply short.
Option B — custom pattern (after you add the same string to goalStewardship.heartbeatPatterns):
GOAL_PULSE_V1
Review registered goals and assess progress; do not run unrelated maintenance.
Verification: confirm heartbeat delivery is plausible
Run:
openclaw hybrid-mem verify
When goalStewardship.enabled is true, the Goal stewardship (heartbeat) section reports:
- Whether
heartbeatStewardshipis on. - How many heartbeat pattern matchers compiled.
- A warning (non-fatal) if
~/.openclaw/cron/jobs.jsonis missing, unreadable, has no job text, or no job message matches your patterns.
This does not prove a specific agent ran — it only checks that some job message could trigger a heartbeat. Combine with logs below.
Also useful:
openclaw hybrid-mem goals config
openclaw hybrid-mem goals status
Logging: how to see if stewardship ran
When the gateway runs the plugin with sufficient log level, look for these plugin log lines (exact wording may evolve slightly):
| Log substring | Meaning |
|---|---|
memory-hybrid: goal stewardship bundle | Heartbeat matched and stewardship prepend was built for at least one goal (includes goal count). |
memory-hybrid: goal stewardship skipped — global dispatch rate limit | Heartbeat matched but global hourly dispatch cap blocked prepend. |
memory-hybrid: goal stewardship injection error | Exception during injection — inspect the stack / message. |
memory-hybrid: task hygiene block appended (heartbeat match) | Task hygiene block was added on the same heartbeat (if activeTask + task hygiene are enabled). |
Practical checks:
- After a scheduled job is supposed to run, search gateway / plugin logs for
goal stewardship bundlefor that time window. - If you never see it, confirm the job’s
payload.messageas seen by the agent actually contains your heartbeat trigger (not only the job title injobs.json). - Run
goal_assessfrom the agent in that turn if you expect progress — the prepend is context; the model must still use the tools.
Deterministic health without LLM (watchdog / manual):
openclaw hybrid-mem goals stewardship-run
This runs the same non-LLM checks as the periodic watchdog (budgets, staleness, mechanical verification, etc.). It does not replace a heartbeat turn.
Each run now prints per-goal outcomes so operators can inspect what happened for every considered goal, not just a global “ok”:
doneblockeddispatchedexecutedwaitingnoop
When a dispatch is observed, outcome rows include task, session, and run metadata when available.
CLI (observability)
All commands are under openclaw hybrid-mem goals:
| Command | Purpose |
|---|---|
list | Active goals (use --all to include completed / failed / abandoned). |
status | Overview: stewardship on/off, goals directory, active goals table (same rows as list). |
status <idOrLabel> | Full detail for one goal (UUID or case-insensitive label). Add --json for raw JSON. |
cancel <idOrLabel> --reason "<text>" | Mark goal abandoned with a reason. |
stewardship-run | Run one deterministic health pass (requires goalStewardship.enabled; forces the same checks as the watchdog for that run). |
audit | JSON snapshot of goals + config subset; add --jsonl for NDJSON (one object per goal). |
Agent tools
Agents use underscore names: goal_register, goal_assess, goal_update, goal_complete, goal_abandon. Stewardship is disabled until goalStewardship.enabled is true; tools then respond with a clear error if disabled.
For promoting work from ACTIVE-TASKS.md into a registered goal, use active_task_propose_goal (draft fields for goal_register; requires activeTask.enabled). Details: TASK-HYGIENE.md.
What the watchdog does (no LLM)
On the timer (when enabled), the plugin checks each non-terminal goal for: dispatch/assessment budgets, escalation after repeated failures, dead subagent PIDs linked to tasks, staleness (idle vs cooldown), and optional mechanical verification when configured on the goal.
| Verifier | Behavior | Opt-in |
|---|---|---|
file_exists | Path relative to workspace (or absolute) must exist | Always on when set |
http_ok | HTTP GET must return a successful status | Always on when set |
command_exit_zero | Run argv split with execFile (no shell); exit 0 | goalStewardship.allowCommandVerification: true |
pr_merged | GitHub REST API: PR must be merged | goalStewardship.allowPrVerification: true and GITHUB_TOKEN or GH_TOKEN set. Target: owner/repo#N or https://github.com/owner/repo/pull/N |
Each run records lastMechanicalCheck on the goal JSON (at, ok, detail). On success, non-terminal active / stalled goals move to verifying (same as before); goal_complete is still for the agent when policy allows.
If an in-progress linked task lacks both sessionKey and runId, stewardship marks that dispatch attempt as failed with an inspectable reason (dispatchFailureReason), blocks the goal, and reports a blocked outcome instead of a vague dispatched/ok summary.
Escalation ladder (watchdog vs tools vs circuit breaker)
Order of enforcement (first match wins as the goal is processed each watchdog pass):
- Terminal goals — skipped.
- Dispatch / assessment budgets — goal becomes
blockedwith a budget reason (watchdog,budget-enforced). escalateAfterFailures— consecutive failure counter (e.g. dead subagent PID) reaches threshold →blocked(watchdog,escalated).- Linked subagent tasks — stale PID may increment failures (
subagent-died). - Staleness — idle beyond
cooldownMinutes * 2whileactive→stalled; activity resumes →activeagain. - Mechanical verification — see table above; outcomes in
lastMechanicalCheckand history (verification-passed/mechanical-fail). - Circuit breaker (during
goal_assess, not the timer) — repeated same blockers / no progress →blocked,escalationKind: circuit_breaker,humanEscalationSummary, optional memory append.
stalled means “no recent activity”; blocked means “do not proceed without human or policy change.” failed is for explicit failure/abandon flows from tools, not the watchdog budget path.
Episodic memory and audit
Successful completions can append a short summary under memory/ (date-stamped files) when the tool path is configured. Goal history is stored on the goal JSON (history array). Event-log entries may be written for budget and staleness when an event log is available.
Audit playbook: GOAL-STEWARDSHIP-AUDIT-PLAYBOOK.md — how to trace failures and correlate CLI, files, and logs.
Automated tests (developers)
Plugin CI runs stewardship tests without OpenClaw core: a mock plugin API (extensions/memory-hybrid/tests/harness/mock-plugin-api.ts) registers the same lifecycle handlers and emits synthetic before_agent_start / subagent events. See tests/goal-stewardship-integration.test.ts for the heartbeat + subagent flow.
Troubleshooting
- No goals in CLI: Confirm workspace path (
OPENCLAW_WORKSPACE),goalsDir, and that JSON files exist under that directory. - No stewardship on heartbeat: Confirm
heartbeatStewardshipis notfalseand that the user message matches a configured or default heartbeat pattern (not only the literal word “heartbeat”). Runopenclaw hybrid-mem verify— it warns when stewardship is enabled but no cron job message matches your patterns. - Goals blocked: Check
currentBlockersviagoals status; budget and escalation are enforced by the watchdog and tools. IfescalationKindiscircuit_breaker, readhumanEscalationSummaryon the goal JSON (or thememory/append) for what failed and what was already tried.