openfused 0.3.5 → 0.3.7
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 +29 -7
- package/dist/cli.js +84 -14
- package/dist/mcp.js +1 -1
- package/dist/registry.js +3 -3
- package/dist/store.d.ts +6 -0
- package/dist/store.js +76 -2
- package/dist/sync.js +3 -0
- package/package.json +3 -3
- package/templates/CHARTER.md +18 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenFused
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The file protocol for AI agent context. Encrypted, signed, peer-to-peer.
|
|
4
4
|
|
|
5
5
|
## What is this?
|
|
6
6
|
|
|
@@ -10,25 +10,32 @@ No vendor lock-in. No proprietary protocol. Just a directory convention that any
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
+
Review the source at [github.com/wearethecompute/openfused](https://github.com/wearethecompute/openfused) before installing.
|
|
14
|
+
|
|
13
15
|
```bash
|
|
14
|
-
# TypeScript (npm)
|
|
16
|
+
# TypeScript (npm) — package: openfused
|
|
15
17
|
npm install -g openfused
|
|
16
18
|
|
|
17
|
-
# Rust (crates.io)
|
|
19
|
+
# Rust (crates.io) — package: openfuse
|
|
18
20
|
cargo install openfuse
|
|
19
21
|
|
|
20
22
|
# Docker (daemon)
|
|
21
23
|
docker compose up
|
|
22
24
|
```
|
|
23
25
|
|
|
26
|
+
**Security:** Only public keys (signing + age recipient) are ever transmitted to peers or the registry. Private keys never leave `.keys/`. All key files are created with `chmod 600`.
|
|
27
|
+
|
|
24
28
|
## Quick Start
|
|
25
29
|
|
|
26
30
|
```bash
|
|
31
|
+
# Agent context store
|
|
27
32
|
openfuse init --name "my-agent"
|
|
28
|
-
```
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
# Shared workspace (multi-agent collaboration)
|
|
35
|
+
openfuse init --name "project-alpha" --workspace
|
|
36
|
+
```
|
|
31
37
|
|
|
38
|
+
### Agent store:
|
|
32
39
|
```
|
|
33
40
|
CONTEXT.md — working memory (what's happening now)
|
|
34
41
|
PROFILE.md — public address card (name, endpoint, keys)
|
|
@@ -36,19 +43,34 @@ inbox/ — messages from other agents (encrypted)
|
|
|
36
43
|
outbox/ — sent message copies (moved to .sent/ after delivery)
|
|
37
44
|
shared/ — files shared with the mesh (plaintext)
|
|
38
45
|
knowledge/ — persistent knowledge base
|
|
39
|
-
history/ —
|
|
46
|
+
history/ — archived [DONE] context (via openfuse compact)
|
|
40
47
|
.keys/ — ed25519 signing + age encryption keypairs
|
|
41
48
|
.mesh.json — mesh config, peers, keyring
|
|
42
49
|
.peers/ — synced peer context (auto-populated)
|
|
43
50
|
```
|
|
44
51
|
|
|
52
|
+
### Shared workspace:
|
|
53
|
+
```
|
|
54
|
+
CHARTER.md — workspace purpose, rules, member list
|
|
55
|
+
CONTEXT.md — shared working memory (all agents read/write)
|
|
56
|
+
tasks/ — task coordination
|
|
57
|
+
messages/ — agent-to-agent DMs (messages/{recipient}/)
|
|
58
|
+
_broadcast/ — all-hands announcements
|
|
59
|
+
shared/ — shared files
|
|
60
|
+
history/ — archived [DONE] context
|
|
61
|
+
```
|
|
62
|
+
|
|
45
63
|
## Usage
|
|
46
64
|
|
|
47
65
|
```bash
|
|
48
|
-
# Read/update context
|
|
66
|
+
# Read/update context (auto-timestamps appended entries)
|
|
49
67
|
openfuse context
|
|
50
68
|
openfuse context --append "## Update\nFinished the research phase."
|
|
51
69
|
|
|
70
|
+
# Mark work as done, then compact to history/
|
|
71
|
+
# (edit CONTEXT.md, add [DONE] to the header, then:)
|
|
72
|
+
openfuse compact
|
|
73
|
+
|
|
52
74
|
# Send a message (auto-encrypted if peer's age key is on file)
|
|
53
75
|
openfuse inbox send agent-bob "Check out shared/findings.md"
|
|
54
76
|
|
package/dist/cli.js
CHANGED
|
@@ -8,18 +8,19 @@ import * as registry from "./registry.js";
|
|
|
8
8
|
import { fingerprint } from "./crypto.js";
|
|
9
9
|
import { resolve } from "node:path";
|
|
10
10
|
import { readFile } from "node:fs/promises";
|
|
11
|
-
const VERSION = "0.3.
|
|
11
|
+
const VERSION = "0.3.7";
|
|
12
12
|
const program = new Command();
|
|
13
13
|
program
|
|
14
14
|
.name("openfuse")
|
|
15
|
-
.description("
|
|
15
|
+
.description("The file protocol for AI agent context. Encrypted, signed, peer-to-peer.")
|
|
16
16
|
.version(VERSION);
|
|
17
17
|
// --- init ---
|
|
18
18
|
program
|
|
19
19
|
.command("init")
|
|
20
|
-
.description("Initialize a new context store")
|
|
20
|
+
.description("Initialize a new context store or shared workspace")
|
|
21
21
|
.option("-n, --name <name>", "Agent name", "agent")
|
|
22
22
|
.option("-d, --dir <path>", "Directory to init", ".")
|
|
23
|
+
.option("--workspace", "Initialize as a shared workspace (CHARTER.md + tasks/ + messages/ + _broadcast/)")
|
|
23
24
|
.action(async (opts) => {
|
|
24
25
|
const store = new ContextStore(resolve(opts.dir));
|
|
25
26
|
if (await store.exists()) {
|
|
@@ -27,14 +28,27 @@ program
|
|
|
27
28
|
process.exit(1);
|
|
28
29
|
}
|
|
29
30
|
const id = nanoid(12);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
if (opts.workspace) {
|
|
32
|
+
await store.initWorkspace(opts.name, id);
|
|
33
|
+
console.log(`Initialized shared workspace: ${store.root}`);
|
|
34
|
+
console.log(` Workspace: ${opts.name} (${id})`);
|
|
35
|
+
console.log(`\nStructure:`);
|
|
36
|
+
console.log(` CHARTER.md — workspace rules and purpose`);
|
|
37
|
+
console.log(` CONTEXT.md — shared working memory`);
|
|
38
|
+
console.log(` tasks/ — task coordination`);
|
|
39
|
+
console.log(` messages/ — agent-to-agent DMs`);
|
|
40
|
+
console.log(` _broadcast/ — all-hands messages`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
await store.init(opts.name, id);
|
|
44
|
+
const config = await store.readConfig();
|
|
45
|
+
console.log(`Initialized context store: ${store.root}`);
|
|
46
|
+
console.log(` Agent ID: ${id}`);
|
|
47
|
+
console.log(` Name: ${opts.name}`);
|
|
48
|
+
console.log(` Signing key: ${config.publicKey}`);
|
|
49
|
+
console.log(` Encryption key: ${config.encryptionKey}`);
|
|
50
|
+
console.log(` Fingerprint: ${fingerprint(config.publicKey)}`);
|
|
51
|
+
}
|
|
38
52
|
});
|
|
39
53
|
// --- status ---
|
|
40
54
|
program
|
|
@@ -73,7 +87,8 @@ program
|
|
|
73
87
|
else if (opts.append) {
|
|
74
88
|
const existing = await store.readContext();
|
|
75
89
|
const text = opts.append.replace(/\\n/g, "\n");
|
|
76
|
-
|
|
90
|
+
const timestamp = `<!-- openfuse:added: ${new Date().toISOString()} -->`;
|
|
91
|
+
await store.writeContext(existing + "\n" + timestamp + "\n" + text);
|
|
77
92
|
console.log("Context appended.");
|
|
78
93
|
}
|
|
79
94
|
else {
|
|
@@ -117,6 +132,40 @@ inbox
|
|
|
117
132
|
console.log(opts.raw ? msg.content : msg.wrappedContent);
|
|
118
133
|
}
|
|
119
134
|
});
|
|
135
|
+
inbox
|
|
136
|
+
.command("archive [file]")
|
|
137
|
+
.description("Archive inbox message(s) to inbox/.read/ — specific file or --all")
|
|
138
|
+
.option("-d, --dir <path>", "Context store directory", ".")
|
|
139
|
+
.option("--all", "Archive all inbox messages")
|
|
140
|
+
.action(async (file, opts) => {
|
|
141
|
+
const store = new ContextStore(resolve(opts.dir));
|
|
142
|
+
const { readdir: rd, mkdir, rename } = await import("node:fs/promises");
|
|
143
|
+
const { join, basename } = await import("node:path");
|
|
144
|
+
const inboxDir = join(store.root, "inbox");
|
|
145
|
+
const readDir = join(inboxDir, ".read");
|
|
146
|
+
await mkdir(readDir, { recursive: true });
|
|
147
|
+
if (opts.all) {
|
|
148
|
+
const files = (await rd(inboxDir)).filter(f => f.endsWith(".json") || f.endsWith(".md"));
|
|
149
|
+
for (const f of files)
|
|
150
|
+
await rename(join(inboxDir, f), join(readDir, f));
|
|
151
|
+
console.log(`Archived ${files.length} messages.`);
|
|
152
|
+
}
|
|
153
|
+
else if (file) {
|
|
154
|
+
const safe = basename(file);
|
|
155
|
+
try {
|
|
156
|
+
await rename(join(inboxDir, safe), join(readDir, safe));
|
|
157
|
+
console.log(`Archived: ${safe}`);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
console.error(`Not found in inbox: ${safe}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.error("Specify a filename or use --all");
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
120
169
|
inbox
|
|
121
170
|
.command("send <peerId> <message>")
|
|
122
171
|
.description("Send a message to a peer's inbox")
|
|
@@ -224,6 +273,21 @@ program
|
|
|
224
273
|
}
|
|
225
274
|
await new Promise(() => { });
|
|
226
275
|
});
|
|
276
|
+
// --- compact ---
|
|
277
|
+
program
|
|
278
|
+
.command("compact")
|
|
279
|
+
.description("Move [DONE] sections from CONTEXT.md to history/")
|
|
280
|
+
.option("-d, --dir <path>", "Context store directory", ".")
|
|
281
|
+
.action(async (opts) => {
|
|
282
|
+
const store = new ContextStore(resolve(opts.dir));
|
|
283
|
+
const { moved, kept } = await store.compactContext();
|
|
284
|
+
if (moved === 0) {
|
|
285
|
+
console.log("Nothing to compact. Mark sections with [DONE] to archive them.");
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
console.log(`Compacted: ${moved} done, ${kept} kept.`);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
227
291
|
// --- share ---
|
|
228
292
|
program
|
|
229
293
|
.command("share <file>")
|
|
@@ -335,19 +399,25 @@ key
|
|
|
335
399
|
console.log(`Key already in keyring (fingerprint: ${fp})`);
|
|
336
400
|
return;
|
|
337
401
|
}
|
|
402
|
+
const autoTrust = config.autoTrust ?? false;
|
|
338
403
|
config.keyring.push({
|
|
339
404
|
name,
|
|
340
405
|
address: opts.address ?? "",
|
|
341
406
|
signingKey,
|
|
342
407
|
encryptionKey: opts.encryptionKey,
|
|
343
408
|
fingerprint: fp,
|
|
344
|
-
trusted:
|
|
409
|
+
trusted: autoTrust,
|
|
345
410
|
added: new Date().toISOString(),
|
|
346
411
|
});
|
|
347
412
|
await store.writeConfig(config);
|
|
348
413
|
console.log(`Imported key for: ${name}`);
|
|
349
414
|
console.log(` Fingerprint: ${fp}`);
|
|
350
|
-
|
|
415
|
+
if (autoTrust) {
|
|
416
|
+
console.log(` Auto-trusted (workspace mode)`);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
console.log(`\nKey is NOT trusted yet. Run: openfuse key trust ${name}`);
|
|
420
|
+
}
|
|
351
421
|
});
|
|
352
422
|
key
|
|
353
423
|
.command("trust <name>")
|
package/dist/mcp.js
CHANGED
|
@@ -23,7 +23,7 @@ const storeDir = process.env.OPENFUSE_DIR || process.argv[3] || ".";
|
|
|
23
23
|
const store = new ContextStore(resolve(storeDir));
|
|
24
24
|
const server = new McpServer({
|
|
25
25
|
name: "openfuse",
|
|
26
|
-
version: "0.3.
|
|
26
|
+
version: "0.3.7",
|
|
27
27
|
});
|
|
28
28
|
// --- Context ---
|
|
29
29
|
server.tool("context_read", "Read the agent's CONTEXT.md (working memory)", async () => {
|
package/dist/registry.js
CHANGED
|
@@ -44,13 +44,13 @@ export async function register(store, endpoint, registry) {
|
|
|
44
44
|
// Discovery: try DNS TXT first (decentralized, no registry needed), fall back to Worker API.
|
|
45
45
|
// DNS format: v=of1 e={endpoint} pk={pubkey} ek={agekey} fp={fingerprint}
|
|
46
46
|
// Self-hosted: _openfuse.{name}.{their-domain} — user manages their own TXT records.
|
|
47
|
-
// Our zone: _openfuse.{name}.openfused.
|
|
47
|
+
// Our zone: _openfuse.{name}.openfused.net — managed by the registry Worker on registration.
|
|
48
48
|
export async function discover(name, registry) {
|
|
49
49
|
// If name contains a dot, it's a domain — try DNS TXT directly
|
|
50
|
-
// Otherwise try DNS at openfused.
|
|
50
|
+
// Otherwise try DNS at openfused.net, then fall back to registry API
|
|
51
51
|
const dnsNames = name.includes(".")
|
|
52
52
|
? [`_openfuse.${name}`]
|
|
53
|
-
: [`_openfuse.${name}.openfused.
|
|
53
|
+
: [`_openfuse.${name}.openfused.net`];
|
|
54
54
|
for (const dnsName of dnsNames) {
|
|
55
55
|
const manifest = await discoverViaDns(dnsName, name);
|
|
56
56
|
if (manifest)
|
package/dist/store.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface MeshConfig {
|
|
|
8
8
|
peers: PeerConfig[];
|
|
9
9
|
keyring: KeyringEntry[];
|
|
10
10
|
trustedKeys?: string[];
|
|
11
|
+
autoTrust?: boolean;
|
|
11
12
|
}
|
|
12
13
|
export interface PeerConfig {
|
|
13
14
|
id: string;
|
|
@@ -22,10 +23,15 @@ export declare class ContextStore {
|
|
|
22
23
|
get configPath(): string;
|
|
23
24
|
exists(): Promise<boolean>;
|
|
24
25
|
init(name: string, id: string): Promise<void>;
|
|
26
|
+
initWorkspace(name: string, id: string): Promise<void>;
|
|
25
27
|
readConfig(): Promise<MeshConfig>;
|
|
26
28
|
writeConfig(config: MeshConfig): Promise<void>;
|
|
27
29
|
readContext(): Promise<string>;
|
|
28
30
|
writeContext(content: string): Promise<void>;
|
|
31
|
+
compactContext(): Promise<{
|
|
32
|
+
moved: number;
|
|
33
|
+
kept: number;
|
|
34
|
+
}>;
|
|
29
35
|
readProfile(): Promise<string>;
|
|
30
36
|
writeProfile(content: string): Promise<void>;
|
|
31
37
|
sendInbox(peerId: string, message: string): Promise<string>;
|
package/dist/store.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// .keys/ — Ed25519 + age keypairs (gitignored)
|
|
12
12
|
// .mesh.json — config, peer list, keyring
|
|
13
13
|
// No database, no daemon required. `ls` is your status command.
|
|
14
|
-
import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
|
|
14
|
+
import { readFile, writeFile, mkdir, readdir, appendFile } from "node:fs/promises";
|
|
15
15
|
import { join, resolve } from "node:path";
|
|
16
16
|
import { existsSync } from "node:fs";
|
|
17
17
|
import { generateKeys, signMessage, signAndEncrypt, verifyMessage, decryptMessage, deserializeSignedMessage, serializeSignedMessage, wrapExternalMessage, fingerprint, } from "./crypto.js";
|
|
@@ -54,6 +54,35 @@ export class ContextStore {
|
|
|
54
54
|
};
|
|
55
55
|
await this.writeConfig(config);
|
|
56
56
|
}
|
|
57
|
+
// Shared workspace: multiple agents mount the same directory.
|
|
58
|
+
// CHARTER.md = system prompt (purpose, rules). CONTEXT.md = shared working memory.
|
|
59
|
+
// tasks/ for coordination, messages/{agent}/ for DMs, _broadcast/ for all-hands.
|
|
60
|
+
async initWorkspace(name, id) {
|
|
61
|
+
await mkdir(this.root, { recursive: true });
|
|
62
|
+
for (const dir of ["tasks", "messages", "_broadcast", "shared", "history"]) {
|
|
63
|
+
await mkdir(join(this.root, dir), { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
const templatesDir = new URL("../templates/", import.meta.url).pathname;
|
|
66
|
+
for (const file of ["CHARTER.md", "CONTEXT.md"]) {
|
|
67
|
+
const templatePath = join(templatesDir, file);
|
|
68
|
+
const destPath = join(this.root, file);
|
|
69
|
+
if (!existsSync(destPath)) {
|
|
70
|
+
const content = await readFile(templatePath, "utf-8");
|
|
71
|
+
await writeFile(destPath, content);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Workspaces auto-trust: all imported keys are trusted by default.
|
|
75
|
+
// Safe because workspaces are private — you control who joins.
|
|
76
|
+
const config = {
|
|
77
|
+
id,
|
|
78
|
+
name,
|
|
79
|
+
created: new Date().toISOString(),
|
|
80
|
+
peers: [],
|
|
81
|
+
keyring: [],
|
|
82
|
+
autoTrust: true,
|
|
83
|
+
};
|
|
84
|
+
await this.writeConfig(config);
|
|
85
|
+
}
|
|
57
86
|
async readConfig() {
|
|
58
87
|
const raw = await readFile(this.configPath, "utf-8");
|
|
59
88
|
const config = JSON.parse(raw);
|
|
@@ -91,6 +120,45 @@ export class ContextStore {
|
|
|
91
120
|
async writeContext(content) {
|
|
92
121
|
await writeFile(join(this.root, "CONTEXT.md"), content);
|
|
93
122
|
}
|
|
123
|
+
// --- Context compaction ---
|
|
124
|
+
// Agents mark sections as [DONE] when work is complete. `openfuse compact`
|
|
125
|
+
// moves done sections to history/YYYY-MM-DD.md, keeping CONTEXT.md lean.
|
|
126
|
+
// Sections are delimited by markdown headers (## or ###).
|
|
127
|
+
async compactContext() {
|
|
128
|
+
const content = await this.readContext();
|
|
129
|
+
const lines = content.split("\n");
|
|
130
|
+
const kept = [];
|
|
131
|
+
const done = [];
|
|
132
|
+
let current = [];
|
|
133
|
+
let currentDone = false;
|
|
134
|
+
const flush = () => {
|
|
135
|
+
if (current.length > 0) {
|
|
136
|
+
(currentDone ? done : kept).push(current.join("\n"));
|
|
137
|
+
current = [];
|
|
138
|
+
currentDone = false;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
if (/^#{1,3}\s/.test(line)) {
|
|
143
|
+
flush();
|
|
144
|
+
currentDone = /\[DONE\]/i.test(line);
|
|
145
|
+
}
|
|
146
|
+
current.push(line);
|
|
147
|
+
}
|
|
148
|
+
flush();
|
|
149
|
+
if (done.length === 0)
|
|
150
|
+
return { moved: 0, kept: kept.length };
|
|
151
|
+
// Write kept sections back to CONTEXT.md
|
|
152
|
+
await this.writeContext(kept.join("\n\n") || "# Context\n\n*Working memory — what's happening right now.*\n");
|
|
153
|
+
// Append done sections to history/YYYY-MM-DD.md
|
|
154
|
+
const historyDir = join(this.root, "history");
|
|
155
|
+
await mkdir(historyDir, { recursive: true });
|
|
156
|
+
const dateStr = new Date().toISOString().split("T")[0];
|
|
157
|
+
const historyFile = join(historyDir, `${dateStr}.md`);
|
|
158
|
+
const header = existsSync(historyFile) ? "\n---\n\n" : `# Context History — ${dateStr}\n\n`;
|
|
159
|
+
await appendFile(historyFile, header + done.join("\n\n") + "\n");
|
|
160
|
+
return { moved: done.length, kept: kept.length };
|
|
161
|
+
}
|
|
94
162
|
async readProfile() {
|
|
95
163
|
return readFile(join(this.root, "PROFILE.md"), "utf-8");
|
|
96
164
|
}
|
|
@@ -128,7 +196,13 @@ export class ContextStore {
|
|
|
128
196
|
const signed = deserializeSignedMessage(raw);
|
|
129
197
|
if (signed) {
|
|
130
198
|
const sigValid = verifyMessage(signed);
|
|
131
|
-
|
|
199
|
+
// autoTrust (workspace mode): any key in keyring is trusted, but key must still
|
|
200
|
+
// be present — prevents random internet keys from appearing verified in a workspace
|
|
201
|
+
// that's accidentally exposed to the network.
|
|
202
|
+
const inKeyring = config.keyring.some((k) => k.signingKey.trim() === signed.publicKey.trim());
|
|
203
|
+
const trusted = config.autoTrust
|
|
204
|
+
? inKeyring
|
|
205
|
+
: config.keyring.some((k) => k.trusted && k.signingKey.trim() === signed.publicKey.trim());
|
|
132
206
|
const verified = sigValid && trusted;
|
|
133
207
|
let content;
|
|
134
208
|
if (signed.encrypted) {
|
package/dist/sync.js
CHANGED
|
@@ -35,6 +35,9 @@ function parseUrl(url) {
|
|
|
35
35
|
if (/[;|`$]/.test(host)) {
|
|
36
36
|
throw new Error("Invalid SSH URL: host contains shell metacharacters");
|
|
37
37
|
}
|
|
38
|
+
if (/[;|`$&(){}]/.test(path)) {
|
|
39
|
+
throw new Error("Invalid SSH URL: path contains shell metacharacters");
|
|
40
|
+
}
|
|
38
41
|
return { type: "ssh", host, path };
|
|
39
42
|
}
|
|
40
43
|
throw new Error(`Unknown URL scheme: ${url}. Use http:// or ssh://`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openfused",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.7",
|
|
4
|
+
"description": "The file protocol for AI agent context. Encrypted, signed, peer-to-peer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"ai",
|
|
48
48
|
"agent",
|
|
49
49
|
"context",
|
|
50
|
-
"
|
|
50
|
+
"messaging",
|
|
51
51
|
"fuse",
|
|
52
52
|
"decentralized",
|
|
53
53
|
"openclaw",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Charter
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
_What is this workspace for? What are we building?_
|
|
5
|
+
|
|
6
|
+
## Members
|
|
7
|
+
_Who participates in this workspace?_
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
- Agents mark completed work with `[DONE]` in CONTEXT.md
|
|
11
|
+
- Messages to all members go in `_broadcast/`
|
|
12
|
+
- Direct messages go in `messages/{recipient}/`
|
|
13
|
+
- Tasks are tracked in `tasks/`
|
|
14
|
+
|
|
15
|
+
## Conventions
|
|
16
|
+
- Sign all messages
|
|
17
|
+
- Encrypt DMs, broadcast in plaintext
|
|
18
|
+
- Check CONTEXT.md before starting new work
|