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/CHANGELOG.md +31 -0
- package/LICENSE +21 -661
- package/README.md +48 -25
- package/dist/adapters/openclaw/index.js +192 -164
- package/dist/adapters/skeln/index.js +177 -112
- package/dist/{chunk-MYZ2CWY6.js → chunk-E2DHUFZK.js} +464 -542
- package/dist/{chunk-LAXNNWHM.js → chunk-EEEL53X4.js} +191 -12
- package/dist/{chunk-TBFAARM5.js → chunk-JSVQILB3.js} +12 -1
- package/dist/{chunk-ELR2HSVC.js → chunk-NNO2V4GH.js} +243 -0
- package/dist/{chunk-P5SB75FK.js → chunk-NOIZQRQV.js} +2 -2
- package/dist/{chunk-575MUIW5.js → chunk-V5CDMHRN.js} +14 -179
- package/dist/cli.js +3 -3
- package/dist/internal-eval-server.js +3 -3
- package/dist/internal-recall-eval-server.js +3 -3
- package/package.json +2 -2
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
|
|
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:
|
|
32
|
-
- Agent tools for `store`, `recall`, `retire`, `update`, and `trace`
|
|
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@
|
|
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
|
|
142
|
-
|
|
|
143
|
-
| `agenr init`
|
|
144
|
-
| `agenr setup`
|
|
145
|
-
| `agenr recall <query>`
|
|
146
|
-
| `agenr
|
|
147
|
-
| `agenr ingest
|
|
148
|
-
| `agenr ingest
|
|
149
|
-
| `agenr ingest
|
|
150
|
-
| `agenr
|
|
151
|
-
| `agenr surgeon
|
|
152
|
-
| `agenr surgeon
|
|
153
|
-
| `agenr surgeon
|
|
154
|
-
| `agenr
|
|
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
|
|
193
|
+
# Run the default autonomous surgeon sequence (dry-run by default)
|
|
174
194
|
agenr surgeon run --budget 2.00
|
|
175
195
|
|
|
176
|
-
# Run
|
|
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]`.
|
|
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.
|
|
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
|
-
|
|
264
|
+
MIT
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
openClawTranscriptParser,
|
|
5
5
|
parseTuiSessionKey,
|
|
6
6
|
readOpenClawSessionsStore
|
|
7
|
-
} from "../../chunk-
|
|
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
|
-
|
|
40
|
+
runFetchMemoryTool,
|
|
43
41
|
runRecallMemoryTool,
|
|
44
42
|
runSessionStart,
|
|
45
43
|
runStoreMemoryTool,
|
|
46
44
|
runUpdateMemoryTool,
|
|
47
|
-
sanitizeUpdateToolParams,
|
|
48
45
|
writeBoundedSingleTranscriptEpisode
|
|
49
|
-
} from "../../chunk-
|
|
46
|
+
} from "../../chunk-E2DHUFZK.js";
|
|
50
47
|
import {
|
|
51
|
-
|
|
52
|
-
|
|
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-
|
|
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.
|
|
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
|
}
|