@vectorize-io/self-driving-agents 0.0.1

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 ADDED
@@ -0,0 +1,52 @@
1
+ # @vectorize-io/self-driving-agents
2
+
3
+ Install self-driving agents with portable memory on any harness.
4
+
5
+ ```bash
6
+ npx @vectorize-io/self-driving-agents install ./my-agent --harness openclaw
7
+ ```
8
+
9
+ ## What it does
10
+
11
+ 1. Reads `bank-template.json` from the agent directory (knowledge pages, missions, directives)
12
+ 2. Reads `content/` directory for reference docs to ingest
13
+ 3. Resolves the Hindsight bank from the harness config
14
+ 4. Imports the template and ingests content
15
+ 5. Creates the harness agent, installs the skill, patches startup
16
+
17
+ ## Agent directory layout
18
+
19
+ ```
20
+ my-agent/
21
+ bank-template.json # optional: bank config + knowledge pages
22
+ content/ # optional: reference docs (.md, .txt, .html, etc.)
23
+ ```
24
+
25
+ Agent name defaults to the directory name. Override with `--agent <name>`.
26
+
27
+ ## Options
28
+
29
+ ```
30
+ npx @vectorize-io/self-driving-agents install <dir> --harness <harness> [options]
31
+
32
+ --harness <h> Required. openclaw | hermes | claude-code
33
+ --agent <name> Agent name (defaults to directory name)
34
+ --api-url <url> Override Hindsight API URL
35
+ --api-token <t> Override API token
36
+ ```
37
+
38
+ ## Example
39
+
40
+ ```bash
41
+ # Clone an agent repo
42
+ git clone https://github.com/vectorize-io/self-driving-agents
43
+ cd self-driving-agents
44
+
45
+ # Install the SEO blog writer
46
+ npx @vectorize-io/self-driving-agents install ./marketing-seo-blog-posts --harness openclaw
47
+
48
+ # Create and start the agent
49
+ openclaw agents add marketing-seo-blog-posts --workspace ~/.hindsight-agents/openclaw/marketing-seo-blog-posts --non-interactive
50
+ openclaw gateway restart
51
+ openclaw tui --session agent:marketing-seo-blog-posts:main:session1
52
+ ```
package/dist/cli.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * self-driving-agents — install a self-driving agent.
4
+ *
5
+ * npx @vectorize-io/self-driving-agents install <agent> --harness openclaw [--agent <name>]
6
+ *
7
+ * Agent resolution:
8
+ * marketing-agent → vectorize-io/self-driving-agents/marketing-agent (default repo)
9
+ * my-org/my-repo/my-agent → my-org/my-repo/my-agent on GitHub
10
+ * ./local-dir → local directory
11
+ * /absolute/path → local directory
12
+ *
13
+ * Directory layout (recursive):
14
+ * bank-template.json — optional: bank config at this level
15
+ * *.md, *.txt, ... — content files (found recursively, excluding bank-template.json)
16
+ */
17
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,392 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * self-driving-agents — install a self-driving agent.
4
+ *
5
+ * npx @vectorize-io/self-driving-agents install <agent> --harness openclaw [--agent <name>]
6
+ *
7
+ * Agent resolution:
8
+ * marketing-agent → vectorize-io/self-driving-agents/marketing-agent (default repo)
9
+ * my-org/my-repo/my-agent → my-org/my-repo/my-agent on GitHub
10
+ * ./local-dir → local directory
11
+ * /absolute/path → local directory
12
+ *
13
+ * Directory layout (recursive):
14
+ * bank-template.json — optional: bank config at this level
15
+ * *.md, *.txt, ... — content files (found recursively, excluding bank-template.json)
16
+ */
17
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync, rmSync, } from "fs";
18
+ import { join, resolve, extname, basename, relative, dirname } from "path";
19
+ import { homedir, tmpdir } from "os";
20
+ import { fileURLToPath } from "url";
21
+ import { execSync } from "child_process";
22
+ import * as p from "@clack/prompts";
23
+ import color from "picocolors";
24
+ import { HindsightClient, sdk, createClient, createConfig } from "@vectorize-io/hindsight-client";
25
+ const DEFAULT_REPO = "vectorize-io/self-driving-agents";
26
+ // ── Content discovery ──────────────────────────────────
27
+ const CONTENT_EXTS = new Set([".md", ".txt", ".html", ".json", ".csv", ".xml"]);
28
+ const IGNORED_FILES = new Set(["bank-template.json"]);
29
+ /** Recursively find all content files under `dir`, returning paths relative to `dir`. */
30
+ function findContentFiles(dir) {
31
+ const results = [];
32
+ function walk(current) {
33
+ for (const entry of readdirSync(current)) {
34
+ const full = join(current, entry);
35
+ if (statSync(full).isDirectory()) {
36
+ walk(full);
37
+ }
38
+ else if (CONTENT_EXTS.has(extname(entry).toLowerCase()) && !IGNORED_FILES.has(entry)) {
39
+ results.push(relative(dir, full));
40
+ }
41
+ }
42
+ }
43
+ walk(dir);
44
+ return results.sort();
45
+ }
46
+ // ── Agent resolution ───────────────────────────────────
47
+ function isLocalPath(input) {
48
+ return (input.startsWith("./") ||
49
+ input.startsWith("../") ||
50
+ input.startsWith("/") ||
51
+ input.startsWith("~"));
52
+ }
53
+ /**
54
+ * Resolve the agent specifier to a local directory.
55
+ *
56
+ * - Local paths (./foo, /foo, ~/foo) → resolve directly
57
+ * - "name" → GitHub: vectorize-io/self-driving-agents/name
58
+ * - "org/repo/path" → GitHub: org/repo/path
59
+ */
60
+ async function resolveAgentDir(input, spinner) {
61
+ if (isLocalPath(input)) {
62
+ const dir = resolve(input.replace(/^~/, homedir()));
63
+ if (!existsSync(dir))
64
+ throw new Error(`Directory not found: ${dir}`);
65
+ return { dir, source: dir };
66
+ }
67
+ // Parse GitHub reference: "name" or "org/repo/path/to/agent"
68
+ const parts = input.split("/");
69
+ let org, repo, subpath;
70
+ if (parts.length === 1) {
71
+ // Just a name → default repo
72
+ org = "vectorize-io";
73
+ repo = "self-driving-agents";
74
+ subpath = parts[0];
75
+ }
76
+ else if (parts.length >= 3) {
77
+ // org/repo/path...
78
+ org = parts[0];
79
+ repo = parts[1];
80
+ subpath = parts.slice(2).join("/");
81
+ }
82
+ else {
83
+ throw new Error(`Invalid agent reference: '${input}'\n` +
84
+ ` Use: <name>, <org>/<repo>/<path>, or a local path (./dir)`);
85
+ }
86
+ spinner.start(`Fetching ${color.cyan(`${org}/${repo}/${subpath}`)} from GitHub...`);
87
+ const tmp = join(tmpdir(), `sda-${Date.now()}`);
88
+ mkdirSync(tmp, { recursive: true });
89
+ try {
90
+ // Download repo tarball and extract the specific subdirectory
91
+ const tarballUrl = `https://github.com/${org}/${repo}/archive/refs/heads/main.tar.gz`;
92
+ execSync(`curl -sL "${tarballUrl}" | tar xz -C "${tmp}" --strip-components=1 "${repo}-main/${subpath}"`, { stdio: "pipe" });
93
+ }
94
+ catch {
95
+ rmSync(tmp, { recursive: true, force: true });
96
+ throw new Error(`Failed to fetch ${org}/${repo}/${subpath}\n` +
97
+ ` Make sure the repository and path exist on GitHub.`);
98
+ }
99
+ const dir = join(tmp, subpath);
100
+ if (!existsSync(dir)) {
101
+ rmSync(tmp, { recursive: true, force: true });
102
+ throw new Error(`Path '${subpath}' not found in ${org}/${repo}`);
103
+ }
104
+ const source = `github.com/${org}/${repo}/${subpath}`;
105
+ spinner.stop(`Fetched ${color.cyan(source)}`);
106
+ return { dir, source, cleanup: () => rmSync(tmp, { recursive: true, force: true }) };
107
+ }
108
+ // ── Skill ───────────────────────────────────────────────
109
+ const __dirname = dirname(fileURLToPath(import.meta.url));
110
+ const SKILL_PATH = join(__dirname, "..", "skill", "SKILL.md");
111
+ const SKILL_MD = readFileSync(SKILL_PATH, "utf-8");
112
+ // ── Plugin management ───────────────────────────────────
113
+ const OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
114
+ function readOpenClawConfig() {
115
+ if (!existsSync(OPENCLAW_CONFIG_PATH))
116
+ return null;
117
+ return JSON.parse(readFileSync(OPENCLAW_CONFIG_PATH, "utf-8"));
118
+ }
119
+ function enableKnowledgeTools() {
120
+ const config = readOpenClawConfig();
121
+ if (!config)
122
+ return;
123
+ const pc = config.plugins?.entries?.["hindsight-openclaw"]?.config;
124
+ if (!pc)
125
+ return;
126
+ if (pc.enableKnowledgeTools === true)
127
+ return;
128
+ pc.enableKnowledgeTools = true;
129
+ writeFileSync(OPENCLAW_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
130
+ }
131
+ function isPluginInstalled() {
132
+ const config = readOpenClawConfig();
133
+ if (!config)
134
+ return false;
135
+ return (config.plugins?.entries?.["hindsight-openclaw"]?.enabled !== false &&
136
+ config.plugins?.entries?.["hindsight-openclaw"] !== undefined);
137
+ }
138
+ function isPluginConfigured() {
139
+ const config = readOpenClawConfig();
140
+ if (!config)
141
+ return false;
142
+ const pc = config.plugins?.entries?.["hindsight-openclaw"]?.config || {};
143
+ return !!(pc.hindsightApiUrl || pc.embedVersion || pc.llmProvider);
144
+ }
145
+ function resolveFromPlugin(agentId) {
146
+ const config = readOpenClawConfig();
147
+ if (!config)
148
+ throw new Error("OpenClaw config not found");
149
+ const pc = config.plugins?.entries?.["hindsight-openclaw"]?.config || {};
150
+ const apiUrl = pc.hindsightApiUrl || `http://localhost:${pc.apiPort || 9077}`;
151
+ const apiToken = pc.hindsightApiToken || undefined;
152
+ let bankId;
153
+ if (pc.dynamicBankId === false && pc.bankId) {
154
+ bankId = pc.bankId;
155
+ }
156
+ else {
157
+ const granularity = pc.dynamicBankGranularity || ["agent", "channel", "user"];
158
+ const fieldMap = {
159
+ agent: agentId,
160
+ channel: "unknown",
161
+ user: "anonymous",
162
+ provider: "unknown",
163
+ };
164
+ const base = granularity.map((f) => encodeURIComponent(fieldMap[f] || "unknown")).join("::");
165
+ bankId = pc.bankIdPrefix ? `${pc.bankIdPrefix}-${base}` : base;
166
+ }
167
+ return { apiUrl, bankId, apiToken };
168
+ }
169
+ function getPluginSummary() {
170
+ const config = readOpenClawConfig();
171
+ if (!config)
172
+ return "Not found";
173
+ const pc = config.plugins?.entries?.["hindsight-openclaw"]?.config || {};
174
+ if (pc.hindsightApiUrl)
175
+ return `External: ${pc.hindsightApiUrl}`;
176
+ if (pc.embedVersion)
177
+ return `Embedded v${pc.embedVersion}`;
178
+ return "Not configured";
179
+ }
180
+ function parseAgentsJson(raw) {
181
+ const clean = raw.replace(/\n?\x1b\[[0-9;]*m[^\n]*/g, "").trim();
182
+ const arrStart = clean.indexOf("\n[");
183
+ const jsonStr = arrStart >= 0 ? clean.slice(arrStart + 1) : clean.startsWith("[") ? clean : "[]";
184
+ return JSON.parse(jsonStr);
185
+ }
186
+ async function ensurePlugin() {
187
+ if (!isPluginInstalled()) {
188
+ p.log.warn("Hindsight plugin not found. Installing...");
189
+ try {
190
+ execSync("openclaw plugins install @vectorize-io/hindsight-openclaw", { stdio: "inherit" });
191
+ }
192
+ catch {
193
+ p.cancel("Failed to install plugin. Run manually:\n openclaw plugins install @vectorize-io/hindsight-openclaw");
194
+ process.exit(1);
195
+ }
196
+ }
197
+ if (!isPluginConfigured()) {
198
+ p.log.warn("Hindsight plugin needs configuration.");
199
+ try {
200
+ execSync("npx --yes --package @vectorize-io/hindsight-openclaw hindsight-openclaw-setup", {
201
+ stdio: "inherit",
202
+ });
203
+ }
204
+ catch {
205
+ p.cancel("Run the wizard manually:\n npx --yes --package @vectorize-io/hindsight-openclaw hindsight-openclaw-setup");
206
+ process.exit(1);
207
+ }
208
+ }
209
+ else {
210
+ const summary = getPluginSummary();
211
+ if (process.stdin.isTTY) {
212
+ const ok = await p.confirm({
213
+ message: `Hindsight: ${color.cyan(summary)}. Use this?\n${color.dim(" Changing this will affect all existing agents — one OpenClaw instance shares a single Hindsight instance.")}`,
214
+ });
215
+ if (p.isCancel(ok)) {
216
+ p.cancel("Cancelled.");
217
+ process.exit(0);
218
+ }
219
+ if (!ok) {
220
+ p.log.info("Launching configuration wizard...");
221
+ try {
222
+ execSync("npx --yes --package @vectorize-io/hindsight-openclaw hindsight-openclaw-setup", { stdio: "inherit" });
223
+ }
224
+ catch {
225
+ p.cancel("Configuration failed. Run manually:\n npx --yes --package @vectorize-io/hindsight-openclaw hindsight-openclaw-setup");
226
+ process.exit(1);
227
+ }
228
+ }
229
+ }
230
+ else {
231
+ p.log.info(`Hindsight: ${color.cyan(summary)}`);
232
+ }
233
+ }
234
+ }
235
+ // ── Main ────────────────────────────────────────────────
236
+ async function main() {
237
+ const args = process.argv.slice(2);
238
+ if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
239
+ console.log(`
240
+ ${color.bold("self-driving-agents")} — install a self-driving agent
241
+
242
+ ${color.dim("Usage:")}
243
+ npx @vectorize-io/self-driving-agents install <agent> --harness <harness> [--agent <name>]
244
+
245
+ ${color.dim("Agent sources:")}
246
+ ${color.cyan("marketing-agent")} → ${DEFAULT_REPO}/marketing-agent
247
+ ${color.cyan("org/repo/my-agent")} → org/repo/my-agent on GitHub
248
+ ${color.cyan("./local-dir")} → local directory
249
+
250
+ ${color.dim("Options:")}
251
+ ${color.cyan("--harness <h>")} Required. openclaw | hermes | claude-code
252
+ ${color.cyan("--agent <name>")} Agent name (defaults to directory name)
253
+ `);
254
+ process.exit(0);
255
+ }
256
+ let dirArg = args[0] === "install" ? args[1] : args[0];
257
+ const restArgs = args[0] === "install" ? args.slice(2) : args.slice(1);
258
+ if (!dirArg) {
259
+ p.cancel("Agent argument required.");
260
+ process.exit(1);
261
+ }
262
+ let harness;
263
+ let agentName;
264
+ for (let i = 0; i < restArgs.length; i++) {
265
+ if (restArgs[i] === "--harness" && restArgs[i + 1])
266
+ harness = restArgs[++i];
267
+ else if (restArgs[i] === "--agent" && restArgs[i + 1])
268
+ agentName = restArgs[++i];
269
+ }
270
+ if (!harness) {
271
+ p.cancel("--harness required (openclaw | hermes | claude-code)");
272
+ process.exit(1);
273
+ }
274
+ p.intro(color.bgCyan(color.black(` self-driving-agents `)));
275
+ // Step 0: Resolve agent directory (local or GitHub)
276
+ const spin = p.spinner();
277
+ const { dir, source, cleanup } = await resolveAgentDir(dirArg, spin);
278
+ try {
279
+ const agentId = agentName || basename(dir);
280
+ // Step 1: Ensure plugin
281
+ if (harness === "openclaw") {
282
+ await ensurePlugin();
283
+ enableKnowledgeTools();
284
+ }
285
+ // Step 2: Resolve bank + API from plugin config
286
+ const { apiUrl, bankId, apiToken } = resolveFromPlugin(agentId);
287
+ const workspaceDir = join(homedir(), ".self-driving-agents", "openclaw", agentId);
288
+ p.log.info([
289
+ `Agent: ${color.bold(agentId)}`,
290
+ `Source: ${color.dim(source)}`,
291
+ `Bank: ${color.dim(bankId)}`,
292
+ `API: ${color.dim(apiUrl)}`,
293
+ `Workspace: ${color.dim(workspaceDir)}`,
294
+ ].join("\n"));
295
+ // Step 3: Create client + health check
296
+ const client = new HindsightClient({
297
+ baseUrl: apiUrl,
298
+ apiKey: apiToken,
299
+ userAgent: "self-driving-agents/0.1.0",
300
+ });
301
+ const lowLevel = createClient(createConfig({
302
+ baseUrl: apiUrl,
303
+ headers: {
304
+ ...(apiToken ? { Authorization: `Bearer ${apiToken}` } : {}),
305
+ "User-Agent": "self-driving-agents/0.1.0",
306
+ },
307
+ }));
308
+ spin.start("Connecting to Hindsight...");
309
+ try {
310
+ await sdk.healthEndpointHealthGet({ client: lowLevel });
311
+ spin.stop("Connected to Hindsight");
312
+ }
313
+ catch {
314
+ spin.stop("Connection failed");
315
+ p.cancel(`Cannot reach Hindsight at ${apiUrl}\nStart the server or reconfigure the plugin.`);
316
+ process.exit(1);
317
+ }
318
+ // Step 4: Import bank template
319
+ const templatePath = join(dir, "bank-template.json");
320
+ if (existsSync(templatePath)) {
321
+ spin.start("Importing bank template...");
322
+ const template = JSON.parse(readFileSync(templatePath, "utf-8"));
323
+ await sdk.importBankTemplate({
324
+ client: lowLevel,
325
+ path: { bank_id: bankId },
326
+ body: template,
327
+ });
328
+ spin.stop("Bank template imported");
329
+ }
330
+ // Step 5: Ingest content (recursive — all text files except bank-template.json)
331
+ const contentFiles = findContentFiles(dir);
332
+ if (contentFiles.length > 0) {
333
+ spin.start(`Ingesting ${contentFiles.length} file(s)...`);
334
+ for (const relPath of contentFiles) {
335
+ const content = readFileSync(join(dir, relPath), "utf-8");
336
+ if (!content.trim())
337
+ continue;
338
+ // Use relative path (without extension) as document ID, e.g. "seo/keyword-research"
339
+ const docId = relPath.replace(/\.[^.]+$/, "");
340
+ await client.retainBatch(bankId, [{ content, document_id: docId }], { async: true });
341
+ spin.message(`Ingesting ${relPath}...`);
342
+ }
343
+ spin.stop(`Ingested ${contentFiles.length} file(s)`);
344
+ }
345
+ // Step 6: Create agent + install skill
346
+ mkdirSync(workspaceDir, { recursive: true });
347
+ const skillDir = join(workspaceDir, "skills", "agent-knowledge");
348
+ mkdirSync(skillDir, { recursive: true });
349
+ writeFileSync(join(skillDir, "SKILL.md"), SKILL_MD);
350
+ p.log.success("Knowledge skill installed");
351
+ if (harness === "openclaw") {
352
+ try {
353
+ const listOut = execSync("openclaw agents list --json 2>/dev/null", { encoding: "utf-8" });
354
+ const agents = parseAgentsJson(listOut);
355
+ if (!agents.some((a) => a.name === agentId || a.id === agentId)) {
356
+ execSync(`openclaw agents add ${agentId} --workspace ${workspaceDir} --non-interactive`, {
357
+ stdio: "pipe",
358
+ });
359
+ p.log.success(`Agent '${agentId}' created`);
360
+ }
361
+ else {
362
+ p.log.info(`Agent '${agentId}' already exists`);
363
+ }
364
+ }
365
+ catch {
366
+ p.log.warn(`Create agent manually:\n openclaw agents add ${agentId} --workspace ${workspaceDir} --non-interactive`);
367
+ }
368
+ }
369
+ // Step 7: Patch startup
370
+ const startupFile = join(workspaceDir, "AGENTS.md");
371
+ if (existsSync(startupFile)) {
372
+ let text = readFileSync(startupFile, "utf-8");
373
+ if (!text.includes("agent-knowledge")) {
374
+ text = text.replace("Don't ask permission. Just do it.", "5. Read `skills/agent-knowledge/SKILL.md` and **execute its mandatory startup sequence**\n\nDon't ask permission. Just do it.");
375
+ writeFileSync(startupFile, text);
376
+ p.log.success("Startup patched");
377
+ }
378
+ }
379
+ p.note([
380
+ `${color.dim("1.")} openclaw gateway restart`,
381
+ `${color.dim("2.")} openclaw tui --session agent:${agentId}:main:session1`,
382
+ ].join("\n"), "Next steps");
383
+ p.outro(color.green(`'${agentId}' is ready`));
384
+ }
385
+ finally {
386
+ cleanup?.();
387
+ }
388
+ }
389
+ main().catch((err) => {
390
+ p.cancel(err.message);
391
+ process.exit(1);
392
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@vectorize-io/self-driving-agents",
3
+ "version": "0.0.1",
4
+ "description": "Install self-driving agents with portable memory on any harness",
5
+ "type": "module",
6
+ "main": "dist/cli.js",
7
+ "bin": {
8
+ "self-driving-agents": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "skill"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "vitest run tests",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "ai-agents",
21
+ "self-driving",
22
+ "hindsight",
23
+ "memory",
24
+ "openclaw",
25
+ "hermes",
26
+ "claude-code"
27
+ ],
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/vectorize-io/hindsight.git",
32
+ "directory": "hindsight-agent-setup"
33
+ },
34
+ "devDependencies": {
35
+ "typescript": "^5.4",
36
+ "vitest": "^4.1.2"
37
+ },
38
+ "dependencies": {
39
+ "@clack/prompts": "^1.2.0",
40
+ "@vectorize-io/hindsight-client": "^0.5.6"
41
+ }
42
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: agent-knowledge
3
+ description: Your long-term knowledge pages. Read them at session start. Create new pages when you learn something worth remembering across sessions. Pages auto-update from your conversations via Hindsight.
4
+ ---
5
+
6
+ # Agent Knowledge
7
+
8
+ You have knowledge pages that persist across sessions and auto-update from your conversations.
9
+
10
+ **How it works:** Your conversations are automatically retained into a Hindsight memory bank. The system extracts observations and uses them to keep your pages current. Each page has a "source query" — a question the system re-answers after every consolidation cycle to rebuild the page content. You create pages; the system maintains them.
11
+
12
+ ## At session start
13
+
14
+ Call `agent_knowledge_list_pages` to see what pages exist, then `agent_knowledge_get_page` for each one you need.
15
+
16
+ ## Reading
17
+
18
+ - `agent_knowledge_list_pages()` — list page IDs and names (no content)
19
+ - `agent_knowledge_get_page(page_id)` — read the full content of a page
20
+
21
+ ## Creating pages
22
+
23
+ When you learn something durable — a user preference, a working procedure, performance data — create a page immediately.
24
+
25
+ `agent_knowledge_create_page(page_id, name, source_query)`
26
+
27
+ - `page_id`: lowercase with hyphens (`editorial-preferences`)
28
+ - `source_query`: a question that produces the page content from observations
29
+
30
+ Examples:
31
+
32
+ - `"What are the user's preferences for tone, length, and formatting?"`
33
+ - `"What content strategies have performed well or poorly? Include numbers."`
34
+ - `"What are the best practices for [topic], preferring our data over generic advice?"`
35
+
36
+ ## Searching memories
37
+
38
+ `agent_knowledge_recall(query)` — search across all retained conversations and documents for specific facts.
39
+
40
+ Use when pages don't cover what you need.
41
+
42
+ ## Ingesting documents
43
+
44
+ `agent_knowledge_ingest(title, content)` — upload raw content into memory. Never summarize before ingesting. Save large content to a file first, read it, then pass the full text.
45
+
46
+ ## Updating and deleting
47
+
48
+ - `agent_knowledge_update_page(page_id, name?, source_query?)` — change what a page tracks
49
+ - `agent_knowledge_delete_page(page_id)` — remove a page
50
+
51
+ ## Important
52
+
53
+ - Pages update automatically — don't edit content directly
54
+ - State preferences clearly in your responses so the system captures them
55
+ - Create pages silently — don't announce it to the user
56
+ - Prefer fewer broad pages over many narrow ones