Initial commit: Discord-Claude Gateway with event-driven agent runtime

This commit is contained in:
2026-02-22 13:44:22 -05:00
parent 68f24d50e1
commit b4f340b610
8 changed files with 335 additions and 76 deletions

View File

@@ -78,18 +78,14 @@ All settings are via environment variables:
## Markdown Config Files
Place these in your `CONFIG_DIR` (default: `./config/`). The gateway reads them fresh on every event — edit them anytime, no restart needed.
Place these in your `CONFIG_DIR` (default: `./config/`). The gateway reads them fresh on every event — edit them anytime, no restart needed (except `agents.md` and `heartbeat.md` which are parsed at startup for cron/heartbeat timers).
| File | Purpose | Required |
|------|---------|----------|
| `identity.md` | Agent name, role, specialization | Yes |
| `soul.md` | Personality, tone, values, behavior defaults | Yes |
| `agents.md` | Operating rules, safety boundaries, cron jobs, hooks | No |
| `user.md` | Info about you: name, preferences, context | No |
| `CLAUDE.md` | Persona: identity, personality, user context, tools — all in one | Yes |
| `agents.md` | Operating rules, cron jobs, hooks (parsed by gateway) | No |
| `memory.md` | Long-term memory (agent can write to this) | No (auto-created) |
| `tools.md` | Tool configs, API notes, usage limits | No |
| `heartbeat.md` | Proactive check definitions | No |
| `boot.md` | Bootstrap configuration | No |
| `heartbeat.md` | Proactive check definitions (parsed by gateway) | No |
Missing optional files are created with default headers on first run.

View File

@@ -148,3 +148,24 @@ Our approach: Since we use `--dangerously-skip-permissions`, the agent can run B
5. Agent-managed tasks (dynamic scheduling)
6. Conversation archiving (audit trail)
7. Everything else as needed
---
## Completed: Config Simplification
Merged `soul.md`, `identity.md`, `user.md`, and `tools.md` into a single `CLAUDE.md` file. The config directory is now:
```
config/
├── CLAUDE.md ← Persona: identity, personality, user context, tools (all in one)
├── agents.md ← Cron jobs + Hooks (parsed by gateway at startup)
├── heartbeat.md ← Heartbeat checks (parsed by gateway at startup)
├── memory.md ← Long-term memory (agent-writable, auto-created)
└── sessions.json ← Channel → session ID map (auto-generated)
```
Why this split:
- `CLAUDE.md` is pure prompt context — the agent reads it but the gateway doesn't parse it
- `agents.md` and `heartbeat.md` are parsed programmatically by the gateway to set up cron timers and heartbeat intervals
- `memory.md` is the only file the agent writes to — keeping it separate prevents the agent from accidentally overwriting persona config
- Fewer files to manage, one place to edit your persona

View File

@@ -119,12 +119,11 @@ When the event reaches the front of the queue, the Agent Runtime reads ALL markd
```
config/
├── identity.md → Agent name, role, vibe
├── soul.md → Personality, tone, values
├── agents.md → Operating rules, safety boundaries
├── user.md → Info about the human
── memory.md → Long-term memory (agent can write to this)
└── tools.md → Tool configs, API notes
├── CLAUDE.md Persona: identity, personality, user context, tools
├── agents.md → Operating rules, cron jobs, hooks
├── memory.md → Long-term memory (agent-writable)
├── heartbeat.md → Proactive check definitions
── sessions.json → Channel → session ID map (auto-generated)
```
Files are read fresh every time — edit them while the gateway is running and the next event picks up changes.

272
package-lock.json generated
View File

@@ -11,7 +11,9 @@
"dependencies": {
"discord.js": "^14.25.1",
"dotenv": "^17.3.1",
"node-cron": "^4.2.1"
"node-cron": "^4.2.1",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3"
},
"devDependencies": {
"@types/node": "^25.3.0",
@@ -601,6 +603,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@pinojs/redact": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
"integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.58.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.58.0.tgz",
@@ -1172,6 +1180,15 @@
"node": ">=12"
}
},
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/chai": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
@@ -1182,6 +1199,21 @@
"node": ">=18"
}
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"license": "MIT"
},
"node_modules/dateformat": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/discord-api-types": {
"version": "0.38.40",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.40.tgz",
@@ -1230,6 +1262,15 @@
"url": "https://dotenvx.com"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
@@ -1322,12 +1363,24 @@
"node": ">=12.17.0"
}
},
"node_modules/fast-copy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz",
"integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"license": "MIT"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -1374,6 +1427,21 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/help-me": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
"license": "MIT"
},
"node_modules/joycon": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
@@ -1402,6 +1470,15 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -1441,6 +1518,24 @@
],
"license": "MIT"
},
"node_modules/on-exit-leak-free": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -1468,6 +1563,67 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pino": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
"integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
"license": "MIT",
"dependencies": {
"@pinojs/redact": "^0.4.0",
"atomic-sleep": "^1.0.0",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "^3.0.0",
"pino-std-serializers": "^7.0.0",
"process-warning": "^5.0.0",
"quick-format-unescaped": "^4.0.3",
"real-require": "^0.2.0",
"safe-stable-stringify": "^2.3.1",
"sonic-boom": "^4.0.1",
"thread-stream": "^4.0.0"
},
"bin": {
"pino": "bin.js"
}
},
"node_modules/pino-abstract-transport": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
"integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
"license": "MIT",
"dependencies": {
"split2": "^4.0.0"
}
},
"node_modules/pino-pretty": {
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz",
"integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==",
"license": "MIT",
"dependencies": {
"colorette": "^2.0.7",
"dateformat": "^4.6.3",
"fast-copy": "^4.0.0",
"fast-safe-stringify": "^2.1.1",
"help-me": "^5.0.0",
"joycon": "^3.1.1",
"minimist": "^1.2.6",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "^3.0.0",
"pump": "^3.0.0",
"secure-json-parse": "^4.0.0",
"sonic-boom": "^4.0.1",
"strip-json-comments": "^5.0.2"
},
"bin": {
"pino-pretty": "bin.js"
}
},
"node_modules/pino-std-serializers": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
"integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
"license": "MIT"
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -1497,6 +1653,32 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/process-warning": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
"integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/pure-rand": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
@@ -1514,6 +1696,21 @@
],
"license": "MIT"
},
"node_modules/quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
"license": "MIT"
},
"node_modules/real-require": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
"license": "MIT",
"engines": {
"node": ">= 12.13.0"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
@@ -1569,6 +1766,31 @@
"fsevents": "~2.3.2"
}
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/secure-json-parse": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
"integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
@@ -1576,6 +1798,15 @@
"dev": true,
"license": "ISC"
},
"node_modules/sonic-boom": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
"integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
"license": "MIT",
"dependencies": {
"atomic-sleep": "^1.0.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1586,6 +1817,15 @@
"node": ">=0.10.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -1600,6 +1840,30 @@
"dev": true,
"license": "MIT"
},
"node_modules/strip-json-comments": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
"integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/thread-stream": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
"integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
"license": "MIT",
"dependencies": {
"real-require": "^0.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -1875,6 +2139,12 @@
"node": ">=8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",

View File

@@ -15,7 +15,9 @@
"dependencies": {
"discord.js": "^14.25.1",
"dotenv": "^17.3.1",
"node-cron": "^4.2.1"
"node-cron": "^4.2.1",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3"
},
"devDependencies": {
"@types/node": "^25.3.0",

View File

@@ -14,15 +14,13 @@ export interface BootstrapResult {
const DEFAULT_OPTIONAL_DEFAULTS: Record<string, string> = {
"agents.md": "# Operating Rules\n",
"user.md": "# User Context\n",
"memory.md": "# Memory\n",
"tools.md": "# Tool Configuration\n",
"heartbeat.md": "# Heartbeat\n",
};
const BUILTIN_BOOT_CONFIG: BootConfig = {
requiredFiles: ["soul.md", "identity.md"],
optionalFiles: ["agents.md", "user.md", "memory.md", "tools.md", "heartbeat.md"],
requiredFiles: ["CLAUDE.md"],
optionalFiles: ["agents.md", "memory.md", "heartbeat.md"],
defaults: { ...DEFAULT_OPTIONAL_DEFAULTS },
};

View File

@@ -2,55 +2,37 @@ import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
export interface MarkdownConfigs {
soul: string | null;
identity: string | null;
/** CLAUDE.md — persona: identity, soul, user context, tools (all in one) */
persona: string | null;
/** agents.md — operating rules, cron jobs, hooks (parsed by gateway) */
agents: string | null;
user: string | null;
/** memory.md — long-term memory (agent-writable) */
memory: string | null;
tools: string | null;
}
const CONFIG_FILES = ["soul.md", "identity.md", "agents.md", "user.md", "memory.md", "tools.md"] as const;
type ConfigKey = "soul" | "identity" | "agents" | "user" | "memory" | "tools";
const FILE_TO_KEY: Record<string, ConfigKey> = {
"soul.md": "soul",
"identity.md": "identity",
"agents.md": "agents",
"user.md": "user",
"memory.md": "memory",
"tools.md": "tools",
};
export class MarkdownConfigLoader {
async loadAll(configDir: string): Promise<MarkdownConfigs> {
const configs: MarkdownConfigs = {
soul: null,
identity: null,
persona: null,
agents: null,
user: null,
memory: null,
tools: null,
};
for (const filename of CONFIG_FILES) {
const key = FILE_TO_KEY[filename];
const filePath = join(configDir, filename);
// CLAUDE.md — main persona file
configs.persona = await this.loadFile(configDir, "CLAUDE.md");
if (!configs.persona) {
console.warn("Warning: CLAUDE.md not found in " + configDir);
}
try {
const content = await readFile(filePath, "utf-8");
configs[key] = content;
} catch {
if (filename === "memory.md") {
const defaultContent = "# Memory\n";
await writeFile(filePath, defaultContent, "utf-8");
configs[key] = defaultContent;
} else {
console.warn(`Warning: ${filename} not found in ${configDir}`);
configs[key] = null;
}
}
// agents.md — parsed by gateway for cron/hooks, also included in prompt
configs.agents = await this.loadFile(configDir, "agents.md");
// memory.md — agent-writable long-term memory
configs.memory = await this.loadFile(configDir, "memory.md");
if (!configs.memory) {
const defaultContent = "# Memory\n";
await writeFile(join(configDir, "memory.md"), defaultContent, "utf-8");
configs.memory = defaultContent;
}
return configs;

View File

@@ -3,29 +3,20 @@ import type { MarkdownConfigs } from "./markdown-config-loader.js";
const PREAMBLE =
"You may update your long-term memory by writing to memory.md using the Write tool. Use this to persist important facts, lessons learned, and context across sessions.";
interface SectionDef {
key: keyof MarkdownConfigs;
header: string;
}
const SECTIONS: SectionDef[] = [
{ key: "identity", header: "## Identity" },
{ key: "soul", header: "## Personality" },
{ key: "agents", header: "## Operating Rules" },
{ key: "user", header: "## User Context" },
{ key: "memory", header: "## Long-Term Memory" },
{ key: "tools", header: "## Tool Configuration" },
];
export class SystemPromptAssembler {
assemble(configs: MarkdownConfigs): string {
const parts: string[] = [PREAMBLE, ""];
for (const { key, header } of SECTIONS) {
const content = configs[key];
if (content != null && content !== "") {
parts.push(`${header}\n\n${content}\n`);
}
if (configs.persona) {
parts.push(`## Persona\n\n${configs.persona}\n`);
}
if (configs.agents) {
parts.push(`## Operating Rules\n\n${configs.agents}\n`);
}
if (configs.memory) {
parts.push(`## Long-Term Memory\n\n${configs.memory}\n`);
}
return parts.join("\n");