/SKILL.md
Etalon memory architecture with Knowledge Graph, session isolation, memory decay, and QMD hybrid search
npx skillsauth add qwexs/engram engramInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
⚠️ READ-ONLY SKILL: This skill is a reference implementation. Do not edit files directly. When installing, copy scripts to your workspace folder:
# Scripts → workspace scripts/ cp skills/engram/scripts/*.js scripts/Templates and assets remain in the skill folder. Specify the path via env:
export ENGRAM_SKILL_DIR=skills/engram # or absolute path bun skills/engram/scripts/init.jsWithout
ENGRAM_SKILL_DIR, scripts look for assets relative to../from their location.
Three-layer memory architecture for OpenClaw agents: curated long-term memory (MEMORY.md), structured knowledge graph (life/), and session-isolated daily notes (memory/).
# 1. Install QMD (if not installed)
bun skills/engram/scripts/install-qmd.js
# 2. Initialize everything
bun skills/engram/scripts/init.js
# Add a group session
bun skills/engram/scripts/add-session.js --platform telegram --id 3382546134
# Validate integrity
bun skills/engram/scripts/validate.js
# Migrate to v2 schema
bun skills/engram/scripts/migrate-v2.js --dry-run
┌─────────────────────────────────────────────┐
║ Layer 3: MEMORY.md (Curated Wisdom) ║
║ Long-term personal insights, decisions ║
├─────────────────────────────────────────────┤
║ Layer 2: Knowledge Graph (life/) ║
║ KG entities with atomic facts ║
├─────────────────────────────────────────────┤
║ Layer 1: Daily Notes (memory/) ║
║ Raw session notes, per-session isolation ║
└─────────────────────────────────────────────┘
Data flows upward: Daily notes → extracted to Knowledge Graph → distilled to MEMORY.md
For full architecture details, see references/architecture.md.
Golden Rule: Memory is isolated by session.
| Session Type | Memory Path | QMD Collection | Access |
|-------------|-------------|----------------|--------|
| Main (personal) | memory/agent-{id}/main/ | openclaw-memory-agent-{id}-main | Full: MEMORY.md + life/ |
| Telegram group | memory/agent-{id}/telegram-{gid}/ | openclaw-memory-agent-{id}-telegram-{gid} | Own daily notes ONLY |
| Discord channel | memory/agent-{id}/discord-{cid}/ | openclaw-memory-agent-{id}-discord-{cid} | Own daily notes ONLY |
Rules:
-c <collection> in QMD queriesFirst, detect init mode based on the incoming message:
Heartbeats need speed, not full context. SOUL.md and USER.md are typically already in project context (OpenClaw loads them automatically).
Automated by hooks (do NOT repeat manually):
- Daily note creation →
engram-daily-note(gateway:startup)<!-- session:start -->marker →engram-session-start(agent:bootstrap)- QMD index refresh →
engram-bootstrap-qmd(agent:bootstrap)
memory/heartbeat-state.jsonSkipped: SOUL.md, USER.md, yesterday's daily note, MEMORY.md, QMD query — none needed for heartbeat flow.
Automated by hooks (do NOT repeat manually):
- SOUL.md / USER.md → injected via OpenClaw project context
- Daily note creation →
engram-daily-note(gateway:startup)<!-- session:start -->marker →engram-session-start(agent:bootstrap)<!-- session:end -->marker →engram-session-end(command:new/reset)- QMD index refresh →
engram-bootstrap-qmd(agent:bootstrap)
## Active Threads onwards, or last 80 lines if marker absent)qmd query "<topic of first user message>" -c life once, on the first substantive user message — not on every message, not during startup before the user speaks
Apply during a session (after Full Init). Run
qmd querywhenever a trigger fires. Main session only — group chats do not have access to the Knowledge Graph (life/).
| Trigger | Condition | Query |
|---------|-----------|-------|
| First substantive request | User's first non-greeting message in a session | qmd query "<topic>" -c life |
| Named entity appears | Project, person, or system mentioned for the first time in this session | qmd query "<entity name>" -c life |
| Decision requested | User asks for advice, recommendation, or "what should I do" | qmd query "<topic>" -c life + check memory/domains/ if relevant |
| Trigger | Condition | |---------|-----------| | Topic shift | Conversation moves to a clearly different domain (e.g. dev → infra → marketing) | | Long session + new subject | >20 messages in session and a new subject appears | | "We did this before" | Request that could have history in prior sessions/notes | | Contradiction detected | Something the user says conflicts with what you believe you know |
# Combined KG + session memory (recommended)
qmd query "topic" -c life -c openclaw-memory-agent-{id}-main
# KG only (when session memory not relevant)
qmd query "topic" -c life
Use the most specific term you can extract. If multiple entities are relevant, run separate queries.
memory/agent-{id}/{session}/YYYY-MM-DD.md# YYYY-MM-DDarchives/YYYY-MM/ (nothing lost)qmd queryStructured memory in life/ using a flat three-folder structure (people/projects/archives):
life/
├── people/<name>/ # People (summary.md + items.json)
├── projects/<name>/ # Active work, tools, groups, AI agents (summary.md + items.json)
├── archives/<name>/ # Inactive entities
└── index.md # Master entity index
Tiered retrieval:
qmd query "topic" -c life — search firstsummary.md — quick context (~90% sufficient)items.json — only for granular detailEntity creation rules:
For the atomic fact schema (v2), see references/fact-schema.md.
| Type | Destination | |------|-------------| | Operational lesson | TOOLS.md or AGENTS.md | | Personal insight | MEMORY.md (main session only) | | Event/fact | Today's daily note | | Durable knowledge | Knowledge Graph (life/) |
When someone says "remember this":
qmd update# Hybrid search (BM25 + vectors + rerank)
qmd query "search text" -c <collection>
# Multi-collection search (searches across multiple collections)
qmd query "search text" -c life -c openclaw-memory-agent-main-main
# BM25-only search (no GPU required, faster)
qmd search "search text" -c <collection>
# Update index after changes
qmd update # BM25 (instant)
qmd embed # Vectors (heartbeat only)
Strategy:
-c flag for session isolation-c col1 -c col2) for cross-cutting searches (e.g., KG + daily notes)qmd search (BM25-only) when GPU is busy or unavailableqmd update after writing memoryqmd embed manually (heartbeat handles it)For QMD installation and configuration, see references/qmd-setup.md.
Add to your HEARTBEAT.md:
## Heartbeat Flow (every 30 minutes)
0. Three-Layer Rotation check (daily note creation → handled by engram-daily-note hook)
1. Monday? → Weekly Synthesis
2. Knowledge Graph Extraction (if notes changed)
3. Memory Maintenance (every few days)
3.5. Domain Supervisor Scan (if domains exist)
4. QMD Index Update (qmd update + qmd embed)
Rewrites summary.md with memory decay applied:
Modifiers:
confidence < 0.5 → Cold threshold is 14 daysaccessCount >= 10 → bumps Cold to Warmprinciple (L3) → always in summarypattern (L2) → in summary if Warm+For full decay rules, see references/decay-rules.md.
During heartbeats, scan daily notes for durable facts:
<!-- extracted:L{N}:{timestamp} --> at the end of each daily note. If found, only parse lines after the last watermark. No watermark = parse entire file (backward compatible).items.json with confidence and abstraction levelsummary.md for new Hot facts<!-- extracted:L{lastLine}:{ISO timestamp} -->For the complete heartbeat flow, see references/HEARTBEAT.md.
If subagent domains exist (memory/domains/), heartbeat acts as supervisor:
qmd query "PROPOSAL" -c domains → auto-approve low-risk, alert user for high-riskstatus.md, alert if missed >2x schedulechangelog.md >1000 lines to archives/For full details, see references/HEARTBEAT.md and references/subagent-memory.md.
The heartbeat spawns subagents (hb-extract, hb-synthesis, hb-domains, hb-rethink, hb-autoresearch, hb-rethink2). The model for each is not hardcoded in the protocol — it is resolved at spawn time by scripts/config.js → resolveSubagentModel(workspace, label). Resolution order:
process.env.ENGRAM_MODEL_<LABEL_UPPER> (e.g. ENGRAM_MODEL_HB_EXTRACT)engram.json → models.heartbeat.subagents[<label>] (e.g. "hb-extract": "<model-id>")SUBAGENT_MODEL_DEFAULTS (currently sonnet-4-6 for all phases)sonnet-4-6Example engram.json override:
{
"models": {
"heartbeat": {
"subagents": {
"hb-extract": "<cheap-model>",
"hb-synthesis": "<capable-model>"
}
}
}
}
Why configurable, not hardcoded: Engram is model-agnostic. The protocol docs use sonnet-4-6 as a sensible default so behavior is reproducible across deployments, but hardcoding deployment-specific aliases (e.g. m3, m2.7) in the protocol would leak private infra and break for other users.
Facts decay based on recency, with modifiers for confidence, frequency, and abstraction:
| Tier | Recency | In summary? | |------|---------|-------------| | Hot | ≤7 days | ✅ Prominent | | Warm | 8-30 days | ✅ Lower priority | | Cold | 30+ days | ❌ (searchable via QMD) |
Full rules: references/decay-rules.md
System observes its own friction — what worked, what failed, what patterns emerged — and accumulates these observations for review.
workspace/ops/
├── observations/ # Operational observations
│ ├── index.json # Registry of all observations
│ └── obs-0001.json # Individual observation files
└── tensions/ # Contradictions between facts
├── index.json # Registry of all tensions
└── tension-0001.json # Individual tension files
Only the agent writes observations — subagents return Flags: in handoffs for the agent to review.
# Friction, surprise, or pattern (inline during session)
bun skills/engram/scripts/memory-observe.js --observation "KG extraction missed facts about email" --category friction
bun skills/engram/scripts/memory-observe.js --observation "..." --category surprise --description "Why this matters"
Categories: friction (weight ×3), surprise (weight ×2), pattern (weight ×1)
Tensions are auto-created when memory-write.js --check-contradictions finds Jaccard ≥0.5 + ≥3 common keywords. Manual creation:
bun skills/engram/scripts/memory-tension.js \
--tension "Fact A contradicts fact B" \
--fact1 "sergey-001" --fact2 "sergey-005" \
--type factual \
--confidence 0.8 \
--description "Context about the contradiction"
Types: factual (default), temporal, priority
# Promote obs → KG fact (with backlink)
bun skills/engram/scripts/memory-promote.js \
--obs-id obs-0002 --entity "projects/engram" \
--fact "Extraction finds no facts in heartbeat-only daily notes" \
--category context --confidence 0.8 --abstraction pattern \
--tags "extraction,heartbeat" [--dry-run]
# Archive (noise, status report, resolved friction)
bun skills/engram/scripts/memory-promote.js --archive \
--obs-id obs-0003 --reason "domain status report, not friction"
Backlink: promoted KG fact gets source: obs-id; obs file gets kgFactId.
Phase 5 computes weighted score and spawns hb-rethink when triggered:
| Condition | Threshold | |-----------|-----------| | Weighted score (f×3 + s×2 + p×1) | ≥ 15 | | Pending tensions | ≥ 3 | | Days since last rethink | ≥ 14 |
hb-rethink (sonnet-4-6) reviews observations + tensions, identifies patterns, generates proposals, and returns a HB-RETHINK HANDOFF block. process-handoff.js auto-executes low-risk actions (archive noise, promote facts) and surfaces an ALERT.
# Resolved: one fact supersedes the other
bun skills/engram/scripts/memory-tension-resolve.js \
--id tension-0001 --resolution "fact-abc superseded by fact-xyz"
# Dissolved: not actually contradictory
bun skills/engram/scripts/memory-tension-resolve.js \
--id tension-0001 --dissolved \
--resolution "facts are scope-dependent (work vs personal context)"
Observation:
{
"id": "obs-0001",
"observation": "KG extraction missed facts about email",
"category": "friction",
"status": "pending | promoted | implemented | archived",
"createdAt": "2026-02-25T12:00:00.000Z",
"promotedAt": null,
"archivedAt": null,
"kgFactId": null,
"accessCount": 0
}
Tension:
{
"id": "tension-0001",
"tension": "Possible contradiction: ...",
"type": "factual | temporal | priority",
"confidence": 0.72,
"fact1": "sergey-001",
"fact1Text": "Prefers Bun over Node.js",
"fact2": "sergey-005",
"fact2Text": "Uses Node.js for all projects",
"description": "Auto-detected (Jaccard 0.72, 4 common words)",
"status": "pending | resolved | dissolved",
"createdAt": "2026-03-03T15:00:00.000Z"
}
index.json stats:
{
"observations": ["obs-0001", ...],
"lastId": 10,
"stats": { "total": 10, "pending": 1, "promoted": 2, "implemented": 1, "archived": 6 }
}
For full OLL details, see references/HEARTBEAT.md (Phase 5) and references/HB-RETHINK.md.
Each fact in items.json includes:
{
"id": "<entity>-NNN",
"fact": "Human-readable statement",
"category": "relationship|milestone|status|preference|context|decision|correction",
"confidence": 0.85,
"abstractionLevel": "episode|pattern|principle",
"tags": ["tag1"],
"timestamp": "2026-02-08",
"source": "2026-02-07",
"status": "active|superseded",
"supersededBy": null,
"relatedEntities": ["people/sergey"],
"lastAccessed": "2026-02-08",
"accessCount": 1
}
No-Deletion Rule: Facts are NEVER deleted. Set status: "superseded" and link via supersededBy.
Write Pipeline Rule: NEVER write items.json directly. Always use bun skills/engram/scripts/memory-write.js. Direct writes bypass dedup, validation, and hash registration, causing schema mismatches (e.g., content vs fact, created vs timestamp). This applies to heartbeats, inline extraction, and entity creation. No exceptions.
Full schema: references/fact-schema.md
Pattern for subagents with cleanup: "delete" and long-term memory via domains.
# Create a domain
bun skills/engram/scripts/add-domain.js --domain monitoring --description "Server monitoring"
# Configure rules in decisions.md
# Launch subagent with prompt from templates/spawn-prompt.md
memory/domains/{domain}/
├── decisions.md # Rules (read-only for subagent)
├── workflow.md # HOW the domain works: scripts, scope, tools (optional)
├── status.md # Current state (written by subagent)
├── changelog.md # Append-only log (written by subagent)
├── archives/ # Changelog rotation
└── README.md
workflow.md — optional file describing the domain's infrastructure (scripts, API, task scope, wiki links). Recommended for domains with 2+ task types. Simple domains (single task) can work without it.
Separation of concerns:
decisions.md — read-only for subagents; changes via PROPOSAL in changelogdomains collection for all domainsDomains can be linked to projects in the Knowledge Graph (life/projects/). This provides a two-way binding:
life/projects/{name}/) — what the bot knows about the project (facts, summary)memory/domains/{name}/) — context for the subagent (decisions, status, changelog)The binding is defined via the domain registry (memory/domains/registry.json):
{
"domains": {
"engram": {
"type": "dev-project",
"kgEntity": "projects/engram",
"description": "Memory architecture skill",
"subagentLabel": "engram",
"spawnTemplate": "dev-project.md"
},
"monitoring": {
"type": "cron-task",
"description": "Server monitoring",
"subagentLabel": "monitoring",
"spawnTemplate": "cron-task.md"
}
}
}
Registry fields:
| Field | Required | Description |
|-------|----------|-------------|
| type | ✅ | dev-project, cron-task, or topic-thread |
| description | ✅ | Brief description |
| spawnTemplate | ⚠️ recommended | File from templates/spawn-prompts/ |
| subagentLabel | ⚠️ recommended | Fixed label for sessions_spawn |
| kgEntity | no | Link to Knowledge Graph entity |
Domain types:
dev-project — development, linked to KG entity, subagent on demandcron-task — periodic tasks, subagent on scheduletopic-thread — Telegram topic as memory contour (not a subagent; the topic itself is the long-lived OpenClaw session), bound via registry.domains[slug].topic = { chatId, topicId }. Hook engram-topic-domain-load injects ## Domain Context block into the topic session's daily note on message:received. Hook engram-topic-auto-domain-suggest injects a ## engram:auto-suggest block in unbound topics once a topic accumulates ≥2 user messages. See references/topic-thread.md for the full contract. Heartbeat-runner archives stale topic-thread domains after staleAfterDays (default 60) to memory/domains/archives/{slug}/ and sets archived: true + archivedAt + archivePath in the registry. Hook engram-topic-domain-load re-activates an archived domain automatically on the next message:received in its topic.Rule: always use a template. Don't write prompts manually — use spawnTemplate from the registry. This ensures the subagent receives the Domain Lifecycle (paths to decisions, status, changelog).
Templates are in templates/spawn-prompts/:
dev-project.md — for development (decisions + status + changelog tail)cron-task.md — for periodic tasks (decisions + status)Templates use placeholders: {{domain}}, {{task}}, {{workflow}}, {{decisions}}, {{status}}, {{changelog_tail}}.
Context chain: Template → workflow.md → decisions.md → wiki (if needed) → execution.
Workflow:
registry.jsonspawnTemplatesubagentLabel and cleanup: "delete"Where to work — not specified in the domain. The bot determines the working directory from its memory and conversation context.
The main agent (not a script) handles spawning:
registry.json → get spawnTemplate, subagentLabelstatus.md + changelog.md (tail) → understand current statedecisions.md + workflow.md → include verbatim in prompttemplates/spawn-prompts/{spawnTemplate}{{domain}}, {{decisions}}, {{workflow}}, {{task}}sessions_spawn(label: subagentLabel, cleanup: "delete", task: <prompt>)Key principle: the agent's judgment in interpreting status/changelog and formulating a precise task IS the value. Templates provide structure, not automation.
Detailed documentation: references/subagent-memory.md
Instead of waiting for heartbeats (up to 30 min), extract high-signal facts inline during conversations.
Message → Signal Scan (regex, <10ms) → Classify
├── HIGH (preference, decision, correction, milestone, instruction, identity)
│ → Dedup (SHA-256) → Contradiction check → Write to KG → QMD update
├── LOW (context, work) → Daily note → Heartbeat extracts later
└── NONE (casual) → Skip
bun skills/engram/scripts/memory-signal.js --text "Я предпочитаю TypeScript"
# → { "signal": "high", "categories": ["preference"], "confidence": 0.88 }
Supports Russian and English. Six categories: correction, preference, decision, identity, instruction, milestone.
bun skills/engram/scripts/memory-write.js \
--entity "people/sergey" \
--fact "Prefers Bun over Node.js" \
--category preference \
--confidence 0.9 \
--abstraction pattern \
--tags "tools,runtime" \
--source "2026-02-16"
# With contradiction detection
bun skills/engram/scripts/memory-write.js --entity "..." --fact "..." --category ... \
--check-contradictions --cross-entity
# With semantic similarity check (BM25)
bun skills/engram/scripts/memory-write.js --entity "..." --fact "..." --category ... \
--semantic-check --search-collections "life,openclaw-memory-agent-main-main"
Automatically: content-hash dedup → optional contradiction/semantic check → write fact → validate KG → update QMD.
# Intra-entity (fast, no QMD)
bun skills/engram/scripts/memory-contradict.js --fact "Uses Node.js" --entity "people/sergey"
# Cross-entity (via QMD BM25, searches all entities)
bun skills/engram/scripts/memory-contradict.js --fact "Uses Node.js" --entity "people/sergey" \
--cross-entity --collections "life,openclaw-memory-agent-main-main"
memory-write.jsDaily notes capture session activity. Two-level protection:
Level 1: Agent inline (primary) — best quality, during session
↓ forgot?
Level 2: Compaction memoryFlush — safety net before context compression
Record to daily note when:
--section decisionsbun skills/engram/scripts/daily-note-append.js \
--session main --section events --text "Fixed 44 semantic duplicates in KG"
bun skills/engram/scripts/daily-note-append.js \
--session main --section decisions --text "Jaccard ≥ 0.5 now blocks writes (was warning-only)"
Configured in OpenClaw (agents.defaults.compaction.memoryFlush). Before context compression, the agent receives a prompt to write lasting notes to memory files. Configure with a specific instruction:
{
"memoryFlush": {
"enabled": true,
"prompt": "Write session events/decisions/learnings to today's daily note via: bun skills/engram/scripts/daily-note-append.js --session main --section <section> --text '<text>'. Then reply NO_REPLY.",
"systemPrompt": "Session nearing compaction. Record substantive work to daily note now."
}
}
events, explicit choices → decisions, insights → learningsthreads and next sectionsbun skills/engram/scripts/install-qmd.js [--variant local|jina] [--jina-key <key>]
Interactive installer for QMD. Two variants:
Handles npm install, API key configuration, .env file creation, and verification.
bun skills/engram/scripts/init.js [--agent-id main] [--qmd-variant auto|local|jina] [--force]
Creates complete directory structure, copies templates, sets up QMD collections, runs initial index. Use --force to merge with existing directories.
bun skills/engram/scripts/add-session.js --platform telegram --id <groupId> [--agent-id main]
Creates session directory, copies group-knowledge templates, adds QMD collection, updates heartbeat-state.json.
bun skills/engram/scripts/add-domain.js --domain <name> [--description "Description"]
Creates memory/domains/{domain}/ with decisions.md, status.md, changelog.md, README.md. Registers QMD collection domains (one for all domains). Warns if >20 domains.
bun skills/engram/scripts/validate.js [--fix] [--agent-id main]
Checks directory structure, required files, items.json validity, v2 schema compliance, ID uniqueness, supersededBy references. Use --fix to auto-repair.
bun skills/engram/scripts/migrate-v2.js [--dry-run]
Adds missing v2 fields (confidence, abstractionLevel, tags) to all items.json files with sensible defaults.
bun skills/engram/scripts/memory-signal.js --text "I prefer TypeScript"
Classifies text as high/low/none signal. Regex-based, no LLM, <10ms. Returns categories, keywords, confidence.
# Write a fact
bun skills/engram/scripts/memory-write.js --entity <path> --fact <text> --category <cat> \
[--confidence 0.9] [--abstraction pattern] [--tags "a,b"] [--source "2026-02-16"] \
[--description "Why this fact matters (max 150 chars)"] \
[--entity-create] [--check-contradictions] [--cross-entity] \
[--semantic-check] [--search-collections "life,collection2"]
# Track access (updates lastAccessed + accessCount for decay)
bun skills/engram/scripts/memory-write.js --access --entity <path> --id <fact-id>
Single entry point for all KG writes. Handles dedup, validation, QMD update, optional contradiction/semantic checks. Use --entity-create to create new entities on the fly. Use --access mode to bump a fact's recency (important for decay tiers).
bun skills/engram/scripts/memory-dedup.js --seed # Index all existing facts
bun skills/engram/scripts/memory-dedup.js --check --hash <sha256> # Check if exists
Manages workspace/memory-state/fact-hashes.json. Run --seed after initial setup or weekly synthesis.
bun skills/engram/scripts/memory-contradict.js --fact <text> --entity <path> \
[--cross-entity] [--collections "life,other"]
Finds conflicting facts via Jaccard similarity. Intra-entity by default; --cross-entity discovers related entities via QMD BM25.
bun skills/engram/scripts/memory-observe.js --observation "text" --category friction [--description "desc"] [--dry-run]
Captures observations about system friction, surprises, or patterns. Categories: friction, surprise, pattern. Includes novelty check (Jaccard >0.7 rejects duplicates). Only the agent writes observations directly — subagents return Flags: in handoffs.
bun skills/engram/scripts/memory-tension.js \
--tension "text" --fact1 <id> --fact2 <id> \
[--type factual|temporal|priority] [--confidence 0.8] [--description "desc"] [--dry-run]
Captures tensions between two KG facts. Validates both IDs exist in KG. Stores fact1Text/fact2Text from KG for hb-rethink review. Auto-created by memory-write.js --check-contradictions when Jaccard ≥0.5 + ≥3 common keywords. Novelty check (>0.7 → skip duplicate).
bun skills/engram/scripts/memory-tension-resolve.js --id tension-0001 --resolution "text"
bun skills/engram/scripts/memory-tension-resolve.js --id tension-0001 --dissolved --resolution "text"
Marks tension as resolved (contradiction fixed) or dissolved (not actually contradictory). Idempotent if already closed.
# Promote obs → KG fact (with backlink: obs.kgFactId ← fact.source = obs-id)
bun skills/engram/scripts/memory-promote.js \
--obs-id obs-0002 --entity "projects/engram" --fact "text" \
--category context --confidence 0.8 [--abstraction pattern] [--tags "..."] [--dry-run]
# Archive observation
bun skills/engram/scripts/memory-promote.js --archive --obs-id obs-0003 --reason "noise"
Updates index.json stats (total/pending/promoted/implemented/archived) after every status change.
bun skills/engram/scripts/daily-note-append.js \
--session main --agent-id main --section events --text "Fixed 44 semantic duplicates in KG"
Atomically appends a bullet entry to a named section of today's daily note. Creates the note from template if it doesn't exist. Sections: events, decisions, learnings, threads, next. Never overwrites existing content, never touches watermarks or Heartbeat Report.
bun skills/engram/scripts/rebuild-summaries.js [--dry-run] [--entity people/sergey] [--apply-decay] [--max-cold-principles 12]
Deterministically regenerates summary.md for all entities in life/ from their items.json. No LLM involved.
Without --apply-decay: groups active facts by category, lists top 5 by confidence, shows counts per category and superseded stats. Outputs { "updated": N, "skipped": N, "errors": N }.
With --apply-decay: applies Memory Decay tiers (Hot/Warm/Cold) based on lastAccessed/createdAt/source date. Summary format changes to tiered sections: ## Current (Hot), ## Background (Warm), ## Enduring (Principles). Cold facts are excluded from summary unless selected by semantic priority. Cold principles are sorted by priority/access/confidence/date and capped by --max-cold-principles (default 12) so summary.md stays quick-context sized; omitted facts remain in items.json and searchable via QMD. Outputs include { "updated": N, "skipped": N, "errors": N, "hot": N, "warm": N, "coldExcluded": N, "omittedOperational": N, "omittedTestArtifacts": N, "includedByPriority": N, "limitedPrinciples": N }.
Decay algorithm: see references/decay-rules.md. Used by HB-SYNTHESIS.md subagent during Monday heartbeat.
Use --dry-run to preview diffs without writing. Use --entity to process one entity.
bun skills/engram/scripts/rotate-notes.js --check --session main # Check if daily note needs rotation
bun skills/engram/scripts/rotate-notes.js --check-domains # Check all domain changelogs
bun skills/engram/scripts/rotate-notes.js --rotate --file <path> --type daily # Rotate daily note (>1000 lines)
bun skills/engram/scripts/rotate-notes.js --rotate --file <path> --type changelog # Rotate changelog
# exit 0: nothing to rotate / done | exit 10: needs rotation (--check mode)
Handles Three-Layer Rotation for daily notes (archive + stub + QMD index) and simple rotation for domain changelogs. Called by heartbeat Phase 0.5. Daily note stubs contain a <!-- STUB: ... --> marker for the agent to fill with a summary later.
bun skills/engram/scripts/heartbeat-state.js --get-all
bun skills/engram/scripts/heartbeat-state.js --set pendingObservations 5
Atomic read/write of memory/heartbeat-state.json. All heartbeat phase trackers (lastExtraction, lastDomainScan, subagentRuns, etc.) must be updated via this script — never edit the JSON directly.
bun skills/engram/scripts/heartbeat-runner.js \
--workspace /path/to/workspace \
--agent-id main \
--session main \
--label-prefix hb
Runs the mechanical heartbeat path without relying on an LLM to interpret
HEARTBEAT.md: lock handling, daily note creation, extraction watermark,
weekly summary rebuild, heartbeat report, validation, qmd update, and
qmd embed. This is the recommended production cron target; see
references/setup.md for the OpenClaw cron payload.
Use --all-active-sessions for workspace-level heartbeat. It reads
activeSessions from memory/heartbeat-state.json, runs extraction/report for
each active session, then runs workspace maintenance once. Use engram.json
qmd.index, qmd.collections, and optional qmd.command when a workspace has a
named QMD index or needs a Windows-safe command path.
bun skills/engram/scripts/heartbeat-report.js --session main --date 2026-02-27 \
--extraction "spawned (result pending)" \
--synthesis "skipped (not Monday)" \
--domains "spawned (result pending)" \
--maintenance "ok — validate-kg.js: 0 errors"
Creates or updates ## Heartbeat Report section in a daily note. Called by heartbeat orchestrator in Phase 6 (initial write) and by process-handoff.js (status update after subagent handoff). Omit any flag to preserve its current value.
printf '%s' "<handoff block>" | bun skills/engram/scripts/process-handoff.js --session main --date 2026-02-27
# exit 0: ok | exit 1: error | exit 2: alerts present ([ALERT] lines in stdout)
Processes === HB-* HANDOFF === blocks from subagent results. Handles HB-EXTRACT (watermark advance, lastExtraction, facts count), HB-DOMAINS (lastDomainScan), HB-SYNTHESIS (lastWeeklySynthesis). Updates heartbeat-state.json, advances watermark in daily note, and calls heartbeat-report.js automatically. Called by the heartbeat orchestrator Handoff Handler — do not call manually.
Engram ships 5 OpenClaw hooks that automate mechanical session tasks. Hooks run automatically — agents do NOT need to repeat these steps manually.
| Hook | Event | What it does |
|------|-------|--------------|
| engram-daily-note | gateway:startup | Creates today's daily note for all sessions |
| engram-session-start | agent:bootstrap | Appends <!-- session:start:{ISO} --> to daily note |
| engram-session-end | command:new, command:reset | Appends <!-- session:end:{ISO} --> to daily note |
| engram-bootstrap-qmd | agent:bootstrap | Runs qmd update (15s timeout, silent skip if unavailable) |
| engram-message-log | message:received | Logs messages to workspace/message-log/YYYY-MM-DD.jsonl |
| engram-session-memory | command:new, command:reset | Save session transcript to sessions/ subdir (QMD-indexed). Replaces native session-memory. |
Note: Disable the built-in
session-memoryhook when enablingengram-session-memory— they serve the same purpose but write to different locations.
/newengram-session-end fires on command:new → writes <!-- session:end -->agent:bootstrap firesengram-session-start → writes <!-- session:start -->engram-bootstrap-qmd → refreshes QMD indexHooks are installed automatically by scripts/init.js (copies skills/engram/hooks/engram-* → hooks/engram-* in workspace root, only if not already present).
Manual installation:
# Copy hooks to workspace
cp -r skills/engram/hooks/engram-* hooks/
# Restart Gateway to activate
openclaw gateway restart
Hook source files are in skills/engram/hooks/. The workspace hooks/ directory contains the live copies — do not edit skill source directly.
Hooks use ENGRAM_TZ (or TZ) environment variable for timezone. Default: UTC.
# Set in OpenClaw config (env.vars) or shell:
export ENGRAM_TZ="Europe/Moscow" # or America/New_York, Asia/Tokyo, etc.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.