agenr 3.0.0 → 3.1.0

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.
package/README.md CHANGED
@@ -18,7 +18,7 @@ agenr gives agents a persistent brain: a local SQLite database of durable knowle
18
18
 
19
19
  It exists because most agent runtimes forget everything important between sessions. Even when a tool has a built-in memory feature, it is often lossy, file-based, or tightly coupled to one surface. agenr keeps memory structured and queryable: facts, decisions, preferences, lessons, milestones, relationships, and session-level episodes live in one local store instead of getting flattened into prompt text.
20
20
 
21
- What makes agenr different is the combination of local-first storage, semantic embeddings, hybrid recall, episodic temporal memory, and adapter-friendly architecture. The core is hexagonal, so multiple agent systems can share the same brain over time. Today the production adapter is the OpenClaw memory plugin, published separately as `@agenr/agenr-plugin`, and the CLI provides offline ingest, recall, and maintenance against that same database.
21
+ What makes agenr different is the combination of local-first storage, semantic embeddings, hybrid recall, episodic temporal memory, and adapter-friendly architecture. The core is hexagonal, so multiple agent systems can share the same brain over time. Today the production host adapters are the OpenClaw memory plugin (`@agenr/agenr-plugin`) and the Skeln extension (`@agenr/skeln-plugin`). The CLI provides offline ingest, recall, and maintenance against that same database.
22
22
 
23
23
  ## Features
24
24
 
@@ -28,11 +28,12 @@ What makes agenr different is the combination of local-first storage, semantic e
28
28
  - LLM-powered knowledge extraction from conversation transcripts.
29
29
  - Semantic deduplication using exact hashes, normalized hashes, embeddings, and within-run clustering.
30
30
  - Session continuity with predecessor resolution, recent transcript tails, and LLM-generated continuity summaries.
31
- - Surgeon maintenance passes for corpus health: run retirement cleanup for stale entries or supersession review for same-slot lineage, both with audit history.
32
- - Agent tools for `store`, `recall`, `retire`, `update`, and `trace` through the OpenClaw plugin.
31
+ - Surgeon maintenance passes for corpus health: claim-key quality, proposal resolution, supersession review, and retirement cleanup, all with audit history.
32
+ - Agent tools for durable memory through the OpenClaw plugin (`store`, `recall`, `retire`, `update`, and `trace`) and the Skeln plugin (`store`, `recall`, `update`, `work`, and goal aliases).
33
33
  - Native OpenClaw memory plugin that replaces OpenClaw's built-in memory slot.
34
+ - Skeln extension with working-memory tools, goal aliases, and shared recall/store semantics.
34
35
  - Local-first storage with SQLite/libSQL. Memory stays on your machine; only model and embedding calls leave it.
35
- - Designed for multi-agent systems so future adapters can share the same database.
36
+ - Designed for multi-agent systems so OpenClaw, Skeln, and future adapters can share the same database.
36
37
 
37
38
  ## Quick Start - The Recommended Way
38
39
 
@@ -67,7 +68,7 @@ The OpenClaw plugin id remains `agenr`, so `plugins.entries.agenr`, `plugins.slo
67
68
  After the plugin is installed, `openclaw plugins update agenr` continues to target that same plugin id.
68
69
  If `plugins.entries.agenr.config` is omitted, the plugin falls back to agenr's normal config resolution: `AGENR_CONFIG_PATH`, then `~/.agenr/config.json`, and the `dbPath` from that config or `~/.agenr/knowledge.db`.
69
70
 
70
- If you want to pin an exact plugin release, install a versioned package spec such as `openclaw plugins install @agenr/agenr-plugin@1.0.1`.
71
+ If you want to pin an exact plugin release, install a versioned package spec such as `openclaw plugins install @agenr/agenr-plugin@3.0.0`.
71
72
 
72
73
  For local development or a custom build path, run `pnpm build` first and point OpenClaw at the plugin package root:
73
74
 
@@ -96,6 +97,17 @@ Migration note:
96
97
  - Existing users who originally installed the plugin from the `agenr` package should reinstall once with `openclaw plugins install @agenr/agenr-plugin` so OpenClaw records the new npm package source.
97
98
  - After that reinstall, `openclaw plugins update agenr` should continue to work because updates key off the plugin id `agenr`.
98
99
 
100
+ ## Quick Start - Skeln Plugin (manual)
101
+
102
+ If you use Skeln instead of OpenClaw, install the Skeln extension after configuring agenr:
103
+
104
+ ```bash
105
+ pnpm link --global ./packages/skeln-plugin
106
+ skeln extension add @agenr/skeln-plugin
107
+ ```
108
+
109
+ The Skeln extension id is also `agenr`, so runtime config uses `extensions.settings.agenr.*`. For packaging, config, tool surface, and local development details, see [docs/SKELN-PLUGIN.md](./docs/SKELN-PLUGIN.md).
110
+
99
111
  ## Configuration
100
112
 
101
113
  `agenr init` and `agenr setup` handle configuration interactively. By default, agenr stores config at `~/.agenr/config.json` and the database at `~/.agenr/knowledge.db`.
@@ -138,23 +150,31 @@ Compatibility policy:
138
150
 
139
151
  The CLI surface is still intentionally compact, but it now covers setup, recall, ingest, and corpus maintenance.
140
152
 
141
- | Command | What it does |
142
- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
143
- | `agenr init` | Interactive first-run wizard: auth, model selection, OpenClaw detection, plugin install, and optional initial ingestion. |
144
- | `agenr setup` | Configure auth, model defaults, embeddings, and the agenr database path. |
145
- | `agenr recall <query>` | Run the hybrid recall pipeline with optional temporal and type/tag filters. |
146
- | `agenr ingest <path>` | Default durable-entry ingest shorthand. Equivalent to `agenr ingest entries <path>`. |
147
- | `agenr ingest entries <path>` | Bulk-ingest one file or directory of OpenClaw transcript files into durable knowledge entries. |
148
- | `agenr ingest episodes [path]` | Backfill episodic summaries from OpenClaw session transcripts, including rotated `.reset.*` and `.deleted.*` files. |
149
- | `agenr ingest procedures [path]` | Sync repo-authored YAML procedures into procedural-memory revisions stored in the knowledge database. |
150
- | `agenr surgeon run` | Execute a surgeon maintenance pass. Defaults to retirement; use `--pass supersession` for lineage review. Dry-run by default; add `--apply` to mutate the corpus. |
151
- | `agenr surgeon status` | Show corpus health, claim-key lifecycle counts, proposal backlog, and the latest surgeon run summary. |
152
- | `agenr surgeon history` | Show recent surgeon runs. |
153
- | `agenr surgeon actions <runId>` | Show the audit trail for one surgeon run. |
154
- | `agenr db reset` | Delete and recreate the knowledge database. |
153
+ | Command | What it does |
154
+ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
155
+ | `agenr init` | Interactive first-run wizard: auth, model selection, OpenClaw detection, plugin install, and optional initial ingestion. |
156
+ | `agenr setup` | Configure auth, model defaults, embeddings, and the agenr database path. |
157
+ | `agenr recall <query>` | Run the hybrid recall pipeline with optional temporal and type/tag filters. |
158
+ | `agenr trace` | Inspect one entry's provenance and claim-family lineage by id, subject, or `--last`. |
159
+ | `agenr ingest <path>` | Default durable-entry ingest shorthand. Equivalent to `agenr ingest entries <path>`. |
160
+ | `agenr ingest entries <path>` | Bulk-ingest one file or directory of OpenClaw transcript files into durable knowledge entries. |
161
+ | `agenr ingest episodes [path]` | Backfill episodic summaries from OpenClaw session transcripts, including rotated `.reset.*` and `.deleted.*` files. |
162
+ | `agenr ingest procedures [path]` | Sync repo-authored YAML procedures into procedural-memory revisions stored in the knowledge database. |
163
+ | `agenr surgeon run` | Execute surgeon maintenance. Defaults to the autonomous multi-pass sequence; use `--pass <type>` for one pass. Dry-run by default; add `--apply` to mutate the corpus. |
164
+ | `agenr surgeon status` | Show corpus health, claim-key lifecycle counts, proposal backlog, and the latest surgeon run summary. |
165
+ | `agenr surgeon history` | Show recent surgeon runs. |
166
+ | `agenr surgeon actions <runId>` | Show the audit trail for one surgeon run. |
167
+ | `agenr surgeon backlog` | List open surgeon proposals across runs. |
168
+ | `agenr surgeon proposals <runId>` | Show proposals recorded for one surgeon run. |
169
+ | `agenr surgeon review <proposalId>` | Apply or reject one open proposal. |
170
+ | `agenr scenarios list` | List repo-local claim-key sandbox scenarios. |
171
+ | `agenr scenarios run` | Run one or more claim-key sandbox scenarios. |
172
+ | `agenr db reset` | Delete and recreate the knowledge database. |
155
173
 
156
174
  The OpenClaw plugin also gives the agent five tools directly inside the runtime: `agenr_store`, `agenr_recall`, `agenr_retire`, `agenr_update`, and `agenr_trace`.
157
175
 
176
+ The Skeln plugin exposes seven tools: `agenr_store`, `agenr_recall`, `agenr_update`, `agenr_work`, `get_goal`, `create_goal`, and `update_goal`. It does not expose `agenr_retire` or `agenr_trace`.
177
+
158
178
  Examples:
159
179
 
160
180
  ```bash
@@ -170,12 +190,15 @@ agenr ingest episodes ~/.openclaw/agents/main/sessions/ --recent 30d
170
190
  # Preview procedure sync changes
171
191
  agenr ingest procedures --dry-run
172
192
 
173
- # Run the surgeon retirement pass (dry-run by default)
193
+ # Run the default autonomous surgeon sequence (dry-run by default)
174
194
  agenr surgeon run --budget 2.00
175
195
 
176
- # Run the surgeon supersession pass
196
+ # Run one explicit surgeon pass
177
197
  agenr surgeon run --pass supersession --budget 2.00
178
198
 
199
+ # Inspect the latest stored entry
200
+ agenr trace --last
201
+
179
202
  # Reset the database
180
203
  agenr db reset
181
204
  ```
@@ -184,7 +207,7 @@ agenr db reset
184
207
 
185
208
  - Hexagonal structure: `src/core/` contains pure logic and `src/adapters/` contains infrastructure.
186
209
  - The core never imports from adapters or CLI code.
187
- - OpenClaw, the CLI, and future adapters all target the same underlying memory brain.
210
+ - OpenClaw, Skeln, the CLI, and future adapters all target the same underlying memory brain.
188
211
 
189
212
  See [AGENTS.md](./AGENTS.md) for the full architecture and repository conventions.
190
213
 
@@ -204,7 +227,7 @@ The two transcript paths parse OpenClaw transcripts first, but they optimize for
204
227
 
205
228
  ## How Procedures Work
206
229
 
207
- Procedures are the durable how-to layer. They are authored in `procedures/` as reviewed YAML, normalized into canonical stored procedure revisions, and synced with `agenr ingest procedures [path]`. Phase 2 ships the authoring and sync path, including source-only updates and semantic supersession, but not yet procedure recall. For the current model, storage shape, and sync semantics, see [docs/PROCEDURES.md](./docs/PROCEDURES.md).
230
+ Procedures are the durable how-to layer. They are authored in `procedures/` as reviewed YAML, normalized into canonical stored procedure revisions, and synced with `agenr ingest procedures [path]`. Procedure recall is live through unified host-plugin `agenr_recall` and before-turn suggestion, but the standalone CLI `agenr recall` command still targets entry recall only. For the current model, storage shape, sync semantics, and read surfaces, see [docs/PROCEDURES.md](./docs/PROCEDURES.md).
208
231
 
209
232
  ## How Episodes Work
210
233
 
@@ -212,7 +235,7 @@ Episodes are session-level memory artifacts stored separately from durable entri
212
235
 
213
236
  ## How the Surgeon Works
214
237
 
215
- The surgeon is a maintenance system for the durable-memory corpus. Today it supports two passes: retirement, which reviews stale entries for conservative cleanup, and supersession, which links older entries to newer replacements without deleting history. `agenr surgeon run` is safe by default because it starts in dry-run mode; `--pass supersession` switches the workflow, and `--apply` is the explicit mutation switch. For runtime details, governance, and audit behavior, see [docs/SURGEON.md](./docs/SURGEON.md).
238
+ The surgeon is a maintenance system for the durable-memory corpus. Four passes are implemented today: `claim_key_quality`, `proposal_resolution`, `supersession`, and `retirement`. `agenr surgeon run` defaults to an autonomous sequence across those passes and is safe by default because it starts in dry-run mode; `--pass <type>` runs one pass, and `--apply` is the explicit mutation switch. For runtime details, governance, and audit behavior, see [docs/SURGEON.md](./docs/SURGEON.md).
216
239
 
217
240
  ## Development
218
241
 
@@ -238,4 +261,4 @@ agenr scenarios run --kind store --preserve --verbose
238
261
 
239
262
  ## License
240
263
 
241
- AGPL-3.0
264
+ MIT
@@ -4,7 +4,7 @@ import {
4
4
  openClawTranscriptParser,
5
5
  parseTuiSessionKey,
6
6
  readOpenClawSessionsStore
7
- } from "../../chunk-TBFAARM5.js";
7
+ } from "../../chunk-JSVQILB3.js";
8
8
  import {
9
9
  BEFORE_TURN_DEBUG_ARTIFACT_DEFAULT_TOP_K,
10
10
  BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K,
@@ -13,10 +13,10 @@ import {
13
13
  } from "../../chunk-GELCEVFA.js";
14
14
  import {
15
15
  EPISODE_SUMMARY_TIMEOUT_MS,
16
+ FETCH_TOOL_PARAMETERS,
16
17
  RECALL_TOOL_PARAMETERS,
17
18
  STORE_TOOL_PARAMETERS,
18
19
  UPDATE_TOOL_PARAMETERS,
19
- asRecord,
20
20
  buildClaimExtractionRuntime,
21
21
  buildRecallToolServices,
22
22
  composeHostPluginServices,
@@ -25,46 +25,51 @@ import {
25
25
  embedEpisodeSummaryWithinBudget,
26
26
  extractRecentTurnsFromMessages,
27
27
  formatAgenrSessionStartRecall,
28
- formatErrorMessage,
29
- formatTargetSelector,
30
28
  formatUnifiedRecallResults,
31
29
  normalizeOptionalBoolean,
32
30
  normalizeOptionalPositiveInteger,
33
31
  normalizePluginInjectionMemoryPolicyConfig,
34
32
  normalizePromptText,
33
+ parseFetchToolParams,
35
34
  parseRecallToolParams,
36
35
  parseStoreToolParams,
37
36
  parseUpdateToolParams,
38
- readBooleanParam,
39
37
  resolveBeforeTurnPolicy,
40
38
  resolveSessionIdentityKey,
41
39
  resolveSessionStartPolicy,
42
- resolveTargetEntry,
40
+ runFetchMemoryTool,
43
41
  runRecallMemoryTool,
44
42
  runSessionStart,
45
43
  runStoreMemoryTool,
46
44
  runUpdateMemoryTool,
47
- sanitizeUpdateToolParams,
48
45
  writeBoundedSingleTranscriptEpisode
49
- } from "../../chunk-MYZ2CWY6.js";
46
+ } from "../../chunk-E2DHUFZK.js";
50
47
  import {
51
- createSingleTranscriptDiscoveryPort
52
- } from "../../chunk-LAXNNWHM.js";
48
+ asRecord,
49
+ buildEntryMemoryResolverPorts,
50
+ createSingleTranscriptDiscoveryPort,
51
+ formatErrorMessage,
52
+ formatTargetSelector,
53
+ readBooleanParam,
54
+ resolveTargetEntry,
55
+ sanitizeFetchToolParams,
56
+ sanitizeUpdateToolParams
57
+ } from "../../chunk-EEEL53X4.js";
53
58
  import {
54
- buildRecallToolDetails,
55
59
  containsAgenrMemoryContext,
56
60
  formatAgenrBeforeTurnRecall,
61
+ runBeforeTurn,
62
+ stripAgenrMemoryContext
63
+ } from "../../chunk-V5CDMHRN.js";
64
+ import {
65
+ EMBEDDING_DIMENSIONS,
66
+ buildRecallToolDetails,
57
67
  formatRecallToolSummary,
58
68
  formatUnifiedRecallLogSummary,
59
- runBeforeTurn,
60
69
  sanitizeRecallToolParams,
61
70
  sanitizeStoreToolParams,
62
- stripAgenrMemoryContext,
63
71
  truncate
64
- } from "../../chunk-575MUIW5.js";
65
- import {
66
- EMBEDDING_DIMENSIONS
67
- } from "../../chunk-ELR2HSVC.js";
72
+ } from "../../chunk-NNO2V4GH.js";
68
73
  import {
69
74
  resolveClaimSlotPolicy
70
75
  } from "../../chunk-5LADPJ4C.js";
@@ -72,6 +77,168 @@ import {
72
77
  // src/adapters/openclaw/index.ts
73
78
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
74
79
 
80
+ // src/adapters/openclaw/tools/shared.ts
81
+ import { failedTextResult, readNumberParam, readStringArrayParam, readStringParam, textResult } from "openclaw/plugin-sdk/agent-runtime";
82
+ var OPENCLAW_PARAM_READER = {
83
+ readString: readStringParam,
84
+ readNumber: readNumberParam,
85
+ readStringArray: readStringArrayParam
86
+ };
87
+ function toOpenClawToolResult(outcome) {
88
+ if (outcome.failed) {
89
+ return failedTextResult(outcome.text, {
90
+ ...outcome.details,
91
+ status: "failed"
92
+ });
93
+ }
94
+ return textResult(outcome.text, outcome.details);
95
+ }
96
+ async function resolveTargetEntry2(services, params, options = {}) {
97
+ return resolveTargetEntry(buildEntryMemoryResolverPorts(services), params, options);
98
+ }
99
+ function logToolCall(logger, toolName, ctx, summary, sanitizedParams) {
100
+ logger.info(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} ${summary}`);
101
+ logger.info(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} params=${JSON.stringify(sanitizedParams)}`);
102
+ }
103
+ function logToolFailure(logger, toolName, ctx, error) {
104
+ logger.warn(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} failed: ${formatErrorMessage(error)}`);
105
+ }
106
+ function sanitizeRetireToolParams(params) {
107
+ return {
108
+ ...params.id ? { id: params.id } : {},
109
+ ...params.subject ? { subject: params.subject } : {},
110
+ ...params.reason !== void 0 ? { reasonLength: params.reason.length } : {}
111
+ };
112
+ }
113
+ function sanitizeTraceToolParams(params) {
114
+ return {
115
+ ...params.id ? { id: params.id } : {},
116
+ ...params.subject ? { subject: params.subject } : {},
117
+ ...params.last !== void 0 ? { last: params.last } : {}
118
+ };
119
+ }
120
+ function formatTrace(entry, supersededBy, supersedes, claimFamily, recallEvents) {
121
+ const slotPolicy = entry.claim_key ? claimFamily ? {
122
+ policy: claimFamily.slotPolicy ?? resolveClaimSlotPolicy(claimFamily.claimKey).policy,
123
+ reason: claimFamily.slotPolicyReason ?? resolveClaimSlotPolicy(claimFamily.claimKey).reason
124
+ } : resolveClaimSlotPolicy(entry.claim_key) : void 0;
125
+ const lines = [
126
+ `Trace for ${entry.id} | ${entry.subject}`,
127
+ `type=${entry.type} expiry=${entry.expiry} importance=${entry.importance} retired=${entry.retired}`,
128
+ `content=${truncate(entry.content, 220)}`
129
+ ];
130
+ if (supersededBy) {
131
+ lines.push(`superseded_by=${supersededBy.id} | ${supersededBy.subject}`);
132
+ }
133
+ if (supersedes.length > 0) {
134
+ lines.push(`supersedes=${supersedes.map((item) => `${item.id} (${item.subject})`).join(", ")}`);
135
+ }
136
+ if (entry.claim_key) {
137
+ lines.push(`claim_key=${entry.claim_key}`);
138
+ if (slotPolicy) {
139
+ lines.push(`slot_policy=${slotPolicy.policy}`);
140
+ lines.push(`slot_policy_reason=${slotPolicy.reason}`);
141
+ }
142
+ }
143
+ if (claimFamily && claimFamily.entries.length > 0) {
144
+ lines.push(
145
+ `claim_family=${claimFamily.claimKey} | slot_policy=${slotPolicy?.policy ?? "exclusive"} | ${claimFamily.entries.map((item) => `${item.id}:${describeTraceEntryState(item)}:${formatClaimLifecycleLabel(item)}`).join(", ")}`
146
+ );
147
+ if (slotPolicy) {
148
+ lines.push(`claim_family_policy_reason=${slotPolicy.reason}`);
149
+ }
150
+ const transitionSummary = summarizeTraceClaimFamilyTransition(claimFamily.entries);
151
+ if (transitionSummary) {
152
+ lines.push(`transition=${transitionSummary}`);
153
+ }
154
+ }
155
+ if (entry.valid_from || entry.valid_to) {
156
+ lines.push(`validity=${entry.valid_from ?? "?"} -> ${entry.valid_to ?? "ongoing"}`);
157
+ }
158
+ if (entry.supersession_kind) {
159
+ lines.push(`supersession_kind=${entry.supersession_kind}${entry.supersession_reason ? ` reason=${truncate(entry.supersession_reason, 120)}` : ""}`);
160
+ }
161
+ if (recallEvents.length > 0) {
162
+ lines.push(
163
+ `recent_recalls=${recallEvents.map((event) => `${event.recalledAt}${event.query ? ` query=${event.query}` : ""}${event.sessionKey ? ` session=${event.sessionKey}` : ""}`).join(" ; ")}`
164
+ );
165
+ }
166
+ return lines.join("\n");
167
+ }
168
+ function toolFailureResult(error) {
169
+ return failedTextResult(formatErrorMessage(error), {
170
+ status: "failed"
171
+ });
172
+ }
173
+ function formatToolSessionContext(ctx) {
174
+ const normalizedSessionId = ctx.sessionId?.trim();
175
+ const normalizedSessionKey = ctx.sessionKey?.trim();
176
+ if (normalizedSessionId && normalizedSessionKey) {
177
+ return `session=${normalizedSessionId} key=${normalizedSessionKey}`;
178
+ }
179
+ if (normalizedSessionId) {
180
+ return `session=${normalizedSessionId}`;
181
+ }
182
+ if (normalizedSessionKey) {
183
+ return `key=${normalizedSessionKey}`;
184
+ }
185
+ return "session=unknown";
186
+ }
187
+ function describeTraceEntryState(entry) {
188
+ if (entry.superseded_by) {
189
+ return "superseded";
190
+ }
191
+ if (entry.retired || entry.valid_to) {
192
+ return "historical";
193
+ }
194
+ return "current";
195
+ }
196
+ function formatClaimLifecycleLabel(entry) {
197
+ if (!entry.claim_key) {
198
+ return "no-key";
199
+ }
200
+ return entry.claim_key_status ?? "legacy";
201
+ }
202
+ function summarizeTraceClaimFamilyTransition(entries) {
203
+ const current = entries.find((entry) => !entry.retired && !entry.superseded_by);
204
+ const prior = [...entries].reverse().find((entry) => entry.id !== current?.id && (entry.superseded_by !== void 0 || entry.retired || entry.valid_to !== void 0));
205
+ if (current && prior) {
206
+ return `${prior.id} -> ${current.id}`;
207
+ }
208
+ if (prior) {
209
+ return `${prior.id} is historical with no current sibling in the traced family`;
210
+ }
211
+ if (current) {
212
+ return `${current.id} is the only current sibling in the traced family`;
213
+ }
214
+ return void 0;
215
+ }
216
+
217
+ // src/adapters/openclaw/tools/fetch.ts
218
+ function createAgenrFetchTool(ctx, servicesPromise, logger) {
219
+ return {
220
+ name: "agenr_fetch",
221
+ label: "Agenr Fetch",
222
+ description: "Fetch the full body and metadata for one durable memory entry by id or subject.",
223
+ parameters: FETCH_TOOL_PARAMETERS,
224
+ async execute(_toolCallId, rawParams) {
225
+ try {
226
+ const params = parseFetchToolParams(rawParams, OPENCLAW_PARAM_READER);
227
+ logToolCall(logger, "agenr_fetch", ctx, `target=${formatTargetSelector(params.id, params.subject)}`, sanitizeFetchToolParams(params));
228
+ const services = await servicesPromise;
229
+ return toOpenClawToolResult(
230
+ await runFetchMemoryTool(params, services, {
231
+ extraDetails: { sessionKey: ctx.sessionKey }
232
+ })
233
+ );
234
+ } catch (error) {
235
+ logToolFailure(logger, "agenr_fetch", ctx, error);
236
+ return toolFailureResult(error);
237
+ }
238
+ }
239
+ };
240
+ }
241
+
75
242
  // src/adapters/openclaw/tools/recall.ts
76
243
  import { textResult as textResult2 } from "openclaw/plugin-sdk/agent-runtime";
77
244
  import { randomUUID } from "crypto";
@@ -297,151 +464,6 @@ function buildTopCandidates(result, reasonsByEntryId, topK) {
297
464
  });
298
465
  }
299
466
 
300
- // src/adapters/openclaw/tools/shared.ts
301
- import { failedTextResult, readNumberParam, readStringArrayParam, readStringParam, textResult } from "openclaw/plugin-sdk/agent-runtime";
302
- var OPENCLAW_PARAM_READER = {
303
- readString: readStringParam,
304
- readNumber: readNumberParam,
305
- readStringArray: readStringArrayParam
306
- };
307
- function toOpenClawToolResult(outcome) {
308
- if (outcome.failed) {
309
- return failedTextResult(outcome.text, {
310
- ...outcome.details,
311
- status: "failed"
312
- });
313
- }
314
- return textResult(outcome.text, outcome.details);
315
- }
316
- async function resolveTargetEntry2(services, params, options = {}) {
317
- return resolveTargetEntry(
318
- {
319
- getEntryById: async (id) => await services.entries.getEntry(id) ?? (await services.memory.getEntryTrace(id))?.entry ?? null,
320
- findEntryBySubject: async (subject) => services.memory.findEntryBySubject(subject),
321
- findMostRecentEntry: async () => services.memory.findMostRecentEntry()
322
- },
323
- params,
324
- options
325
- );
326
- }
327
- function logToolCall(logger, toolName, ctx, summary, sanitizedParams) {
328
- logger.info(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} ${summary}`);
329
- logger.info(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} params=${JSON.stringify(sanitizedParams)}`);
330
- }
331
- function logToolFailure(logger, toolName, ctx, error) {
332
- logger.warn(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} failed: ${formatErrorMessage(error)}`);
333
- }
334
- function sanitizeRetireToolParams(params) {
335
- return {
336
- ...params.id ? { id: params.id } : {},
337
- ...params.subject ? { subject: params.subject } : {},
338
- ...params.reason !== void 0 ? { reasonLength: params.reason.length } : {}
339
- };
340
- }
341
- function sanitizeTraceToolParams(params) {
342
- return {
343
- ...params.id ? { id: params.id } : {},
344
- ...params.subject ? { subject: params.subject } : {},
345
- ...params.last !== void 0 ? { last: params.last } : {}
346
- };
347
- }
348
- function formatTrace(entry, supersededBy, supersedes, claimFamily, recallEvents) {
349
- const slotPolicy = entry.claim_key ? claimFamily ? {
350
- policy: claimFamily.slotPolicy ?? resolveClaimSlotPolicy(claimFamily.claimKey).policy,
351
- reason: claimFamily.slotPolicyReason ?? resolveClaimSlotPolicy(claimFamily.claimKey).reason
352
- } : resolveClaimSlotPolicy(entry.claim_key) : void 0;
353
- const lines = [
354
- `Trace for ${entry.id} | ${entry.subject}`,
355
- `type=${entry.type} expiry=${entry.expiry} importance=${entry.importance} retired=${entry.retired}`,
356
- `content=${truncate(entry.content, 220)}`
357
- ];
358
- if (supersededBy) {
359
- lines.push(`superseded_by=${supersededBy.id} | ${supersededBy.subject}`);
360
- }
361
- if (supersedes.length > 0) {
362
- lines.push(`supersedes=${supersedes.map((item) => `${item.id} (${item.subject})`).join(", ")}`);
363
- }
364
- if (entry.claim_key) {
365
- lines.push(`claim_key=${entry.claim_key}`);
366
- if (slotPolicy) {
367
- lines.push(`slot_policy=${slotPolicy.policy}`);
368
- lines.push(`slot_policy_reason=${slotPolicy.reason}`);
369
- }
370
- }
371
- if (claimFamily && claimFamily.entries.length > 0) {
372
- lines.push(
373
- `claim_family=${claimFamily.claimKey} | slot_policy=${slotPolicy?.policy ?? "exclusive"} | ${claimFamily.entries.map((item) => `${item.id}:${describeTraceEntryState(item)}:${formatClaimLifecycleLabel(item)}`).join(", ")}`
374
- );
375
- if (slotPolicy) {
376
- lines.push(`claim_family_policy_reason=${slotPolicy.reason}`);
377
- }
378
- const transitionSummary = summarizeTraceClaimFamilyTransition(claimFamily.entries);
379
- if (transitionSummary) {
380
- lines.push(`transition=${transitionSummary}`);
381
- }
382
- }
383
- if (entry.valid_from || entry.valid_to) {
384
- lines.push(`validity=${entry.valid_from ?? "?"} -> ${entry.valid_to ?? "ongoing"}`);
385
- }
386
- if (entry.supersession_kind) {
387
- lines.push(`supersession_kind=${entry.supersession_kind}${entry.supersession_reason ? ` reason=${truncate(entry.supersession_reason, 120)}` : ""}`);
388
- }
389
- if (recallEvents.length > 0) {
390
- lines.push(
391
- `recent_recalls=${recallEvents.map((event) => `${event.recalledAt}${event.query ? ` query=${event.query}` : ""}${event.sessionKey ? ` session=${event.sessionKey}` : ""}`).join(" ; ")}`
392
- );
393
- }
394
- return lines.join("\n");
395
- }
396
- function toolFailureResult(error) {
397
- return failedTextResult(formatErrorMessage(error), {
398
- status: "failed"
399
- });
400
- }
401
- function formatToolSessionContext(ctx) {
402
- const normalizedSessionId = ctx.sessionId?.trim();
403
- const normalizedSessionKey = ctx.sessionKey?.trim();
404
- if (normalizedSessionId && normalizedSessionKey) {
405
- return `session=${normalizedSessionId} key=${normalizedSessionKey}`;
406
- }
407
- if (normalizedSessionId) {
408
- return `session=${normalizedSessionId}`;
409
- }
410
- if (normalizedSessionKey) {
411
- return `key=${normalizedSessionKey}`;
412
- }
413
- return "session=unknown";
414
- }
415
- function describeTraceEntryState(entry) {
416
- if (entry.superseded_by) {
417
- return "superseded";
418
- }
419
- if (entry.retired || entry.valid_to) {
420
- return "historical";
421
- }
422
- return "current";
423
- }
424
- function formatClaimLifecycleLabel(entry) {
425
- if (!entry.claim_key) {
426
- return "no-key";
427
- }
428
- return entry.claim_key_status ?? "legacy";
429
- }
430
- function summarizeTraceClaimFamilyTransition(entries) {
431
- const current = entries.find((entry) => !entry.retired && !entry.superseded_by);
432
- const prior = [...entries].reverse().find((entry) => entry.id !== current?.id && (entry.superseded_by !== void 0 || entry.retired || entry.valid_to !== void 0));
433
- if (current && prior) {
434
- return `${prior.id} -> ${current.id}`;
435
- }
436
- if (prior) {
437
- return `${prior.id} is historical with no current sibling in the traced family`;
438
- }
439
- if (current) {
440
- return `${current.id} is the only current sibling in the traced family`;
441
- }
442
- return void 0;
443
- }
444
-
445
467
  // src/adapters/openclaw/tools/recall.ts
446
468
  function createAgenrRecallTool(ctx, servicesPromise, logger) {
447
469
  return {
@@ -727,6 +749,7 @@ function createAgenrUpdateTool(ctx, servicesPromise, logger) {
727
749
  function registerAgenrOpenClawTools(api, servicesPromise, logger) {
728
750
  api.registerTool((ctx) => createAgenrStoreTool(ctx, servicesPromise, logger), { names: ["agenr_store"] });
729
751
  api.registerTool((ctx) => createAgenrRecallTool(ctx, servicesPromise, logger), { names: ["agenr_recall"] });
752
+ api.registerTool((ctx) => createAgenrFetchTool(ctx, servicesPromise, logger), { names: ["agenr_fetch"] });
730
753
  api.registerTool((ctx) => createAgenrRetireTool(ctx, servicesPromise, logger), { names: ["agenr_retire"] });
731
754
  api.registerTool((ctx) => createAgenrUpdateTool(ctx, servicesPromise, logger), { names: ["agenr_update"] });
732
755
  api.registerTool((ctx) => createAgenrTraceTool(ctx, servicesPromise, logger), { names: ["agenr_trace"] });
@@ -736,11 +759,11 @@ function registerAgenrOpenClawTools(api, servicesPromise, logger) {
736
759
  var openclaw_plugin_default = {
737
760
  id: "agenr",
738
761
  name: "agenr",
739
- version: "3.0.0",
762
+ version: "3.1.0",
740
763
  description: "agenr memory plugin for OpenClaw",
741
764
  kind: "memory",
742
765
  contracts: {
743
- tools: ["agenr_store", "agenr_recall", "agenr_retire", "agenr_update", "agenr_trace"]
766
+ tools: ["agenr_store", "agenr_recall", "agenr_fetch", "agenr_retire", "agenr_update", "agenr_trace"]
744
767
  },
745
768
  uiHints: {
746
769
  dbPath: {
@@ -1156,6 +1179,7 @@ function normalizeStoreNudgeConfig(value) {
1156
1179
  // src/adapters/openclaw/format/prompt-section.ts
1157
1180
  var MEMORY_TOOL_NAMES = {
1158
1181
  recall: "agenr_recall",
1182
+ fetch: "agenr_fetch",
1159
1183
  store: "agenr_store",
1160
1184
  update: "agenr_update",
1161
1185
  retire: "agenr_retire",
@@ -1177,6 +1201,7 @@ function buildAgenrMemoryPromptSection({
1177
1201
  "## Memory Recall",
1178
1202
  "Before answering anything about prior work, decisions, preferences, people, dates, unfinished work, or past sessions, call agenr_recall first. Session-start recall is automatic, and conservative before-turn recall may also appear as injected background context; use agenr_recall mid-session when you need context you do not already have.",
1179
1203
  "agenr_recall supports exact fact recall plus historical and episodic recall behind one tool: use mode=entries for exact facts, decisions, thresholds, and versions; use mode=auto for prior-state questions like what was the previous approach, what did we use before, or what changed from X to Y; use mode=episodes when you explicitly want session narrative recall.",
1204
+ "agenr_recall returns truncated entry previews with ids, scores, and preview_truncated flags.",
1180
1205
  "For temporal narrative questions, put the time phrase in the query itself: examples include yesterday, last week, this month, 2 weeks ago, or in March.",
1181
1206
  "One focused agenr_recall call with the right scope beats several broad ones.",
1182
1207
  "When Agenr injects memory automatically, treat it as non-user background context and use it silently when relevant rather than forcing it into the reply.",
@@ -1215,6 +1240,9 @@ function buildAgenrMemoryPromptSection({
1215
1240
  if (availableTools.has(MEMORY_TOOL_NAMES.update) || availableTools.has(MEMORY_TOOL_NAMES.retire)) {
1216
1241
  lines.push("When memory is contradicted by live evidence, fix it with agenr_update or agenr_retire instead of silently working around it.");
1217
1242
  }
1243
+ if (availableTools.has(MEMORY_TOOL_NAMES.fetch)) {
1244
+ lines.push("Call agenr_fetch with id when preview_truncated=true or exact stored wording is required.");
1245
+ }
1218
1246
  if (availableTools.has(MEMORY_TOOL_NAMES.trace)) {
1219
1247
  lines.push("Use agenr_trace when provenance, recall history, or supersession matters.");
1220
1248
  }