@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 +52 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +392 -0
- package/package.json +42 -0
- package/skill/SKILL.md +56 -0
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
|