@iamoberlin/chorus 1.3.2 → 1.3.3

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/index.ts CHANGED
@@ -38,7 +38,7 @@ import {
38
38
  import * as prayers from "./src/prayers/prayers.js";
39
39
  import * as prayerStore from "./src/prayers/store.js";
40
40
 
41
- const VERSION = "1.3.2"; // Fix: pass prompt via stdin not --message flag
41
+ const VERSION = "1.3.0"; // Async spawn: unblock gateway event loop during choir execution
42
42
 
43
43
  const plugin = {
44
44
  id: "chorus",
@@ -2,7 +2,7 @@
2
2
  "id": "chorus",
3
3
  "name": "CHORUS",
4
4
  "description": "CHORUS: Hierarchy Of Recursive Unified Self-improvement",
5
- "version": "1.3.2",
5
+ "version": "1.1.3",
6
6
  "author": "Oberlin",
7
7
  "homepage": "https://chorus.oberlin.ai",
8
8
  "repository": "https://github.com/iamoberlin/chorus",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamoberlin/chorus",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "CHORUS: Hierarchy Of Recursive Unified Self-improvement — with Prayer Requests social network",
5
5
  "author": "Oberlin <iam@oberlin.ai>",
6
6
  "license": "MIT",
package/src/choirs.ts CHANGED
@@ -30,7 +30,7 @@ export const CHOIRS: Record<string, Choir> = {
30
30
  seraphim: {
31
31
  id: "seraphim",
32
32
  name: "Seraphim",
33
- emoji: "🔥",
33
+ emoji: "",
34
34
  triad: "contemplation",
35
35
  frequencyPerDay: 1, // 1×/day
36
36
  intervalMinutes: 1440, // Once per day
@@ -59,7 +59,7 @@ Pass illumination to Cherubim.`,
59
59
  cherubim: {
60
60
  id: "cherubim",
61
61
  name: "Cherubim",
62
- emoji: "📚",
62
+ emoji: "",
63
63
  triad: "contemplation",
64
64
  frequencyPerDay: 2, // 2×/day
65
65
  intervalMinutes: 720, // Every 12 hours
@@ -96,7 +96,7 @@ Pass illumination to Thrones.`,
96
96
  thrones: {
97
97
  id: "thrones",
98
98
  name: "Thrones",
99
- emoji: "⚖️",
99
+ emoji: "",
100
100
  triad: "contemplation",
101
101
  frequencyPerDay: 3, // 3×/day
102
102
  intervalMinutes: 480, // Every 8 hours
@@ -131,7 +131,7 @@ Pass illumination to Dominions.`,
131
131
  dominions: {
132
132
  id: "dominions",
133
133
  name: "Dominions",
134
- emoji: "👑",
134
+ emoji: "",
135
135
  triad: "governance",
136
136
  frequencyPerDay: 4, // 4×/day
137
137
  intervalMinutes: 360, // Every 6 hours
@@ -161,47 +161,72 @@ Pass illumination to Virtues.`,
161
161
  virtues: {
162
162
  id: "virtues",
163
163
  name: "Virtues",
164
- emoji: "🔧",
164
+ emoji: "",
165
165
  triad: "governance",
166
166
  frequencyPerDay: 6, // 6×/day — THE RSI ENGINE
167
167
  intervalMinutes: 240, // Every 4 hours
168
168
  function: "Recursive self-improvement (RSI)",
169
- output: "CHANGELOG.md, config modifications",
170
- prompt: `You are VIRTUES — the Builder.
169
+ output: "CHANGELOG.md, config modifications, state updates",
170
+ prompt: `You are VIRTUES — the Builder. THIS IS THE RSI ENGINE.
171
171
 
172
- Your role: Improve capabilities and create new things. THIS IS THE RSI ENGINE.
172
+ Your role: Ensure purposes are being fulfilled. When they're not, FIX THE SYSTEM.
173
173
 
174
- Tasks:
175
- 1. Review what worked well recently — why?
176
- 2. Review what failed or was inefficient — why?
177
- 3. Identify ONE concrete improvement to make
178
- 4. If low-risk: implement directly
179
- 5. If higher-risk: write to proposals/ for review
180
-
181
- Calibration — learn from past beliefs:
182
- - Look in OPPORTUNITIES.md for closed positions and resolved trades
183
- - Check research/*.md and memory/*.md for past beliefs that have been tested by time
184
- - Ask: What did we believe? What actually happened? What does this teach us?
185
- - When you find a belief that turned out right or wrong, note the lesson in MEMORY.md
186
- - Example: "We believed X. It turned out Y. Lesson: Z"
174
+ ## Core Loop
187
175
 
188
- Context from Dominions: {dominions_context}
176
+ 1. Check purposes in ~/.chorus/purposes.json
177
+ 2. For each purpose, ask: Is this actually producing results?
178
+ 3. If NO: Update local state to fix it. DO NOT just observe — ACT.
179
+ 4. If YES: Note what's working.
180
+
181
+ ## When a Purpose Isn't Producing Results
182
+
183
+ DO NOT just write "trading isn't happening" and move on.
184
+ DO update local state files to make it happen:
185
+
186
+ - Update ~/.chorus/purposes.json (increase frequency, change criteria)
187
+ - Update workspace files (trading/OPPORTUNITIES.md, HEARTBEAT.md)
188
+ - Modify behavioral configs to enforce execution
189
+ - The next cycle should run DIFFERENTLY because of your changes
190
+
191
+ Example: If Trading purpose has 0 opportunities logged:
192
+ 1. Read ~/.chorus/purposes.json
193
+ 2. Increase researchFrequency for Trading purpose
194
+ 3. Update trading/RESEARCH-SESSION.md with stricter output requirements
195
+ 4. Add to HEARTBEAT.md: "Trading gate: Block other work until 1 opportunity logged"
196
+ 5. Log the change to CHANGELOG.md
197
+
198
+ ## Calibration — Learn From Beliefs
189
199
 
190
- CRITICAL: You can modify your own configuration, scripts, prompts, and processes.
191
- This is recursive self-improvement. The system literally improves itself.
200
+ - Check OPPORTUNITIES.md for resolved positions
201
+ - Ask: What did we believe? What happened? What does this teach us?
202
+ - Update MEMORY.md with calibration lessons
203
+
204
+ ## What You Can Modify (Local State)
205
+
206
+ - ~/.chorus/purposes.json — purpose configs
207
+ - ~/.chorus/run-state.json — execution state
208
+ - Workspace files (trading/, research/, memory/, *.md)
209
+ - HEARTBEAT.md, PLAN.md, PROJECTS.md
210
+
211
+ ## What You Cannot Modify
212
+
213
+ - CHORUS plugin source code
214
+ - OpenClaw system config
215
+ - Anything requiring npm publish
216
+
217
+ Context from Dominions: {dominions_context}
192
218
 
193
219
  Risk levels:
194
- - LOW: Config tweaks, documentation, minor prompt adjustments → auto-apply
195
- - MEDIUM: New automations, workflow changes → apply and flag
196
- - HIGH: System architecture, security changes → proposals/ only
220
+ - LOW: State file updates, config tweaks → auto-apply
221
+ - MEDIUM: Behavioral changes, new workflows → apply and flag
222
+ - HIGH: Anything uncertain → proposals/ only
197
223
 
198
- Output: What was improved. What was learned. Any calibration lessons from resolved beliefs.
224
+ Output:
225
+ 1. Purpose fulfillment status (which purposes are producing, which aren't)
226
+ 2. Changes made to local state to fix gaps
227
+ 3. Calibration lessons learned
199
228
 
200
- Append to CHANGELOG.md:
201
- - Timestamp
202
- - Change description
203
- - Risk level
204
- - Rationale
229
+ Append to CHANGELOG.md with timestamp, change, risk level, rationale.
205
230
 
206
231
  Pass illumination to Powers.`,
207
232
  passesTo: ["powers"],
@@ -211,7 +236,7 @@ Pass illumination to Powers.`,
211
236
  powers: {
212
237
  id: "powers",
213
238
  name: "Powers",
214
- emoji: "🛡️",
239
+ emoji: "",
215
240
  triad: "governance",
216
241
  frequencyPerDay: 8, // 8×/day
217
242
  intervalMinutes: 180, // Every 3 hours
@@ -262,7 +287,7 @@ If thesis is seriously threatened or security issue found: ALERT immediately.`,
262
287
  principalities: {
263
288
  id: "principalities",
264
289
  name: "Principalities",
265
- emoji: "🔭",
290
+ emoji: "",
266
291
  triad: "action",
267
292
  frequencyPerDay: 12, // 12×/day
268
293
  intervalMinutes: 120, // Every 2 hours
@@ -304,7 +329,7 @@ Pass illumination to Archangels.`,
304
329
  archangels: {
305
330
  id: "archangels",
306
331
  name: "Archangels",
307
- emoji: "📢",
332
+ emoji: "",
308
333
  triad: "action",
309
334
  frequencyPerDay: 18, // 18×/day
310
335
  intervalMinutes: 80, // Every ~80 minutes
@@ -340,7 +365,7 @@ Output: Briefing or alert message to deliver.`,
340
365
  angels: {
341
366
  id: "angels",
342
367
  name: "Angels",
343
- emoji: "💫",
368
+ emoji: "",
344
369
  triad: "action",
345
370
  frequencyPerDay: 48, // 48×/day — continuous presence
346
371
  intervalMinutes: 30, // Every 30 minutes
@@ -11,7 +11,7 @@ import { recordExecution, type ChoirExecution } from "./metrics.js";
11
11
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
12
12
  import { join } from "path";
13
13
  import { homedir } from "os";
14
- import { spawn } from "child_process";
14
+ import { spawnSync } from "child_process";
15
15
 
16
16
  // Workspace path for research output
17
17
  const WORKSPACE_PATH = process.env.OPENCLAW_WORKSPACE || join(homedir(), ".openclaw", "workspace");
@@ -274,28 +274,17 @@ CRITICAL: If sending alerts via iMessage, use PLAIN TEXT ONLY (no markdown).
274
274
  }
275
275
 
276
276
  if (!result) {
277
- // CLI fallback - use stdin to avoid arg length limits (async to avoid blocking event loop)
277
+ // CLI fallback - use stdin to avoid arg length limits
278
278
  log.debug(`[purpose-research] Using CLI fallback for "${purpose.name}"`);
279
- result = await new Promise<any>((resolve) => {
280
- const child = spawn("openclaw", [
281
- "agent",
282
- "--session-id", `chorus:purpose:${purpose.id}`,
283
- "--json",
284
- ], { stdio: ['pipe', 'pipe', 'pipe'] });
285
-
286
- let stdout = '';
287
- let stderr = '';
288
- const maxBuffer = 1024 * 1024;
289
- child.stdout.on('data', (d: Buffer) => { if (stdout.length < maxBuffer) stdout += d.toString(); });
290
- child.stderr.on('data', (d: Buffer) => { if (stderr.length < maxBuffer) stderr += d.toString(); });
291
-
292
- // Write prompt to stdin
293
- child.stdin.write(prompt);
294
- child.stdin.end();
295
-
296
- const timer = setTimeout(() => { child.kill('SIGTERM'); }, config.researchTimeoutMs);
297
- child.on('close', (code) => { clearTimeout(timer); resolve({ status: code, stdout, stderr }); });
298
- child.on('error', (err) => { clearTimeout(timer); resolve({ status: 1, stdout: '', stderr: String(err) }); });
279
+ result = spawnSync("openclaw", [
280
+ "agent",
281
+ "--session-id", `chorus:purpose:${purpose.id}`,
282
+ "--json",
283
+ ], {
284
+ input: prompt,
285
+ encoding: "utf-8",
286
+ timeout: config.researchTimeoutMs,
287
+ maxBuffer: 1024 * 1024, // 1MB
299
288
  });
300
289
 
301
290
  if (result.status === 0 && result.stdout) {
package/src/scheduler.ts CHANGED
@@ -12,7 +12,7 @@ import { recordExecution, type ChoirExecution } from "./metrics.js";
12
12
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
13
13
  import { join } from "path";
14
14
  import { homedir } from "os";
15
- import { spawn } from "child_process";
15
+ import { spawnSync } from "child_process";
16
16
 
17
17
  interface ChoirContext {
18
18
  choirId: string;
@@ -128,31 +128,20 @@ export function createChoirScheduler(
128
128
  try {
129
129
  const prompt = buildPrompt(choir);
130
130
 
131
- // Use openclaw agent CLI (async to avoid blocking event loop)
132
- const result = await new Promise<{ status: number | null; stdout: string; stderr: string }>((resolve) => {
133
- const child = spawn('openclaw', [
134
- 'agent',
135
- '--session-id', `chorus:${choir.id}`,
136
- '--json',
137
- ], { stdio: ['pipe', 'pipe', 'pipe'] });
138
-
139
- // Pass prompt via stdin (--message flag doesn't work)
140
- child.stdin.write(prompt);
141
- child.stdin.end();
142
-
143
- let stdout = '';
144
- let stderr = '';
145
- const maxBuffer = 1024 * 1024;
146
- child.stdout.on('data', (d: Buffer) => { if (stdout.length < maxBuffer) stdout += d.toString(); });
147
- child.stderr.on('data', (d: Buffer) => { if (stderr.length < maxBuffer) stderr += d.toString(); });
148
-
149
- const timer = setTimeout(() => { child.kill('SIGTERM'); }, 300000); // 5 min
150
- child.on('close', (code) => { clearTimeout(timer); resolve({ status: code, stdout, stderr }); });
151
- child.on('error', (err) => { clearTimeout(timer); resolve({ status: 1, stdout: '', stderr: String(err) }); });
131
+ // Use openclaw agent CLI (same as Vision mode)
132
+ const result = spawnSync('openclaw', [
133
+ 'agent',
134
+ '--session-id', `chorus:${choir.id}`,
135
+ '--message', prompt,
136
+ '--json',
137
+ ], {
138
+ encoding: 'utf-8',
139
+ timeout: 300000, // 5 min
140
+ maxBuffer: 1024 * 1024, // 1MB
152
141
  });
153
142
 
154
143
  let output = "(no response)";
155
-
144
+
156
145
  if (result.status === 0 && result.stdout) {
157
146
  // Extract JSON from output (may have plugin logs before it)
158
147
  const stdout = result.stdout;