hyperstack-core 1.0.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/LICENSE +21 -0
- package/README.md +154 -0
- package/SKILL.md +118 -0
- package/adapters/openclaw.js +221 -0
- package/cli.js +500 -0
- package/examples/before-after.js +110 -0
- package/examples/openclaw-multiagent.js +214 -0
- package/index.js +18 -0
- package/package.json +50 -0
- package/src/client.js +267 -0
- package/templates/openclaw-multiagent.json +98 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* example-openclaw-multiagent.js
|
|
3
|
+
*
|
|
4
|
+
* Complete example: 3 OpenClaw agents coordinating via HyperStack
|
|
5
|
+
* instead of GOALS.md + DECISIONS.md files.
|
|
6
|
+
*
|
|
7
|
+
* Run: HYPERSTACK_API_KEY=hs_your_key node example-openclaw-multiagent.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { HyperStackClient } from "hyperstack-core";
|
|
11
|
+
|
|
12
|
+
const hs = new HyperStackClient({ workspace: "my-project" });
|
|
13
|
+
|
|
14
|
+
// ─── Step 1: Register your agents ──────────────────────
|
|
15
|
+
|
|
16
|
+
async function setup() {
|
|
17
|
+
// Register each agent as a card in the graph
|
|
18
|
+
await hs.registerAgent({
|
|
19
|
+
id: "coordinator",
|
|
20
|
+
name: "Coordinator",
|
|
21
|
+
role: "Routes tasks, monitors blockers, prevents duplicate work",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await hs.registerAgent({
|
|
25
|
+
id: "researcher",
|
|
26
|
+
name: "Research Agent",
|
|
27
|
+
role: "Investigates technical questions, stores findings",
|
|
28
|
+
owns: ["project-root"],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await hs.registerAgent({
|
|
32
|
+
id: "builder",
|
|
33
|
+
name: "Builder Agent",
|
|
34
|
+
role: "Implements code, records architecture decisions",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Create project root
|
|
38
|
+
await hs.store({
|
|
39
|
+
slug: "project-root",
|
|
40
|
+
title: "SaaS Dashboard Project",
|
|
41
|
+
body: "Next.js 14 + Clerk auth + Neon PostgreSQL. Deploying to Vercel.",
|
|
42
|
+
cardType: "project",
|
|
43
|
+
stack: "projects",
|
|
44
|
+
keywords: ["project", "saas", "dashboard", "nextjs"],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log("✅ Setup complete: 3 agents + project root");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Step 2: Researcher investigates auth options ──────
|
|
51
|
+
|
|
52
|
+
async function researcherFlow() {
|
|
53
|
+
const researcher = new HyperStackClient({
|
|
54
|
+
workspace: "my-project",
|
|
55
|
+
agentId: "researcher",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Check if someone already researched this
|
|
59
|
+
const existing = await researcher.search("authentication options");
|
|
60
|
+
if (existing.results?.length) {
|
|
61
|
+
console.log("📋 Already researched:", existing.results[0].title);
|
|
62
|
+
return; // No duplicate work!
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Store findings
|
|
66
|
+
await researcher.store({
|
|
67
|
+
slug: "finding-clerk-pricing",
|
|
68
|
+
title: "Clerk pricing: free to 10K MAU",
|
|
69
|
+
body: "Clerk offers free tier up to 10,000 monthly active users. After that $0.02/MAU. Auth0 starts charging at 7,500 MAU at $23/mo. For our expected 5K users, Clerk is free vs Auth0 $23/mo.",
|
|
70
|
+
cardType: "event",
|
|
71
|
+
stack: "general",
|
|
72
|
+
keywords: ["clerk", "auth0", "pricing", "authentication"],
|
|
73
|
+
links: [{ target: "project-root", relation: "related" }],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await researcher.store({
|
|
77
|
+
slug: "finding-nextauth-deprecated",
|
|
78
|
+
title: "NextAuth.js → Auth.js migration ongoing",
|
|
79
|
+
body: "NextAuth.js is being renamed to Auth.js. Migration guide exists but community reports rough edges with App Router. Not recommended for new projects right now.",
|
|
80
|
+
cardType: "event",
|
|
81
|
+
stack: "general",
|
|
82
|
+
keywords: ["nextauth", "authjs", "migration", "risk"],
|
|
83
|
+
links: [{ target: "project-root", relation: "related" }],
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.log("✅ Researcher stored 2 findings");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Step 3: Builder makes a decision based on research ─
|
|
90
|
+
|
|
91
|
+
async function builderFlow() {
|
|
92
|
+
const builder = new HyperStackClient({
|
|
93
|
+
workspace: "my-project",
|
|
94
|
+
agentId: "builder",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Search for research findings before deciding
|
|
98
|
+
const research = await builder.search("authentication pricing comparison");
|
|
99
|
+
console.log(`📋 Builder found ${research.results?.length || 0} relevant cards`);
|
|
100
|
+
|
|
101
|
+
// Make and record a decision
|
|
102
|
+
await builder.decide({
|
|
103
|
+
slug: "decision-use-clerk",
|
|
104
|
+
title: "Use Clerk for authentication",
|
|
105
|
+
body: "Based on research: Clerk free at our scale (<10K MAU), better Next.js integration than Auth0, and NextAuth.js has migration risks. Clerk is the clear choice.",
|
|
106
|
+
decidedBy: "agent-builder",
|
|
107
|
+
affects: ["project-root"],
|
|
108
|
+
keywords: ["clerk", "auth", "decision"],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Record the implementation task
|
|
112
|
+
await builder.store({
|
|
113
|
+
slug: "task-clerk-integration",
|
|
114
|
+
title: "Integrate Clerk with Next.js App Router",
|
|
115
|
+
body: "Install @clerk/nextjs, configure middleware, add sign-in/sign-up pages, protect API routes.",
|
|
116
|
+
cardType: "workflow",
|
|
117
|
+
stack: "workflows",
|
|
118
|
+
keywords: ["clerk", "integration", "nextjs", "task"],
|
|
119
|
+
links: [
|
|
120
|
+
{ target: "decision-use-clerk", relation: "depends_on" },
|
|
121
|
+
{ target: "agent-builder", relation: "assigned_to" },
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Record a blocker
|
|
126
|
+
await builder.store({
|
|
127
|
+
slug: "blocker-env-vars",
|
|
128
|
+
title: "Need Clerk API keys from team lead",
|
|
129
|
+
body: "Cannot proceed with Clerk integration until CLERK_SECRET_KEY and NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY are provided.",
|
|
130
|
+
cardType: "event",
|
|
131
|
+
stack: "general",
|
|
132
|
+
keywords: ["blocker", "clerk", "env", "keys"],
|
|
133
|
+
links: [
|
|
134
|
+
{ target: "task-clerk-integration", relation: "blocks" },
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
console.log("✅ Builder: decision recorded, task created, blocker flagged");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── Step 4: Coordinator checks status ─────────────────
|
|
142
|
+
|
|
143
|
+
async function coordinatorFlow() {
|
|
144
|
+
const coordinator = new HyperStackClient({
|
|
145
|
+
workspace: "my-project",
|
|
146
|
+
agentId: "coordinator",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// What blocks the Clerk integration?
|
|
150
|
+
const blockers = await coordinator.blockers("task-clerk-integration");
|
|
151
|
+
console.log(`\n🚧 Blockers for Clerk integration: ${blockers.blockers?.length || 0}`);
|
|
152
|
+
for (const b of blockers.blockers || []) {
|
|
153
|
+
console.log(` [${b.slug}] ${b.title}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Full graph from project root
|
|
157
|
+
try {
|
|
158
|
+
const graph = await coordinator.graph("project-root", { depth: 2 });
|
|
159
|
+
console.log(`\n📊 Project graph: ${graph.nodes?.length || 0} nodes, ${graph.edges?.length || 0} edges`);
|
|
160
|
+
for (const e of graph.edges || []) {
|
|
161
|
+
console.log(` ${e.from} --${e.relation}--> ${e.to}`);
|
|
162
|
+
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
// Graph traversal needs Pro plan
|
|
165
|
+
if (err.status === 403) {
|
|
166
|
+
console.log("\n📊 Graph traversal requires Pro plan. Using search fallback.");
|
|
167
|
+
const all = await coordinator.list();
|
|
168
|
+
console.log(` Total cards: ${all.count}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check: has anyone already done research on databases?
|
|
173
|
+
const dbResearch = await coordinator.search("database postgresql selection");
|
|
174
|
+
if (!dbResearch.results?.length) {
|
|
175
|
+
console.log("\n⚠️ No database research found. Assigning to researcher.");
|
|
176
|
+
await coordinator.store({
|
|
177
|
+
slug: "task-research-database",
|
|
178
|
+
title: "Research database options (Neon vs Supabase vs PlanetScale)",
|
|
179
|
+
body: "Need comparison of managed PostgreSQL options for our Next.js project. Consider: pricing, connection pooling, branching, edge compatibility.",
|
|
180
|
+
cardType: "workflow",
|
|
181
|
+
stack: "workflows",
|
|
182
|
+
keywords: ["database", "research", "neon", "supabase", "planetscale"],
|
|
183
|
+
links: [
|
|
184
|
+
{ target: "agent-researcher", relation: "assigned_to" },
|
|
185
|
+
{ target: "project-root", relation: "related" },
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── Run the full flow ─────────────────────────────────
|
|
192
|
+
|
|
193
|
+
async function main() {
|
|
194
|
+
console.log("🃏 HyperStack Multi-Agent Demo\n");
|
|
195
|
+
|
|
196
|
+
await setup();
|
|
197
|
+
console.log();
|
|
198
|
+
|
|
199
|
+
await researcherFlow();
|
|
200
|
+
console.log();
|
|
201
|
+
|
|
202
|
+
await builderFlow();
|
|
203
|
+
console.log();
|
|
204
|
+
|
|
205
|
+
await coordinatorFlow();
|
|
206
|
+
|
|
207
|
+
console.log("\n✅ Demo complete. All agents share one typed graph.");
|
|
208
|
+
console.log(" No DECISIONS.md. No GOALS.md. Just queryable cards + relations.");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
main().catch(err => {
|
|
212
|
+
console.error("Error:", err.message);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
});
|
package/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hyperstack-core
|
|
3
|
+
*
|
|
4
|
+
* Typed graph memory for AI agents.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { HyperStackClient } from "hyperstack-core";
|
|
8
|
+
* const hs = new HyperStackClient({ apiKey: "hs_..." });
|
|
9
|
+
* await hs.store({ slug: "use-clerk", title: "Use Clerk for auth", cardType: "decision" });
|
|
10
|
+
* await hs.blockers("deploy-prod"); // → typed blockers
|
|
11
|
+
*
|
|
12
|
+
* OpenClaw:
|
|
13
|
+
* import { createOpenClawAdapter } from "hyperstack-core/adapters/openclaw";
|
|
14
|
+
* const adapter = createOpenClawAdapter({ agentId: "researcher" });
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export { HyperStackClient } from "./src/client.js";
|
|
18
|
+
export { createOpenClawAdapter } from "./adapters/openclaw.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hyperstack-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Typed graph memory for AI agents. Replace GOALS.md with queryable cards + relations. Works with OpenClaw, Claude Desktop, Cursor.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hyperstack-core": "./cli.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./index.js",
|
|
12
|
+
"./client": "./src/client.js",
|
|
13
|
+
"./adapters/openclaw": "./adapters/openclaw.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"index.js",
|
|
17
|
+
"cli.js",
|
|
18
|
+
"src/",
|
|
19
|
+
"adapters/",
|
|
20
|
+
"templates/",
|
|
21
|
+
"examples/",
|
|
22
|
+
"SKILL.md",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"hyperstack",
|
|
28
|
+
"knowledge-graph",
|
|
29
|
+
"ai-agents",
|
|
30
|
+
"memory",
|
|
31
|
+
"openclaw",
|
|
32
|
+
"multi-agent",
|
|
33
|
+
"mcp",
|
|
34
|
+
"typed-relations",
|
|
35
|
+
"graph-memory",
|
|
36
|
+
"agent-coordination"
|
|
37
|
+
],
|
|
38
|
+
"author": "CascadeAI <deeq.yaqub1@gmail.com>",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/deeqyaqub1-cmd/hyperstack-core"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://cascadeai.dev/hyperstack",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {},
|
|
49
|
+
"devDependencies": {}
|
|
50
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hyperstack-core — HyperStack SDK for typed graph memory
|
|
3
|
+
*
|
|
4
|
+
* Lightweight client for the HyperStack API. Works in Node.js 18+.
|
|
5
|
+
* No dependencies. Used by the OpenClaw adapter and CLI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const DEFAULT_BASE = "https://hyperstack-cloud.vercel.app";
|
|
9
|
+
|
|
10
|
+
// ESM-compatible sync file reading
|
|
11
|
+
import { readFileSync, existsSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
|
|
15
|
+
function loadCredApiKey() {
|
|
16
|
+
try {
|
|
17
|
+
const credFile = join(homedir(), ".hyperstack", "credentials.json");
|
|
18
|
+
if (existsSync(credFile)) {
|
|
19
|
+
const creds = JSON.parse(readFileSync(credFile, "utf-8"));
|
|
20
|
+
if (creds.api_key) return creds.api_key;
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class HyperStackClient {
|
|
27
|
+
/**
|
|
28
|
+
* @param {object} opts
|
|
29
|
+
* @param {string} opts.apiKey — HyperStack API key (hs_...)
|
|
30
|
+
* @param {string} [opts.workspace="default"] — workspace slug
|
|
31
|
+
* @param {string} [opts.baseUrl] — API base URL
|
|
32
|
+
* @param {string} [opts.agentId] — agent identifier for multi-agent setups
|
|
33
|
+
*/
|
|
34
|
+
constructor(opts = {}) {
|
|
35
|
+
this.apiKey = opts.apiKey || process.env.HYPERSTACK_API_KEY || loadCredApiKey();
|
|
36
|
+
this.workspace = opts.workspace || process.env.HYPERSTACK_WORKSPACE || "default";
|
|
37
|
+
this.baseUrl = opts.baseUrl || process.env.HYPERSTACK_BASE_URL || DEFAULT_BASE;
|
|
38
|
+
this.agentId = opts.agentId || null;
|
|
39
|
+
|
|
40
|
+
if (!this.apiKey) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"HYPERSTACK_API_KEY required.\n" +
|
|
43
|
+
"Run: npx hyperstack-core login\n" +
|
|
44
|
+
"Or: export HYPERSTACK_API_KEY=hs_your_key\n" +
|
|
45
|
+
"Get a free account: https://cascadeai.dev/hyperstack"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @private */
|
|
51
|
+
async _request(method, path, body = null) {
|
|
52
|
+
const url = `${this.baseUrl}${path}`;
|
|
53
|
+
const opts = {
|
|
54
|
+
method,
|
|
55
|
+
headers: {
|
|
56
|
+
"X-API-Key": this.apiKey,
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"User-Agent": "hyperstack-core/1.0.0",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
if (body) opts.body = JSON.stringify(body);
|
|
62
|
+
|
|
63
|
+
const res = await fetch(url, opts);
|
|
64
|
+
const data = await res.json();
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const err = new Error(data.error || `HTTP ${res.status}`);
|
|
67
|
+
err.status = res.status;
|
|
68
|
+
err.body = data;
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Cards ───────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create or update a card (upsert by slug).
|
|
78
|
+
* @param {object} card
|
|
79
|
+
* @param {string} card.slug — unique identifier
|
|
80
|
+
* @param {string} card.title — short title
|
|
81
|
+
* @param {string} [card.body] — description (2-5 sentences)
|
|
82
|
+
* @param {string} [card.cardType] — person|project|decision|preference|workflow|event|general
|
|
83
|
+
* @param {string} [card.stack] — projects|people|decisions|preferences|workflows|general
|
|
84
|
+
* @param {string[]} [card.keywords] — search terms
|
|
85
|
+
* @param {Array<{target: string, relation: string}>} [card.links] — typed relations
|
|
86
|
+
* @param {object} [card.meta] — freeform metadata
|
|
87
|
+
* @returns {Promise<{slug: string, updated: boolean}>}
|
|
88
|
+
*/
|
|
89
|
+
async store(card) {
|
|
90
|
+
if (!card.slug) throw new Error("card.slug required");
|
|
91
|
+
if (!card.title) throw new Error("card.title required");
|
|
92
|
+
|
|
93
|
+
// Auto-tag with agentId if set
|
|
94
|
+
if (this.agentId) {
|
|
95
|
+
card.meta = card.meta || {};
|
|
96
|
+
card.meta.agentId = this.agentId;
|
|
97
|
+
card.keywords = card.keywords || [];
|
|
98
|
+
if (!card.keywords.includes(`agent:${this.agentId}`)) {
|
|
99
|
+
card.keywords.push(`agent:${this.agentId}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return this._request("POST", `/api/cards?workspace=${this.workspace}`, card);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Search cards by query (hybrid semantic + keyword).
|
|
108
|
+
* @param {string} query
|
|
109
|
+
* @returns {Promise<{results: Array}>}
|
|
110
|
+
*/
|
|
111
|
+
async search(query) {
|
|
112
|
+
return this._request("GET", `/api/search?workspace=${this.workspace}&q=${encodeURIComponent(query)}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* List all cards in workspace.
|
|
117
|
+
* @returns {Promise<{cards: Array, count: number, plan: string}>}
|
|
118
|
+
*/
|
|
119
|
+
async list() {
|
|
120
|
+
return this._request("GET", `/api/cards?workspace=${this.workspace}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Delete a card by slug.
|
|
125
|
+
* @param {string} slug
|
|
126
|
+
* @returns {Promise<{deleted: boolean}>}
|
|
127
|
+
*/
|
|
128
|
+
async delete(slug) {
|
|
129
|
+
return this._request("DELETE", `/api/cards?workspace=${this.workspace}&id=${slug}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─── Graph ───────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Traverse the knowledge graph from a starting card.
|
|
136
|
+
* @param {string} from — starting card slug
|
|
137
|
+
* @param {object} [opts]
|
|
138
|
+
* @param {number} [opts.depth=1] — hops to traverse (1-3)
|
|
139
|
+
* @param {string} [opts.relation] — filter by relation type
|
|
140
|
+
* @param {string} [opts.type] — filter by card type
|
|
141
|
+
* @param {string} [opts.at] — ISO timestamp for time-travel
|
|
142
|
+
* @returns {Promise<{nodes: Array, edges: Array}>}
|
|
143
|
+
*/
|
|
144
|
+
async graph(from, opts = {}) {
|
|
145
|
+
let url = `/api/graph?workspace=${this.workspace}&from=${from}`;
|
|
146
|
+
if (opts.depth) url += `&depth=${opts.depth}`;
|
|
147
|
+
if (opts.relation) url += `&relation=${opts.relation}`;
|
|
148
|
+
if (opts.type) url += `&type=${opts.type}`;
|
|
149
|
+
if (opts.at) url += `&at=${encodeURIComponent(opts.at)}`;
|
|
150
|
+
return this._request("GET", url);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── Multi-Agent Helpers ──────────────────────────────
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Query cards that block a specific card/task.
|
|
157
|
+
* Shorthand for graph traversal with relation="blocks".
|
|
158
|
+
* @param {string} slug — card being blocked
|
|
159
|
+
* @returns {Promise<{blockers: Array}>}
|
|
160
|
+
*/
|
|
161
|
+
async blockers(slug) {
|
|
162
|
+
try {
|
|
163
|
+
const result = await this.graph(slug, { depth: 2, relation: "blocks" });
|
|
164
|
+
const blockers = (result.edges || [])
|
|
165
|
+
.filter(e => e.relation === "blocks" && e.to === slug)
|
|
166
|
+
.map(e => {
|
|
167
|
+
const node = (result.nodes || []).find(n => n.slug === e.from);
|
|
168
|
+
return node || { slug: e.from };
|
|
169
|
+
});
|
|
170
|
+
return { blockers, graph: result };
|
|
171
|
+
} catch (err) {
|
|
172
|
+
// If graph API not available (free tier), fallback to search
|
|
173
|
+
if (err.status === 403) {
|
|
174
|
+
const searchResult = await this.search(`blocks ${slug}`);
|
|
175
|
+
return {
|
|
176
|
+
blockers: (searchResult.results || []).filter(c =>
|
|
177
|
+
c.links?.some(l => l.relation === "blocks" && l.target === slug)
|
|
178
|
+
),
|
|
179
|
+
fallback: true,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Find cards owned by a specific agent.
|
|
188
|
+
* @param {string} [agentId] — defaults to this.agentId
|
|
189
|
+
* @returns {Promise<{cards: Array}>}
|
|
190
|
+
*/
|
|
191
|
+
async agentCards(agentId) {
|
|
192
|
+
const id = agentId || this.agentId;
|
|
193
|
+
if (!id) throw new Error("agentId required");
|
|
194
|
+
return this.search(`agent:${id}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Record a decision with full provenance.
|
|
199
|
+
* Creates a decision card + links to who decided and what it affects.
|
|
200
|
+
* @param {object} decision
|
|
201
|
+
* @param {string} decision.slug
|
|
202
|
+
* @param {string} decision.title
|
|
203
|
+
* @param {string} decision.body — rationale
|
|
204
|
+
* @param {string} [decision.decidedBy] — agent/person slug
|
|
205
|
+
* @param {string[]} [decision.affects] — slugs of affected cards
|
|
206
|
+
* @param {string[]} [decision.blocks] — slugs of things this blocks
|
|
207
|
+
* @param {object} [decision.meta]
|
|
208
|
+
*/
|
|
209
|
+
async decide(decision) {
|
|
210
|
+
const links = [];
|
|
211
|
+
if (decision.decidedBy) {
|
|
212
|
+
links.push({ target: decision.decidedBy, relation: "decided" });
|
|
213
|
+
}
|
|
214
|
+
if (decision.affects) {
|
|
215
|
+
for (const a of decision.affects) {
|
|
216
|
+
links.push({ target: a, relation: "triggers" });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (decision.blocks) {
|
|
220
|
+
for (const b of decision.blocks) {
|
|
221
|
+
links.push({ target: b, relation: "blocks" });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return this.store({
|
|
226
|
+
slug: decision.slug,
|
|
227
|
+
title: decision.title,
|
|
228
|
+
body: decision.body,
|
|
229
|
+
cardType: "decision",
|
|
230
|
+
stack: "decisions",
|
|
231
|
+
links,
|
|
232
|
+
meta: { ...decision.meta, decidedAt: new Date().toISOString() },
|
|
233
|
+
keywords: decision.keywords || [],
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Register an agent as a card in the graph.
|
|
239
|
+
* @param {object} agent
|
|
240
|
+
* @param {string} agent.id — unique agent ID
|
|
241
|
+
* @param {string} agent.name — display name
|
|
242
|
+
* @param {string} agent.role — what this agent does
|
|
243
|
+
* @param {string[]} [agent.owns] — slugs this agent owns
|
|
244
|
+
*/
|
|
245
|
+
async registerAgent(agent) {
|
|
246
|
+
const links = [];
|
|
247
|
+
if (agent.owns) {
|
|
248
|
+
for (const o of agent.owns) {
|
|
249
|
+
links.push({ target: o, relation: "owns" });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return this.store({
|
|
254
|
+
slug: `agent-${agent.id}`,
|
|
255
|
+
title: `Agent: ${agent.name}`,
|
|
256
|
+
body: agent.role,
|
|
257
|
+
cardType: "person",
|
|
258
|
+
stack: "people",
|
|
259
|
+
links,
|
|
260
|
+
keywords: ["agent", agent.id, agent.name],
|
|
261
|
+
meta: { agentId: agent.id, registeredAt: new Date().toISOString() },
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export { HyperStackClient };
|
|
267
|
+
export default HyperStackClient;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclaw-multiagent",
|
|
3
|
+
"description": "Pre-configured typed cards and relations for OpenClaw multi-agent coordination. Replaces GOALS.md + DECISIONS.md with structured, queryable graph memory.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
|
|
6
|
+
"cardTypes": {
|
|
7
|
+
"task": {
|
|
8
|
+
"description": "A unit of work assigned to an agent",
|
|
9
|
+
"fields": ["status", "priority", "assignee", "deadline"],
|
|
10
|
+
"statuses": ["todo", "in-progress", "blocked", "done", "cancelled"]
|
|
11
|
+
},
|
|
12
|
+
"decision": {
|
|
13
|
+
"description": "A choice made by an agent with rationale",
|
|
14
|
+
"fields": ["rationale", "alternatives", "decidedBy", "decidedAt"]
|
|
15
|
+
},
|
|
16
|
+
"blocker": {
|
|
17
|
+
"description": "Something preventing progress on a task",
|
|
18
|
+
"fields": ["severity", "resolvedBy", "resolvedAt"]
|
|
19
|
+
},
|
|
20
|
+
"goal": {
|
|
21
|
+
"description": "A high-level objective (replaces GOALS.md)",
|
|
22
|
+
"fields": ["deadline", "progress", "owner"]
|
|
23
|
+
},
|
|
24
|
+
"agent": {
|
|
25
|
+
"description": "An agent registration card",
|
|
26
|
+
"fields": ["role", "model", "capabilities"]
|
|
27
|
+
},
|
|
28
|
+
"context": {
|
|
29
|
+
"description": "Shared context that multiple agents need",
|
|
30
|
+
"fields": ["scope", "expiresAt"]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
"relationTypes": {
|
|
35
|
+
"owns": "Agent/person owns a task or project",
|
|
36
|
+
"assigned_to": "Task is assigned to an agent",
|
|
37
|
+
"blocks": "This card blocks another card",
|
|
38
|
+
"blocked_by": "This card is blocked by another card",
|
|
39
|
+
"depends_on": "This card depends on another card",
|
|
40
|
+
"decided": "Agent/person made this decision",
|
|
41
|
+
"triggers": "Change to this card triggers effects on target",
|
|
42
|
+
"subtask_of": "This task is a subtask of a larger task",
|
|
43
|
+
"related": "General association"
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
"rules": [
|
|
47
|
+
{
|
|
48
|
+
"name": "auto-blocked",
|
|
49
|
+
"description": "If a task has incoming 'blocks' relations, mark it as blocked",
|
|
50
|
+
"when": "card.cardType === 'task' && card.incomingLinks.some(l => l.relation === 'blocks')",
|
|
51
|
+
"suggest": "Consider updating task status to 'blocked'"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "unowned-task",
|
|
55
|
+
"description": "Tasks without an owner should be flagged",
|
|
56
|
+
"when": "card.cardType === 'task' && !card.links.some(l => l.relation === 'assigned_to')",
|
|
57
|
+
"suggest": "This task has no assigned agent"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "decision-needs-rationale",
|
|
61
|
+
"description": "Decisions should include rationale",
|
|
62
|
+
"when": "card.cardType === 'decision' && (!card.body || card.body.length < 20)",
|
|
63
|
+
"suggest": "Decision lacks rationale — add a body explaining why"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
|
|
67
|
+
"starterCards": [
|
|
68
|
+
{
|
|
69
|
+
"slug": "project-root",
|
|
70
|
+
"title": "Project Root",
|
|
71
|
+
"body": "Root node for the project knowledge graph. All agents, goals, and major decisions link here.",
|
|
72
|
+
"cardType": "project",
|
|
73
|
+
"stack": "projects",
|
|
74
|
+
"keywords": ["project", "root", "main"]
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
|
|
78
|
+
"agentSetup": {
|
|
79
|
+
"description": "Recommended agent configuration for multi-agent OpenClaw + HyperStack",
|
|
80
|
+
"agents": {
|
|
81
|
+
"coordinator": {
|
|
82
|
+
"role": "Routes tasks, monitors blockers, ensures no duplicate work",
|
|
83
|
+
"model": "claude-sonnet-4-20250514",
|
|
84
|
+
"tools": ["hs_search", "hs_blockers", "hs_graph", "hs_decide"]
|
|
85
|
+
},
|
|
86
|
+
"researcher": {
|
|
87
|
+
"role": "Investigates questions, stores findings as context cards",
|
|
88
|
+
"model": "claude-sonnet-4-20250514",
|
|
89
|
+
"tools": ["hs_search", "hs_store", "hs_my_cards"]
|
|
90
|
+
},
|
|
91
|
+
"builder": {
|
|
92
|
+
"role": "Implements code, records technical decisions",
|
|
93
|
+
"model": "claude-sonnet-4-20250514",
|
|
94
|
+
"tools": ["hs_search", "hs_store", "hs_decide", "hs_blockers"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|