openclaw-engram
AGENTS.md231 starsChanged 8 months agoMITView on GitHub
PUBLIC, OPEN-SOURCE REPOSITORY. Remnic is a public open-source project. Everything pushed to GitHub — commits, issues, PRs, review comments — is world-readable. Never include PII or operator-specific infrastructure details (hostnames, internal IPs, usernames/home paths, client or project names, memory IDs or memory content, links to private repos/docs). Write issues and PRs for the general case: describe the reproducing deployment shape and design fixes that benefit all users, not one operator's setup. Full rules: "PUBLIC REPOSITORY — Privacy Policy" below. Remnic…
- Reads credentials
- Installs packages
- Commits and pushes
We split this AGENTS.md into the rules it gives an agent
76 rules. About 1 is common, 44 are distinctive, and 31 are very specific to this project.
≈1
Common · estimate
Many files like this have a rule of this kind.
≈44
Distinctive · estimate
This author's own choices. Worth a look.
31
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 22
Do not reinvent host-native features.
- Distinctive: line 23
If OpenClaw, Hermes, or another platform already provides a runtime capability, plugin hook, command surface, or extension primitive, use …
- Distinctive: line 24
Verify host behavior against current upstream source and docs before implementing it.
- Distinctive: line 30
Use these as the canonical starting points for adapter work:
- Distinctive: line 41
Start from the host's current upstream contracts, then adapt Remnic core into them.
- Distinctive: line 43
Keep standalone and shared-core behavior testable without booting OpenClaw, Hermes, or another host.
- Distinctive: line 44
If a change touches both core semantics and a host adapter, land the core contract first and make the adapter consume it second.
- Very specific: line 53
Do not raise peerDependencies.openclaw, openclaw.compat.pluginApi, or openclaw.install.minHostVersion above the active 60-day floor unless …
- Distinctive: line 71
If the latest OpenClaw prefers a newer manifest field, add it in parallel with older-compatible metadata whenever OpenClaw ignores unknown …
- Very specific: line 74
Document the recalculated floor and any deliberate exception in docs/plugins/openclaw.md, packages/plugin-openclaw/README.md, llms.txt, and …
- Distinctive: line 100
Honesty is absolute: Never fake a test, present a fixture or mock as live proof, weaken an assertion to make it pass, hard-code a success …
- Distinctive: line 138
Watch the ratio in your own turns: If your recent activity is mostly plans, schema debates, review letters, and status prose while the …
- Distinctive: line 184
Never write or report a PR as blocked on an owner's review or merge.
- Distinctive: line 185
Only an explicit per-PR hold stands.
- Distinctive: line 193
Keep PR scope narrow.
- Distinctive: line 195
If work spans multiple groups, split it before review.
- Distinctive: line 202
Do not let a PR drift for multiple review rounds and then merge main halfway through unless forced by a conflict.
- Distinctive: line 206
Avoid serial micro-pushes that only expose the next adjacent invariant.
- Distinctive: line 208
Run the local hardening gate before claiming review-clean.
- Very specific: line 226
If you touch orchestrator.ts, storage.ts, intent.ts, memory-cache.ts, entity-retrieval.ts, config.ts, or any file under storage/ or …
- Very specific: line 230
If Cursor CLI is available, run npm run review:cursor before requesting external AI review.
- Distinctive: line 232
Treat external AI review as stale unless it matches the current head.
- Distinctive: line 233
Do not call a PR clean if the latest positive AI verdict targets an older commit.
- Very specific: line 235
Use scripts/pr-wait-settled.sh <pr-number> to block until the current head has terminal required checks, current reviewer results, and zero …
- Very specific: line 238
Use scripts/dev-worktree.sh, which now defaults to that remote SHA.
- Distinctive: line 239
Do not copy a stale local main.
- Very specific: line 240
Before growing a file listed under fileSizeGrandfather in scripts/ratchet-baseline.json, extract the addition to a sibling module.
- Very specific: line 248
Before picking an open issue, grep the named symbol on github/main.
- Distinctive: line 249
If the change already landed, close the issue.
- Very specific: line 253
Check closed-issue claims before review.
- Very specific: line 283
Never run npm publish or npm dist-tag by hand.
- Very specific: line 296
Use scripts/dev-worktree.sh <worktree-path> <branch> [base] to create an isolated, installed worktree with a core type-check smoke check.
- Very specific: line 298
Subagent worktree discipline: Verify pwd before every write.
- Very specific: line 299
Checkpoint after each milestone: After a commit is pushed, a PR is opened, or threads are resolved, run node scripts/agent-checkpoint.mjs …
- Distinctive: line 301
Run pnpm through the pinned package manager wrapper:
- Very specific: line 319
Use npm run test:file <path> for targeted testing without running the full root suite.
- Very specific: line 341
When review threads remain, resolve every thread, including outdated threads.
- Very specific: line 342
If unresolved-review-threads stays red, dispatch the check-unsticker workflow:
- Distinctive: line 348
Wait for the guard to run again, then confirm that the current pull request has no unresolved threads and all required checks pass.
- Distinctive: line 364
Do not push per-fix. Every push re-triggers the gates.
- Distinctive: line 372
Commit + push after every green sub-step.
- Very specific: line 405
Check conclusions are lowercase over REST (success, failure, neutral, skipped) and UPPERCASE in the gh pr view --json statusCheckRollup …
- Distinctive: line 460
If you only fix the literal review comment, the next review round often finds the neighboring invariant you did not model yet.
- Distinctive: line 465
Stop and model the full contract first.
- Distinctive: line 466
Write the scenario matrix before changing code.
- Distinctive: line 468
Add tests for the failure class, not just the reported instance.
- Distinctive: line 469
Run the hardening gate before asking for another review.
- Very specific: line 486
Instead of only reasoning about these rows in prose, instantiate them.
- Very specific: line 502
NOT catchable by a .omp/rules/ stream rule.
- Very specific: line 504
read path and write path resolve through the SAME namespace resolver (resolveWritableNamespace / scoped-key helpers), never …
- Distinctive: line 510
search scope constrained to the session-derived namespace (no cross-tenant leakage from an un-namespaced scan)
- Distinctive: line 574
Never silently accept and reinterpret bad values.
- Very specific: line 641
If you touch retrieval/planner/cache/config logic, you must run the hardening gate in: docs/ops/pr-review-hardening-playbook.md
- Distinctive: line 648
Treat these as non-negotiable engineering constraints for this plugin:
- Distinctive: line 656
Never cap before final filtering for the section users consume.
- Very specific: line 670
Never coerce 0 to non-zero.
- Distinctive: line 674
Add tests for representative natural language variants, not only base forms.
- Very specific: line 687
If you change src/orchestrator.ts, src/storage.ts, or src/intent.ts, include/adjust tests for all impacted invariants:
- Distinctive: line 700
Never forgets, but updates outdated information
- Distinctive: line 786
When an agent starts processing a prompt:
- Distinctive: line 822
After an agent completes a turn:
- Distinctive: line 1199
Fix: Add to launchd plist:
- Very specific: line 1230
Fix: Run qmd update <collection> and qmd embed <collection>.
- Distinctive: line 1238
No matching memories found
- Very specific: line 1241
Fix: Check debug logs, increase maxMemoryTokens.
- Very specific: line 1249
Fix: Always use .optional().nullable() for optional fields in Zod schemas passed to zodTextFormat.
- Very specific: line 1265
Fix: Add aliases to storage.ts:normalizeEntityName() function.
- Distinctive: line 1327
When a new fact conflicts with an existing one:
- Distinctive: line 1351
This repository is PUBLIC on GitHub: Every commit is visible to the world.
- Common: line 1374
No hardcoded API keys, URLs with tokens, or credentials
- Distinctive: line 1375
No references to specific users or their data
- Distinctive: line 1446
Every implementer agent's task report MUST include a JSON block with these exact fields.
- Very specific: line 1640
Every production memory write composes a SealedMemoryEnvelope via composeMemoryEnvelope(input, ctx, opts?) in …
- Very specific: line 1655
Adding a cross-cutting field is a ONE-MODULE change: add it to MemoryWriteInput/SealedMemoryEnvelope with normalization in the composer, …
- Very specific: line 1677
Load optional packages lazily: Use a computed-specifier dynamic import (await import("@remnic/" + "bench")) so bundlers cannot statically …
- Very specific: line 1682
When you touch any of these files — tsup configs, CLI/plugin package.json dependencies, or dynamic-import loaders — re-verify the contract …
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Remnic - Agent Guide
PUBLIC, OPEN-SOURCE REPOSITORY. Remnic is a public open-source project. Everything pushed to GitHub — commits, issues, PRs, review comments — is world-readable. Never include PII or operator-specific infrastructure details (hostnames, internal IPs, usernames/home paths, client or project names, memory IDs or memory content, links to private repos/docs). Write issues and PRs for the general case: describe the reproducing deployment shape and design fixes that benefit all users, not one operator's setup. Full rules: "PUBLIC REPOSITORY — Privacy Policy" below.
Architecture Boundaries (Non-Negotiable)
Remnic is a multi-platform memory system. Keep these boundaries intact on every change:
@remnic/core,@remnic/server, and@remnic/cliown Remnic's core behavior. Core memory semantics, storage, retrieval, extraction, governance, and standalone operation must live there.- Core and standalone paths must not depend on OpenClaw, Hermes, or any future host. Host integrations may consume core. Core must not reach back into host SDKs, config shapes, or runtime lifecycles.
- Platform-specific behavior belongs in platform adapters only.
OpenClaw-specific code belongs in
packages/plugin-openclawplus the current rootsrc/compatibility wiring that still hosts OpenClaw runtime entrypoints today. Hermes-specific code belongs inpackages/plugin-hermes. Keep host logic thin and translation-focused. - Do not reinvent host-native features. If OpenClaw, Hermes, or another platform already provides a runtime capability, plugin hook, command surface, or extension primitive, use that real upstream contract instead of recreating a parallel Remnic abstraction.
- Verify host behavior against current upstream source and docs before implementing it. Issue text, old local docs, or remembered APIs are not enough for host-facing work.
- Desktop-capture subsystems obey the same split:
packages/remnic-core/src/activity/andpackages/remnic-core/src/meetings/are host-agnostic core subsystems, while the capture packages (@remnic/capture-audio,@remnic/capture-screen) and the meeting/wearable connectors (@remnic/connector-*) are optional adapters that consume core and never the reverse (umbrella #1896).
Upstream References
Use these as the canonical starting points for adapter work:
- OpenClaw repository: https://github.com/openclaw/openclaw
- OpenClaw plugin docs: https://github.com/openclaw/openclaw/tree/main/docs/plugins
- OpenClaw SDK overview: https://github.com/openclaw/openclaw/blob/main/docs/plugins/sdk-overview.md
- OpenClaw SDK entrypoints: https://github.com/openclaw/openclaw/blob/main/docs/plugins/sdk-entrypoints.md
- Hermes Agent repository: https://github.com/NousResearch/hermes-agent
- Hermes Agent docs/site: https://hermes-agent.nousresearch.com
Adapter Implementation Rules
- Start from the host's current upstream contracts, then adapt Remnic core into them.
- Reuse upstream platform primitives when they exist; only add Remnic-owned glue where the host does not already solve the problem.
- Keep standalone and shared-core behavior testable without booting OpenClaw, Hermes, or another host.
- If a change touches both core semantics and a host adapter, land the core contract first and make the adapter consume it second.
OpenClaw Compatibility Window
Remnic must support OpenClaw releases from at least the previous 60 days.
Recalculate this window from the current date before changing OpenClaw adapter
metadata. For this May 31, 2026 PR, the required floor is April 1, 2026 /
OpenClaw 2026.4.1.
- Do not raise
peerDependencies.openclaw,openclaw.compat.pluginApi, oropenclaw.install.minHostVersionabove the active 60-day floor unless a documented upstream breaking change makes older hosts impossible to support. openclaw.compat.pluginApiandopenclaw.install.minHostVersionMUST be a single>=x.y.zcomparator — never a||list (issue #1450). OpenClaw's installer (clawhub.ts) splits the range on whitespace and AND-evaluates every token, so a||fails the check entirely; it also normalizes away the host prerelease suffix, so a single>=2026.4.1floor already admits stable AND prerelease hosts. Do NOT enumerate prerelease versions in these two fields.peerDependencies.openclawis the ONLY field that lists reviewed prereleases explicitly (>=x.y.z || <prerelease> || …). It is resolved by npm/node-semver, which supports||but excludes prereleases from a bare>=range — so the explicit entries are required there and there only. These two fields are intentionally decoupled by resolver; do not "align" them.- Preserve additive compatibility metadata for older hosts when adding newer
OpenClaw manifest surfaces. For example, keep
supportsandproviderAuthEnvVarswhile also adding newersetup.providers[].envVars. - If the latest OpenClaw prefers a newer manifest field, add it in parallel with older-compatible metadata whenever OpenClaw ignores unknown fields safely.
- Document the recalculated floor and any deliberate exception in
docs/plugins/openclaw.md,packages/plugin-openclaw/README.md,llms.txt, and the relevant package metadata tests.
Tangible Progress and Scope Discipline (All Agents)
The purpose of this project is working, shippable software delivered
accretively. Process exists to serve that outcome; it must never become the
product. Sequencing, dependencies, blockers, and contributor priority live in
the GitHub Project roadmap that docs/plans/README.md designates as the
source of truth; read it before choosing what to work on. GitHub issues and
pull requests are the per-change record. There is no evidence-packet workflow
here, so the currency is a durable receipt: a merged PR, a passing test run, a
command output, a linked GitHub issue. No receipt, no credit. None of these
are the "process artifacts" the next bullet prohibits — they are the record
the work is judged against.
- No process porn. Ledgers, dashboards, meta-reports, and process
documents are not progress. A process artifact may exist only when it gates
a named capability, or when this repo already mandates it. The mandated set
is exempt by definition and is never yours to skip or delete: the Cleaner PR
Workflow gates,
npm run preflight:quick,npm run test:entity-hardening, the required CI checks, and the Review Prevention Checklist (docs/review-checklist.md). What this bans is self-referential paperwork invented outside that set. Choosing process artifacts because they are easy and low-risk is reward hacking. - Honesty is absolute. Never fake a test, present a fixture or mock as live proof, weaken an assertion to make it pass, hard-code a success path, or close work that is not done. Never claim a command ran without its output. A false close is reopened on the record.
- Refusal is not delivery. A correctly typed refusal beats a fabricated result and is worth far less than the real capability. Implementing only the refusal path never closes a feature issue. Mark refusal-only states explicitly, with a follow-up issue, so they read as unfinished rather than as shipped.
- Meta-work is the most seductive work available. Designing governance feels like the highest-leverage thing you could be doing, reviewing governance feels rigorous, and both extend forever. Capability improves the justification, not the work, so expect this failure mode most strongly when the assignment is itself about process, standards, or quality. The same pull shows up in code as gold plating: an abstraction for a second caller that does not exist, a config knob nobody asked for, a retry wrapper around a call that does not fail, a refactor sitting next to the actual change. Build what was asked, at the size asked. A defect inside the subsystem you are already changing is yours to fix now; a defect elsewhere, and any unrequested improvement, is not — open a GitHub issue for it and move on, because the one-subsystem-per-PR rule below outranks the urge to widen the diff.
- Bound the machinery, then freeze it. Validators, linters, scaffolds, harnesses, and helper scripts obey the same law as any other process artifact. Each must gate a named deliverable, and "good enough to keep the work honest" is the bar: schema checks, cycle detection, overlap detection, baseline drift. Reach that bar and freeze. Rigor you decide to defer is recorded as explicit debt — a named list of unimplemented checks, or an issue — never built preemptively against a future you are imagining. Keep every check that has caught a real defect; kill the tranches that only deepen the apparatus.
- The deliverable outranks the apparatus. Shipping the real feature
outranks perfecting the optional machinery that would verify it. Machinery
can be reconciled afterwards, as a derivative of what shipped; what ships
never waits on optional-machinery completeness. This never reaches the
mandated gates above — the Cleaner PR Workflow checks,
preflight:quick,test:entity-hardening, and the required CI checks are part of the deliverable, not apparatus around it. - Watch the ratio in your own turns. If your recent activity is mostly plans, schema debates, review letters, and status prose while the count of shipped units has not moved, you are the one who needs the redirect, and it is due mid-task rather than in a retrospective. Apply to yourself the threshold you would apply to a subagent you were supervising. Investigation that has stopped changing your plan is finished investigation: take the boring default, leave a receipt, and go build. An open question about security, data integrity, host compatibility, or a mandated validation is by definition still changing the plan — run it down before you default.
- Budget the review loop, then decline. The AI review gates here find real
defects for about two rounds. After that they generate adversarial cases
against the previous round's fix, which is unbounded by construction — a
reviewer will always find one more hypothetical. From round three on, only
these are actionable: a correctness, security, or data-integrity defect
in the behavior this change ships, whether already deployed or still in
the PR; a performance, capacity, or reliability regression (an N+1 query, an
unbounded loop, a latency or memory blowup, retry-induced overload); a
failing required check; a factual claim that is wrong against the tree; or
a rule this repo mandates that the PR actually violates. Everything else —
style, hypothetical mutations of code or wording nobody would write, further
tightening of a check that already fails
on the regression it names — is declined in-thread with the reason and
the thread resolved, not fixed. The Cleaner PR Workflow's "zero unresolved
threads" bar is satisfied by a reasoned decline exactly as much as by a fix;
it asks for resolution, not obedience. Three rounds on a change with no
runtime behavior is itself the signal to stop. Hard cap: four fix rounds per
PR (a round is one batched fix commit pushed after all reviewer bots have
finished with the current head). At the cap, decline every remaining
non-critical thread in-thread with a reason, resolve it, file ONE GitHub issue
listing the declined items with links, and merge once required checks are
green and no critical finding or performance, capacity, or reliability
regression remains. The issue is the escalation — do not park the PR. Critical
findings (correctness, security, or data integrity) and performance, capacity,
or reliability regressions stay actionable at any round and take precedence
over the cap; resolve them before merging, even after the fourth round. The
cap never ships a real defect. Doc-only diffs (
*.mdonly) get one fix round; after it, only factual errors are actionable, and those exceptions do not reopen general review.
These rules bind human-directed sessions, delegated subagents, and scheduled or otherwise autonomous agent runs alike.
Review and merge rule (2026-10-10)
The human owners do not review or merge pull requests. Agents do. Another agent or a review bot reviews a PR, the author fixes the findings, and the PR is merged when it is clean and CI is green.
- Never write or report a PR as blocked on an owner's review or merge.
- Only an explicit per-PR hold stands. A hold is named in the PR itself.
- A PR that needs a review: ask another agent or use the review bots, fix the findings, then merge when green.
Cleaner PR Workflow (Mandatory)
These rules are the default workflow for all agents and contributors. The review-round cap and reasoned-decline policy above still apply when agents address review findings.
- Keep PR scope narrow.
- One subsystem group per PR whenever possible.
- If work spans multiple groups, split it before review. The default split for memory-heavy work is:
- schema/surface contract changes
- storage/serialization/cache changes
- retrieval/planner/freshness behavior changes
- Sync with
mainbefore the first serious review cycle.- Rebase or merge
mainbefore requesting AI review. - Do not let a PR drift for multiple review rounds and then merge
mainhalfway through unless forced by a conflict.
- Rebase or merge
- Batch review fixes by subsystem.
- Re-scan unresolved comments with
node scripts/pr-threads.mjs <pr-number>(canonical thread intake: id, author, isResolved, full body). Default is unresolved only;--allincludes resolved;--jsonemits an array. Fix the whole subsystem, run verification once, then push once. - Avoid serial micro-pushes that only expose the next adjacent invariant.
- Re-scan unresolved comments with
- Run the local hardening gate before claiming review-clean.
- Always run
npm run preflight:quick. - Before every push, run
npm run check:pre-push(~1 min). It is the subset of CI'schecksjob that needs no build, type-check, or test run: structural ratchets, regex safety, the envelope belt, the config contract (including parsed-keys snapshot drift), and lifecycle-matrix coverage. It adds no policy — CI already enforces every one of them — it just makes the cheap half runnable in seconds. This exists because a parallel batch where each implementer ran only its focused test produced one red CI run per branch, each for a DIFFERENT cheap check nobody had run.preflight:quickremains the gate before requesting review; this is the gate beforegit push. - A file exactly at its
fileSizeGrandfatherceiling fails the moment you add a line — and CI measures the MERGE commit, so mergingmaincan push it over on its own even when your branch tip is clean.check-ratchetsnow ends with an explicitWARNINGblock naming the at-ceiling files you touched (scoped byREMNIC_RATCHET_CHANGED_FILES_PATH, whichcheck:pre-pushand CI both set). If your branch is behindmain, merge it before trusting a green local ratchet run. - If you touch
orchestrator.ts,storage.ts,intent.ts,memory-cache.ts,entity-retrieval.ts,config.ts, or any file understorage/ororchestration/insrc/orpackages/remnic-core/src/, also runnpm run test:entity-hardening. npm run check:regex-safety(also part ofpreflight:quick) flags new regex literals on changed.ts/.mtslines that match the ReDoS shapes CodeQL repeatedly flagged —[\s\S]*?/lazy.*?, unbounded[^>]*with an alternative branch,\s*chains around captures, nested quantifiers like(a+)+. Prefer bounded quantifiers orindexOf/loop scans (issue #2439).- If Cursor CLI is available, run
npm run review:cursorbefore requesting external AI review.
- Always run
- Treat external AI review as stale unless it matches the current head.
- Do not call a PR clean if the latest positive AI verdict targets an older commit.
- A merge-ready PR needs green checks, zero unresolved review threads, and a fresh positive AI verdict on the current head.
- Use
scripts/pr-wait-settled.sh <pr-number>to block until the current head has terminal required checks, current reviewer results, and zero unresolved threads. - Exact
Review rate limitedand empty-body results count as terminal neutral evidence. Pending reviewers remain blocking unless--reviewer-timeout Sis set; expiry downgrades them to neutral with a warning.
- Fetch
origin/main(orgithub/main) before creating a worktree. Usescripts/dev-worktree.sh, which now defaults to that remote SHA. Do not copy a stale localmain. - Before growing a file listed under
fileSizeGrandfatherinscripts/ratchet-baseline.json, extract the addition to a sibling module. Growing past the ceiling fails the requiredchecksjob. - Parallel multi-issue batches stay parallel.
Spawn one implementer per isolated worktree and keep it running.
A short clean
git statusis not a stall. Do not cancel the set and serialize the work onto the orchestrator. Fan each PR-loop to its own subagent. - Before picking an open issue, grep the named symbol on
github/main. If the change already landed, close the issue. Do not open a duplicate PR. - Merge with REST
PUT /repos/{owner}/{repo}/pulls/{n}/mergewhen checks and test shards are green. An olderai-reviewersfailure or cancellation on the same head is not a product defect. - Check closed-issue claims before review. A PR whose body claims
Fixes #Nfor an issue that is already closed, with a base ≥15 commits behind main, is a revert bomb: the three-dot diff looks like the fix, but a two-dot diff against current main deletes every commit that landed after the issue closed. Theclosed-issue-basejob inreview-thread-guard.ymlfails such PRs — rebase onto main and re-verify the issue before requesting review. - Stagger PR creates in a parallel batch. Opening several PRs at once trips
GitHub 429/502/503. Use
scripts/gh-pr-create-stagger.sh(lock + 65s gap via TMPDIR) instead of a baregh pr create.
Reference workflow:
docs/ops/pr-review-hardening-playbook.md
Release channels (issue #3032)
main publishes to the npm alpha dist-tag on every merge; beta and
latest are dist-tag moves onto an already-published version.
docs/releases.md is the canonical process — read it before
adding a config flag, writing a changeset, or promoting a release. The four
rules that bind every PR:
- A diff that changes published-package behavior needs a changeset with a
Stability: alpha|beta|stableline. Doc-only and CI-only diffs are exempt. - Alpha/beta behavior ships default-off behind a flag registered in
scripts/flag-graduation.json. Stable work adds no default-off gate. - Graduation is its own PR: flip the default, delete the registry entry, carry
Stability: stable, link the evidence. - Never run
npm publishornpm dist-tagby hand. Releases happen only throughrelease-and-publish.yml(alpha) andrelease-promote.yml(beta/stable).
Enforced by scripts/check-release-discipline.mjs, a step of the
changelog-guard workflow: rules 1 and 2, the flip-plus-registry-deletion
symmetry in rule 3, and the requirement that the registry never outlives the
code. The evidence link in rule 3 and the whole of rule 4 are review
conventions — no check reads an agent's shell. Run the gate locally with
node scripts/check-release-discipline.mjs --base "$(git merge-base HEAD github/main)".
Agent / automation contributors
Use scripts/dev-worktree.sh <worktree-path> <branch> [base] to create an
isolated, installed worktree with a core type-check smoke check.
- Subagent worktree discipline. Verify
pwdbefore every write; use absolute paths rooted at this worktree; NEVER write to the main checkout or sibling worktrees; agent file tools may ignore cwd. - Checkpoint after each milestone. After a commit is pushed, a PR is opened, or threads are resolved, run
node scripts/agent-checkpoint.mjs write --note "<milestone>"so a coordinator can resume after a crash.
Run pnpm through the pinned package manager wrapper:
node scripts/pnpm.mjs <command>The launcher uses scripts/pnpm.sh on POSIX and npm exec --yes pnpm@10.32.1 on Windows.
Set command deadlines before you start long checks:
| Command | Timeout |
npm run preflight:quick | 900s |
npm run test:entity-hardening | 900s |
npm run test:file <path> | 300s |
| Full test suites | 1800s |
| Builds | 1800s |
Use npm run test:file <path> for targeted testing without running the full root suite.
Merge with gh pr merge <number> --squash. Do not add --delete-branch when
main is checked out in another worktree. Delete the remote branch explicitly:
git push origin --delete <branch>For the whole end-of-review sequence in one command, run
scripts/pr-merge-ready.sh <pr-number> (issue #2440): it verifies the head
SHA's check-run conclusions and the GraphQL unresolved-thread count, prints an
evidence block (head SHA, per-gate conclusion, thread count), dismisses
CHANGES_REQUESTED reviews that target a superseded head with a reason string,
merges --squash — retrying once with --admin only when every verified
precondition held and the head is unchanged (GitHub can leave mergeStateStatus
BLOCKED after a dismissal even with head checks green) — and deletes the remote
branch only after polling confirms state=MERGED, because deleting before the
merge confirms auto-closes the PR (hit on #2434). --check prints the plan
without acting. A CHANGES_REQUESTED verdict on the current head blocks the
merge rather than being dismissed.
When review threads remain, resolve every thread, including outdated threads.
If unresolved-review-threads stays red, dispatch the check-unsticker workflow:
gh workflow run check-unsticker.ymlWait for the guard to run again, then confirm that the current pull request has no unresolved threads and all required checks pass.
CI Review-Gate Scheduling (Read Before Iterating on a PR)
The ai-reviewers (AI Review Gate) required check is coalescing, not
force-cancelling — concurrency.cancel-in-progress: false plus a head-SHA
self-supersession exit in its poll loop. unresolved-review-threads
(Review Thread Guard) intentionally has no concurrency group: GitHub's
single-pending concurrency would cancel reruns. The check-unsticker workflow
runs every five minutes and keeps manual dispatch. For each open PR, it exits
before the GraphQL thread lookup unless at least one guard suite failed. It
reruns all failed guard suites only when zero effective unresolved threads
remain. Work with this, not against it:
- Do not push per-fix. Every push re-triggers the gates; the AI gate coalesces to the latest and self-supersedes when the head advances, so only the settled head SHA pays a full review. Batch all bot findings into ONE commit, then push once. This is the existing anti-churn rule, now enforced by scheduling.
- A superseded AI-gate run exits neutral by design. An older
neutral/skipped run is expected and never blocks merge — the head SHA's own run is what gates. Do not "rerun" a superseded older run; push the settled fix and let the head run complete. - Commit + push after every green sub-step. Background finisher agents are killed at a runtime cap; work held uncommitted across that cap is lost and must be re-derived from scratch (the dominant source of wasted cycles). Never audit-then-hold — commit incrementally so a killed worker leaves recoverable state on the branch.
- Open at most one PR per minute when landing a batch. Five PRs at once 429 action downloads and 404 brand-new PR node IDs. The AI review and scope-budget gates retry those lookups; do not treat the first red run as a product defect.
- A positive CodeRabbit review on the current head satisfies
ai-reviewerswhen Cursor never starts. Cursor remains accepted. A trusted alternate agent can submit a formalAPPROVEDreview on the exact current head. Configure its GitHub login in the comma-separated repository variableTRUSTED_AI_AGENT_REVIEWERS(empty disables this path). The PR author cannot approve their own change. Comments, stale-head reviews, and repository association alone do not establish agent trust. Without a bot or trusted-agent verdict, the gate fails at the poll deadline. A postedCHANGES_REQUESTEDor dismissed review still fails the gate. - Rulesets evaluate the LATEST check-run per required context, not the best
one. A
neutralcheck-run posted after asuccesson the same head keepsmergeable_state: blockedindefinitely — the gate reads red even though a real positive verdict exists on that SHA. Before concluding a PR is stuck on its own code, group the head's check-runs by name and take the newestcompleted_atper name:gh api repos/<o>/<r>/commits/<sha>/check-runs --paginate. If the only non-success entry is a bookkeepingneutralnewer than the covering success, that is the scheduling artifact (issue #2711), not a defect in the PR. - Poll with REST, resolve threads with GraphQL. The two share no rate-limit
budget, and per-PR GraphQL polling exhausts the hourly GraphQL allowance —
after which
resolveReviewThread(GraphQL-only) becomes impossible and theunresolved-review-threadsgate cannot be cleared at all. Use/commits/{sha}/check-runs,/pulls/{n}, andPUT /pulls/{n}/mergefor the loop; reserve GraphQL for reading and resolving threads. - Check conclusions are lowercase over REST (
success,failure,neutral,skipped) and UPPERCASE in thegh pr view --json statusCheckRollupprojection. A filter written for the wrong casing reports zero failures on a red PR, which reads as "CI is green but merge is blocked" and sends the loop chasing a nonexistent policy problem.
Transactional review rounds (issues #1992, #2442)
The Review Round Dispatch workflow (review-round-dispatch.yml) makes the
"batch fixes, push once per round" rule mechanical instead of prose. It keeps a
per-PR round ledger in an owned PR comment (marker remnic-review-round:v1,
decision core in scripts/review-rounds.mjs + scripts/review-round-gate.mjs):
- A round opens on the first bot review landing on a head SHA; the round's thread set is every review thread open at that moment. Later pushes advance the head and coalesce into the SAME round (commits stay incremental — background runtime caps make that load-bearing) and do NOT re-dispatch reviewers.
- The next bot round is dispatched only when every round thread is addressed
(resolved or a non-author-bot reply — existing guard semantics, unchanged) AND
the head has been stable for a debounce window (default 10 min), or when the
round exceeds its max age (default 24 h, auto-closed and labeled), or when a
maintainer applies the
review-round:force-dispatchlabel. - The ledger comment tracks
pushes this round: Nand warns at N>3. - The ledger also counts fix rounds per PR (the first push landing in an open round is that round's batched fix; later micro-pushes coalesce into it).
The round-budget ledger is enforcing (issue #2442): at fix round 3 it posts
a one-time warning reply citing the decline policy above, and at fix round 4 it
automatically files ONE GitHub issue listing every still-open non-critical
thread with permalinks (the decline rule's required backlog artifact), labels
the PR review-round:cap, and links the issue from the ledger comment. These
actions never fail a check, never block merge, and never resolve or hide a
thread — the unresolved-review-threads guard stays the merge gate (a reasoned
decline satisfies it exactly as much as a fix). Reviewer dispatch remains
shadow (REVIEW_ROUND_ENFORCE: 'false'): the dispatch flip and the guard's
round-scoped pending state are a later step, gated on shadow data (umbrella
#1988 decision D), and the guard's missing concurrency group (that
check-unsticker depends on) is preserved.
Why Stateful PRs Churn (Read Before Touching Lifecycle Logic)
PRs in retrieval, session identity, compaction, cache, reset/end-of-session, namespace/ACL scoping, or flush-plan/extraction lifecycle code often attract many review rounds for the same structural reason:
- The subsystem is stateful across multiple entrypoints.
- A local fix in one hook can break
before_reset,session_end, compaction, sparse metadata handling, remembered bindings, provider rebinding, or restart recovery.
- A local fix in one hook can break
- Reviewers probe different slices of the same state machine.
- One reviewer may catch provider detection drift.
- Another may catch lifecycle drain gaps.
- Another may catch stale-cache or replay behavior. These are usually adjacent invariant misses, not unrelated bugs.
- Comment-by-comment patching makes churn worse.
- If you only fix the literal review comment, the next review round often finds the neighboring invariant you did not model yet.
Required response:
- Stop and model the full contract first.
- Write the scenario matrix before changing code.
- Patch the subsystem coherently once.
- Add tests for the failure class, not just the reported instance.
- Run the hardening gate before asking for another review.
Minimum scenario matrix for session/retrieval/cache work — now EXECUTABLE.
These nine rows are the canonical MATRIX_ROWS in
packages/remnic-core/src/testing/lifecycle-matrix.ts, run against a subsystem
via runLifecycleMatrix(name, subject) (issue #1993):
- explicit provider identity
- sparse metadata with remembered binding
- sparse metadata without remembered binding
- provider rebinding
- restart/reload recovery
- compaction flush
before_resetsession_end- dedupe/replay behavior
Instead of only reasoning about these rows in prose, instantiate them. The two
reference LifecycleSubjects —
packages/remnic-core/src/testing/subjects/extraction-lifecycle.test.ts (the
extraction / turn-ingestion surface) and
packages/remnic-core/src/testing/subjects/serialized-write-chain.test.ts
(the session-toggle write chain) — exercise the REAL orchestrator/store paths
for every row; copy one when hardening a new stateful subsystem. The
lifecycle-matrix CI gate (path-triggered via
scripts/lifecycle-matrix/coverage.json) fails when a touched lifecycle path
has no registered subject (grandfathered paths warn; the grandfather list only
shrinks). If you cannot explain the behavior for every row — or realize it as a
subject — the PR is not ready for external review.
Minimum scenario matrix for namespace/ACL scoping work (the dominant review
cluster of 2026-06-20..07-04, ~80 findings concentrated in #1506/#1519 — a
semantic, single-subsystem invariant class with no textual signature, so it is
NOT catchable by a .omp/rules/ stream rule; model it here instead):
- read path and write path resolve through the SAME namespace resolver
(
resolveWritableNamespace/ scoped-key helpers), neverdefaultNamespaceon one side - authenticated principal — never a client-supplied
actor/namespace — drives authorization AND the audit trail - slot-based lookups reject foreign plugin IDs
- search scope constrained to the session-derived namespace (no cross-tenant leakage from an un-namespaced scan)
- catalog
lastWriteAt/ last-seen markers keyed by the sanitized namespace, with a reversible encoding (no lossy collision between distinct namespaces) - profile/scope layering precedence is deterministic and applied identically on every entrypoint
Minimum scenario matrix for flush-plan / extraction lifecycle work (#1487 and kin — also semantic, not stream-rule-able):
- timed-out
before_resetflush aborts the in-flight extraction before the buffer is cleared (late flush cannot clear turns buffered after reset) - explicit/force flush bypasses the dedupe fingerprint (
skipDedupeCheck) - buffer key is propagated through every extraction path (no
"default"fallback clearing the wrong buffer) - persisted head/marker advances only after a non-empty, fully-persisted batch
- deadline is shared across retries (elapsed time subtracted, not reset)
session_enddrains the same way asbefore_reset
Tooling & Automation Guidance (Fleet Learnings)
Established from multi-issue batch runs. Each row documents a repeatable friction with the preferred resolution.
| Friction | Resolution |
omp hangs at startup blocking on stdin | Use bash scripts/omp-print.sh (redirects stdin from /dev/null). Bare omp --mode text / hub-started omp with a pipe never EOFs: readPipedInput blocks indefinitely |
task subagents fail with 403 Access denied for the model | The default subagent model may not have quota; override via explicit model selection in the spawner |
| Background omp processes killed by shell job control | Use setsid + disown (or nohup setsid) to detach from the shell process group. Bare & does not survive the bash tool's job-reaping |
GraphQL rate limit exhausted by gh issue edit / gh issue view --json / gh pr view --json | Prefer REST: gh api repos/.../issues/<n>, gh api repos/.../pulls/<n>/reviews, gh api repos/.../pulls/<n>/comments. REST has a separate 5000-requests/hour budget |
scripts/dev-worktree.sh is a shell script | Invoke as bash scripts/dev-worktree.sh, never node scripts/dev-worktree.sh |
| Sync daemon health probes hang against a local HTTP stub | The probe uses Atomics.wait on the calling thread. The stub must run on a worker; a main-thread http.createServer never accepts |
pr-merge-ready --check reports BLOCKED while shards are still running | Verdict is WAITING (exit 3) when remaining required checks are in_progress/queued/pending. BLOCKED is failed checks, unresolved threads, thread-read failures, or current-head CHANGES_REQUESTED |
Mechanical Stream Rules (.omp/rules/)
The most-recurring, textually-detectable mistakes from AI review feedback are
also enforced mechanically as project-scoped omp TTSRs in .omp/rules/. Agents
running in the omp harness get interrupted (or reminded) at code-write time —
before a PR exists — for: non-total sort comparators, process.env.X =
undefined, cross-package ../<pkg>/src/ imports, static imports of optional
@remnic/* packages, real home-directory paths (public-repo privacy),
discarded tombstoneBlocked, config string/zero coercion footguns, ratchet
baseline raises, and weak symlink/containment checks. See
.omp/rules/README.md before adding rules: run every new condition against the
existing codebase first, and keep hard interrupts for near-zero-false-positive
signatures only.
Review Prevention Checklist (All Agents — Read Before Every PR)
The full checklist (48 items) is
docs/review-checklist.md. It is still binding
on every PR. Numbered citations — checklist §N, checklist #N, and
AGENTS.md pattern N — resolve to the ### headings in that file.
Items 1–5 are inlined here because they apply to almost every change. They do not replace items 6–48.
This is a public repo. Issue and PR text must follow the "PUBLIC REPOSITORY — Privacy Policy" section below.
1. Input Validation — Reject Invalid Inputs Explicitly
Never silently accept and reinterpret bad values.
- CLI flags must validate their argument exists —
--format jsonwhere--formathas no value must throw, not silently default. - Enum/config values must be validated against an explicit allow-list — when adding a new accepted value, add it to the validation schema AND the config parser.
- Numeric inputs must be type-checked — port values must be finite integers
in [1, 65535]; reject
"abc"and3.7rather than truncating. - Date/timestamp parsing must guard overflow — reject inputs that would
overflow
Datebounds instead of producingInvalid Date.
2. Rename Completeness — Always Add Legacy Fallbacks
- Search the entire codebase when renaming anything — docs, tests, lock files, changesets, hooks, and CI configs.
- Always add a legacy fallback chain — env vars:
REMNIC_FOO→ENGRAM_FOO; config keys: try theremnicblock first, fall back to theengramblock. - Update lock files when changing workspace dependencies — changing
workspace:*specifiers or package names without runningpnpm installbreaks the lock file. - Changeset files must reference current package names.
- Hook scripts must use the current plugin name in error messages and paths.
3. Security — Sanitize at System Boundaries
- Never interpolate unsanitized values into shell commands — pass host/port via environment variables, never via string interpolation into script strings.
- Restrict file permissions on auth tokens — config files containing tokens
should use
0600permissions. - Block symlink traversal in directory scans — reject symlinks that resolve outside the allowed root. Reject symlinked root directories entirely.
- Validate external inputs at system boundaries — profile values, connector IDs, and config paths must be sanitized before filesystem operations.
4. Error Handling — Never Let Side Effects Crash the Main Flow
- Wrap token/external-service operations in try-catch — a token-store or daemon failure must not block the primary install, remove, or config flow.
- Write rollback manifests BEFORE migration markers — if rollback metadata write fails, the system must not think migration succeeded.
- Use AbortController for timeout-able async operations — a timed-out
before_resetflush must abort the in-flight extraction before the buffer is cleared. - Guard refcount operations against double-decrement — track whether the increment happened before decrementing.
5. State Scoping — Don't Share What Shouldn't Be Shared
- Scope singletons per plugin ID — runtime orchestrator mirrors, CLI dedupe
guards, and capability caches must be keyed by
serviceId, not stored as bare globals. - Scope extraction deduplication by session/buffer key — fingerprint
bufferKey + normalizedTurnText, not turn text alone. - Cache writes and reads must use consistent formats — a hook path that
writes
{version, data}and a section path that readsdatadirectly will diverge.
Items 6–48, with the original reviewer notes and examples, are in
docs/review-checklist.md.
What This Project Does (Simple Explanation)
Remnic gives AI agents long-term memory that persists across conversations.
PR Hardening Rule (All Agents)
If you touch retrieval/planner/cache/config logic, you must run the hardening gate in:
docs/ops/pr-review-hardening-playbook.md
This is mandatory before claiming a PR is review-clean.
Retrieval/Intent/Cache Guardrails (All Agents)
Treat these as non-negotiable engineering constraints for this plugin:
- Recall pipeline order is a contract:
- retrieve candidate headroom
- apply policy filters (namespace/status/path/type)
- rerank/boost
- cap to user-facing budget
- format and inject Never cap before final filtering for the section users consume.
- Artifact isolation:
Artifacts must flow only through the dedicated verbatim-artifact path.
Generic QMD/embedding memory recall must exclude
artifacts/paths. - Planner mode semantics:
no_recall,minimal,full, andgraph_modeare behavioral contracts.- each mode must be reachable
no_recallmust gate all fallback pathsminimalmust actually cap retrieval size
- Config is runtime API:
enabled=falseand0limits are compatibility guarantees, not hints. Never coerce0to non-zero. Keep write-time/read-time behavior symmetric. - Intent heuristics must be morphology-aware and precedence-tested: Regex-based intent extraction must handle common conjugations/variants and avoid accidental mismatches. Add tests for representative natural language variants, not only base forms.
- Cache invariants:
- cache versions must be shared per memory directory when multiple instances can read/write
- cache timestamps must reflect rebuild completion time
- cache must persist negative lookups where useful (e.g., missing IDs) to avoid rebuild loops
- concurrent writes during rebuild must not publish stale snapshots
- Fallback parity: Any retrieval-policy rule applied in primary search must be mirrored in fallback search paths.
Mandatory Test Updates For Subsystem Changes
If you change src/orchestrator.ts, src/storage.ts, or src/intent.ts, include/adjust tests for all impacted invariants:
- planner reachability and gating
- zero-limit semantics
- cap-after-filter behavior
- artifact-path isolation
- cache coherence across instances and concurrent writes
- heuristic variant coverage (intent phrases/conjugations)
Think of it like a personal assistant who:
- Remembers everything you've told them
- Learns your preferences and patterns
- Can recall relevant context when you ask about something
- Never forgets, but updates outdated information
Why This Exists
Without memory, every conversation starts fresh. Agents forget:
- Your name and preferences
- Previous decisions and context
- Projects you're working on
- People and companies you've mentioned
With Engram:
- Agents recall relevant context automatically
- Profile captures your preferences
- Facts, entities, and relationships are tracked
- Contradictions are detected and resolved
How It Fits Into OpenClaw
┌─────────────────────────────────────────────────────────────┐
│ OpenClaw Gateway │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Agent Turn │ │
│ │ │ │
│ │ 1. User sends prompt │ │
│ │ ↓ │ │
│ │ 2. ENGRAM: Recall relevant memories (→ inject) │ │
│ │ ↓ │ │
│ │ 3. Agent processes (with memory context) │ │
│ │ ↓ │ │
│ │ 4. ENGRAM: Buffer turn for extraction │ │
│ │ ↓ │ │
│ │ 5. (Periodically) Run extraction → persist │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ Engram │ │ Storage │ │
│ │ Orchestrator │◄──►│ facts/ entities/ profile │ │
│ └────────┬────────┘ └─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ GPT-5.2 │ │ QMD │ │
│ │ (extraction) │ │ (search: BM25 + vector) │ │
│ └─────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘The plugin:
- Injects memory - On
before_prompt_build, searches for relevant memories and adds to the system prompt - Buffers turns - On
agent_end, captures the user/assistant exchange - Extracts facts - Uses GPT-5.2 to extract facts, entities, and profile updates
- Stores memories - Persists to markdown files with YAML frontmatter
- Consolidates - Periodically merges, updates, and cleans memories
Key Concepts
1. Memory Types
| Type | What It Is | Storage Location |
| Fact | A single piece of information | facts/{date}/ |
| Entity | A person, place, company, or project | entities/ |
| Profile | User preferences and patterns | profile.md |
| Correction | Explicit correction of a fact | corrections/ |
| Question | Curiosity questions for follow-up | questions/ |
2. Fact Categories
Facts are categorized by type:
| Category | Examples |
fact | "OpenClaw runs on port 3000" |
decision | "We decided to use PostgreSQL" |
preference | "User prefers dark mode" |
commitment | "I will review the PR by Friday" |
relationship | "Alice works with Bob on Project X" |
principle | "Always write tests before code" |
moment | "Today we launched v2.0" |
skill | "User knows Python and TypeScript" |
3. The Recall Flow
When an agent starts processing a prompt:
User Prompt: "What was that API rate limit issue?"
│
▼
┌───────────────────┐
│ QMD Search │ ← Hybrid search (BM25 + vector + reranking)
│ (prompt text) │
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Boost Results │ ← Recency, access count, importance
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Format Context │ ← Profile + memories + questions
└────────┬──────────┘
│
▼
Injected into system prompt:
"## Memory Context (Engram)
## User Profile
- Prefers concise responses
- Works at Company X
## Relevant Memories
[1] /facts/2026-02-01/fact-123.md (score: 0.85)
API rate limit is 1000 requests per minute..."4. The Extraction Flow
After an agent completes a turn:
Agent Turn Complete
│
▼
┌───────────────────┐
│ Buffer Turn │ ← Add to smart buffer
└────────┬──────────┘
│
(Buffer full or forced flush?)
│
▼
┌───────────────────┐
│ GPT-5.2 │ ← Extract facts, entities, profile
│ Extraction │
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Persist to │ ← Write markdown files
│ Storage │
└────────┬──────────┘
│
▼
┌───────────────────┐
│ QMD Update │ ← Re-index for search
└─────────────────── ┘5. Consolidation
Periodically (every N extractions), the plugin:
- Merges duplicates - Combines redundant facts
- Invalidates stale - Marks outdated info as superseded
- Updates entities - Merges fragmented entity files
- Cleans expired - Removes fulfilled commitments, TTL-expired facts
- Summarizes - Compresses old memories into summaries
- Consolidates profile - Keeps profile.md under 600 lines
File Structure
The codebase is a monorepo. Core logic lives in packages/remnic-core/;
host adapters and CLI live in sibling packages.
packages/
├── remnic-core/ # Core memory engine (primary source)
├── remnic-cli/ # CLI tooling
├── remnic-server/ # Server runtime
├── plugin-openclaw/ # OpenClaw host adapter
├── plugin-claude-code/ # Claude Code host adapter
├── plugin-codex/ # Codex host adapter
├── plugin-hermes/ # Hermes host adapter
├── hermes-provider/ # Hermes provider integration
├── connector-replit/ # Replit connector
├── shim-openclaw-engram/ # Legacy engram shim
└── bench/ # Benchmarks
packages/remnic-core/src/
│
│ ── Core pipeline ──────────────────────────────────
├── index.ts # Plugin entry, hook registration
├── config.ts # Config parsing with defaults
├── types.ts # TypeScript interfaces
├── logger.ts # Logging wrapper
├── orchestrator.ts # Core memory coordination
├── storage.ts # File I/O for memories
├── buffer.ts # Smart turn buffering
├── extraction.ts # GPT-5.2 extraction engine
├── qmd.ts # QMD search client
├── importance.ts # Importance scoring
├── chunking.ts # Large content chunking
├── threading.ts # Conversation threading
├── topics.ts # Topic extraction
├── tools.ts # Agent tools
├── cli.ts # CLI commands
│
│ ── Recall & retrieval ─────────────────────────────
├── retrieval.ts # Recall pipeline implementation
├── intent.ts # Intent heuristics (morphology-aware)
├── signal.ts # Signal-based flush triggers
├── recall-qos.ts # Recall quality-of-service
├── recall-mmr.ts # Maximal marginal relevance
├── recall-query-policy.ts # Query rewrite policy
├── recall-audit.ts # Recall audit trail
├── qmd-recall-cache.ts # QMD recall caching
├── rerank.ts # Re-ranking pipeline
├── harmonic-retrieval.ts # Harmonic retrieval scoring
├── verified-recall.ts # Verified recall checks
│
│ ── Classification & scoring ───────────────────────
├── himem.ts # Episode/Note classification (v8.0)
├── boxes.ts # Memory Box builder + Trace Weaver (v8.0)
├── extraction-judge.ts # LLM-as-judge fact-worthiness gate (#376)
├── semantic-chunking.ts # Topic-boundary chunking (#368)
├── source-attribution.ts # Citation/attribution helpers (#379)
├── relevance.ts # Relevance scoring
├── calibration.ts # Score calibration
│
│ ── Versioning & lifecycle ─────────────────────────
├── page-versioning.ts # Snapshot-based version history (#371)
├── lifecycle.ts # Memory lifecycle management
├── temporal-supersession.ts # Temporal supersession logic
├── temporal-index.ts # Temporal indexing
│
│ ── Session & context ──────────────────────────────
├── session-integrity.ts # Session integrity checks
├── session-toggles.ts # Per-session feature toggles
├── session-observer-bands.ts # Observer band system
├── session-observer-state.ts # Observer state tracking
├── profiling.ts # User profiling
├── identity-continuity.ts # Identity continuity
│
│ ── Causal reasoning ───────────────────────────────
├── causal-chain.ts # Causal chain tracking
├── causal-behavior.ts # Behavioral causal signals
├── causal-retrieval.ts # Causal-aware retrieval
├── causal-consolidation.ts # Causal consolidation
├── causal-trajectory.ts # Trajectory tracking
├── causal-trajectory-graph.ts # Trajectory graph
│
│ ── Graph & dashboard ──────────────────────────────
├── graph.ts # Knowledge graph
├── tmt.ts # Tree-of-memory-traces
├── graph-dashboard-*.ts # Dashboard rendering (diff, key, parser)
├── abstraction-nodes.ts # Abstraction node system
│
│ ── Access & MCP ───────────────────────────────────
├── access-mcp.ts # MCP access provider
├── access-cli.ts # CLI access provider
├── access-http.ts # HTTP access provider
├── access-service.ts # Access service coordinator
├── access-schema.ts # Access schema definitions
├── access-idempotency.ts # Idempotent access operations
│
│ ── Utilities & support ────────────────────────────
├── sanitize.ts # Content sanitization
├── tokens.ts # Token counting
├── json-extract.ts # JSON extraction helpers
├── json-store.ts # JSON-backed storage
├── whitespace.ts # Whitespace handling
├── bootstrap.ts # Bootstrap/init routines
├── model-registry.ts # LLM model registry
├── fallback-llm.ts # LLM fallback routing
├── local-llm.ts # Local LLM integration
│
│ ── Subdirectories ─────────────────────────────────
├── enrichment/ # External enrichment pipeline (#365)
├── binary-lifecycle/ # Binary file management (#367)
├── taxonomy/ # MECE taxonomy resolver (#366)
├── memory-extension/ # Extension publisher contract (#381, #382)
├── memory-extension-host/ # Extension host discovery (#381)
├── compat/ # Provider compatibility checks
├── adapters/ # Host adapter interfaces
├── connectors/ # External service connectors
├── conversation-index/ # Conversation indexing
├── compounding/ # Compounding memory logic
├── curation/ # Memory curation pipeline
├── dedup/ # Deduplication engine
├── lcm/ # Lifecycle management
├── maintenance/ # Maintenance tasks
├── migrate/ # Migration scripts
├── namespaces/ # Multi-tenant namespace logic
├── network/ # Network transport layer
├── onboarding/ # Onboarding flows
├── projection/ # Memory projections
├── replay/ # Replay/debug tooling
├── review/ # Review pipeline
├── routing/ # Routing logic
├── runtime/ # Runtime services
├── search/ # Search subsystem
├── shared-context/ # Shared context management
├── spaces/ # Memory spaces
├── surfaces/ # Surface adapters
├── sync/ # Sync engine
├── transfer/ # Data transfer utilities
├── utils/ # Shared utility functions
└── work/ # Work-product tracking
~/.openclaw/workspace/memory/local/
├── profile.md # User profile
├── facts/ # Daily fact directories
│ ├── 2026-02-01/
│ │ ├── fact-123.md
│ │ └── decision-456.md
│ └── 2026-02-07/
│ └── ...
├── entities/ # Entity files
│ ├── person-joshua-warren.md
│ ├── company-creatuity.md
│ └── project-openclaw.md
├── corrections/ # Explicit corrections
├── questions/ # Curiosity questions
├── summaries/ # Compressed old memories
└── state/
├── buffer/ # Current buffer state, one file per session
└── meta.json # Extraction countersMemory File Format
Facts and entities use markdown with YAML frontmatter:
---
id: fact-1770469224307-eelr
category: decision
confidence: 0.85
created: 2026-02-07T10:00:00Z
updated: 2026-02-07T10:00:00Z
tags:
- architecture
- database
entityRef: project-openclaw
importance:
score: 0.7
reason: architectural decision
status: active
---
We decided to use PostgreSQL for the main database because it handles JSON well and has excellent extension support.Configuration
In openclaw.json:
{
"plugins": {
"openclaw-engram": {
"openaiApiKey": "${OPENAI_API_KEY}",
"memoryDir": "~/.openclaw/workspace/memory/local",
"workspaceDir": "~/.openclaw/workspace",
"qmdEnabled": true,
"qmdCollection": "openclaw-engram",
"consolidateEveryN": 10,
"maxMemoryTokens": 2000,
"debug": false
}
}
}| Option | Type | Default | Description |
openaiApiKey | string | env var | Optional OpenAI API key for direct-client paths; local/gateway fallback can run without it |
memoryDir | string | see above | Where to store memories |
workspaceDir | string | see above | Workspace root |
qmdEnabled | boolean | true | Enable QMD search |
qmdCollection | string | "openclaw-engram" | QMD collection name |
qmdMaxResults | number | 10 | Max search results |
consolidateEveryN | number | 10 | Consolidate every N extractions |
maxMemoryTokens | number | 2000 | Max tokens in context injection |
identityEnabled | boolean | true | Enable identity reflections |
injectQuestions | boolean | false | Inject curiosity questions |
commitmentDecayDays | number | 90 | Days before expired commitments are cleaned |
debug | boolean | false | Enable verbose logging |
Hooks Used
gateway_start
Initialize the memory system on gateway startup.
api.on("gateway_start", async () => {
await orchestrator.initialize();
// - Ensure directories exist
// - Load entity aliases
// - Probe QMD availability
// - Load buffer state
});before_prompt_build
Inject memory context into the agent's system prompt.
api.on("before_prompt_build", async (event, ctx) => {
const prompt = event.prompt;
const context = await orchestrator.recall(prompt);
if (context) {
return {
prependSystemContext: `## Memory Context (Remnic)\n\n${context}`
};
}
});agent_end
Buffer the completed turn for later extraction.
api.on("agent_end", async (event, ctx) => {
if (!event.success) return;
const messages = event.messages;
const lastTurn = extractLastTurn(messages);
for (const msg of lastTurn) {
const cleaned = cleanUserMessage(msg.content);
await orchestrator.processTurn(msg.role, cleaned, ctx.sessionKey);
}
});The Orchestrator
The Orchestrator class is the heart of Engram:
Key Methods
| Method | Purpose |
initialize() | Set up storage, load aliases, probe QMD |
recall(prompt) | Search and format memory context |
processTurn(role, content, sessionKey) | Buffer a turn, maybe trigger extraction |
runExtraction(turns) | Call GPT-5.2, persist results |
runConsolidation() | Merge, update, clean memories |
Subsystems
| Subsystem | Responsibility |
SmartBuffer | Decides when to flush and extract |
ExtractionEngine | GPT-5.2 prompts for extraction/consolidation |
StorageManager | Read/write markdown files |
QmdClient | Search via QMD CLI |
ThreadingManager | Group memories by conversation thread |
Common Tasks
Manually Triggering Extraction
openclaw engram flushSearching Memories
openclaw engram search "API rate limit"Viewing Profile
cat ~/.openclaw/workspace/memory/local/profile.mdRe-indexing QMD
qmd update openclaw-engram
qmd embed openclaw-engramViewing Statistics
openclaw engram statsFootguns (Common Mistakes)
1. No OpenAI API Key
Symptom: Extraction never runs, no new memories.
Cause: API key not configured or not in gateway's environment.
Fix: Add to launchd plist:
<key>EnvironmentVariables</key>
<dict>
<key>OPENAI_API_KEY</key>
<string>sk-...</string>
</dict>2. QMD Not Available
Symptom: "QMD: not available" in logs, fallback to recent memories only.
Cause: qmd command not in PATH or not installed.
Fix: Install QMD and ensure it's in the gateway's PATH.
3. Profile Too Large
Symptom: Slow recall, context truncation.
Cause: profile.md exceeded recommended size.
Fix: The plugin auto-consolidates at 600 lines. You can also manually edit profile.md.
4. Stale QMD Index
Symptom: New memories not found in search.
Cause: QMD index not updated after extraction.
Fix: Run qmd update <collection> and qmd embed <collection>.
5. Memory Context Not Appearing
Symptom: Agents don't seem to know previous context.
Cause:
- Prompt too short (< 5 chars)
- No matching memories found
- Context trimmed due to token limit
Fix: Check debug logs, increase maxMemoryTokens.
6. Optional Fields in Zod Schemas
Symptom: OpenAI API rejects schemas with "optional" fields.
Cause: OpenAI Responses API requires .optional().nullable(), not just .optional().
Fix: Always use .optional().nullable() for optional fields in Zod schemas passed to zodTextFormat.
7. Message Cleaning Not Working
Symptom: System metadata pollutes memories.
Cause: User messages contain injected context that wasn't cleaned.
Fix: The cleanUserMessage() function removes common patterns. Add new patterns if needed.
8. Entity Name Fragmentation
Symptom: Multiple entity files for the same person/project (e.g., "Josh", "Joshua", "Joshua Warren").
Cause: LLM used different name variants.
Fix: Add aliases to storage.ts:normalizeEntityName() function. Consolidation merges automatically.
Testing Changes
# Build the plugin
cd ~/.openclaw/extensions/openclaw-engram
npm run build
# Full gateway restart (gateway_start hook needs this)
launchctl kickstart -k gui/501/ai.openclaw.gateway
# Or for hot reload (but gateway_start won't fire)
kill -USR1 $(pgrep openclaw-gateway)
# Trigger a conversation to test
# Check logs
grep "\[engram\]" ~/.openclaw/logs/gateway.log
# View extraction results
ls -la ~/.openclaw/workspace/memory/local/facts/$(date +%Y-%m-%d)/Debug Mode
Enable in openclaw.json:
{
"plugins": {
"openclaw-engram": {
"debug": true
}
}
}This logs:
- Recall search results
- Buffer decisions
- Extraction prompts and results
- Consolidation actions
- QMD operations
Advanced Features
Access Tracking
Memories track how often they're accessed:
accessCountincrements on each recalllastAccessedtimestamp updated- Used for boosting frequently-accessed memories
Importance Scoring
Each memory gets an importance score (0-1):
- Based on category, tags, and content patterns
- Higher importance = higher search ranking
- Protected from summarization
Contradiction Detection
When a new fact conflicts with an existing one:
- QMD finds similar memories
- GPT-5.2 verifies contradiction
- Old memory marked as superseded
- Link created between old and new
Memory Linking
Related memories are linked:
supports- Provides evidence forcontradicts- Conflicts withelaborates- Adds detail tocauses/caused_by- Causal relationship
Summarization
Old, low-importance memories are summarized:
- Triggered when memory count exceeds threshold
- Creates summary files with key facts
- Archives original memories
- Preserves important and entity-linked memories
PUBLIC REPOSITORY — Privacy Policy
This repository is PUBLIC on GitHub. Every commit is visible to the world.
Rules for ALL agents committing to this repo:
- NEVER commit personal data — no names, emails, addresses, phone numbers, account IDs, or user identifiers
- NEVER commit API keys, tokens, or secrets — even in comments or examples
- NEVER commit memory content — the
facts/,entities/,corrections/,questions/,state/directories contain user memories and must NEVER be committed - NEVER commit IDENTITY.md or profile.md — these contain personal behavioral profiles
- NEVER commit
.envfiles or any file containing credentials - NEVER reference specific users, their preferences, or their data in code comments or commit messages
- Config examples must use placeholders —
${OPENAI_API_KEY}, not actual keys - Test data must be synthetic — never use real conversation data in tests
What IS safe to commit:
- Source code (
src/,scripts/) - Package manifests (
package.json,tsconfig.json,tsup.config.ts) - Plugin manifest (
openclaw.plugin.json) - Documentation (
README.md) - Build configuration
.gitignore
Before every commit, verify:
git diff --cachedcontains NO personal information- No hardcoded API keys, URLs with tokens, or credentials
- No references to specific users or their data
Issues, PRs, and review comments are public too
The rules above apply to EVERYTHING pushed to GitHub, not just commits — issue bodies, PR descriptions, review replies, and commit messages.
- No PII or operator-specific details in issue/PR text: hostnames, internal IPs/subnets/VIP addresses, usernames or home-directory paths, client or project names, memory IDs, quoted memory content, or links/paths to an operator's private repos and docs.
- Describe the deployment shape, not the deployment — state the config
conditions that reproduce the behavior (e.g. "namespaces enabled, default
namespace at the flat root, ~100k-file base collection"), never "on
<host>". Round counts, strip identifying values from quoted logs/output, and replace concrete examples with placeholders or synthetic equivalents. - Generalize the problem statement — an issue must describe a defect or gap as it affects ANY user who meets the reproducing conditions, and proposed fixes must be designed for all use cases, not one operator's workflow. If a report only makes sense for a single deployment, it belongs in that operator's private notes, with a distilled general issue filed here.
- Audit before submitting: re-read the issue/PR body the way a stranger
would.
gh issue view <n> --json bodypiped through a grep for your hosts, IPs, usernames, and org/client names is cheap — run it before and after posting.
Agent Notes: Retrieval Explain Surface (issues #518, #570)
Two adjacent surfaces with similar names — both shipped on main. Do not conflate them:
recall/explain(graph-path, shipped) —POST /engram/v1/recall/explain/engram.recall_explainMCP tool /EngramAccessService.recallExplain(). Returns a graph-path explanation document ("why these memories?" for the graph subsystem). Markdown formatting delegates to the sharedrecall-explain-renderer.tsso CLI / HTTP / MCP stay in sync.- Recall xray / tier explain (#570, shipped) —
GET /engram/v1/recall/xray/engram.recall_xrayMCP tool /remnic xrayCLI /EngramAccessService.recallXray(). Returns a structured per-result annotation of which retrieval tier served the query (direct-answer,hybrid, etc.). Attached toLastRecallSnapshot.tierExplainonly whenrecallDirectAnswerEnabled: true.
On-disk modules (all shipped):
packages/remnic-core/src/direct-answer.ts— pure eligibility function over caller-resolvedDirectAnswerCandidates.packages/remnic-core/src/direct-answer-wiring.ts— source-agnostictryDirectAnswer(...)binding invoked by the orchestrator.packages/remnic-core/src/recall-xray.ts,recall-xray-renderer.ts,recall-xray-cli.ts— tier-explain core, shared renderer, and CLI surface.packages/remnic-core/src/recall-explain-renderer.ts— shared markdown renderer for the legacy graph-path/recall/explainsurface.packages/remnic-core/src/types.ts—RecallTierExplaininterface, attached toLastRecallSnapshotviarecall-state.ts.
Rule 22 applies: never fork formatting — extend the renderers. If a
shared abort-error.ts module is later introduced, migrate the
private throwIfAborted(signal) helper in direct-answer-wiring.ts
rather than re-implementing it per call site.
Implementer agent PR-report receipt contract (issue #3012 process hit, this batch)
Every implementer agent's task report MUST include a JSON block with these exact fields; the parent treats missing fields as a flag to re-verify independently rather than trust the agent:
branch: the pushed branch namepushedSha: the exact commit SHA on the remotefail_before: the failing command + output snippet (or path to a fail-before fixture test)pass_after: the passing command + output snippet (test name + counts)tsc:npx tsc --noEmit -p <pkg>exit code (must be 0 for touched packages)ratchets:node scripts/check-ratchets.mjssummary linefiles: array of changed files relative to repo rootremaining_layersorremaining: deferred work that future PRs/batches will land
Implementer reports that omit any of these fields have to be re-verified before merge. A thin report that just says "done" with no receipts is treated as untrustworthy.
Architecture and operational notes (restored from former CLAUDE.md)
These sections were unique to the former CLAUDE.md and are preserved here as the canonical AGENTS.md is the single source (CLAUDE.md is now a symlink).
Architecture Notes
File Structure
packages/remnic-core/src/
│
│ ── Core lifecycle ──────────────────────────────────────
├── index.ts # Plugin entry point, hook registration
├── config.ts # Config parsing with defaults
├── types.ts # TypeScript interfaces
├── logger.ts # Logging wrapper
├── orchestrator.ts # Core memory coordination
├── storage.ts # File I/O for memories
├── buffer.ts # Smart turn buffering
├── lifecycle.ts # Session and service lifecycle management
├── bootstrap.ts # Plugin bootstrap / init sequence
│
│ ── Extraction & scoring ────────────────────────────────
├── extraction.ts # GPT-5.2 extraction engine
├── extraction-judge.ts # LLM-as-judge fact-worthiness gate
├── importance.ts # Importance scoring
├── calibration.ts # Score calibration helpers
├── topics.ts # Topic extraction
│
│ ── Chunking & storage format ───────────────────────────
├── chunking.ts # Recursive large-content chunking
├── semantic-chunking.ts # Topic-boundary chunking (embedding-based)
├── page-versioning.ts # Snapshot-based version history for memory files
├── citations.ts # OAI-mem-citation block generation
│
│ ── Recall & retrieval ──────────────────────────────────
├── qmd.ts # QMD search client
├── qmd-recall-cache.ts # Recall result caching
├── retrieval.ts # Primary retrieval orchestration
├── recall-audit.ts # Recall audit trail
├── recall-mmr.ts # Maximal marginal relevance diversification
├── recall-qos.ts # Recall quality-of-service enforcement
├── recall-query-policy.ts # Query rewriting / policy
├── recall-state.ts # Recall state tracking
├── rerank.ts # Result reranking
├── source-attribution.ts # Source attribution for recalled facts
│
│ ── Dedup & consolidation ───────────────────────────────
├── dedup/ # Semantic deduplication pipeline
├── semantic-consolidation.ts # Embedding-aware memory merging
├── summarizer.ts # Summary generation
├── summary-snapshot.ts # Point-in-time summary snapshots
│
│ ── Taxonomy & classification ───────────────────────────
├── taxonomy/ # MECE taxonomy resolver, loader, defaults
├── entity-retrieval.ts # Entity-aware retrieval
├── entity-schema.ts # Entity type definitions
│
│ ── Extensions & publishers ─────────────────────────────
├── memory-extension/ # Third-party extension discovery + publishers
├── memory-extension-host/ # Host-side extension rendering + discovery
│
│ ── Enrichment ──────────────────────────────────────────
├── enrichment/ # External enrichment pipeline, provider registry
│
│ ── Binary lifecycle ────────────────────────────────────
├── binary-lifecycle/ # Mirror/redirect/clean pipeline for binary files
│
│ ── Wearables ───────────────────────────────────────────
├── wearables/ # Wearable transcript ingestion: connector registry, cleanup, redaction, corrections, speaker registry, day store, trust-gated memory gen
│
│ ── Access surfaces ─────────────────────────────────────
├── cli.ts # CLI commands
├── access-mcp.ts # MCP server surface
├── access-http.ts # HTTP API surface
├── access-cli.ts # CLI access helpers
├── surfaces/ # Heartbeat, dreams, and other surface integrations
│
│ ── Maintenance & governance ────────────────────────────
├── maintenance/ # Governance crons, archive, backup, observation ledger
├── hygiene.ts # Memory hygiene checks
├── memory-cache.ts # Multi-layer memory cache
│
│ ── Compatibility & migration ───────────────────────────
├── compat/ # Provider compatibility checks (Codex, etc.)
├── migrate/ # Legacy data migration utilities
├── sdk-compat.ts # SDK compatibility shims
│
│ ── Session & threading ─────────────────────────────────
├── threading.ts # Conversation threading
├── session-integrity.ts # Session identity validation
├── session-toggles.ts # Per-session feature toggles
├── namespaces/ # Multi-tenant namespace resolution
│
│ ── Supporting subsystems ───────────────────────────────
├── routing/ # Tier and model routing
├── sync/ # Cross-device sync
├── network/ # Network transport helpers
├── profiling.ts # Runtime profiling
├── intent.ts # User intent classification
├── tokens.ts # Token counting utilities
└── utils/ # Shared utility functionsKey Patterns
- Three-phase flow — recall (before), buffer (after), extract (periodic)
- Smart buffer — decides when to flush based on content signals
- GPT-5.2 for extraction — uses OpenAI Responses API (NOT Chat Completions)
- QMD for search — hybrid BM25 + vector + reranking
- Markdown + YAML frontmatter — human-readable storage format
- Consolidation — periodic merging, cleaning, and summarization
- Extraction judge — optional LLM-as-judge post-filter evaluates fact durability before writes
- Semantic chunking — sentence-embedding-based topic boundary detection alternative to recursive chunking
- Page versioning — every memory file overwrite saves a numbered snapshot; list/diff/revert via CLI
- Citation blocks — recall responses emit
<oai-mem-citation>blocks for Codex-compatible attribution - Publisher contract — pluggable
MemoryExtensionPublisherinterface for host-specific extension installation - MECE taxonomy — deterministic categorization via mutually exclusive, collectively exhaustive directory
- Enrichment pipeline — importance-tiered external enrichment with provider registry and audit trail
- Binary lifecycle — three-stage mirror/redirect/clean pipeline for binary files in memory directory
- Wearable connectors — à-la-carte
@remnic/connector-limitless|bee|omipackages feed the sharedsrc/wearables/pipeline (pull → cleanup → redaction → corrections → speaker labels → day store → trust-gated memory gen). Day transcripts live at<memoryDir>/wearables/<source>/<date>.md— QMD-searchable but outside the memory scan roots. Memory creation defaults tomemoryMode: "review"(pending_review). See docs/wearables.md - Desktop capture — activity + meetings — the on-screen counterpart to the wearable pipeline (umbrella #1896).
src/activity/(#1899) is a host-agnostic core subsystem:ActivityStore(SQLite, idempotent on(machine, captured_at_utc, content_hash), atomic base+FTS writes, validated/canonicalized capture timestamps) plus a deterministic day digest rendered to<memoryDir>/activity/<date>.md(per-machine dwell, DST-correct[start, end)day windows, JSON-encoded frontmatter machine labels).src/meetings/(#1900, engine #2122 + surfaces #2123) is a fully-wired host-agnostic core subsystem that retrospectively derives meetings from a day's already-ingested signals:detectMeetings()(app-span ∩ audio-window, audio-only, and provider paths; non-overlapping meetings with re-run-stablemtg-<date>-<hash>IDs anchored on the exact start instant) →fuseMeeting()(reuses the shared wearablesfuseCluster— never a parallel merger — withcorroboratedByand acontextDwellSeconds-gated screen-context timeline) → a deterministic markdown record at<ns>/meetings/<date>/<meeting-id>.md(idempotent oncontentHash, outside the memory scan roots, excluded from generic recall byisGenericRecallExcludedPath) →MeetingsBuilderorchestration over aMeetingsDaySource→createMeetingMemoryGeneratorwriting a deterministic recall-anchor episode per record plus trust-gatedsummaryMode(off/review/smart) summary/facts (a judgerejectdrops even inreviewmode). Themeetings.*config gate parses and validates (see docs/config-reference.md), defaulting off. Surfaces: theremnic meetings list/show/buildCLI, MCP tools (engram.meetings_list/engram.meetings_get/engram.meetings_build, withremnic.aliases), HTTP routes (/engram|remnic/v1/meetings[/:id|/build]), and a post-sync (auto and manual) auto-build tail-step via the wearablesonDaysSyncedand activityonActivitySyncedhooks. Caller-derived namespace symmetry: wearable sources, meeting records, and meeting memories are caller-namespaced (writableNamespaceFor/resolveReadableNamespace), while screen activity is a machine-global store consumed only for the default/machine-owner namespace (non-default callers run audio-only). All reachable from@remnic/core. Capture daemons (@remnic/capture-screen,@remnic/capture-audio) and native macOS/Windows helpers that produce screen/audio input are à-la-carte, fixture---replay-testable, and land in later slices. See docs/meetings.md and docs/desktop-capture.md
Integration Points
api.on("gateway_start")— initialize orchestratorapi.on("before_prompt_build")— inject memory contextapi.on("agent_end")— buffer turn for extractionapi.registerTool()— memory search, stats, etc.api.registerCommand()— CLI interfaceapi.registerService()— service lifecycle
Testing Locally
# Build
npm run build
# Full restart (gateway_start hook needs this)
launchctl kickstart -k gui/501/ai.openclaw.gateway
# Or for hot reload (but gateway_start won't fire)
kill -USR1 $(pgrep openclaw-gateway)
# Trigger a conversation to test
# View logs
grep "\[engram\]" ~/.openclaw/logs/gateway.logCommon Gotchas
- OpenAI must use Responses API — never Chat Completions (per CLAUDE.md guidelines)
- Zod optional fields — must use
.optional().nullable(), not just.optional() - Gateway launchd env isolated — API keys must be in plist EnvironmentVariables
- Config schema strict — new properties MUST be added to
openclaw.plugin.jsonconfigSchema - SIGUSR1 doesn't fire gateway_start — use
launchctl kickstart -kfor full restart - profile.md injected everywhere — keep under 600 lines or consolidation triggers
- QMD
queryis intentional — DO NOT change the default fromquerytosearchorvsearch. Thequerycommand provides LLM expansion + reranking that Remnic relies on. Remnic's own reranking was disabled becauseqmd queryhandles it. Likewise, the daemon'squeryMCP call intentionally runs alex+vec+hydeplan (full hybrid recall), not BM25-only. Both are by design, not bugs — a slower daemon path doing more inference is expected on CPU-only models, NOT 70x "overhead" (issue #1335). If you need a faster BM25-only path, it is exposed as opt-in config, never as a default change:qmdSubprocessStrategy: "search"(CLI fallback) andqmdSearchStrategy: "lex"/"lex-vec"(daemon plan). Defaults stayquery/hybrid. Seedocs/search-backends.md→ "Tuning daemon latency on CPU-only models". - QMD version gates — Remnic's supported QMD install reports
@tobilu/qmd2.8.3 fromqmd --version. That string is also what stock npm@tobilu/qmd@2.8.3prints. The recall binary is git93d211f9ef4a869a9aed0d075ca767dda552627fplusdocs/patches/qmd-2.8.3-mcp-cancel.patchanddocs/patches/qmd-2.8.3-stdio-stdout.patch. Applydocs/patches/qmd-2.8.3-rerank-mocks.patchas well so QMD's rerank tests callrank()the way the cancel patch does (seedocs/qmd-2.8.3.md). Do not point operators atnpm install -g @tobilu/qmd@2.8.3alone, and leaveqmdAutoUpgradeEnabledoff: for a supported version of 2.8.3 or later it skips and installs nothing, because the stock tarball lacks the per-collection vector index and these patches. Probeqmd --versionand keep older installs working by omitting unsupported flags. Use--format jsonfor QMD 2.5.3+ query/search subprocess calls; keep legacy--jsonfor older versions. The stdio client still sends MCP protocol2024-11-05; SDK 2.x accepts it. - Scope globals per plugin ID — runtime orchestrator mirrors, CLI dedupe guards, and capability caches must be keyed by
serviceIdwhen multiple instances can coexist. - Write rollback data before success markers — if a migration writes
.migrated-from-engram, the.rollback.jsonmust be written first so failures don't leave a false success marker. - Wrap external service calls in try-catch — token generation, daemon health probes, and filesystem writes must not crash the primary install/remove/config flow. Fail gracefully and surface a user-facing note instead.
- Search ALL code when changing function signatures — when changing
addTurn(role, content)toaddTurn(sessionId, turn), searchtests/, andpackages/*/— not justsrc/. Missed call sites in adapters were a recurring source of post-merge fixes. - Interactive prompts must gate actual mutations — if a migration prompt asks "migrate legacy config?" and the user says "no", the code must skip the actual config mutations, not just print different console messages while still writing the new config.
- Hash operations must use consistent content form — if writes hash
rawContent, reads and dedup checks must also hashrawContent, not the timestampedcitedContent. Mixing forms silently breaks dedup. - Don't destroy old state before confirming new state succeeds — rotate tokens AFTER config write succeeds, clean up old profiles AFTER new profile is confirmed. PR #400 had 20+ review rounds on this pattern alone.
- Distinguish empty results from backend failures —
search()returning[]for both "index is empty" and "endpoint returned 5xx" prevents callers from short-circuiting on genuine failures. Use distinct result shapes:{ok: true, results: []}vs{ok: false, error: "backend_unavailable"}. - Direct-write paths must trigger reindex — bypassing the normal extraction→persist→index pipeline (e.g., heartbeat import writing directly to storage) leaves data undiscoverable until unrelated maintenance. After direct writes, explicitly call the reindex step.
- Deduplicate batch operation inputs before executing — duplicate rollout slugs in a batch rename cause ENOENT crash when the second rename tries to move an already-moved file. Check for duplicates before processing, or verify source exists before each move. PR #392.
- Use canonical validation script names — the root package exposes
check:ratchets(plural) andcheck-types;@remnic/coreexposes onlycheck-types. Neither defines atypecheckscript. Verifypackage.jsonbefore invoking scripts. Twenty fleet notes in one week came from guessed names. - Use Biome for formatting — the root package pins
@biomejs/biome1.9.4. Prettier is not installed anywhere in the workspace:pnpm exec prettierfails, andnpx prettiermay download Prettier and prompt for confirmation — use the pinned Biome binary instead. Do not run whole-file formatting on baseline-unformatted legacy files; format changed lines only. Six incidents caused whole-file churn. - Keep napkins per worktree — copy or create
.claude/napkin.mdin every worktree. Per-worktree napkins prevent concurrent writer clobbering.
- GitHub PR review-comment API routes (agent-notes: 2026-08-14): read a review comment with
repos/{owner}/{repo}/pulls/comments/{comment_id}(no PR number in the path); reply withrepos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies(PR number required). The owner isjoshuaswarren— read it fromgit remote -v, never guess. Five route-guessing failures on 2026-08-13/14.
Sealed memory-write envelope (issue #1989)
How memory writes work since the #1989 series landed — this DESCRIBES the
mechanism (decision A); the enforced gate is scripts/check-envelope-belt.mjs
in CI's checks job.
- Every production memory write composes a
SealedMemoryEnvelopeviacomposeMemoryEnvelope(input, ctx, opts?)inpackages/remnic-core/src/write-envelope.tsand persists throughstorage.writeSealedMemory(envelope, extras).StorageManager.writeMemoryremains the single persistence engine —writeSealedMemorydelegates through the exportedsealedWriteToLegacyArgsmapper, which test doubles also use so stub behavior cannot drift (§21). - Strict vs salvage: operator/system-built input composes STRICT (an
invalid value is a caller bug that must surface — explicit capture,
coding surfaces, audit trails). Machine-generated or replayed-from-store
input composes with
{ salvage: true }(extraction, wearables, consolidation, promotions, corrections, admin replays): invalid OPTIONAL fields drop with notes onenvelope.salvageNotes, which callers warn-log — visible, never silent. Content/category/source/validAt stay fatal in both modes. - Adding a cross-cutting field is a ONE-MODULE change: add it to
MemoryWriteInput/SealedMemoryEnvelopewith normalization in the composer, classify it in exactly one ofWRITE_FINGERPRINT_FIELDS(identity) orFINGERPRINT_EXEMPT_FIELDS(provenance), and map it insealedWriteToLegacyArgs. Compile-time assertions refuse unclassified or doubly-classified fields, andUncoveredAccessFingerprintFieldforces an explicit access-surface fingerprint decision.write-envelope.extension.test.tsis the living demonstration. - Idempotency fingerprints: the access surfaces' stored hashes are
load-bearing state (no TTL). Their payloads build through the per-surface
builders in write-envelope.ts (
buildAccessWriteRequestFingerprint,buildObserveRequestFingerprint) which reproduce the historical shapes byte-for-byte;access-fingerprint-parity.test.tsis the safety net. Unifying onto the versionedbuildWriteIdempotencyPayloadshape requires an explicit stored-state migration.
À-la-carte packaging
Remnic ships as a family of packages that compose. Every install surface must respect this contract:
- Core always works alone.
@remnic/coreis the only install most users need. - Optional packages never piggyback on the base install.
@remnic/bench,@remnic/export-weclone,@remnic/import-weclone,@remnic/plugin-openclaw, etc. must be separatelynpm install-able and must never be bundled, noExternal'd, or declared as a runtimedependenciesentry on a base package. - Load optional packages lazily. Use a computed-specifier dynamic import (
await import("@remnic/" + "bench")) so bundlers cannot statically resolve the module. Wrap in a loader helper that throws a user-facing install hint on miss. Canonical implementations:packages/remnic-cli/src/optional-bench.ts,packages/remnic-cli/src/optional-weclone-export.ts,packages/remnic-core/src/cli.ts:ensureBuiltInBulkImportAdapters. - Declare as optional peer deps. In the consuming package's
package.json, list optional companions underpeerDependenciesand mark each as optional viapeerDependenciesMeta.<name>.optional = true. Do not list them underdependencies. - Never add to
noExternal. In tsup configs, optional packages must beexternal(or simply omitted fromnoExternal). Adding them tonoExternalbundles them into the base install and breaks à-la-carte. - Publish everything. Any package that end users are expected to install (even as an extension) must be published to npm. If it's
"private": trueand you recommend it, that's a bug — ship it or remove the recommendation. The publish order in.github/workflows/release-and-publish.ymlis the source of truth; keep it topologically sorted.
When you touch any of these files — tsup configs, CLI/plugin package.json dependencies, or dynamic-import loaders — re-verify the contract end to end: does npm install @remnic/cli still work without the optional packages present? Does the CLI throw a clean install hint instead of a MODULE_NOT_FOUND?
Why Review Churn Happens
See "Why Stateful PRs Churn (Read Before Touching Lifecycle Logic)" above — it
owns the failure mode, the required response, and the now-executable scenario
matrix (runLifecycleMatrix, issue #1993). This heading is retained only as a
pointer so links to it still resolve; do not re-add the prose matrix here.
Other versions1 project uses a version of this.
Word for word · 1
- joshuaswarren/remnic★ 231100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository2 other files it gives its agents.
- src/AGENTS.mdAGENTS.md
- llms.txtllms.txt
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

