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.
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.
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.