clawpub/OPENCLAW_TRICKS.md
user f0a2a5eb62
Some checks are pending
check / check (push) Waiting to run
docs: update Gitea notification section — webhook vs poller, flag-file approach
- Replaced wake-event poller with flag-file approach (prevents DM spam)
- Added Option A (webhooks for VPS) vs Option B (poller for NAT)
- Documented the wake-event failure mode and why we switched
2026-02-28 03:30:49 -08:00

1683 lines
60 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# OPENCLAW_TRICKS.md — Configuration Recipes for OpenClaw Agents
A collection of tested patterns, prompts, and file structures for configuring an
OpenClaw agent as a proactive personal assistant. These are extracted from a
production setup that's been running since early 2026.
---
## How This System Evolved
This section explains what we built, why, and how the pieces fit together. The
rest of the document has the specific prompts and schemas — this part gives you
the big picture so you understand the design decisions.
### The Starting Point
OpenClaw gives you a persistent workspace directory and the ability to run tools
(shell, web, browser, messaging). Out of the box, the agent wakes up fresh every
session with no memory of what happened before. The core challenge is: **how do
you turn a stateless LLM into a stateful personal assistant that knows who you
are, where you are, what you need, and what it was working on?**
The answer is files. The workspace IS the agent's brain. Every piece of state,
every rule, every memory lives in files that the agent reads on startup and
updates as things change.
### The Workspace Files — Separation of Concerns
We started with one big AGENTS.md that had everything: rules, procedures,
medication instructions, git workflow, travel preferences. It worked at first
but quickly became unwieldy. The context window was getting eaten by
instructions the agent didn't need for most tasks.
The solution was **factoring out rulesets into focused files:**
- **AGENTS.md** — the master file. Responsibilities, session startup procedure,
high-level rules. Think of it as the table of contents that points to
everything else.
- **SOUL.md** — personality, tone, values. Separated because it's philosophical,
not procedural. Also: it's fun to let the agent evolve this one over time.
- **USER.md** — info about the human. Timezone, communication preferences,
basics the agent needs every session.
- **MEMORY.md** — curated long-term memory. Only loaded in private sessions
(security — prevents leaking personal context in group chats).
- **HEARTBEAT.md** — what to check on periodic heartbeat polls. Kept small
intentionally to minimize token burn on frequent checks.
- **TOOLS.md** — environment-specific notes (hostnames, device names, channel
IDs). Separated from skills because this is YOUR setup, skills are shared.
Then the procedural rulesets got their own files too:
- **`memory/medications-instructions.md`** — full medication protocol. Dosages,
schedules, safety rules, overdose prevention logic. Only loaded when
medication tasks come up.
- **`memory/checklist-pr.md`** — PR quality gate. What to verify before pushing,
after sub-agent pushes, before assigning to the human for review.
- **`memory/checklist-flights.md`** — flight time verification, prep block
creation, landing checklist triggers.
- **`memory/checklist-medications.md`** — pre-action verification before
reporting medication status or sending reminders.
- **`memory/checklist-messaging.md`** — rules for every outgoing message: URL
verification, timezone conversion, status claim verification.
- **`memory/landing-checklist.md`** — post-flight procedures (update location,
timezone, check meds, sync calendar).
The key insight: **MEMORY.md has a "checklists" section at the very top** that
says "before doing X, read file Y." The agent reads MEMORY.md at session start,
sees the checklist index, and knows which file to consult before any action.
This way the detailed instructions aren't always in context — they're loaded
on-demand.
```markdown
## ⛔ CHECKLISTS (read the relevant file before acting)
- **Any PR/code work** → `memory/checklist-pr.md`
- **Any message to the user** → `memory/checklist-messaging.md`
- **Medications** → `memory/checklist-medications.md`
- **Flights/travel** → `memory/checklist-flights.md`
```
### The Daily Context State File
This was probably the single most impactful addition. It's a JSON file
(`memory/daily-context.json`) that every session reads on every message. It
tracks the current state of the human: where they are, what timezone they're in,
whether they're sleeping, when they last took meds, when they last sent a
message.
Why JSON instead of markdown? Because it's machine-readable. The agent can
update individual fields programmatically, and the structure is unambiguous.
Markdown is great for instructions; JSON is great for state.
The daily context file means:
- The agent always knows the human's timezone (critical for a frequent traveler)
- Medication reminders fire based on actual timestamps, not guesses
- Sleep predictions inform when to send alerts vs stay quiet
- Location tracking happens automatically
### The Two-Tier Memory System
Daily files (`memory/YYYY-MM-DD.md`) are raw logs — what happened each day.
Topic tables, decisions, lessons, notes. Think of these as a daily journal.
MEMORY.md is curated long-term memory — the distilled essence. Standing rules,
key facts, lessons learned. Think of this as what a human would "just know"
about their life.
During heartbeats, the agent periodically reviews recent daily files and
promotes significant items to MEMORY.md, and removes stale info. It's explicitly
modeled on how human memory works: raw experience gets processed into lasting
knowledge, and irrelevant details fade.
The security model: MEMORY.md is only loaded in private (1:1 DM) sessions. In
group chats, the agent works from daily files and daily-context.json only. This
prevents personal context from leaking into shared channels.
### Medication Tracking
This is a safety-critical system. The design is deliberately paranoid:
- **CSV as source of truth.** Not the daily-context boolean, not the agent's
memory — the CSV log file is authoritative.
- **Double-verification before any action.** The daily context has a
`hasTakenDailyMedsToday` boolean AND a `dailyMedsTimestamp`. A midnight cron
resets the boolean. But if the human is in a timezone ahead of the server, the
reset happens at the wrong time. So the rule is: always verify the timestamp
falls on today's date in the human's timezone. The boolean is a convenience
hint, never the source of truth.
- **Interval medications anchored to actual doses.** Some meds are "every 5
days" or "every 14 days." The next dose date is calculated from the last
actual dose timestamp, not from the intended schedule. If a dose is missed,
the next date is unknown until the missed dose is taken and logged.
- **Overdose prevention.** The agent blocks logging if it detects a same-day
duplicate batch. This is the highest-priority safety rule — a duplicate daily
batch of certain medications could cause cardiac arrest.
- **Escalating alerts.** If daily meds are >26h since last dose, the agent
escalates aggressively — ntfy push notification if chat messages go
unacknowledged.
The medication instructions file is loaded on-demand (not every session),
keeping context costs low for non-medication conversations.
### Sleep Tracking
The agent infers sleep from activity gaps rather than requiring explicit "I'm
going to sleep" statements. On every message, it checks: was there a gap since
the last message that overlaps with the predicted sleep window? If so, log sleep
start = last activity before gap, wake = first activity after.
Activity isn't just chat messages — it includes Gitea commits, comments,
scheduled flight departures, and any other observable actions.
Sleep predictions drift over time (the human in this setup tends to sleep ~30min
later each day), so the agent tracks the trend and extrapolates. Caffeine intake
adjusts predictions forward. The predictions feed into:
- When to send alerts vs stay quiet
- Sitrep "sleep conflict" warnings (appointment during predicted sleep)
- General awareness of the human's schedule
### Location & Timezone Tracking
The agent always knows where the human is. This matters because:
- Times must always be displayed in the human's local timezone, not the server's
timezone
- "Today" and "tomorrow" mean different things in different timezones
- Medication timing needs to account for timezone changes
- Flight prep needs local airport info
The landing checklist (triggered automatically after every flight) updates
location, timezone, nearest airport, and lodging in the daily context file. It
also checks if any cron jobs have hardcoded timezones that need updating.
### Gitea Notification Delivery
There are two approaches for getting Gitea notifications to your agent,
depending on your network setup.
#### Option A: Direct Webhooks (VPS / Public Server)
If your OpenClaw instance runs on a VPS or other publicly reachable server, the
simplest approach is direct webhooks. Run Traefik (or any reverse proxy with
automatic TLS) on the same server and configure Gitea webhooks to POST directly
to OpenClaw's webhook endpoint. This is push-based and realtime — notifications
arrive instantly.
Setup: add a webhook on each Gitea repo (or use an organization-level webhook)
pointing to `https://your-openclaw-host/hooks/gitea`. OpenClaw handles the rest.
#### Option B: Notification Poller (Local Machine Behind NAT)
If your OpenClaw runs on a dedicated local machine behind NAT (like a home Mac
or Linux workstation), Gitea can't reach it directly. This is our setup —
OpenClaw runs on a Mac Studio on a home LAN.
The solution: a lightweight Python script that polls the Gitea notifications API
every few seconds. When new notifications appear, it writes a flag file that the
agent checks during heartbeats.
Key design decisions:
- **The poller never marks notifications as read.** The agent does that after
processing. This prevents lost notifications if the agent fails to process.
- **It tracks notification IDs, not counts.** Only fires on genuinely new
notifications, not re-reads of existing ones.
- **Flag file instead of wake events.** We initially used OpenClaw's
`/hooks/wake` endpoint, but wake events target the main (DM) session — any
model response during processing leaked to DM as a notification. The flag file
approach is processed during heartbeats, where output routing is controlled.
- **Zero dependencies.** Just Python stdlib. Runs anywhere.
Tradeoff: notifications are processed at heartbeat cadence (~30 min) instead of
realtime. For code review and issue triage, this is fine.
```python
#!/usr/bin/env python3
"""
Gitea notification poller (flag-file approach).
Polls for unread notifications and writes a flag file when new ones
appear. The agent checks this flag during heartbeats and processes
notifications via the Gitea API directly.
Required env vars:
GITEA_URL - Gitea instance URL
GITEA_TOKEN - Gitea API token
Optional env vars:
FLAG_PATH - Path to flag file (default: workspace/memory/gitea-notify-flag)
POLL_DELAY - Delay between polls in seconds (default: 5)
"""
import json
import os
import sys
import time
import urllib.request
import urllib.error
GITEA_URL = os.environ.get("GITEA_URL", "").rstrip("/")
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "")
POLL_DELAY = int(os.environ.get("POLL_DELAY", "5"))
FLAG_PATH = os.environ.get(
"FLAG_PATH",
os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"memory",
"gitea-notify-flag",
),
)
def check_config():
if not GITEA_URL or not GITEA_TOKEN:
print("ERROR: GITEA_URL and GITEA_TOKEN required", file=sys.stderr)
sys.exit(1)
def gitea_unread_ids():
req = urllib.request.Request(
f"{GITEA_URL}/api/v1/notifications?status-types=unread",
headers={"Authorization": f"token {GITEA_TOKEN}"},
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return {n["id"] for n in json.loads(resp.read())}
except Exception as e:
print(f"WARN: Gitea API failed: {e}", file=sys.stderr, flush=True)
return set()
def write_flag(count):
os.makedirs(os.path.dirname(FLAG_PATH), exist_ok=True)
with open(FLAG_PATH, "w") as f:
f.write(json.dumps({
"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"count": count,
}))
def main():
check_config()
print(f"Gitea poller started (delay={POLL_DELAY}s, flag={FLAG_PATH})", flush=True)
last_seen_ids = gitea_unread_ids()
print(f"Initial unread: {len(last_seen_ids)}", flush=True)
while True:
time.sleep(POLL_DELAY)
current_ids = gitea_unread_ids()
new_ids = current_ids - last_seen_ids
if not new_ids:
last_seen_ids = current_ids
continue
ts = time.strftime("%H:%M:%S")
print(f"[{ts}] {len(new_ids)} new ({len(current_ids)} total), flag written", flush=True)
write_flag(len(new_ids))
last_seen_ids = current_ids
if __name__ == "__main__":
main()
```
Run it as a background service (launchd on macOS, systemd on Linux) with the env
vars set. It's intentionally simple — no frameworks, no async, no dependencies.
### The Daily Diary
Every day gets a `memory/YYYY-MM-DD.md` file. The agent appends a topic summary
to a table after every meaningful conversation:
```markdown
## Topics
| Time (TZ) | Topic |
| --------- | ------------------------------------------------ |
| 14:30 | Discussed PR review workflow |
| 16:00 | Medication logged |
| 18:45 | Flight prep blocks created for tomorrow's flight |
```
This serves multiple purposes:
- Any session can see what's been discussed today without loading full
conversation history
- The agent can use `sessions_history` to get details on a specific topic if
needed
- During memory maintenance, the agent reviews these to decide what's worth
promoting to MEMORY.md
- It's a simple audit trail of what happened
### The Requirement Capture Rule
One of the most important rules: **every single requirement, preference, or
instruction the human provides MUST be captured in a file immediately.** Not
"mentally noted" — written to disk. Because the agent wakes up fresh every
session, a "mental note" is worthless. If it's not in a file, it didn't happen.
This applies to everything: project rules ("no mocks in tests"), workflow
preferences ("fewer PRs, don't over-split"), corrections, new policies.
Immediate write to the daily file, and to MEMORY.md if it's a standing rule.
### PII-Aware Output Routing
A lesson learned the hard way: **the audience determines what you can say, not
who asked.** If the human asks for a medication status report in a group
channel, the agent can't just dump it there — other people can read it. The
rule: if the output would contain PII and the channel isn't private, redirect to
DM and reply in-channel with "sent privately."
This is enforced at multiple levels:
- AGENTS.md has a warning banner at the top
- The checklist system catches it before action
- Channel-specific rule files (like our `memory/JAMES_CHAT.md`) define what's
shareable per-channel
### Sub-Agent Isolation
When spawning coding sub-agents for PR work, each one MUST clone to a fresh
temporary directory. Never share git clones between agents — dirty working
directories cause false CI results, merge conflicts, and wrong file state. The
rule: `cd $(mktemp -d) && git clone <url> . && ...`
### The Heartbeat System
OpenClaw polls the agent periodically with a configurable prompt. The agent
reads HEARTBEAT.md and decides what to do. We keep HEARTBEAT.md small (focused
checklist) to minimize token burn on these frequent checks.
The heartbeat handles:
- Gitea inbox triage (check for new assignments)
- Flight prep block creation (look ahead 7 days)
- Open project review (can I take a step on anything?)
- Workspace sync (commit and push changes)
- Periodic memory maintenance
State tracking in `memory/heartbeat-state.json` prevents redundant checks (e.g.,
don't re-check email if you checked 10 minutes ago).
The key output rule: heartbeats should either be `HEARTBEAT_OK` (nothing to do)
or a direct alert. Work narration goes to a designated status channel, never to
the human's DM.
### The Workspace as a Git Repo (Disaster Recovery + Debugging)
The entire workspace is itself a git repo, aggressively committed and pushed
after every change. A cron job runs every 6 hours as a safety net, but the agent
pushes promptly after any modification.
This gives you three things:
1. **Disaster recovery.** The git remote is on a different machine (ideally in a
different building/city/country from the OpenClaw host). If the host dies,
you clone the workspace onto a new machine and the agent picks up where it
left off — all memory files, daily context, logs, checklists intact.
2. **State snapshots.** Every commit is a snapshot of the agent's complete state
at that moment. If the agent corrupts a file, overwrites something important,
or makes a bad edit, you can `git log` and `git checkout` to recover any
previous version.
3. **Prompt debugging.** When the agent behaves unexpectedly, you can look at
the git history to see exactly what its state files contained at the time.
What did daily-context.json say? What was in MEMORY.md? What checklist
version was it using? This turns "why did it do that?" from guesswork into
forensics.
The agent treats this repo differently from code repos — no PRs, no branches,
just direct commits to main. It's a state mirror, not a development workflow.
```markdown
### State Repo (clawstate.git)
- Commit and push workspace changes after any change to workspace files
- Push promptly — the remote repo should reflect the workspace in effectively
realtime
- Auto-sync cron runs every 6h as a safety net
```
**Important:** Code repos should be cloned OUTSIDE the workspace (e.g.
`~/repos/` or a fast external drive) to avoid embedding git repos inside the
workspace repo. The workspace repo tracks workspace files only.
**A deliberate policy exception:** The workspace repo violates one of the most
important repo policies — "never commit secrets." The workspace contains API
keys, tokens, and credentials in files like TOOLS.md because those are part of
the agent's operational state. This is an accepted exception because the repo is
permanently private and serves as a backup/DR system, not a development repo. If
your workspace state repo were ever to become public, it would be a catastrophic
leak. Treat it accordingly: private visibility, restricted access, no forks.
### Git Quality Standards — Interlocking Automated Checks
An AI agent will forget things. It will skip the formatter, push without
testing, weaken a linter rule to make CI pass, or use `git add .` and commit
junk files. You cannot rely on "be careful" — you need automated gates that make
it structurally impossible to ship bad code.
The approach we use is based on
[sneak/prompts](https://git.eeqj.de/sneak/prompts), a repo of standardized
policies that every project copies. The key document is
[REPO_POLICIES.md](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md),
which defines the interlocking check system.
#### The Interlocking Chain
The checks form a dependency chain where each layer requires the previous:
```
Gitea Actions CI
└── docker build .
└── make check (inside Dockerfile)
├── make fmt-check (code formatting)
├── make lint (static analysis)
└── make test (unit/integration tests)
```
This means:
- **CI runs `docker build .`** — that's it, one command
- **The Dockerfile runs `make check`** as a build step — if checks fail, the
Docker build fails, CI fails
- **`make check` depends on `fmt-check`, `lint`, and `test`** — all three must
pass
- **You can't skip any layer** — they're structurally linked
From
[REPO_POLICIES.md](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md):
> Every repo with software must have a root `Makefile` with these targets:
> `make test`, `make lint`, `make fmt` (writes), `make fmt-check` (read-only),
> `make check` (prereqs: `test`, `lint`, `fmt-check`), `make docker`, and
> `make hooks` (installs pre-commit hook).
> Every repo should have a `Dockerfile`. All Dockerfiles must run `make check`
> as a build step so the build fails if the branch is not green.
> Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
> runs `docker build .` on push. Since the Dockerfile already runs `make check`,
> a successful build implies all checks pass.
#### Why Docker as the CI Runner
Running `make check` inside `docker build` solves the "works on my machine"
problem:
- **Clean environment every time.** No stale caches, no leftover files, no wrong
toolchain version
- **Reproducible.** The Dockerfile pins the base image by SHA (not tag), so the
build environment is identical everywhere
- **No CI configuration drift.** The CI workflow is one line: `docker build .`.
All the actual logic lives in the Dockerfile and Makefile, which are
version-controlled in the repo
From REPO_POLICIES.md:
> ALL external references must be pinned by cryptographic hash. This includes
> Docker base images, Go modules, npm packages, GitHub Actions, and anything
> else fetched from a remote source. Version tags (`@v4`, `@latest`, `:3.21`,
> etc.) are server-mutable and therefore remote code execution vulnerabilities.
#### The Makefile as Authoritative Documentation
The Makefile isn't just a build tool — it's the single source of truth for how
the repo works:
> The Makefile is authoritative documentation for how the repo is used. Beyond
> the required targets above, it should have targets for every common operation:
> running a local development server (`make run`, `make dev`), re-initializing
> or migrating the database (`make db-reset`, `make migrate`), building
> artifacts (`make build`), generating code, seeding data, or anything else a
> developer would do regularly. If someone checks out the repo and types
> `make<tab>`, they should see every meaningful operation available.
This isn't an AI-specific pattern — it predates agents entirely. A Makefile in
every repo has always been good engineering practice because it gives any new
developer (human or AI) a plain, direct, authoritative reference for how to
interact with the repo. It's authoritative _because it's how we actually invoke
the tools_ — it's not documentation that can drift from reality, it IS the
reality. `make<tab>` shows you everything you can do. No wikis to read, no
READMEs to hope are up-to-date.
The deeper insight: **humans also have limited state, limited memory, and start
dropping things when context gets too big.** A developer switching between a Go
service, a Python scraper, and a JS frontend in the same afternoon doesn't want
to remember three different ways to run tests, three different linter
invocations, three different formatting commands. `make test` is `make test`
everywhere. `make check` is `make check` everywhere. `make fmt` is `make fmt`
everywhere. The language, the framework, the toolchain — all abstracted behind a
universal interface.
This reduces cognitive load and allows better flow and focus. You don't have to
re-orient when switching repos. You don't have to look up "was it `pytest` or
`python -m pytest` or `make test-unit`?" The Makefile answers all of that with
one consistent vocabulary across every project.
The agent can implement and enforce these policies — auto-generating Makefiles,
running the checklists, catching drift — but everyone benefits. Humans get
consistency. Agents get predictability. New team members (human or AI) get
instant orientation. The policies serve all three audiences simultaneously.
For AI agents, there's an additional benefit:
- The agent always uses `make test`, never `go test ./...` directly — the
Makefile encapsulates flags, timeouts, environment setup
- A new sub-agent spawned on any repo can immediately see every available
operation without reading docs or asking questions
- The human never has to explain "how to run the tests" — it's always
`make test`, everywhere, every repo
#### Pre-Commit Hooks
Local enforcement before code even reaches the remote:
> Pre-commit hook: `make check` if local testing is possible, otherwise
> `make lint && make fmt-check`. The Makefile should provide a `make hooks`
> target to install the pre-commit hook.
Our PR checklist requires agents to install hooks after every clone:
```sh
echo '#!/bin/sh\nmake check' > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
```
#### Linter Config Is Sacred
One of the most dangerous failure modes (documented in the failures section
above) is an agent modifying linter config to make checks pass:
> `.golangci.yml` is standardized and must _NEVER_ be modified by an agent, only
> manually by the user.
This is enforced in our PR checklist:
```markdown
## After sub-agent pushes code
1. Check diff for .golangci.yml / linter / test config changes
2. Check diff for `-short` / `-timeout` flags added to test commands
3. If any config files changed: reject and rework
```
The principle: **if the check fails, fix the code, not the check.** This applies
universally — linter rules, test assertions, formatting config, CI workflows.
Weakening a gate to make it pass is worse than a loud failure, because loud
failures get fixed while silent rot compounds.
#### Formatting Standards
From
[REPO_POLICIES.md](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md):
> Use platform-standard formatters: `black` for Python, `prettier` for
> JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
> two exceptions: four-space indents (except Go), and `proseWrap: always` for
> Markdown (hard-wrap at 80 columns).
This means formatting is never a judgment call — run the formatter, done. The
`make fmt` target writes changes, `make fmt-check` verifies without modifying
(used in CI). Agents run `make fmt` before committing, CI runs `make fmt-check`
to catch anything that slipped through.
#### Test Requirements
> All repos with software must have tests that run via the platform-standard
> test framework (`go test`, `pytest`, `jest`/`vitest`, etc.). If no meaningful
> tests exist yet, add the most minimal test possible — e.g. importing the
> module under test to verify it compiles/parses. There is no excuse for
> `make test` to be a no-op.
> `make test` must complete in under 20 seconds. Add a 30-second timeout in the
> Makefile.
The timeout is critical for agents — without it, a hanging test blocks the
entire sub-agent session. If tests take too long, that's a bug to file, not a
timeout to increase.
#### Git Hygiene Rules
From REPO_POLICIES.md and our operational experience:
- **Never `git add -A` or `git add .`** — always stage files explicitly. Agents
love to `git add .` and accidentally commit `.DS_Store`, editor swap files, or
debug output
- **Never force-push to main** — feature branches only
- **Branch protection on main** — enforce this with Gitea/GitHub branch
protection rules: require PR reviews, require CI to pass, block force-pushes.
This is the server-side interlock that makes the "never push directly to main"
rule structural rather than aspirational. An agent (or a tired human at 3am)
physically cannot bypass the review and CI gates.
- **Each change = separate commit** — formatting changes go in their own commit
before logic changes
- **Rebase before and after** — PRs must be mergeable at time of push
- **Never commit secrets** — `.env`, credentials, API keys in `.gitignore`
#### The PR Pipeline
Our agent follows a strict PR lifecycle:
```markdown
## PR pipeline (every PR, no exceptions)
1. **Review/rework loop**: code review → rework → re-review → repeat until clean
2. **Check/rework loop**: `make check` + `docker build .` → rework → re-check →
repeat until clean
3. Only after BOTH loops pass with zero issues: assign to human
- "Passes checks" ≠ "ready for human"
- Never weaken tests/linters. Fix the code.
- Pre-existing failures are YOUR problem. Fix them as part of your PR.
```
The agent doesn't just create a PR and hand it off — it drives the PR through
review, rework, and verification until it's genuinely ready. A PR assigned to
the human means: all checks pass, code reviewed, review feedback addressed,
rebased against main, no conflicts. Anything less is the agent's open task.
#### New Repo Bootstrap
Every new repo follows a checklist from REPO_POLICIES.md:
> New repos must contain at minimum: `README.md`, `.git`, `.gitignore`,
> `.editorconfig`, `LICENSE`, `REPO_POLICIES.md`, `Makefile`, `Dockerfile`,
> `.dockerignore`, `.gitea/workflows/check.yml`
Plus language-specific files (Go: `go.mod`, `.golangci.yml`; JS: `package.json`,
`.prettierrc`; Python: `pyproject.toml`).
The full standardized configs are available at
`https://git.eeqj.de/sneak/prompts/raw/branch/main/<filename>` — agents fetch
them when bootstrapping a new repo, ensuring consistency across all projects.
#### Why This Matters — For Everyone
These interlocking checks aren't just agent-proofing — they're human-proofing
too. Humans also cut corners under deadline pressure, forget to run the
formatter, skip tests "just this once," or push a quick fix without checking CI.
The automated gates don't care who's committing:
- Can't push unformatted code → `make fmt-check` in pre-commit hook
- Can't skip tests → `make check` depends on `make test`
- Can't weaken linters → config file changes flagged in PR review
- Can't claim "CI passes" without proof → Docker build is pass/fail
- Can't ship without review → PR assignment rules
AI agents do have a unique failure mode on top of this: they're confidently
wrong. An agent will assert that checks pass without having run them, or
silently weaken a gate to make the build green. The interlocking system catches
this too — confidence doesn't override a failing build.
Nobody needs willpower or attention to detail when the system makes doing the
wrong thing fail loudly. That's the point: good engineering infrastructure
benefits every contributor, regardless of whether they're carbon or silicon.
#### Checklists Over Prose — Why Redundancy Is the Point
REPO_POLICIES.md describes everything: what files are required, how the Makefile
should work, how formatting works, how CI works. It's comprehensive. But
comprehensive prose doesn't keep an agent on track — **point-by-point checklists
do.**
That's why we also maintain two separate checklists alongside REPO_POLICIES.md:
- [**NEW_REPO_CHECKLIST.md**](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/NEW_REPO_CHECKLIST.md)
— step-by-step when creating a repo from scratch
- [**EXISTING_REPO_CHECKLIST.md**](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/EXISTING_REPO_CHECKLIST.md)
— step-by-step when starting work in a repo that may not conform yet
These are intentionally redundant with REPO_POLICIES.md. That's the point.
**Why redundancy works for AI agents:**
LLMs are good at linear, sequential processing. Give them a prose document with
30 requirements scattered across 10 paragraphs, and they'll miss things. Give
them a numbered checklist where each item is a concrete action with a checkbox,
and they'll march through it reliably.
The
[NEW_REPO_CHECKLIST.md](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/NEW_REPO_CHECKLIST.md)
walks through repo creation in five phases:
```markdown
# 1. Initialize
- [ ] `git init`
- [ ] Ask the user for the license
# 2. First Commit (README only)
- [ ] Create README.md with all required sections
- [ ] `git add README.md && git commit`
# 3. Scaffolding (feature branch)
- [ ] Fetch .gitignore, .editorconfig from templates
- [ ] Create LICENSE, REPO_POLICIES.md, Dockerfile
- [ ] Configure Makefile with all required targets
# 4. Verify
- [ ] `make check` passes
- [ ] `make docker` succeeds
- [ ] No secrets in repo
# 5. Merge and Set Up
- [ ] Merge to main, install hooks, push
```
The
[EXISTING_REPO_CHECKLIST.md](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/EXISTING_REPO_CHECKLIST.md)
does the same for existing repos — check each item, fix gaps before starting
your actual task:
```markdown
# Formatting (do this first)
- [ ] Run `make fmt` as standalone commit before any other changes
# Required Files
- [ ] README.md, LICENSE, REPO_POLICIES.md, .gitignore, .editorconfig
- [ ] Dockerfile, .dockerignore, Gitea Actions workflow
# Makefile
- [ ] All required targets exist and work
- [ ] `make check` passes
# Git Hygiene
- [ ] Pre-commit hook installed, no secrets, all refs pinned
# Final
- [ ] `make check` passes, `docker build` succeeds
- [ ] Fix everything before starting your actual task
```
**The same principle applies everywhere in the agent's configuration.** We have
checklists for:
- **PR quality gates** (`memory/checklist-pr.md`) — what to verify before
pushing, after sub-agent pushes, before assigning to the human
- **Medication actions** (`memory/checklist-medications.md`) — what to verify
before reporting status or sending reminders
- **Flight actions** (`memory/checklist-flights.md`) — what to verify before
stating flight times
- **Messaging** (`memory/checklist-messaging.md`) — what to verify before
sending any message (URLs resolve? times converted? status verified?)
Each checklist is loaded on-demand (referenced from a checklist index at the top
of MEMORY.md). The agent reads the relevant checklist before the relevant
action. It's the same pattern as REPO_POLICIES + NEW_REPO_CHECKLIST: prose
document for understanding, checklist for execution.
**The meta-lesson: AI agents are linear thinkers.** They follow step-by-step
instructions reliably. They follow scattered prose requirements unreliably.
Structure your rules as checklists, accept the redundancy, and treat it as a
feature — not a code smell.
### Putting It All Together
The system works as a loop:
1. **Session starts** → read SOUL, USER, daily-context, today's daily file
2. **Message arrives** → check daily-context for state changes (sleep gap?
location change? meds overdue?), respond to the message, update state
3. **Heartbeat fires** → check inbox, projects, flights, sync workspace
4. **External event** (Gitea poller wake) → process notification, respond via
appropriate channel
5. **Session ends** → state persists in files for next session
The files are the continuity. The agent is stateless; the workspace is not.
---
## Table of Contents
1. [Workspace Bootstrapping](#workspace-bootstrapping)
2. [Daily Context — The State File](#daily-context--the-state-file)
3. [Sitrep (Situation Report)](#sitrep-situation-report)
4. [Sleep Tracking](#sleep-tracking)
5. [Location & Timezone Tracking](#location--timezone-tracking)
6. [Medication Tracking](#medication-tracking)
7. [Flight & Travel Logging](#flight--travel-logging)
8. [Landing Checklist](#landing-checklist)
9. [Memory Architecture](#memory-architecture)
10. [Heartbeat Configuration](#heartbeat-configuration)
11. [Gitea Integration & Notification Polling](#gitea-integration--notification-polling)
12. [Sub-Agent Management](#sub-agent-management)
13. [Urgent Notifications via ntfy](#urgent-notifications-via-ntfy)
14. [Group Chat Behavior](#group-chat-behavior)
15. [Cron vs Heartbeat — When to Use Each](#cron-vs-heartbeat)
16. [Requirement Capture](#requirement-capture)
---
## Workspace Bootstrapping
Your workspace is the agent's home directory. Core files:
```
workspace/
├── AGENTS.md # Responsibilities, rules, procedures
├── SOUL.md # Personality, tone, values
├── USER.md # Info about your human (name, tz, prefs)
├── IDENTITY.md # Agent's own identity
├── HEARTBEAT.md # What to check on heartbeat polls
├── MEMORY.md # Curated long-term memory
├── TOOLS.md # Environment-specific notes (hosts, keys, devices)
└── memory/
├── daily-context.json
├── YYYY-MM-DD.md # Daily raw notes
├── sleep-log.csv
├── location-log.csv
├── flight-log.csv
├── medications-log-YYYY-MM.csv
├── medications-instructions.md
├── landing-checklist.md
├── checklist-*.md # Various operational checklists
└── heartbeat-state.json
```
### Session startup prompt (put in AGENTS.md):
```markdown
## Every Session
Before doing anything else:
1. Read `SOUL.md` — this is who you are
2. Read `USER.md` — this is who you're helping
3. Read `memory/daily-context.json` — current state (location, timezone, sleep,
meds)
4. Read `memory/YYYY-MM-DD.md` (today + yesterday) for recent context
5. **If in MAIN SESSION** (direct chat with your human): Also read `MEMORY.md`
Don't ask permission. Just do it.
```
This ensures the agent always has context on wake. The key insight is that the
agent wakes up fresh every session — files ARE its memory.
---
## Daily Context — The State File
This is the single most important file. It's a JSON blob that every session
reads on every message. It tracks the current state of your human.
### Schema: `memory/daily-context.json`
```json
{
"isSleeping": false,
"lastKnownWakeTime": "2026-02-28T12:30:00+07:00",
"predictedSleepTime": "2026-03-01T05:30:00+07:00",
"predictedWakeTime": "2026-03-01T12:45:00+07:00",
"hasTakenDailyMedsToday": false,
"dailyMedsTimestamp": "2026-02-27T11:43:00+07:00",
"lastCaffeineTimestamp": null,
"currentLocation": "City (IATA)",
"currentTimezone": "Asia/Bangkok",
"currentLodging": "Hotel Name",
"travelLeg": "Description of current travel phase",
"nearestAirport": "ICAO code",
"lastMessageFromUser": "2026-02-28T05:20:00+07:00",
"lastMessageChannel": "mattermost",
"isAvailable": true,
"lastUpdated": "2026-02-28T06:00:00+07:00",
"lastSleep": "actual: 2026-02-28 05:15-12:30 ICT (~7h15m). Stable pattern."
}
```
### AGENTS.md prompt:
```markdown
## Daily Context
Stored in `memory/daily-context.json`. All agents: review on every
conversation/message to detect if any state has changed. Update the JSON file
directly.
```
### Why it works:
- Every session reads this first, so the agent always knows where you are
- Sleep predictions help the agent decide when to alert vs stay quiet
- Medication timestamps enable overdue detection
- Timezone field means the agent always converts times correctly
- `lastMessageFromUser` lets the agent infer activity gaps = sleep
---
## Sitrep (Situation Report)
The sitrep is triggered by a keyword (e.g. "sitrep") and produces a concise
status briefing. The key design feature is that the agent can **add sections
dynamically** based on what it thinks you should know.
### AGENTS.md prompt:
```markdown
### Sitrep (Situation Report)
When the user says "sitrep", provide a concise summary covering:
1. **Daily meds** — taken today or not
2. **Interval meds** — any due/overdue or upcoming in next 48h. ALWAYS include
"X days from now" countdown. ALWAYS include day of week.
3. **Open issues assigned to me** — count
4. **Open issues assigned to user** — count + brief summary
5. **Upcoming travel** — flights in next 72h with times/airports. ALWAYS also
include the next known flight even if beyond 72h.
6. **Upcoming appointments** — next 48h
7. **Todo list** — overdue, due today, due tomorrow (separate categories)
8. **Unbooked travel** — flights that need booking. Flag deadlines.
9. **Sleep conflicts** — appointments in next 48h that fall within predicted
sleep window
10. **Sleep** — one-line summary of last sleep + drift trend + concerns
11. **Unanswered questions** — questions the agent asked that haven't been
answered
12. **Weather alerts** — only if significant or extreme. Use METAR from
aviationweather.gov. Omit if nothing notable.
13. **Overdue reminders** — anything pending
14. **Open projects** — one-line status per project
Before generating a sitrep, review the last 3 days of daily memory files for
context. **If anything notable isn't covered by the items above — recent
lessons, pending decisions, things you think the user should know about — add
additional sections as needed.**
Omit any item that is "none" or zero. Keep it scannable. Use bullet points, not
prose. Only surface things the user might not know about or needs to act on.
```
### Key design decisions:
- **"Add additional sections as needed"** — this is the magic line. It lets the
agent surface things you didn't think to ask about. If there's a pending
decision from 2 days ago, or a lesson from a recent mistake, it'll include it.
- **"Omit any item that is none or zero"** — keeps it clean. No "Overdue
reminders: none" clutter.
- **Relative times** ("4 days from now, Tuesday") are more useful than bare
dates
- **Review last 3 days** ensures the agent has recent context, not just today
- The sitrep pulls from multiple data sources (daily context, calendars, issue
trackers, memory files) — it's a dashboard in prose form
### Time display tip:
```markdown
Include current time in local timezone, UTC, and any other relevant timezone at
the top.
```
This anchors the reader and makes all the relative times unambiguous.
---
## Sleep Tracking
The agent infers sleep from message activity gaps, not from explicit "I'm going
to sleep" statements (though those help too).
### File: `memory/sleep-log.csv`
```csv
Date,SleepTime,WakeTime,TimeZone,Duration,Status,Notes
2026-02-07,08:30,15:30,America/Los_Angeles,7h00m,actual,Approximate times
2026-02-08,~12:00,~15:30,America/Los_Angeles,~3h30m,actual,Short sleep
```
### AGENTS.md prompt:
```markdown
### Sleep Tracking
- **`memory/sleep-log.csv`** — columns: Date, SleepTime, WakeTime, TimeZone,
Duration, Status, Notes
- Infer sleep/wake times from message activity and explicit statements
- User's sleep pattern drifts later each day; typical duration 48 hours
- Update daily-context.json isSleeping field accordingly
- **On every message from user:** if there was a communication gap overlapping
predicted sleep time, infer a sleep window (sleep start = last activity before
gap, wake = first activity after gap). Activity includes direct messages, git
commits/comments, scheduled flight departures, and any other observable
actions — not just chat messages. Log based on observed gaps only, never from
mathematical predictions.
```
### Daily context fields for sleep:
```json
{
"isSleeping": false,
"lastKnownWakeTime": "2026-02-28T12:30:00+07:00",
"predictedSleepTime": "2026-03-01T05:30:00+07:00",
"predictedWakeTime": "2026-03-01T12:45:00+07:00",
"lastSleep": "actual: 2026-02-28 05:15-12:30 ICT (~7h15m). Stable pattern."
}
```
### How prediction works:
The agent observes the pattern drift (e.g., sleeping 30min later each day) and
extrapolates. Predictions are updated after each actual observation. The agent
uses predictions to:
- Avoid sending non-urgent alerts during predicted sleep
- Flag sleep conflicts with upcoming appointments in sitreps
- Factor caffeine intake into predictions (caffeine = 5-6h minimum awake)
### Caffeine integration:
```json
{
"lastCaffeineTimestamp": "2026-02-10T00:45:00-08:00"
}
```
Track caffeine in daily-context.json. If the user has caffeine, adjust sleep
prediction forward by their typical caffeine-to-sleep duration.
---
## Location & Timezone Tracking
The agent always knows where you are and adjusts all time displays accordingly.
### File: `memory/location-log.csv`
```csv
Date,City,Country,Timezone
2026-02-08,Las Vegas,US,America/Los_Angeles
2026-02-11,Berlin,DE,Europe/Berlin
2026-02-18,Bangkok,TH,Asia/Bangkok
```
### Timezone rules (AGENTS.md):
```markdown
## Timezone/Time Rules
- System clock is PST. User's TZ is in daily-context.json — always convert.
- "Today"/"tomorrow" = user's local TZ, not system clock.
- Never state times without explicit conversion.
- When logging medication times, always use the user's current timezone.
```
### Why this matters:
OpenClaw runs on a server (probably in a fixed timezone). Your human travels.
Without explicit timezone tracking:
- "Take your meds" fires at 3am local time
- "Today" means the wrong day
- Sleep predictions are off by hours
- Calendar events show wrong times
The daily-context.json `currentTimezone` field is the single source of truth.
Everything else derives from it.
---
## Medication Tracking
This is a safety-critical system. The design prioritizes never missing a dose
and never double-dosing over convenience.
### Files:
- `memory/medications-instructions.md` — the authoritative medication list,
dosages, schedules, and safety rules
- `memory/medications-log-YYYY-MM.csv` — one row per ingestion, split by month
- `memory/checklist-medications.md` — pre-action verification checklist
### Log format: `memory/medications-log-YYYY-MM.csv`
```csv
Date,Time,TimeZone,Medication,Dosage
2026-02-27,11:43,Asia/Bangkok,Metoprolol,50 mg
2026-02-27,11:43,Asia/Bangkok,Omeprazole,40 mg
2026-02-27,11:43,Asia/Bangkok,Aspirin,162 mg
```
### Instructions file structure (`memory/medications-instructions.md`):
```markdown
# Medication Instructions
## General Rules
- Maintain cumulative CSV logs split by month as the authoritative record
- CSV columns (fixed order): Date,Time,TimeZone,Medication,Dosage
- "Today"/"yesterday"/"tomorrow" always mean relative to currentTimezone in
daily-context.json — NEVER relative to system clock
- Always spell medication names correctly; silently correct transcription errors
## Daily Medications
One batch per calendar day unless explicitly confirmed otherwise. "Log my daily
meds" = log each as a separate row.
**CRITICAL: Never trust hasTakenDailyMedsToday without verifying
dailyMedsTimestamp.**
- Resolve the current date in user's currentTimezone FIRST
- Check if dailyMedsTimestamp falls on THAT date in THAT timezone
- If the timestamp is from a previous calendar day, meds are NOT taken today
regardless of the boolean
**Ideal dosing window:** every 24h ±4h. Hard minimum: 14h between doses.
| Medication | Dosage |
| ----------- | ------ |
| (your meds) | (dose) |
## Interval-Based Medications
Scheduling anchored to last actual dose, NOT intended dates.
| Medication | Dosage | Interval |
| ---------- | ------ | ------------ |
| (med name) | (dose) | Every X days |
**Missed dose rules:**
- If missed, next dose date is unknown until the missed dose is taken and logged
- Interval restarts from actual ingestion timestamp
- Missed doses block future scheduling
## Safety Rules (HIGHEST PRIORITY)
1. Always triple-check before instructing any medication:
- Last actual dose taken
- Required dosing interval
- Current eligibility without overdose risk
2. If any ambiguity exists, **do not instruct dosing**
3. Immediately block logging if same-day duplicate daily-med batch or other
overdose pattern appears
4. Require explicit confirmation in any overdose-risk scenario
```
### Verification checklist (`memory/checklist-medications.md`):
```markdown
# Medications Checklist
## Before reporting medication status
1. Check medications-log-YYYY-MM.csv for actual entries, not dailyMedsTimestamp
2. Verify dailyMedsTimestamp is from today in user's timezone (not system clock)
3. Cross-reference medications-instructions.md for what's due
## Before sending medication reminders
1. Confirm current time in user's timezone
2. Check if already taken today (verify against CSV, not boolean)
3. For interval meds: calculate days since last dose from CSV
4. Never send PII (medication names) via public notification channels
```
### Overdue escalation:
```markdown
**CRITICAL: If daily meds are >26h since last dose, escalate aggressively.** Use
urgent notification channel if chat messages go unacknowledged. Do not let this
slide silently. Always include hours since last dose in reminders (e.g. "daily
meds overdue — last dose was 27h ago").
```
### Why the double-check on the boolean:
The daily-context.json has a `hasTakenDailyMedsToday` boolean AND a
`dailyMedsTimestamp`. A midnight-reset cron flips the boolean to false. But if
the user is in a timezone ahead of the server, the boolean may reset before
their actual "today" ends — or after it began. The rule: **always verify the
timestamp falls on today's date in the user's timezone.** The boolean is a
convenience hint, not the source of truth.
---
## Flight & Travel Logging
### Files:
- `memory/flight-log.csv` — one row per flight segment
- `memory/location-log.csv` — one row per calendar day
- `memory/travel.md` — upcoming travel plans, booking status
- `memory/checklist-flights.md` — pre-action checklist
### Flight log format:
```csv
Date,FlightNumber,Origin,Destination,Duration,Alliance
2026-02-10,DL9670,LAS,AMS,10h10m,SkyTeam
2026-02-10,KL1856,AMS,BER,1h25m,SkyTeam
```
### Flight prep blocks (put in HEARTBEAT.md):
```markdown
## Flight Prep Blocks (daily)
Run `khal list today 7d`. For flights missing "shower and dress" / "travel to
airport" blocks, create them:
- shower_start = flight_departure - airport_buffer - travel_buffer - 1h
- Airport buffer: 2h domestic, 2.5h international
- Travel to airport: 1h default
- Create "shower and dress" block at shower_start (1h duration)
- Create "travel to airport" block after shower
- Set cron reminders: 15min before shower start, at departure time
- Skip layover connections (already in airport)
```
This means the agent automatically works backwards from flight times to create
preparation blocks in the calendar. No manual planning needed.
---
## Landing Checklist
Triggered automatically after every flight lands. The agent doesn't wait to be
asked.
### File: `memory/landing-checklist.md`
```markdown
# Landing Checklist
Run this after EVERY flight, regardless of whether location/timezone changes.
## Immediate (within minutes of landing)
- [ ] Update daily-context.json → currentLocation, currentLodging,
currentTimezone, nearestAirport, travelLeg
- [ ] Update midnight-reset cron job timezone to new currentTimezone
- [ ] Update location-log.csv with new city for today
- [ ] Log flight segment in flight-log.csv
## Within 1 hour
- [ ] Check if daily meds are due/overdue (timezone change may shift the window)
- [ ] Check if any interval meds are due today
- [ ] Sync calendar and review next 48h for the new timezone
- [ ] Check for any cron jobs with hardcoded timezones that need updating
## If entering a new country
- [ ] Check for medication resupply needs
- [ ] Note any upcoming reminders that reference the old timezone
```
### AGENTS.md prompt:
```markdown
### Landing Checklist
- After EVERY flight the user takes, run through `memory/landing-checklist.md`
- Trigger: calendar event landing time, or user messages after a flight
- Do not wait to be asked — run it proactively
```
---
## Memory Architecture
The two-tier memory system: daily files (raw) + MEMORY.md (curated).
### Daily files: `memory/YYYY-MM-DD.md`
Raw logs of what happened each day. Topics discussed, decisions made, tasks
completed. Every session can read these.
```markdown
# 2026-02-28
## Topics
| Time | Topic | Session |
| ----- | ---------------------------- | ------- |
| 14:30 | Discussed PR review workflow | main |
| 16:00 | Medication logged | main |
## Notes
- Decided to switch deployment strategy for project X
- User prefers Y approach over Z — standing rule
```
### MEMORY.md — Long-term curated memory
```markdown
## 🧠 MEMORY.md - Your Long-Term Memory
- **ONLY load in main session** (direct chats with your human)
- **DO NOT load in shared contexts** (group chats, sessions with others)
- This is for **security** — contains personal context that shouldn't leak
- Write significant events, thoughts, decisions, opinions, lessons learned
- This is your curated memory — the distilled essence, not raw logs
- Over time, review daily files and update MEMORY.md with what's worth keeping
```
### Memory maintenance (in AGENTS.md or HEARTBEAT.md):
```markdown
### 🔄 Memory Maintenance (During Heartbeats)
Periodically (every few days), use a heartbeat to:
1. Read through recent memory/YYYY-MM-DD.md files
2. Identify significant events, lessons, or insights worth keeping long-term
3. Update MEMORY.md with distilled learnings
4. Remove outdated info from MEMORY.md that's no longer relevant
Think of it like reviewing a journal and updating a mental model. Daily files
are raw notes; MEMORY.md is curated wisdom.
```
### Critical rule — write it down:
```markdown
### 📝 Write It Down - No "Mental Notes"!
- **Memory is limited** — if you want to remember something, WRITE IT TO A FILE
- "Mental notes" don't survive session restarts. Files do.
- When someone says "remember this" → update memory file
- When you learn a lesson → update AGENTS.md or relevant file
- When you make a mistake → document it so future-you doesn't repeat it
- **Text > Brain** 📝
```
---
## Heartbeat Configuration
Heartbeats are periodic polls from OpenClaw. The agent can do background work or
stay quiet.
### HEARTBEAT.md structure:
```markdown
# HEARTBEAT.md
## Inbox Check (PRIORITY)
(check notifications, issues, emails — whatever applies)
## Flight Prep Blocks (daily)
(create calendar prep blocks for upcoming flights)
## Open Projects Review
(check project status, take next steps, or ask if blocked)
## Workspace Sync
(commit and push workspace changes)
## Rules
- Post status updates to a designated channel, not the main chat
- Update state files after any state change
- If nothing needs attention, reply HEARTBEAT_OK
## Output Rules
Never send internal thinking or status narration to user's DM. Output should be:
- HEARTBEAT_OK (if nothing needs attention)
- A direct question or alert (if action needed)
- Work narration → send to a status channel via message tool
```
### Tracking heartbeat state: `memory/heartbeat-state.json`
```json
{
"lastChecks": {
"email": 1703275200,
"calendar": 1703260800,
"weather": null,
"gitea": 1703280000
},
"lastWeeklyDocsReview": "2026-02-24"
}
```
### Heartbeat vs Cron:
```markdown
**Use heartbeat when:**
- Multiple checks can batch together
- You need conversational context from recent messages
- Timing can drift slightly (every ~30 min is fine)
- You want to reduce API calls by combining periodic checks
**Use cron when:**
- Exact timing matters ("9:00 AM sharp every Monday")
- Task needs isolation from main session history
- You want a different model or thinking level for the task
- One-shot reminders ("remind me in 20 minutes")
```
---
## Gitea Integration & Notification Polling
For self-hosted Gitea instances, you can set up a notification poller that
injects Gitea events (issue assignments, PR reviews, @-mentions) into the
agent's session.
### Workflow rules (HEARTBEAT.md / AGENTS.md):
```markdown
## Gitea Work Scope
Find issues/PRs assigned to me or where I'm @-mentioned → do the work.
- If @-mentioned with work but not assigned → assign myself, remove other
assignees
- When nothing is assigned to me, my Gitea work is done
- PRs assigned to user = their queue, not my backlog
Workflow: issue → branch → PR "(closes #X)" → review/rework → assign user when
all checks pass + reviewed.
### Rules
- Respond in the medium addressed: Gitea → Gitea comment, not chat
- Before acting on ANY issue or PR: read ALL existing comments first
- Never do work on a PR/issue that isn't assigned to you
- Work done, no more needed → close the issue
- Your part done, need feedback → unassign yourself, assign next person
```
### Notification poller:
A Python script that polls the Gitea notifications API and injects events into
OpenClaw sessions. It runs via launchd/systemd. Ask me for the full source code
if you want to set this up — I can share it (it's a general-purpose tool with no
PII).
---
## Sub-Agent Management
For complex coding tasks, spawn isolated sub-agents.
### Key rules:
```markdown
### Sub-Agent Git Isolation (MANDATORY)
- NEVER let multiple sub-agents share the same git clone
- Each sub-agent MUST clone to a unique temporary directory (e.g. `mktemp -d`)
- When spawning, always instruct: `cd $(mktemp -d) && git clone <url> . && ...`
- Dirty working directories cause false CI results
### Sub-Agent PR Quality Gate (MANDATORY)
- `make check` must pass with ZERO failures. No exceptions.
- Pre-existing failures are YOUR problem. Fix them as part of your PR.
- NEVER modify linter config to make checks pass. Fix the code.
- Every PR must include full `make check` output
- Rebase before and after committing
- Never self-review
```
---
## Urgent Notifications via ntfy
For time-sensitive alerts when chat messages might go unacknowledged (e.g.,
overdue medications):
```markdown
## Urgent Notifications
Send urgent messages via ntfy.sh:
curl -d "MESSAGE HERE" ntfy.sh/YOUR-PRIVATE-TOPIC-ID
Use this for time-sensitive alerts (overdue meds, critical issues, etc.).
**Never send PII via ntfy.** Keep messages generic (e.g. "daily meds overdue"
not medication names or personal details).
```
Create a random topic ID for your ntfy channel. The agent can `curl` to it
directly.
---
## Group Chat Behavior
Rules for when the agent is in group chats with multiple people:
```markdown
### Know When to Speak
**Respond when:**
- Directly mentioned or asked a question
- You can add genuine value (info, insight, help)
- Something witty/funny fits naturally
- Correcting important misinformation
**Stay silent when:**
- Just casual banter between humans
- Someone already answered the question
- Your response would just be "yeah" or "nice"
- The conversation is flowing fine without you
**The human rule:** Humans don't respond to every message. Neither should you.
Quality > quantity.
### React Like a Human
Use emoji reactions naturally:
- Appreciate something but don't need to reply → 👍, ❤️
- Something made you laugh → 😂
- Interesting/thought-provoking → 🤔, 💡
- Acknowledge without interrupting → ✅, 👀
One reaction per message max.
```
---
## Requirement Capture
Never lose a rule or preference your human states:
```markdown
### Requirement Capture (MANDATORY)
- **Every single requirement, rule, preference, or instruction the user provides
MUST be captured in the daily memory file immediately**
- This includes: project rules, workflow preferences, corrections, new policies,
technical decisions
- If the user says something should be done a certain way, write it down in
memory/YYYY-MM-DD.md AND in MEMORY.md if it's a standing rule
- Nothing is to be missed. If in doubt, log it.
```
---
## PII Output Routing — Audience-Aware Responses
A critical security pattern: **the audience determines what you can say, not who
asked.** If your human asks for a sitrep (or any PII-containing info) in a group
channel, you can't just dump it there — other people can read it.
### AGENTS.md / checklist prompt:
```markdown
## PII Output Routing (CRITICAL)
- NEVER output PII in any non-private channel, even if your human asks for it
- If a request would produce PII (medication status, travel details, financial
info, etc.) in a shared channel: send the response via DM instead, and reply
in-channel with "sent privately"
- The rule is: the audience determines what you can say, not who asked
- This applies to: group chats, public issue trackers, shared Mattermost
channels, Discord servers — anywhere that isn't a 1:1 DM
```
### Why this matters:
This is a real failure mode. If someone asks "sitrep" in a group channel and you
respond with medication names, partner details, travel dates, and hotel names —
you just leaked all of that to everyone in the channel. The human asking is
authorized to see it; the channel audience is not. Always check WHERE you're
responding, not just WHO asked.
---
## General Tips
### Bias toward action
Don't ask blocking questions with obvious answers. Figure it out. Come back with
results, not requests for permission.
### Checklists over trust
For safety-critical operations (meds, deployments, external communications),
maintain checklists in `memory/checklist-*.md` and reference them from
AGENTS.md. The agent reads the checklist before acting.
### State repo
Keep your workspace in a git repo that auto-syncs. This gives you version
history and recovery:
```markdown
### State Repo
- Commit and push workspace changes after any change
- Push promptly — remote should reflect workspace in near-realtime
- Auto-sync cron runs every 6h as a safety net
```
### Error handling philosophy
```markdown
## On Errors
When something goes wrong:
1. Identify what input caused the wrong output
2. Fix the input (rule, checklist, prompt, automation)
3. Move on
No apologies. No promises. Fix the system.
```
### Never weaken checks
```markdown
## On Integrity
Never cheat. When a check fails, fix the code — don't weaken the check. When a
test fails, fix the bug — don't loosen the assertion. When a linter flags
something, fix the finding — don't suppress the rule.
```
---
_This document is a living reference. Patterns here have been tested in
production and refined through real-world use._