@alexandrgreen/anchorclaw 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +9 -0
  3. package/README.md +252 -0
  4. package/dist/api.d.ts +3 -0
  5. package/dist/api.d.ts.map +1 -0
  6. package/dist/api.js +3 -0
  7. package/dist/api.js.map +1 -0
  8. package/dist/config.d.ts +46 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +309 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/config.test.d.ts +2 -0
  13. package/dist/config.test.d.ts.map +1 -0
  14. package/dist/config.test.js +100 -0
  15. package/dist/config.test.js.map +1 -0
  16. package/dist/identity-policy.d.ts +3 -0
  17. package/dist/identity-policy.d.ts.map +1 -0
  18. package/dist/identity-policy.js +7 -0
  19. package/dist/identity-policy.js.map +1 -0
  20. package/dist/identity-policy.test.d.ts +2 -0
  21. package/dist/identity-policy.test.d.ts.map +1 -0
  22. package/dist/identity-policy.test.js +23 -0
  23. package/dist/identity-policy.test.js.map +1 -0
  24. package/dist/identity.d.ts +22 -0
  25. package/dist/identity.d.ts.map +1 -0
  26. package/dist/identity.js +113 -0
  27. package/dist/identity.js.map +1 -0
  28. package/dist/identity.test.d.ts +2 -0
  29. package/dist/identity.test.d.ts.map +1 -0
  30. package/dist/identity.test.js +22 -0
  31. package/dist/identity.test.js.map +1 -0
  32. package/dist/importer.d.ts +12 -0
  33. package/dist/importer.d.ts.map +1 -0
  34. package/dist/importer.js +297 -0
  35. package/dist/importer.js.map +1 -0
  36. package/dist/index.d.ts +3 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +92 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/index.test.d.ts +2 -0
  41. package/dist/index.test.d.ts.map +1 -0
  42. package/dist/index.test.js +770 -0
  43. package/dist/index.test.js.map +1 -0
  44. package/dist/memory/forget.d.ts +22 -0
  45. package/dist/memory/forget.d.ts.map +1 -0
  46. package/dist/memory/forget.js +84 -0
  47. package/dist/memory/forget.js.map +1 -0
  48. package/dist/memory/get.d.ts +36 -0
  49. package/dist/memory/get.d.ts.map +1 -0
  50. package/dist/memory/get.js +282 -0
  51. package/dist/memory/get.js.map +1 -0
  52. package/dist/memory/get.test.d.ts +2 -0
  53. package/dist/memory/get.test.d.ts.map +1 -0
  54. package/dist/memory/get.test.js +180 -0
  55. package/dist/memory/get.test.js.map +1 -0
  56. package/dist/memory/limits.d.ts +10 -0
  57. package/dist/memory/limits.d.ts.map +1 -0
  58. package/dist/memory/limits.js +18 -0
  59. package/dist/memory/limits.js.map +1 -0
  60. package/dist/memory/manager.d.ts +93 -0
  61. package/dist/memory/manager.d.ts.map +1 -0
  62. package/dist/memory/manager.js +344 -0
  63. package/dist/memory/manager.js.map +1 -0
  64. package/dist/memory/manager.test.d.ts +2 -0
  65. package/dist/memory/manager.test.d.ts.map +1 -0
  66. package/dist/memory/manager.test.js +201 -0
  67. package/dist/memory/manager.test.js.map +1 -0
  68. package/dist/memory/paths.d.ts +12 -0
  69. package/dist/memory/paths.d.ts.map +1 -0
  70. package/dist/memory/paths.js +31 -0
  71. package/dist/memory/paths.js.map +1 -0
  72. package/dist/memory/paths.test.d.ts +2 -0
  73. package/dist/memory/paths.test.d.ts.map +1 -0
  74. package/dist/memory/paths.test.js +35 -0
  75. package/dist/memory/paths.test.js.map +1 -0
  76. package/dist/memory/prompt.d.ts +37 -0
  77. package/dist/memory/prompt.d.ts.map +1 -0
  78. package/dist/memory/prompt.js +152 -0
  79. package/dist/memory/prompt.js.map +1 -0
  80. package/dist/memory/prompt.test.d.ts +2 -0
  81. package/dist/memory/prompt.test.d.ts.map +1 -0
  82. package/dist/memory/prompt.test.js +47 -0
  83. package/dist/memory/prompt.test.js.map +1 -0
  84. package/dist/memory/read-file-shared.d.ts +17 -0
  85. package/dist/memory/read-file-shared.d.ts.map +1 -0
  86. package/dist/memory/read-file-shared.js +53 -0
  87. package/dist/memory/read-file-shared.js.map +1 -0
  88. package/dist/memory/read-file-shared.test.d.ts +2 -0
  89. package/dist/memory/read-file-shared.test.d.ts.map +1 -0
  90. package/dist/memory/read-file-shared.test.js +82 -0
  91. package/dist/memory/read-file-shared.test.js.map +1 -0
  92. package/dist/memory/recall.d.ts +22 -0
  93. package/dist/memory/recall.d.ts.map +1 -0
  94. package/dist/memory/recall.js +58 -0
  95. package/dist/memory/recall.js.map +1 -0
  96. package/dist/memory/search.d.ts +30 -0
  97. package/dist/memory/search.d.ts.map +1 -0
  98. package/dist/memory/search.js +110 -0
  99. package/dist/memory/search.js.map +1 -0
  100. package/dist/memory/search.test.d.ts +2 -0
  101. package/dist/memory/search.test.d.ts.map +1 -0
  102. package/dist/memory/search.test.js +103 -0
  103. package/dist/memory/search.test.js.map +1 -0
  104. package/dist/memory/sessions-index-sync.d.ts +28 -0
  105. package/dist/memory/sessions-index-sync.d.ts.map +1 -0
  106. package/dist/memory/sessions-index-sync.js +253 -0
  107. package/dist/memory/sessions-index-sync.js.map +1 -0
  108. package/dist/memory/sessions-index-sync.test.d.ts +2 -0
  109. package/dist/memory/sessions-index-sync.test.d.ts.map +1 -0
  110. package/dist/memory/sessions-index-sync.test.js +280 -0
  111. package/dist/memory/sessions-index-sync.test.js.map +1 -0
  112. package/dist/memory/sessions-index.d.ts +30 -0
  113. package/dist/memory/sessions-index.d.ts.map +1 -0
  114. package/dist/memory/sessions-index.js +238 -0
  115. package/dist/memory/sessions-index.js.map +1 -0
  116. package/dist/memory/sessions-index.test.d.ts +2 -0
  117. package/dist/memory/sessions-index.test.d.ts.map +1 -0
  118. package/dist/memory/sessions-index.test.js +266 -0
  119. package/dist/memory/sessions-index.test.js.map +1 -0
  120. package/dist/memory/sessions-visibility.d.ts +18 -0
  121. package/dist/memory/sessions-visibility.d.ts.map +1 -0
  122. package/dist/memory/sessions-visibility.js +110 -0
  123. package/dist/memory/sessions-visibility.js.map +1 -0
  124. package/dist/memory/sessions-visibility.test.d.ts +2 -0
  125. package/dist/memory/sessions-visibility.test.d.ts.map +1 -0
  126. package/dist/memory/sessions-visibility.test.js +137 -0
  127. package/dist/memory/sessions-visibility.test.js.map +1 -0
  128. package/dist/memory/sessions.d.ts +26 -0
  129. package/dist/memory/sessions.d.ts.map +1 -0
  130. package/dist/memory/sessions.js +250 -0
  131. package/dist/memory/sessions.js.map +1 -0
  132. package/dist/memory/sessions.test.d.ts +2 -0
  133. package/dist/memory/sessions.test.d.ts.map +1 -0
  134. package/dist/memory/sessions.test.js +257 -0
  135. package/dist/memory/sessions.test.js.map +1 -0
  136. package/dist/memory/store.d.ts +25 -0
  137. package/dist/memory/store.d.ts.map +1 -0
  138. package/dist/memory/store.js +176 -0
  139. package/dist/memory/store.js.map +1 -0
  140. package/dist/migrations-fs.d.ts +5 -0
  141. package/dist/migrations-fs.d.ts.map +1 -0
  142. package/dist/migrations-fs.js +19 -0
  143. package/dist/migrations-fs.js.map +1 -0
  144. package/dist/migrations.d.ts +17 -0
  145. package/dist/migrations.d.ts.map +1 -0
  146. package/dist/migrations.js +44 -0
  147. package/dist/migrations.js.map +1 -0
  148. package/dist/migrations.test.d.ts +2 -0
  149. package/dist/migrations.test.d.ts.map +1 -0
  150. package/dist/migrations.test.js +66 -0
  151. package/dist/migrations.test.js.map +1 -0
  152. package/dist/plugin/capability.d.ts +7 -0
  153. package/dist/plugin/capability.d.ts.map +1 -0
  154. package/dist/plugin/capability.js +92 -0
  155. package/dist/plugin/capability.js.map +1 -0
  156. package/dist/plugin/capability.test.d.ts +2 -0
  157. package/dist/plugin/capability.test.d.ts.map +1 -0
  158. package/dist/plugin/capability.test.js +75 -0
  159. package/dist/plugin/capability.test.js.map +1 -0
  160. package/dist/plugin/lifecycle.d.ts +6 -0
  161. package/dist/plugin/lifecycle.d.ts.map +1 -0
  162. package/dist/plugin/lifecycle.js +31 -0
  163. package/dist/plugin/lifecycle.js.map +1 -0
  164. package/dist/plugin/prompt-cache.d.ts +10 -0
  165. package/dist/plugin/prompt-cache.d.ts.map +1 -0
  166. package/dist/plugin/prompt-cache.js +58 -0
  167. package/dist/plugin/prompt-cache.js.map +1 -0
  168. package/dist/plugin/runtime-context.d.ts +27 -0
  169. package/dist/plugin/runtime-context.d.ts.map +1 -0
  170. package/dist/plugin/runtime-context.js +91 -0
  171. package/dist/plugin/runtime-context.js.map +1 -0
  172. package/dist/plugin/runtime-helpers.d.ts +3 -0
  173. package/dist/plugin/runtime-helpers.d.ts.map +1 -0
  174. package/dist/plugin/runtime-helpers.js +12 -0
  175. package/dist/plugin/runtime-helpers.js.map +1 -0
  176. package/dist/plugin/session-delta-helpers.d.ts +11 -0
  177. package/dist/plugin/session-delta-helpers.d.ts.map +1 -0
  178. package/dist/plugin/session-delta-helpers.js +62 -0
  179. package/dist/plugin/session-delta-helpers.js.map +1 -0
  180. package/dist/plugin/session-delta.d.ts +12 -0
  181. package/dist/plugin/session-delta.d.ts.map +1 -0
  182. package/dist/plugin/session-delta.js +307 -0
  183. package/dist/plugin/session-delta.js.map +1 -0
  184. package/dist/plugin/tools/common.d.ts +7 -0
  185. package/dist/plugin/tools/common.d.ts.map +1 -0
  186. package/dist/plugin/tools/common.js +2 -0
  187. package/dist/plugin/tools/common.js.map +1 -0
  188. package/dist/plugin/tools/index.d.ts +3 -0
  189. package/dist/plugin/tools/index.d.ts.map +1 -0
  190. package/dist/plugin/tools/index.js +16 -0
  191. package/dist/plugin/tools/index.js.map +1 -0
  192. package/dist/plugin/tools/memory-forget.d.ts +3 -0
  193. package/dist/plugin/tools/memory-forget.d.ts.map +1 -0
  194. package/dist/plugin/tools/memory-forget.js +80 -0
  195. package/dist/plugin/tools/memory-forget.js.map +1 -0
  196. package/dist/plugin/tools/memory-forget.test.d.ts +2 -0
  197. package/dist/plugin/tools/memory-forget.test.d.ts.map +1 -0
  198. package/dist/plugin/tools/memory-forget.test.js +58 -0
  199. package/dist/plugin/tools/memory-forget.test.js.map +1 -0
  200. package/dist/plugin/tools/memory-get.d.ts +3 -0
  201. package/dist/plugin/tools/memory-get.d.ts.map +1 -0
  202. package/dist/plugin/tools/memory-get.js +136 -0
  203. package/dist/plugin/tools/memory-get.js.map +1 -0
  204. package/dist/plugin/tools/memory-recall.d.ts +3 -0
  205. package/dist/plugin/tools/memory-recall.d.ts.map +1 -0
  206. package/dist/plugin/tools/memory-recall.js +176 -0
  207. package/dist/plugin/tools/memory-recall.js.map +1 -0
  208. package/dist/plugin/tools/memory-recall.test.d.ts +2 -0
  209. package/dist/plugin/tools/memory-recall.test.d.ts.map +1 -0
  210. package/dist/plugin/tools/memory-recall.test.js +169 -0
  211. package/dist/plugin/tools/memory-recall.test.js.map +1 -0
  212. package/dist/plugin/tools/memory-search.d.ts +3 -0
  213. package/dist/plugin/tools/memory-search.d.ts.map +1 -0
  214. package/dist/plugin/tools/memory-search.js +332 -0
  215. package/dist/plugin/tools/memory-search.js.map +1 -0
  216. package/dist/plugin/tools/memory-search.test.d.ts +2 -0
  217. package/dist/plugin/tools/memory-search.test.d.ts.map +1 -0
  218. package/dist/plugin/tools/memory-search.test.js +205 -0
  219. package/dist/plugin/tools/memory-search.test.js.map +1 -0
  220. package/dist/plugin/tools/memory-status.d.ts +3 -0
  221. package/dist/plugin/tools/memory-status.d.ts.map +1 -0
  222. package/dist/plugin/tools/memory-status.js +134 -0
  223. package/dist/plugin/tools/memory-status.js.map +1 -0
  224. package/dist/plugin/tools/memory-store.d.ts +3 -0
  225. package/dist/plugin/tools/memory-store.d.ts.map +1 -0
  226. package/dist/plugin/tools/memory-store.js +88 -0
  227. package/dist/plugin/tools/memory-store.js.map +1 -0
  228. package/dist/plugin/tools/memory-store.test.d.ts +2 -0
  229. package/dist/plugin/tools/memory-store.test.d.ts.map +1 -0
  230. package/dist/plugin/tools/memory-store.test.js +64 -0
  231. package/dist/plugin/tools/memory-store.test.js.map +1 -0
  232. package/dist/plugin/tools/memory-visible-output.d.ts +20 -0
  233. package/dist/plugin/tools/memory-visible-output.d.ts.map +1 -0
  234. package/dist/plugin/tools/memory-visible-output.js +61 -0
  235. package/dist/plugin/tools/memory-visible-output.js.map +1 -0
  236. package/dist/plugin/types.d.ts +61 -0
  237. package/dist/plugin/types.d.ts.map +1 -0
  238. package/dist/plugin/types.js +2 -0
  239. package/dist/plugin/types.js.map +1 -0
  240. package/dist/postgres.d.ts +7 -0
  241. package/dist/postgres.d.ts.map +1 -0
  242. package/dist/postgres.js +55 -0
  243. package/dist/postgres.js.map +1 -0
  244. package/migrations/0001_init.sql +228 -0
  245. package/migrations/0002_session_index.sql +71 -0
  246. package/openclaw.plugin.json +314 -0
  247. package/package.json +63 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Green
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,9 @@
1
+ # Notice
2
+
3
+ AnchorClaw was originally created by Alexander Green.
4
+
5
+ The canonical AnchorClaw repository is:
6
+ https://github.com/Alexander-Green/anchorClaw
7
+
8
+ The AnchorClaw name and logo identify the original project maintained by
9
+ Alexander Green.
package/README.md ADDED
@@ -0,0 +1,252 @@
1
+ <p align="center">
2
+ <img src="./assets/logo.png" alt="AnchorClaw logo" width="800" height="600" style="display:block;margin:0 auto" />
3
+ </p>
4
+
5
+ # AnchorClaw — Postgres Memory Plugin (OpenClaw)
6
+
7
+ > Alpha preview. API may change before stable release.
8
+
9
+ **AnchorClaw** is created and maintained by Alexander Green.
10
+ The canonical repository is https://github.com/Alexander-Green/anchorClaw.
11
+
12
+ **AnchorClaw** is an OpenClaw memory plugin that replaces file-based durable memory (`MEMORY.md`) with a Postgres-backed, SQL-first durable store while keeping OpenClaw’s memory tooling and CLI/doctor/status flows compatible.
13
+
14
+ ## Why We Built This
15
+
16
+ OpenClaw’s default memory model is excellent for transparency (plain files) but it becomes harder to:
17
+
18
+ - do deterministic retrieval and updates (avoid duplicates, enforce stable ordering)
19
+ - support multi-user/workspace isolation cleanly
20
+ - evolve toward advanced features (semantic recall, personas, episodes, knowledge graphs) without turning memory into an opaque blob
21
+
22
+ AnchorClaw makes **Postgres the source of truth** for durable memory while preserving OpenClaw’s UX expectations (tools, corpuses, `MEMORY.md` compatibility).
23
+
24
+ ## What Works Today (MVP)
25
+
26
+ - **Durable memory in Postgres** (`memory_items`):
27
+ - `memory_store` (canonical upsert via `canonicalKey`)
28
+ - `memory_search` (`corpus="memory"`) via Postgres FTS (deterministic ordering)
29
+ - `memory_get` reads synthetic paths (`db-memory/items/<uuid>.md`) with bounded excerpts
30
+ - `memory_forget` soft-deletes items (+ audit trail in DB)
31
+ - `memory_recall` shortcut (query → search; empty query → top items)
32
+ - **OpenClaw compatibility**
33
+ - `registerMemoryCapability` + a `MemorySearchManager` adapter so `status/doctor/CLI` can work
34
+ - `memory_get` accepts both parameter styles:
35
+ - AnchorClaw-native: `{ lookup, fromLine, lineCount }`
36
+ - OpenClaw aliases: `{ path, from, lines }`
37
+ - Reading `MEMORY.md` via `memory_get`/runtime returns a **virtual snapshot generated from Postgres** (keeps legacy flows compatible while DB stays source-of-truth)
38
+ - **Sessions corpus (Phase 1 + Phase 2 live-pass complete)**
39
+ - `memory_search(corpus="sessions")` uses Postgres-backed sessions index (`session_index_files` + `session_index_chunks`) with FTS ranking
40
+ - `memory_get(path="sessions/<agentId>/<file>")` is DB-first; file fallback is used only on `index_miss`
41
+ - `sessions.visibility` modes: `off | current | visible` (default: `current`)
42
+ - `sessions.sync.deltaBytes` / `sessions.sync.deltaMessages` control delta reindex thresholds (defaults: `100000` / `50`)
43
+ - state/session path resolution follows OpenClaw-compatible order: `OPENCLAW_STATE_DIR` -> `OPENCLAW_HOME/.openclaw` (or `HOME/.openclaw`) -> legacy `HOME/.clawdbot`
44
+ - Phase 2 live/delta indexing is enabled (`onSessionTranscriptUpdate` + debounce + targeted sync)
45
+ - visibility behavior is runtime-verified:
46
+ - `current`: cross-agent delta updates are ignored
47
+ - `visible`: cross-agent delta updates are accepted and indexed
48
+ - `off`: sessions delta listener is disabled
49
+ - **Migration support**
50
+ - One-time idempotent import of legacy `MEMORY.md` into Postgres (by file hash)
51
+ - Optional (default on) cleanup of `MEMORY.md` after import to avoid duplicate prompt injection
52
+
53
+ ---
54
+
55
+ ## 🛠 Prerequisites
56
+
57
+ - OpenClaw host that supports memory plugin slots (see `package.json` → `openclaw.install.minHostVersion`)
58
+ - Node.js (plugin runtime)
59
+ - PostgreSQL (no embeddings required for MVP)
60
+
61
+ ---
62
+
63
+ ## 🚀 Quick Start
64
+
65
+ ### 1) Install
66
+
67
+ ```bash
68
+ openclaw plugins install @anchorclaw/anchorclaw
69
+ ```
70
+
71
+ ### 2) Configure
72
+
73
+ Select the memory slot and configure Postgres:
74
+
75
+ ```json
76
+ {
77
+ "plugins": {
78
+ "slots": { "memory": "anchorclaw" },
79
+ "entries": {
80
+ "anchorclaw": {
81
+ "enabled": true,
82
+ "config": {
83
+ "sessions": {
84
+ "visibility": "current",
85
+ "sync": {
86
+ "deltaBytes": 100000,
87
+ "deltaMessages": 50
88
+ }
89
+ },
90
+ "identity": {
91
+ "externalId": "family-main-01"
92
+ },
93
+ "postgres": {
94
+ "host": "localhost",
95
+ "database": "anchorclaw",
96
+ "user": "postgres",
97
+ "password": "${ANCHORCLAW_DB_PASSWORD}"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### 3) Restart
107
+
108
+ ```bash
109
+ openclaw gateway restart
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Memory Tooling (MVP)
115
+
116
+ AnchorClaw exposes both “native” and compatibility surfaces via OpenClaw tool contracts:
117
+
118
+ - `memory_store({ content, canonicalKey?, type? })` where `type` is `fact|note` (MVP)
119
+ - `memory_search({ query, corpus?, maxResults?, minScore? })`
120
+ - `corpus="memory"` (default): Postgres durable memory
121
+ - `corpus="sessions"`: Postgres sessions index (DB-first)
122
+ - `corpus="all"`: deterministic merge of `memory + sessions`
123
+ - `corpus="wiki"`: stub for now (use `wiki_search/wiki_get` from `memory-wiki`)
124
+ - `memory_get({ lookup|path, fromLine|from?, lineCount|lines? })`
125
+ - `MEMORY.md` is a virtual DB snapshot (source-of-truth is Postgres)
126
+ - `memory_forget({ lookup|path? , id? })`
127
+ - `memory_recall({ query? })`
128
+ - `memory_status({ check? })`
129
+ - default (`check` omitted / `false`): cached runtime degraded-state report
130
+ - active mode (`check: true`): lightweight healthcheck for DB connectivity/schema + sessions dir accessibility (`exists` + explicit `readable` check)
131
+
132
+ ---
133
+
134
+ ## Importing `MEMORY.md` and Avoiding Duplicate Prompt Memory
135
+
136
+ OpenClaw core injects `MEMORY.md` as a bootstrap file. AnchorClaw also injects Postgres-backed durable memory via its memory capability.
137
+
138
+ To avoid duplicated prompt memory, AnchorClaw **cleans up `MEMORY.md` after a successful import by default**:
139
+
140
+ - backup: `.openclaw-repair/anchorclaw/MEMORY.md.anchorclaw-backup.<timestamp>.md`
141
+ - replacement: `MEMORY.md` becomes an HTML-comment-only stub
142
+
143
+ To disable cleanup:
144
+
145
+ ```json
146
+ { "import": { "cleanupMemoryMdAfterImport": false } }
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Defaults
152
+
153
+ - `postgres.port`: `5432`
154
+ - `postgres.pool.max`: `10`
155
+ - `postgres.pool.connectionTimeoutMs`: `5000`
156
+ - `postgres.pool.idleTimeoutMs`: `30000`
157
+ - `sessions.sync.deltaBytes`: `100000` (OpenClaw-compatible default)
158
+ - `sessions.sync.deltaMessages`: `50` (OpenClaw-compatible default)
159
+ - Import cleanup: `import.cleanupMemoryMdAfterImport = true`
160
+
161
+ ---
162
+
163
+ ## Identity & Workspace Scoping (MVP)
164
+
165
+ AnchorClaw scopes all reads/writes by `(user_id, workspace_id)` derived from the current runtime identity.
166
+
167
+ - Preferred identity (Docker/production): set `identity.externalId` (max 20 chars). This becomes the stable `user_identities` key (`channel=anchorclaw-config`).
168
+ - Fallback identity (dev convenience): if `identity.externalId` is not set, identity is derived from OS username (`external_id = sha256(normalized username)`, `channel=openclaw-cli`).
169
+ - If multiple people share the same OS user account, they will share the same AnchorClaw `user_id`.
170
+ - AnchorClaw logs a startup warning on every start when fallback mode is active.
171
+ - Workspace identity: workspaces are isolated per user and per workspace directory (`workspace name = dir:<sha256(resolved workspaceDir)>`).
172
+
173
+ Recommended for Docker/production:
174
+
175
+ ```json
176
+ {
177
+ "plugins": {
178
+ "entries": {
179
+ "anchorclaw": {
180
+ "config": {
181
+ "identity": { "externalId": "family-main-01" }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Postgres SSL
192
+
193
+ AnchorClaw supports two mutually exclusive SSL configuration styles:
194
+
195
+ - Simple flag: `postgres.ssl: true|false`
196
+ - Explicit mode: `postgres.sslMode: "disable"|"require"|"verify-full"` (+ optional `postgres.sslCa`)
197
+
198
+ If you need strict certificate verification (recommended for production), use:
199
+
200
+ ```json
201
+ {
202
+ "postgres": {
203
+ "host": "db.example.com",
204
+ "port": "${PGPORT}",
205
+ "database": "anchorclaw",
206
+ "user": "anchorclaw",
207
+ "password": "${ANCHORCLAW_DB_PASSWORD}",
208
+ "sslMode": "verify-full",
209
+ "sslCa": "${ANCHORCLAW_DB_SSL_CA_PEM}"
210
+ }
211
+ }
212
+ ```
213
+
214
+ `postgres.ssl` and `postgres.sslMode` cannot be set at the same time.
215
+
216
+ ---
217
+
218
+ ## Postgres Pool
219
+
220
+ Optional pool tuning:
221
+
222
+ ```json
223
+ {
224
+ "postgres": {
225
+ "host": "localhost",
226
+ "database": "anchorclaw",
227
+ "user": "postgres",
228
+ "pool": {
229
+ "max": 10,
230
+ "connectionTimeoutMs": 5000,
231
+ "idleTimeoutMs": 30000
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Roadmap (Planned)
240
+
241
+ AnchorClaw intentionally starts with deterministic SQL-first durability. Next layers are planned to reach PostClaw parity and beyond:
242
+
243
+ - **Semantic layer**: embeddings + semantic search (hybrid retrieval: lexical + vector; optional and non-breaking)
244
+ - **Persona context in DB**: dynamic persona/profile retrieval and injection into the system prompt (separate budgets/policy)
245
+ - **Knowledge graph**: `entity_edges`-style relationships and multi-hop retrieval to pull secondary context automatically
246
+ - **Wiki integration / AnchorClaw-native wiki**: either integrate OpenClaw supplements (`memory-wiki`) or build a DB-native wiki layer
247
+
248
+ ## Current Status
249
+
250
+ - Durable memory MVP: implemented and green in repo tests.
251
+ - Sessions Phase 1 and Phase 2: implemented, reviewed, and runtime-verified on VPS `server-166`.
252
+ - Session delta indexing parity path is active in runtime (listener + debounce + targeted sync + lifecycle cleanup compatibility fallback).
package/dist/api.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2
+ export { registerMemoryCapability } from "openclaw/plugin-sdk/memory-core-host-runtime-core";
3
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC7F,OAAO,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAC"}
package/dist/api.js ADDED
@@ -0,0 +1,3 @@
1
+ export { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ export { registerMemoryCapability } from "openclaw/plugin-sdk/memory-core-host-runtime-core";
3
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAA0B,MAAM,kCAAkC,CAAC;AAC7F,OAAO,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAC"}
@@ -0,0 +1,46 @@
1
+ export type AnchorClawConfig = {
2
+ sessions?: {
3
+ visibility?: "current" | "off" | "visible";
4
+ sync?: {
5
+ deltaBytes?: number;
6
+ deltaMessages?: number;
7
+ };
8
+ };
9
+ identity?: {
10
+ externalId?: string;
11
+ };
12
+ postgres: {
13
+ host: string;
14
+ port?: number;
15
+ database: string;
16
+ schema?: string;
17
+ user: string;
18
+ password?: string;
19
+ ssl?: boolean;
20
+ sslMode?: "disable" | "require" | "verify-full";
21
+ sslCa?: string;
22
+ pool?: {
23
+ max?: number;
24
+ connectionTimeoutMs?: number;
25
+ idleTimeoutMs?: number;
26
+ };
27
+ };
28
+ import?: {
29
+ /**
30
+ * After successfully importing `MEMORY.md` into Postgres, overwrite `MEMORY.md` with an empty stub
31
+ * (to prevent duplicate prompt injection from OpenClaw bootstrap + AnchorClaw DB injection).
32
+ */
33
+ cleanupMemoryMdAfterImport?: boolean;
34
+ };
35
+ limits?: {
36
+ maxResults?: number;
37
+ getMaxChars?: number;
38
+ getDefaultLines?: number;
39
+ };
40
+ };
41
+ export declare const DEFAULT_SESSION_DELTA_BYTES = 100000;
42
+ export declare const DEFAULT_SESSION_DELTA_MESSAGES = 50;
43
+ export declare const anchorClawConfigSchema: {
44
+ parse(value: unknown): AnchorClawConfig;
45
+ };
46
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE;QACT,UAAU,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;QAC3C,IAAI,CAAC,EAAE;YACL,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,aAAa,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;KACH,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,GAAG,CAAC,EAAE,OAAO,CAAC;QACd,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;QAChD,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE;YACL,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,mBAAmB,CAAC,EAAE,MAAM,CAAC;YAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;KACH,CAAC;IACF,MAAM,CAAC,EAAE;QACP;;;WAGG;QACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;KACtC,CAAC;IACF,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,2BAA2B,SAAU,CAAC;AACnD,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAuHjD,eAAO,MAAM,sBAAsB;iBACpB,OAAO,GAAG,gBAAgB;CA0NxC,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,309 @@
1
+ export const DEFAULT_SESSION_DELTA_BYTES = 100_000;
2
+ export const DEFAULT_SESSION_DELTA_MESSAGES = 50;
3
+ function asRecord(value) {
4
+ return value && typeof value === "object" && !Array.isArray(value)
5
+ ? value
6
+ : undefined;
7
+ }
8
+ function assertAllowedKeys(value, allowed, label) {
9
+ const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
10
+ if (unknown.length === 0) {
11
+ return;
12
+ }
13
+ throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
14
+ }
15
+ function resolveEnvVars(value) {
16
+ return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
17
+ const envValue = process.env[envVar];
18
+ if (!envValue) {
19
+ throw new Error(`Environment variable ${envVar} is not set`);
20
+ }
21
+ return envValue;
22
+ });
23
+ }
24
+ function readOptionalString(value, label) {
25
+ if (value === undefined || value === null) {
26
+ return undefined;
27
+ }
28
+ if (typeof value !== "string") {
29
+ throw new Error(`${label} must be a string`);
30
+ }
31
+ const trimmed = value.trim();
32
+ if (!trimmed) {
33
+ return undefined;
34
+ }
35
+ return resolveEnvVars(trimmed);
36
+ }
37
+ function readOptionalNonEmptyString(value, label) {
38
+ if (value === undefined || value === null) {
39
+ return undefined;
40
+ }
41
+ if (typeof value !== "string") {
42
+ throw new Error(`${label} must be a string`);
43
+ }
44
+ const trimmed = value.trim();
45
+ if (!trimmed) {
46
+ throw new Error(`${label} must be non-empty`);
47
+ }
48
+ return resolveEnvVars(trimmed);
49
+ }
50
+ function readRequiredString(value, label) {
51
+ if (typeof value !== "string" || !value.trim()) {
52
+ throw new Error(`${label} required`);
53
+ }
54
+ return resolveEnvVars(value.trim());
55
+ }
56
+ function readOptionalPort(value, label) {
57
+ if (value === undefined || value === null) {
58
+ return undefined;
59
+ }
60
+ const resolved = typeof value === "string" ? resolveEnvVars(value.trim()) : value;
61
+ if (typeof resolved === "string") {
62
+ if (!resolved) {
63
+ return undefined;
64
+ }
65
+ const parsed = Number(resolved);
66
+ if (!Number.isInteger(parsed)) {
67
+ throw new Error(`${label} must be an integer`);
68
+ }
69
+ value = parsed;
70
+ }
71
+ else {
72
+ value = resolved;
73
+ }
74
+ if (typeof value !== "number" || !Number.isInteger(value)) {
75
+ throw new Error(`${label} must be an integer`);
76
+ }
77
+ if (value < 1 || value > 65535) {
78
+ throw new Error(`${label} must be between 1 and 65535`);
79
+ }
80
+ return value;
81
+ }
82
+ function readOptionalBoolean(value, label) {
83
+ if (value === undefined || value === null) {
84
+ return undefined;
85
+ }
86
+ if (typeof value !== "boolean") {
87
+ throw new Error(`${label} must be a boolean`);
88
+ }
89
+ return value;
90
+ }
91
+ function readOptionalIntegerInRange(params) {
92
+ if (params.value === undefined || params.value === null) {
93
+ return undefined;
94
+ }
95
+ if (typeof params.value !== "number" || !Number.isInteger(params.value)) {
96
+ throw new Error(`${params.label} must be an integer`);
97
+ }
98
+ if (params.value < params.min || params.value > params.max) {
99
+ throw new Error(`${params.label} must be between ${params.min} and ${params.max}`);
100
+ }
101
+ return params.value;
102
+ }
103
+ export const anchorClawConfigSchema = {
104
+ parse(value) {
105
+ const obj = asRecord(value);
106
+ if (!obj) {
107
+ throw new Error("anchorclaw config required");
108
+ }
109
+ assertAllowedKeys(obj, ["sessions", "identity", "postgres", "import", "limits"], "anchorclaw config");
110
+ const sessionsObj = asRecord(obj.sessions);
111
+ if (obj.sessions !== undefined && !sessionsObj) {
112
+ throw new Error("sessions must be an object");
113
+ }
114
+ if (sessionsObj) {
115
+ assertAllowedKeys(sessionsObj, ["visibility", "sync"], "sessions");
116
+ }
117
+ const visibilityRaw = sessionsObj
118
+ ? readOptionalString(sessionsObj.visibility, "sessions.visibility")
119
+ : undefined;
120
+ const sessionsVisibility = visibilityRaw
121
+ ? visibilityRaw === "current" || visibilityRaw === "off" || visibilityRaw === "visible"
122
+ ? visibilityRaw
123
+ : (() => {
124
+ throw new Error("sessions.visibility must be one of: current, off, visible");
125
+ })()
126
+ : "current";
127
+ const sessionsSyncObj = sessionsObj ? asRecord(sessionsObj.sync) : undefined;
128
+ if (sessionsObj?.sync !== undefined && !sessionsSyncObj) {
129
+ throw new Error("sessions.sync must be an object");
130
+ }
131
+ if (sessionsSyncObj) {
132
+ assertAllowedKeys(sessionsSyncObj, ["deltaBytes", "deltaMessages"], "sessions.sync");
133
+ }
134
+ const sessionsDeltaBytes = sessionsSyncObj
135
+ ? readOptionalIntegerInRange({
136
+ value: sessionsSyncObj.deltaBytes,
137
+ label: "sessions.sync.deltaBytes",
138
+ min: 0,
139
+ max: Number.MAX_SAFE_INTEGER,
140
+ })
141
+ : undefined;
142
+ const sessionsDeltaMessages = sessionsSyncObj
143
+ ? readOptionalIntegerInRange({
144
+ value: sessionsSyncObj.deltaMessages,
145
+ label: "sessions.sync.deltaMessages",
146
+ min: 0,
147
+ max: Number.MAX_SAFE_INTEGER,
148
+ })
149
+ : undefined;
150
+ const identityObj = asRecord(obj.identity);
151
+ if (obj.identity !== undefined && !identityObj) {
152
+ throw new Error("identity must be an object");
153
+ }
154
+ if (identityObj) {
155
+ assertAllowedKeys(identityObj, ["externalId"], "identity");
156
+ }
157
+ const identityExternalId = identityObj
158
+ ? readOptionalNonEmptyString(identityObj.externalId, "identity.externalId")
159
+ : undefined;
160
+ if (identityExternalId && identityExternalId.length > 20) {
161
+ throw new Error("identity.externalId must be at most 20 characters");
162
+ }
163
+ const postgresObj = asRecord(obj.postgres);
164
+ if (!postgresObj) {
165
+ throw new Error("postgres config required");
166
+ }
167
+ assertAllowedKeys(postgresObj, ["host", "port", "database", "schema", "user", "password", "ssl", "sslMode", "sslCa", "pool"], "postgres config");
168
+ const host = readRequiredString(postgresObj.host, "postgres.host");
169
+ const database = readRequiredString(postgresObj.database, "postgres.database");
170
+ const schema = readOptionalString(postgresObj.schema, "postgres.schema");
171
+ const user = readRequiredString(postgresObj.user, "postgres.user");
172
+ const password = readOptionalString(postgresObj.password, "postgres.password");
173
+ const port = readOptionalPort(postgresObj.port, "postgres.port");
174
+ const ssl = readOptionalBoolean(postgresObj.ssl, "postgres.ssl");
175
+ const sslModeRaw = readOptionalString(postgresObj.sslMode, "postgres.sslMode");
176
+ const sslCa = readOptionalString(postgresObj.sslCa, "postgres.sslCa");
177
+ const poolObj = asRecord(postgresObj.pool);
178
+ if (postgresObj.pool !== undefined && !poolObj) {
179
+ throw new Error("postgres.pool must be an object");
180
+ }
181
+ if (poolObj) {
182
+ assertAllowedKeys(poolObj, ["max", "connectionTimeoutMs", "idleTimeoutMs"], "postgres.pool");
183
+ }
184
+ const sslMode = sslModeRaw
185
+ ? sslModeRaw === "disable" || sslModeRaw === "require" || sslModeRaw === "verify-full"
186
+ ? sslModeRaw
187
+ : (() => {
188
+ throw new Error("postgres.sslMode must be one of: disable, require, verify-full");
189
+ })()
190
+ : undefined;
191
+ if (typeof ssl === "boolean" && sslMode) {
192
+ throw new Error("postgres.ssl and postgres.sslMode are mutually exclusive; use only sslMode");
193
+ }
194
+ if (sslCa && !sslMode) {
195
+ throw new Error("postgres.sslCa requires postgres.sslMode=verify-full");
196
+ }
197
+ if (sslMode && sslMode !== "verify-full" && sslCa) {
198
+ throw new Error("postgres.sslCa is only valid with postgres.sslMode=verify-full");
199
+ }
200
+ const poolMax = poolObj
201
+ ? readOptionalIntegerInRange({
202
+ value: poolObj.max,
203
+ label: "postgres.pool.max",
204
+ min: 1,
205
+ max: 100,
206
+ })
207
+ : undefined;
208
+ const poolConnectionTimeoutMs = poolObj
209
+ ? readOptionalIntegerInRange({
210
+ value: poolObj.connectionTimeoutMs,
211
+ label: "postgres.pool.connectionTimeoutMs",
212
+ min: 100,
213
+ max: 600_000,
214
+ })
215
+ : undefined;
216
+ const poolIdleTimeoutMs = poolObj
217
+ ? readOptionalIntegerInRange({
218
+ value: poolObj.idleTimeoutMs,
219
+ label: "postgres.pool.idleTimeoutMs",
220
+ min: 100,
221
+ max: 3_600_000,
222
+ })
223
+ : undefined;
224
+ const importObj = asRecord(obj.import);
225
+ if (obj.import !== undefined && !importObj) {
226
+ throw new Error("import must be an object");
227
+ }
228
+ if (importObj) {
229
+ assertAllowedKeys(importObj, ["cleanupMemoryMdAfterImport"], "import");
230
+ }
231
+ const cleanupMemoryMdAfterImport = importObj
232
+ ? readOptionalBoolean(importObj.cleanupMemoryMdAfterImport, "import.cleanupMemoryMdAfterImport")
233
+ : undefined;
234
+ const limitsObj = asRecord(obj.limits);
235
+ if (obj.limits !== undefined && !limitsObj) {
236
+ throw new Error("limits must be an object");
237
+ }
238
+ if (limitsObj) {
239
+ assertAllowedKeys(limitsObj, ["maxResults", "getMaxChars", "getDefaultLines"], "limits");
240
+ }
241
+ const limitMaxResults = limitsObj
242
+ ? readOptionalIntegerInRange({
243
+ value: limitsObj.maxResults,
244
+ label: "limits.maxResults",
245
+ min: 1,
246
+ max: 10,
247
+ })
248
+ : undefined;
249
+ const limitGetMaxChars = limitsObj
250
+ ? readOptionalIntegerInRange({
251
+ value: limitsObj.getMaxChars,
252
+ label: "limits.getMaxChars",
253
+ min: 1000,
254
+ max: 12_000,
255
+ })
256
+ : undefined;
257
+ const limitGetDefaultLines = limitsObj
258
+ ? readOptionalIntegerInRange({
259
+ value: limitsObj.getDefaultLines,
260
+ label: "limits.getDefaultLines",
261
+ min: 10,
262
+ max: 120,
263
+ })
264
+ : undefined;
265
+ return {
266
+ sessions: {
267
+ visibility: sessionsVisibility,
268
+ sync: {
269
+ deltaBytes: sessionsDeltaBytes ?? DEFAULT_SESSION_DELTA_BYTES,
270
+ deltaMessages: sessionsDeltaMessages ?? DEFAULT_SESSION_DELTA_MESSAGES,
271
+ },
272
+ },
273
+ ...(identityExternalId ? { identity: { externalId: identityExternalId } } : {}),
274
+ postgres: {
275
+ host,
276
+ ...(typeof port === "number" ? { port } : {}),
277
+ database,
278
+ ...(schema ? { schema } : {}),
279
+ user,
280
+ ...(password ? { password } : {}),
281
+ ...(typeof ssl === "boolean" ? { ssl } : {}),
282
+ ...(sslMode ? { sslMode } : {}),
283
+ ...(sslCa ? { sslCa } : {}),
284
+ ...(poolMax || poolConnectionTimeoutMs || poolIdleTimeoutMs
285
+ ? {
286
+ pool: {
287
+ ...(poolMax ? { max: poolMax } : {}),
288
+ ...(poolConnectionTimeoutMs
289
+ ? { connectionTimeoutMs: poolConnectionTimeoutMs }
290
+ : {}),
291
+ ...(poolIdleTimeoutMs ? { idleTimeoutMs: poolIdleTimeoutMs } : {}),
292
+ },
293
+ }
294
+ : {}),
295
+ },
296
+ import: { cleanupMemoryMdAfterImport: cleanupMemoryMdAfterImport ?? true },
297
+ ...(limitMaxResults || limitGetMaxChars || limitGetDefaultLines
298
+ ? {
299
+ limits: {
300
+ ...(limitMaxResults ? { maxResults: limitMaxResults } : {}),
301
+ ...(limitGetMaxChars ? { getMaxChars: limitGetMaxChars } : {}),
302
+ ...(limitGetDefaultLines ? { getDefaultLines: limitGetDefaultLines } : {}),
303
+ },
304
+ }
305
+ : {}),
306
+ };
307
+ },
308
+ };
309
+ //# sourceMappingURL=config.js.map