volute 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/channel-Q642YUZE.js +90 -0
- package/dist/chunk-5YW4B7CG.js +181 -0
- package/dist/chunk-A5ZJEMHT.js +40 -0
- package/dist/chunk-D424ZQGI.js +31 -0
- package/dist/chunk-GSPKUPKU.js +120 -0
- package/dist/chunk-H5XQARAP.js +48 -0
- package/dist/chunk-KSMIWOCN.js +84 -0
- package/dist/chunk-N4QN44LC.js +74 -0
- package/dist/chunk-XZN4WPNC.js +34 -0
- package/dist/cli.js +95 -0
- package/dist/connect-LW6G23AV.js +48 -0
- package/dist/connectors/discord.js +213 -0
- package/dist/create-3K6O2SDC.js +62 -0
- package/dist/daemon-client-ZTHW7ROS.js +10 -0
- package/dist/daemon.js +1731 -0
- package/dist/delete-JNGY7ZFH.js +54 -0
- package/dist/disconnect-ACVTKTRE.js +30 -0
- package/dist/down-FYCUYC5H.js +71 -0
- package/dist/env-7SLRN3MG.js +159 -0
- package/dist/fork-BB3DZ426.js +112 -0
- package/dist/import-W2AMTEV5.js +410 -0
- package/dist/logs-BUHRIQ2L.js +35 -0
- package/dist/merge-446QTE7Q.js +219 -0
- package/dist/schedule-KKSOVUDF.js +113 -0
- package/dist/send-WQSVSRDD.js +50 -0
- package/dist/start-LKMWS6ZE.js +29 -0
- package/dist/status-CIEKUI3V.js +50 -0
- package/dist/stop-YTOAGYE4.js +29 -0
- package/dist/up-AJJ4GCXY.js +111 -0
- package/dist/upgrade-JACA6YMO.js +211 -0
- package/dist/variants-HPY4DEWU.js +60 -0
- package/dist/web-assets/assets/index-DNNPoxMn.js +158 -0
- package/dist/web-assets/index.html +15 -0
- package/package.json +76 -0
- package/templates/_base/.init/MEMORY.md +2 -0
- package/templates/_base/.init/SOUL.md +2 -0
- package/templates/_base/.init/memory/.gitkeep +0 -0
- package/templates/_base/_skills/memory/SKILL.md +30 -0
- package/templates/_base/_skills/volute-agent/SKILL.md +53 -0
- package/templates/_base/biome.json.tmpl +21 -0
- package/templates/_base/home/VOLUTE.md +19 -0
- package/templates/_base/src/lib/auto-commit.ts +46 -0
- package/templates/_base/src/lib/logger.ts +47 -0
- package/templates/_base/src/lib/types.ts +24 -0
- package/templates/_base/src/lib/volute-server.ts +98 -0
- package/templates/_base/tsconfig.json +13 -0
- package/templates/_base/volute.json.tmpl +3 -0
- package/templates/agent-sdk/.init/CLAUDE.md +36 -0
- package/templates/agent-sdk/package.json.tmpl +20 -0
- package/templates/agent-sdk/src/lib/agent.ts +199 -0
- package/templates/agent-sdk/src/lib/hooks/auto-commit.ts +14 -0
- package/templates/agent-sdk/src/lib/hooks/identity-reload.ts +26 -0
- package/templates/agent-sdk/src/lib/hooks/pre-compact.ts +20 -0
- package/templates/agent-sdk/src/lib/message-channel.ts +37 -0
- package/templates/agent-sdk/src/server.ts +158 -0
- package/templates/agent-sdk/volute-template.json +9 -0
- package/templates/pi/.init/AGENTS.md +26 -0
- package/templates/pi/package.json.tmpl +20 -0
- package/templates/pi/src/lib/agent.ts +205 -0
- package/templates/pi/src/server.ts +121 -0
- package/templates/pi/volute-template.json +9 -0
- package/templates/pi/volute.json.tmpl +3 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>volute</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,300;0,400;0,500;0,600;1,400&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-DNNPoxMn.js"></script>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "volute",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for creating and managing self-modifying AI agents powered by the Claude Agent SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/mimsy/volute.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cli",
|
|
13
|
+
"ai",
|
|
14
|
+
"agents",
|
|
15
|
+
"claude",
|
|
16
|
+
"anthropic"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"volute": "dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist/",
|
|
26
|
+
"templates/"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "tsx src/cli.ts",
|
|
30
|
+
"build": "tsup && npm run build:web",
|
|
31
|
+
"build:web": "vite build --config src/web/frontend/vite.config.ts",
|
|
32
|
+
"dev:web": "vite --config src/web/frontend/vite.config.ts",
|
|
33
|
+
"test": "node --import tsx --import ./test/setup.ts --test --test-force-exit --test-concurrency=1 test/*.test.ts",
|
|
34
|
+
"lint": "biome check src/ test/",
|
|
35
|
+
"lint:fix": "biome check --write src/ test/",
|
|
36
|
+
"format": "biome format --write src/ test/",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"typecheck:templates": "tsc --noEmit -p templates/agent-sdk/tsconfig.json && tsc --noEmit -p templates/pi/tsconfig.json",
|
|
39
|
+
"lint:templates": "biome check templates/",
|
|
40
|
+
"typecheck:web": "tsc --noEmit -p src/web/frontend/tsconfig.json",
|
|
41
|
+
"prepare": "lefthook install",
|
|
42
|
+
"prepublishOnly": "npm run build",
|
|
43
|
+
"db:generate": "drizzle-kit generate",
|
|
44
|
+
"db:migrate": "drizzle-kit migrate"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@hono/node-server": "^1.19.9",
|
|
48
|
+
"@hono/zod-validator": "^0.7.6",
|
|
49
|
+
"@libsql/client": "^0.17.0",
|
|
50
|
+
"bcryptjs": "^3.0.3",
|
|
51
|
+
"cron-parser": "^5.5.0",
|
|
52
|
+
"discord.js": "^14.25.1",
|
|
53
|
+
"drizzle-orm": "^0.45.1",
|
|
54
|
+
"hono": "^4.11.7",
|
|
55
|
+
"zod": "^4.3.6"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.34",
|
|
59
|
+
"@biomejs/biome": "2.3.14",
|
|
60
|
+
"@mariozechner/pi-ai": "^0.52.7",
|
|
61
|
+
"@mariozechner/pi-coding-agent": "^0.52.7",
|
|
62
|
+
"@types/bcryptjs": "^2.4.6",
|
|
63
|
+
"@types/node": "^25.2.0",
|
|
64
|
+
"@types/react": "^19.2.11",
|
|
65
|
+
"@types/react-dom": "^19.2.3",
|
|
66
|
+
"@vitejs/plugin-react": "^5.1.3",
|
|
67
|
+
"drizzle-kit": "^0.31.8",
|
|
68
|
+
"lefthook": "^2.1.0",
|
|
69
|
+
"react": "^19.2.4",
|
|
70
|
+
"react-dom": "^19.2.4",
|
|
71
|
+
"tsup": "^8.0.0",
|
|
72
|
+
"tsx": "^4.0.0",
|
|
73
|
+
"typescript": "^5.7.0",
|
|
74
|
+
"vite": "^7.3.1"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Memory
|
|
3
|
+
description: This skill should be used when managing agent memory, writing daily logs, consolidating memory, or understanding the memory system. Covers "MEMORY.md", "daily logs", "memory consolidation", "write memory", "update daily log".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Memory Management
|
|
7
|
+
|
|
8
|
+
Detailed guidance for your two-tier memory system.
|
|
9
|
+
|
|
10
|
+
## Long-term Memory (`MEMORY.md`)
|
|
11
|
+
|
|
12
|
+
- Keep it organized by topic and concise, since it's always in your context window.
|
|
13
|
+
- Review with `git log -- MEMORY.md` to see how your memory has evolved.
|
|
14
|
+
- Update when you discover a lasting preference, make a key decision, or during consolidation.
|
|
15
|
+
|
|
16
|
+
## Daily Logs (`memory/YYYY-MM-DD.md`)
|
|
17
|
+
|
|
18
|
+
- Use today's date for the filename (e.g. `memory/2025-01-15.md`).
|
|
19
|
+
- Update after significant work, learning something new, or when compaction is imminent.
|
|
20
|
+
- Summarize conversations, decisions, and progress.
|
|
21
|
+
|
|
22
|
+
## Consolidation
|
|
23
|
+
|
|
24
|
+
Periodically maintain your memory:
|
|
25
|
+
|
|
26
|
+
1. Review old daily logs for entries worth keeping long-term.
|
|
27
|
+
2. Promote important patterns, decisions, and context to `MEMORY.md`.
|
|
28
|
+
3. Trim or remove daily logs that have been absorbed.
|
|
29
|
+
|
|
30
|
+
This keeps your long-term memory fresh and your daily logs manageable.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Volute CLI
|
|
3
|
+
description: This skill should be used when working with the volute CLI, understanding variants, forking, merging, or managing the agent server. Covers "create variant", "merge variant", "send to variant", "fork", "volute CLI", "variant workflow", "agent server", "supervisor", "channel", "discord", "send message", "read messages".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Self-Management
|
|
7
|
+
|
|
8
|
+
You manage yourself through the `volute` CLI. Use `$VOLUTE_AGENT` for your own name in commands.
|
|
9
|
+
|
|
10
|
+
## Commands
|
|
11
|
+
|
|
12
|
+
| Command | Purpose |
|
|
13
|
+
|---------|---------|
|
|
14
|
+
| `volute status` | Check your status |
|
|
15
|
+
| `volute logs [--follow] [-n N]` | Read your own logs |
|
|
16
|
+
| `volute fork <name> [--soul "..."] [--port N]` | Create a variant for testing changes |
|
|
17
|
+
| `volute variants` | List your variants |
|
|
18
|
+
| `volute merge <name> [--summary "..." --memory "..."]` | Merge a variant back |
|
|
19
|
+
| `volute upgrade [--template <name>] [--continue]` | Upgrade your server code |
|
|
20
|
+
| `volute channel read discord:<id> [--limit N]` | Read channel history |
|
|
21
|
+
| `volute channel send discord:<id> "<msg>"` | Send a message proactively |
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
Your `volute.json` (at `../volute.json` from your cwd) controls your model and other settings.
|
|
26
|
+
|
|
27
|
+
## Variant Workflow
|
|
28
|
+
|
|
29
|
+
For changes to your server code (`src/`), use variants to test safely:
|
|
30
|
+
|
|
31
|
+
1. `volute fork experiment` — creates an isolated copy with its own server
|
|
32
|
+
2. Make changes in the variant's worktree (at `../.worktrees/experiment/`)
|
|
33
|
+
3. Test: `volute send $VOLUTE_AGENT@experiment "hello"`
|
|
34
|
+
4. `volute merge experiment --summary "..." --memory "..."` — merges back after verification
|
|
35
|
+
|
|
36
|
+
After a merge, you receive orientation context about what changed. Update your memory accordingly.
|
|
37
|
+
|
|
38
|
+
## Upgrade Workflow
|
|
39
|
+
|
|
40
|
+
`volute upgrade` merges the latest template code into a testable variant:
|
|
41
|
+
|
|
42
|
+
1. `volute upgrade` — creates an `upgrade` variant
|
|
43
|
+
2. Resolve any merge conflicts if prompted, then `volute upgrade --continue`
|
|
44
|
+
3. Test: `volute send $VOLUTE_AGENT@upgrade "hello"`
|
|
45
|
+
4. `volute merge upgrade` — merge back
|
|
46
|
+
|
|
47
|
+
## Git Introspection
|
|
48
|
+
|
|
49
|
+
Your cwd is `home/`, so use `git -C ..` for project-level operations:
|
|
50
|
+
|
|
51
|
+
- `git -C .. log --oneline -10` — recent project history
|
|
52
|
+
- `git -C .. diff` — current changes
|
|
53
|
+
- `git log -- MEMORY.md` — history of your memory changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
|
3
|
+
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
4
|
+
"formatter": {
|
|
5
|
+
"enabled": true,
|
|
6
|
+
"indentStyle": "space",
|
|
7
|
+
"indentWidth": 2,
|
|
8
|
+
"lineWidth": 100
|
|
9
|
+
},
|
|
10
|
+
"linter": {
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"rules": {
|
|
13
|
+
"recommended": true,
|
|
14
|
+
"suspicious": { "noExplicitAny": "off" },
|
|
15
|
+
"style": { "noNonNullAssertion": "off" }
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": {
|
|
19
|
+
"includes": ["**", "!**/dist/", "!**/node_modules/"]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Volute Agent
|
|
2
|
+
|
|
3
|
+
You are a volute agent — a persistent server that receives messages from multiple channels.
|
|
4
|
+
|
|
5
|
+
## Channels
|
|
6
|
+
|
|
7
|
+
| Channel | Shows tool calls | Notes |
|
|
8
|
+
|---------|------------------|-------|
|
|
9
|
+
| Web UI | Yes | Full detail including tool calls |
|
|
10
|
+
| Discord | No | Text responses only |
|
|
11
|
+
| CLI | Yes | Direct terminal via `volute send` |
|
|
12
|
+
| System | No | Automated messages (upgrades, health checks) |
|
|
13
|
+
|
|
14
|
+
**Just respond normally.** Your response routes back to the source automatically. Do not use `volute channel send` to reply — that would send a duplicate.
|
|
15
|
+
|
|
16
|
+
## Skills
|
|
17
|
+
|
|
18
|
+
- Use the **volute-agent** skill for CLI commands, variants, upgrades, and self-management.
|
|
19
|
+
- Use the **memory** skill for detailed memory management and consolidation.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { log } from "./logger.js";
|
|
4
|
+
|
|
5
|
+
function exec(cmd: string, args: string[], cwd: string): Promise<{ code: number }> {
|
|
6
|
+
return new Promise((r) => {
|
|
7
|
+
execFile(cmd, args, { cwd }, (_err) => {
|
|
8
|
+
r({ code: _err ? 1 : 0 });
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Serialize git operations to prevent concurrent commits from conflicting
|
|
14
|
+
let pending = Promise.resolve();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Commit a file change in the agent's home directory.
|
|
18
|
+
* Called by the PostToolUse hook when Edit or Write completes.
|
|
19
|
+
*/
|
|
20
|
+
export function commitFileChange(filePath: string, cwd: string): void {
|
|
21
|
+
// Only commit files under the home directory
|
|
22
|
+
const homeDir = resolve(cwd);
|
|
23
|
+
const resolved = resolve(cwd, filePath);
|
|
24
|
+
if (!resolved.startsWith(`${homeDir}/`) && resolved !== homeDir) return;
|
|
25
|
+
|
|
26
|
+
const relativePath = resolved.slice(homeDir.length + 1);
|
|
27
|
+
if (!relativePath) return;
|
|
28
|
+
|
|
29
|
+
pending = pending.then(async () => {
|
|
30
|
+
if ((await exec("git", ["add", relativePath], cwd)).code !== 0) {
|
|
31
|
+
log("auto-commit", `git add failed for ${relativePath}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Check if there are staged changes
|
|
35
|
+
if ((await exec("git", ["diff", "--cached", "--quiet"], cwd)).code === 0) return;
|
|
36
|
+
|
|
37
|
+
const message = `Update ${relativePath}`;
|
|
38
|
+
if ((await exec("git", ["commit", "-m", message], cwd)).code === 0) {
|
|
39
|
+
log("auto-commit", message);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function waitForCommits(): Promise<void> {
|
|
45
|
+
return pending.then(() => {});
|
|
46
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const DEBUG = process.env.VOLUTE_DEBUG === "1";
|
|
2
|
+
|
|
3
|
+
function truncate(str: string, maxLen = 200): string {
|
|
4
|
+
return str.length > maxLen ? `${str.slice(0, maxLen)}...` : str;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function log(category: string, ...args: unknown[]) {
|
|
8
|
+
const ts = new Date().toLocaleString();
|
|
9
|
+
try {
|
|
10
|
+
console.error(`[${ts}] [${category}]`, ...args);
|
|
11
|
+
} catch {
|
|
12
|
+
// EPIPE — parent closed pipes (detached mode). Ignore.
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function debug(category: string, ...args: unknown[]) {
|
|
17
|
+
if (!DEBUG) return;
|
|
18
|
+
log(category, ...args);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function logThinking(thinking: string) {
|
|
22
|
+
log("thinking", DEBUG ? thinking : truncate(thinking));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function logToolUse(name: string, input: unknown) {
|
|
26
|
+
const inputStr = DEBUG ? JSON.stringify(input, null, 2) : truncate(JSON.stringify(input), 100);
|
|
27
|
+
log("tool", `${name}: ${inputStr}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function logToolResult(name: string, output: string, isError?: boolean) {
|
|
31
|
+
const prefix = isError ? "error" : "result";
|
|
32
|
+
log("tool", `${name} ${prefix}: ${DEBUG ? output : truncate(output)}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function logText(text: string) {
|
|
36
|
+
log("text", DEBUG ? text : truncate(text));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function logMessage(direction: "in" | "out", content: string, channel?: string) {
|
|
40
|
+
const arrow = direction === "in" ? "<<" : ">>";
|
|
41
|
+
const channelStr = channel ? ` [${channel}]` : "";
|
|
42
|
+
log("msg", `${arrow}${channelStr}`, DEBUG ? content : truncate(content));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Prevent EPIPE on stderr from crashing the process (detached variant mode)
|
|
46
|
+
process.stderr?.on?.("error", () => {});
|
|
47
|
+
process.stdout?.on?.("error", () => {});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type VoluteContentPart =
|
|
2
|
+
| { type: "text"; text: string }
|
|
3
|
+
| { type: "image"; media_type: string; data: string };
|
|
4
|
+
|
|
5
|
+
export type ChannelMeta = {
|
|
6
|
+
channel?: string;
|
|
7
|
+
sender?: string;
|
|
8
|
+
platform?: string;
|
|
9
|
+
isDM?: boolean;
|
|
10
|
+
channelName?: string;
|
|
11
|
+
guildName?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type VoluteRequest = {
|
|
15
|
+
content: VoluteContentPart[];
|
|
16
|
+
session?: string;
|
|
17
|
+
} & ChannelMeta;
|
|
18
|
+
|
|
19
|
+
export type VoluteEvent =
|
|
20
|
+
| { type: "text"; content: string }
|
|
21
|
+
| { type: "image"; media_type: string; data: string }
|
|
22
|
+
| { type: "tool_use"; name: string; input: unknown }
|
|
23
|
+
| { type: "tool_result"; output: string; is_error?: boolean }
|
|
24
|
+
| { type: "done" };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createServer, type IncomingMessage, type Server } from "node:http";
|
|
2
|
+
import { log } from "./logger.js";
|
|
3
|
+
import type { ChannelMeta, VoluteContentPart, VoluteEvent, VoluteRequest } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export type VoluteAgent = {
|
|
6
|
+
sendMessage: (content: string | VoluteContentPart[], meta?: ChannelMeta) => void;
|
|
7
|
+
onMessage: (listener: (event: VoluteEvent) => void) => () => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const chunks: Buffer[] = [];
|
|
13
|
+
req.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
14
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
15
|
+
req.on("error", reject);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createVoluteServer(options: {
|
|
20
|
+
agent: VoluteAgent;
|
|
21
|
+
port: number;
|
|
22
|
+
name: string;
|
|
23
|
+
version: string;
|
|
24
|
+
}): Server {
|
|
25
|
+
const { agent, port, name, version } = options;
|
|
26
|
+
|
|
27
|
+
const server = createServer(async (req, res) => {
|
|
28
|
+
const url = new URL(req.url!, "http://localhost");
|
|
29
|
+
|
|
30
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
31
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
32
|
+
res.end(JSON.stringify({ status: "ok", name, version }));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (req.method === "POST" && url.pathname === "/message") {
|
|
37
|
+
try {
|
|
38
|
+
const body = JSON.parse(await readBody(req)) as VoluteRequest;
|
|
39
|
+
|
|
40
|
+
res.writeHead(200, {
|
|
41
|
+
"Content-Type": "application/x-ndjson",
|
|
42
|
+
"Cache-Control": "no-cache",
|
|
43
|
+
Connection: "keep-alive",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const removeListener = agent.onMessage((event) => {
|
|
47
|
+
try {
|
|
48
|
+
res.write(`${JSON.stringify(event)}\n`);
|
|
49
|
+
if (event.type === "done") {
|
|
50
|
+
removeListener();
|
|
51
|
+
res.end();
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
removeListener();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
res.on("close", () => {
|
|
59
|
+
removeListener();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
agent.sendMessage(body.content, {
|
|
63
|
+
channel: body.channel,
|
|
64
|
+
sender: body.sender,
|
|
65
|
+
platform: body.platform,
|
|
66
|
+
isDM: body.isDM,
|
|
67
|
+
channelName: body.channelName,
|
|
68
|
+
guildName: body.guildName,
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
res.writeHead(400);
|
|
72
|
+
res.end("Bad Request");
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
res.writeHead(404);
|
|
78
|
+
res.end("Not Found");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
let retries = 0;
|
|
82
|
+
const maxRetries = 5;
|
|
83
|
+
server.on("error", (err: NodeJS.ErrnoException) => {
|
|
84
|
+
if (err.code === "EADDRINUSE") {
|
|
85
|
+
retries++;
|
|
86
|
+
if (retries > maxRetries) {
|
|
87
|
+
log("server", `port ${port} in use after ${maxRetries} retries, exiting`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
log("server", `port ${port} in use, retrying in 1s... (${retries}/${maxRetries})`);
|
|
91
|
+
setTimeout(() => server.listen(port), 1000);
|
|
92
|
+
} else {
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return server;
|
|
98
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Agent Mechanics
|
|
2
|
+
|
|
3
|
+
You are an autonomous agent running as a persistent server in a git repository. Your working directory is `home/` within the project root.
|
|
4
|
+
|
|
5
|
+
## Message Format
|
|
6
|
+
|
|
7
|
+
Messages arrive with a context prefix built by your server code:
|
|
8
|
+
```
|
|
9
|
+
[Discord: username in #general in My Server — 1/15/2025, 10:30:00 AM]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Identity Files
|
|
13
|
+
|
|
14
|
+
These files define who you are and are loaded into your system prompt on startup:
|
|
15
|
+
|
|
16
|
+
- `SOUL.md` — Your core personality and purpose
|
|
17
|
+
- `MEMORY.md` — Your long-term memory
|
|
18
|
+
- `VOLUTE.md` — Your communication channels
|
|
19
|
+
|
|
20
|
+
**Editing any identity file triggers an automatic restart** — your server restarts so the updated file takes effect in your system prompt. Your session resumes automatically.
|
|
21
|
+
|
|
22
|
+
## Memory System
|
|
23
|
+
|
|
24
|
+
Two-tier memory, both managed via file tools:
|
|
25
|
+
|
|
26
|
+
- **`MEMORY.md`** — Long-term knowledge, key decisions, learned preferences. Loaded into your system prompt on every startup. Update when you learn something worth keeping permanently.
|
|
27
|
+
- **`memory/YYYY-MM-DD.md`** — Daily logs for session-level context. The two most recent logs are included in your system prompt. Update throughout the day as you work.
|
|
28
|
+
- Periodically consolidate old daily log entries into `MEMORY.md` and clean up the daily logs.
|
|
29
|
+
|
|
30
|
+
See the **memory** skill for detailed guidance on consolidation and when to update.
|
|
31
|
+
|
|
32
|
+
## Sessions
|
|
33
|
+
|
|
34
|
+
- Your conversation may be **resumed** from a previous session — orient yourself by reading recent daily logs if needed.
|
|
35
|
+
- On a **fresh session**, check `MEMORY.md` and recent daily logs in `memory/` to recall context.
|
|
36
|
+
- On **compaction**, update today's daily log to preserve context before the conversation is trimmed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "tsx watch src/server.ts",
|
|
7
|
+
"lint": "biome check src/",
|
|
8
|
+
"lint:fix": "biome check --write src/",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@anthropic-ai/claude-agent-sdk": "^0.1.0",
|
|
13
|
+
"tsx": "^4.0.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@biomejs/biome": "2.3.14",
|
|
17
|
+
"@types/node": "^25.2.0",
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|