open-agents-ai 0.43.0 → 0.44.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.
Files changed (3) hide show
  1. package/README.md +43 -1
  2. package/dist/index.js +578 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -37,6 +37,7 @@ An autonomous multi-turn tool-calling agent that reads your code, makes changes,
37
37
  - **Sub-agent delegation** — spawn independent agents for parallel workstreams
38
38
  - **Ralph Loop** — iterative task execution that keeps retrying until completion criteria are met
39
39
  - **Dream Mode** — creative idle exploration modeled after real sleep architecture (NREM→REM cycles)
40
+ - **Autoresearch Swarm** — 5-agent GPU experiment loop during REM sleep: Researcher, Monitor, Evaluator, Critic, Flow Maintainer autonomously run ML training experiments, keep improvements, discard regressions
40
41
  - **Live Listen** — bidirectional voice communication with real-time Whisper transcription
41
42
  - **Neural TTS** — hear what the agent is doing via GLaDOS or Overwatch ONNX voices, with personality-driven expressiveness
42
43
  - **Personality Core** — SAC framework-based style control (concise/balanced/verbose/pedagogical) that shapes agent response depth, voice expressiveness, and system prompt behavior
@@ -293,6 +294,47 @@ Each cycle expands through all four stages then contracts (evaluation, pruning o
293
294
 
294
295
  All proposals are indexed in `.oa/dreams/PROPOSAL-INDEX.md` for easy review.
295
296
 
297
+ ### Autoresearch Swarm — 5-Agent GPU Experiment Loop
298
+
299
+ When a GPU is detected and the model tier is "large", the REM stage of Dream Mode activates the **Autoresearch Swarm** instead of the standard multi-agent creative exploration. This is a 5-agent system inspired by [Karpathy's autoresearch](https://github.com/karpathy/autoresearch) that autonomously runs ML training experiments.
300
+
301
+ The swarm operates in four phases:
302
+
303
+ | Phase | What Happens |
304
+ |-------|-------------|
305
+ | **Phase 0: Load** | Reads autoresearch memory (best config, experiment log, failed approaches, hypothesis queue, architectural insights) + detects GPU specs |
306
+ | **Phase 1: Hypothesis** | Critic generates 5-8 hypotheses; Flow Maintainer plans experiment ordering and round budget |
307
+ | **Phase 2: Experiment** | Sequential rounds (up to 3): Critic pre-screens → Researcher modifies train.py + runs → Monitor watches GPU → Evaluator keeps/discards → Flow Maintainer decides continue/stop |
308
+ | **Phase 3: Summary** | Flow Maintainer writes consolidated summary to memory + dream report to `.oa/dreams/` |
309
+
310
+ #### The 5 Agent Roles
311
+
312
+ | Role | MaxTurns | Temp | Purpose |
313
+ |------|----------|------|---------|
314
+ | **Researcher** | 25 | 0.4 | Modifies train.py, runs experiments via `autoresearch` tool |
315
+ | **Monitor** | 5 | 0.1 | Watches GPU utilization, reports status (detachable between rounds) |
316
+ | **Evaluator** | 12 | 0.3 | Compares results to best val_bpb, calls keep/discard, writes insights to memory |
317
+ | **Critic** | 8 | 0.5 | Generates hypotheses, pre-screens before GPU time is spent |
318
+ | **Flow Maintainer** | 10 | 0.3 | Orchestrates rounds, manages hypothesis queue, writes final summary |
319
+
320
+ #### Bidirectional Memory
321
+
322
+ The swarm maintains persistent memory in `.oa/memory/autoresearch.json` with five keys:
323
+
324
+ - **best_config** — best val_bpb and what train.py changes produced it
325
+ - **experiment_log** — chronological list of experiments with hypotheses, results, and verdicts
326
+ - **architectural_insights** — patterns learned (what architectures work, what doesn't)
327
+ - **failed_approaches** — things NOT to try again (with reasons)
328
+ - **hypothesis_queue** — pending ideas for future experiments
329
+
330
+ Memory flows bidirectionally: the swarm reads all 5 keys at startup (Phase 0) and writes results back after each experiment. The DMN's gather phase naturally discovers autoresearch learnings when searching all memory, and DMN proposals with category `"autoresearch"` execute through the normal agentic loop.
331
+
332
+ #### Monitor Detachability
333
+
334
+ The Monitor agent can be "detached" between experiment rounds by the Flow Maintainer. When detached, the monitor receives a sub-task (e.g., "analyze GPU memory patterns from last 3 runs") instead of its standard watch prompt. This lets the swarm use idle monitoring capacity for useful analysis work.
335
+
336
+ If no GPU is detected, the REM stage falls back to the standard multi-agent creative exploration (Visionary + Pragmatist + Cross-Pollinator + Synthesizer).
337
+
296
338
  ## Blessed Mode — Infinite Warm Loop
297
339
 
298
340
  `/full-send-bless` activates an infinite warm loop that keeps model weights loaded in VRAM and the agent ready for instant response. The engine sends periodic keep-alive pings to the inference backend (every 2 minutes) to prevent Ollama's automatic model unloading.
@@ -322,7 +364,7 @@ Inspired by the brain's Default Mode Network (Raichle 2001), the DMN activates d
322
364
 
323
365
  Each DMN cycle runs a lightweight LLM agent (15 max turns, temperature 0.4) with read-only file access plus full memory tools. The DMN writes insights back to memory, creating a self-reinforcing knowledge loop.
324
366
 
325
- **Task categories**: directive (standing orders), exploration (knowledge gaps), capability (underused tools), maintenance (system health), social (communication)
367
+ **Task categories**: directive (standing orders), exploration (knowledge gaps), capability (underused tools), maintenance (system health), social (communication), autoresearch (autonomous GPU ML experiment loop)
326
368
 
327
369
  **Backoff**: After 3 consecutive cycles with no actionable task, the DMN enters extended rest. A 30-second cooldown between null cycles prevents spin-looping.
328
370
 
package/dist/index.js CHANGED
@@ -23446,6 +23446,31 @@ var init_edit_history = __esm({
23446
23446
  import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync11, readFileSync as readFileSync19, existsSync as existsSync26, cpSync, rmSync, readdirSync as readdirSync10 } from "node:fs";
23447
23447
  import { join as join36, basename as basename12 } from "node:path";
23448
23448
  import { execSync as execSync22 } from "node:child_process";
23449
+ function loadAutoresearchMemory(repoRoot) {
23450
+ const memoryPath = join36(repoRoot, ".oa", "memory", "autoresearch.json");
23451
+ if (!existsSync26(memoryPath))
23452
+ return "";
23453
+ try {
23454
+ const raw = readFileSync19(memoryPath, "utf-8");
23455
+ const data = JSON.parse(raw);
23456
+ const sections = [];
23457
+ for (const key of AUTORESEARCH_MEMORY_KEYS) {
23458
+ if (data[key] !== void 0 && data[key] !== null) {
23459
+ const value = typeof data[key] === "string" ? data[key] : JSON.stringify(data[key], null, 2);
23460
+ sections.push(`### ${key}
23461
+ ${value}`);
23462
+ }
23463
+ }
23464
+ if (sections.length === 0)
23465
+ return "";
23466
+ return `
23467
+ ## AUTORESEARCH MEMORY (from previous experiments)
23468
+
23469
+ ${sections.join("\n\n")}`;
23470
+ } catch {
23471
+ return "";
23472
+ }
23473
+ }
23449
23474
  function adaptTool(tool) {
23450
23475
  return {
23451
23476
  name: tool.name,
@@ -23584,14 +23609,130 @@ function renderDreamEnd(state) {
23584
23609
 
23585
23610
  `);
23586
23611
  }
23587
- var SLEEP_STAGES, DreamFileWriteTool, DreamFileEditTool, DreamShellTool, DreamEngine;
23612
+ function renderSwarmPhase(phase, description) {
23613
+ const phaseLabels = ["Load", "Hypothesis", "Experiment", "Summary"];
23614
+ const label = phaseLabels[phase] ?? `Phase ${phase}`;
23615
+ process.stdout.write(`
23616
+ ${c2.yellow("\u2B21")} ${c2.bold(`Swarm Phase ${phase}: ${label}`)} ${c2.dim(`\u2014 ${description}`)}
23617
+ `);
23618
+ }
23619
+ function renderSwarmExperiment(round, total, hypothesis) {
23620
+ const truncated = hypothesis.length > 80 ? hypothesis.slice(0, 77) + "..." : hypothesis;
23621
+ process.stdout.write(`
23622
+ ${c2.cyan("\u25C8")} ${c2.bold(`Experiment ${round}/${total}`)} ${c2.dim(truncated)}
23623
+ `);
23624
+ }
23625
+ function renderSwarmComplete(workspace) {
23626
+ const kept = workspace.experimentResults.filter((r) => r.verdict === "keep").length;
23627
+ const discarded = workspace.experimentResults.filter((r) => r.verdict === "discard").length;
23628
+ const best = workspace.bestValBpb === Infinity ? "N/A" : workspace.bestValBpb.toFixed(6);
23629
+ process.stdout.write(`
23630
+ ${c2.green("\u2B22")} ${c2.bold("Autoresearch Swarm Complete")}
23631
+ `);
23632
+ process.stdout.write(` Rounds: ${workspace.roundsCompleted} | Kept: ${kept} | Discarded: ${discarded} | Best val_bpb: ${best}
23633
+ `);
23634
+ process.stdout.write(` Report: ${c2.cyan(".oa/dreams/")}
23635
+ `);
23636
+ }
23637
+ var SWARM_ROLE_CONFIG, AutoresearchFileWriteTool, AutoresearchFileEditTool, AUTORESEARCH_MEMORY_KEYS, SLEEP_STAGES, DreamFileWriteTool, DreamFileEditTool, DreamShellTool, DreamEngine;
23588
23638
  var init_dream_engine = __esm({
23589
23639
  "packages/cli/dist/tui/dream-engine.js"() {
23590
23640
  "use strict";
23591
23641
  init_dist5();
23592
23642
  init_dist2();
23593
23643
  init_project_context();
23644
+ init_setup();
23594
23645
  init_render();
23646
+ SWARM_ROLE_CONFIG = {
23647
+ researcher: { maxTurns: 25, temperature: 0.4 },
23648
+ monitor: { maxTurns: 5, temperature: 0.1 },
23649
+ evaluator: { maxTurns: 12, temperature: 0.3 },
23650
+ critic: { maxTurns: 8, temperature: 0.5 },
23651
+ flow_maintainer: { maxTurns: 10, temperature: 0.3 }
23652
+ };
23653
+ AutoresearchFileWriteTool = class {
23654
+ autoresearchDir;
23655
+ name = "file_write";
23656
+ description = "Write a file (autoresearch mode: writes confined to .oa/autoresearch/ directory)";
23657
+ parameters = {
23658
+ type: "object",
23659
+ properties: {
23660
+ path: { type: "string", description: "File path (relative to .oa/autoresearch/)" },
23661
+ content: { type: "string", description: "File content to write" }
23662
+ },
23663
+ required: ["path", "content"]
23664
+ };
23665
+ constructor(autoresearchDir) {
23666
+ this.autoresearchDir = autoresearchDir;
23667
+ }
23668
+ async execute(args) {
23669
+ const start = Date.now();
23670
+ const rawPath = String(args["path"] ?? "");
23671
+ const content = String(args["content"] ?? "");
23672
+ if (!rawPath)
23673
+ return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
23674
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ? join36(this.autoresearchDir, basename12(rawPath)) : join36(this.autoresearchDir, rawPath);
23675
+ if (!targetPath.startsWith(this.autoresearchDir)) {
23676
+ return { success: false, output: "", error: "Autoresearch mode: writes are confined to .oa/autoresearch/", durationMs: Date.now() - start };
23677
+ }
23678
+ try {
23679
+ const dir = join36(targetPath, "..");
23680
+ mkdirSync12(dir, { recursive: true });
23681
+ writeFileSync11(targetPath, content, "utf-8");
23682
+ return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
23683
+ } catch (err) {
23684
+ return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
23685
+ }
23686
+ }
23687
+ };
23688
+ AutoresearchFileEditTool = class {
23689
+ autoresearchDir;
23690
+ name = "file_edit";
23691
+ description = "Edit a file (autoresearch mode: edits confined to .oa/autoresearch/ directory)";
23692
+ parameters = {
23693
+ type: "object",
23694
+ properties: {
23695
+ path: { type: "string", description: "File path (relative to .oa/autoresearch/)" },
23696
+ old_string: { type: "string", description: "Text to replace" },
23697
+ new_string: { type: "string", description: "Replacement text" }
23698
+ },
23699
+ required: ["path", "old_string", "new_string"]
23700
+ };
23701
+ constructor(autoresearchDir) {
23702
+ this.autoresearchDir = autoresearchDir;
23703
+ }
23704
+ async execute(args) {
23705
+ const start = Date.now();
23706
+ const rawPath = String(args["path"] ?? "");
23707
+ const oldStr = String(args["old_string"] ?? "");
23708
+ const newStr = String(args["new_string"] ?? "");
23709
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ? join36(this.autoresearchDir, basename12(rawPath)) : join36(this.autoresearchDir, rawPath);
23710
+ if (!targetPath.startsWith(this.autoresearchDir)) {
23711
+ return { success: false, output: "", error: "Autoresearch mode: edits are confined to .oa/autoresearch/", durationMs: Date.now() - start };
23712
+ }
23713
+ try {
23714
+ if (!existsSync26(targetPath)) {
23715
+ return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
23716
+ }
23717
+ let content = readFileSync19(targetPath, "utf-8");
23718
+ if (!content.includes(oldStr)) {
23719
+ return { success: false, output: "", error: "old_string not found in file", durationMs: Date.now() - start };
23720
+ }
23721
+ content = content.replace(oldStr, newStr);
23722
+ writeFileSync11(targetPath, content, "utf-8");
23723
+ return { success: true, output: `Edited ${rawPath}`, durationMs: Date.now() - start };
23724
+ } catch (err) {
23725
+ return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
23726
+ }
23727
+ }
23728
+ };
23729
+ AUTORESEARCH_MEMORY_KEYS = [
23730
+ "best_config",
23731
+ "experiment_log",
23732
+ "failed_approaches",
23733
+ "hypothesis_queue",
23734
+ "architectural_insights"
23735
+ ];
23595
23736
  SLEEP_STAGES = [
23596
23737
  { name: "NREM-1", label: "Light Scan", description: "Quick codebase overview, surface observations" },
23597
23738
  { name: "NREM-2", label: "Pattern Detection", description: "Identify recurring patterns, technical debt, gaps" },
@@ -23772,7 +23913,11 @@ var init_dream_engine = __esm({
23772
23913
  renderDreamStage(stage.name, stage.label, stage.description);
23773
23914
  const startMs = Date.now();
23774
23915
  let result;
23775
- if (stage.name === "REM" && modelTier === "large") {
23916
+ if (stage.name === "REM" && modelTier === "large" && detectSystemSpecs().gpuVramGB > 0) {
23917
+ renderInfo("REM: Autoresearch swarm \u2014 5-agent GPU experiment loop (Researcher + Monitor + Evaluator + Critic + Flow Maintainer)");
23918
+ const swarmResult = await this.runAutoresearchSwarm(cycle, previousFindings, onEvent);
23919
+ result = { summary: swarmResult.summary, turns: 0, toolCalls: 0 };
23920
+ } else if (stage.name === "REM" && modelTier === "large") {
23776
23921
  renderInfo("REM: Multi-agent creative mode \u2014 parallel Visionary + Pragmatist + Cross-Pollinator");
23777
23922
  const remResult = await this.runMultiAgentREM(cycle, totalCycles, previousFindings, mode === "lucid" ? "full" : "sandboxed", onEvent);
23778
23923
  result = { summary: remResult.summary, turns: 0, toolCalls: 0 };
@@ -23980,6 +24125,433 @@ INSTRUCTIONS:
23980
24125
  After synthesis, call task_complete with the final prioritized summary.`, toolMode, onEvent);
23981
24126
  return synthesisResult;
23982
24127
  }
24128
+ // ── Autoresearch Swarm ────────────────────────────────────────────────
24129
+ /**
24130
+ * Run a single swarm sub-agent with role-specific tools and config.
24131
+ * Follows the runBrainRegionAgent() pattern from dmn-engine.ts.
24132
+ */
24133
+ async runSwarmAgent(role, prompt, workspace, onEvent) {
24134
+ const backend = new OllamaAgenticBackend(this.config.backendUrl, this.config.model, this.config.apiKey);
24135
+ const roleConfig = SWARM_ROLE_CONFIG[role];
24136
+ const modelTier = getModelTier(this.config.model);
24137
+ const runner = new AgenticRunner(backend, {
24138
+ maxTurns: roleConfig.maxTurns,
24139
+ maxTokens: 8192,
24140
+ temperature: roleConfig.temperature,
24141
+ requestTimeoutMs: this.config.timeoutMs,
24142
+ taskTimeoutMs: role === "researcher" ? this.config.timeoutMs * 5 : this.config.timeoutMs * 2,
24143
+ compactionThreshold: modelTier === "small" ? 8e3 : 16e3,
24144
+ modelTier
24145
+ });
24146
+ const tools = this.buildSwarmTools(role, workspace);
24147
+ runner.registerTools(tools);
24148
+ if (onEvent) {
24149
+ runner.onEvent((event) => {
24150
+ const taggedEvent = {
24151
+ ...event,
24152
+ content: event.type === "status" ? `[${role}] ${event.content ?? ""}` : event.content
24153
+ };
24154
+ onEvent(taggedEvent);
24155
+ });
24156
+ }
24157
+ const result = await runner.run(prompt, `Swarm role: ${role}. Working directory: ${this.repoRoot}`);
24158
+ return result.summary || "{}";
24159
+ }
24160
+ /** Build role-specific tool sets for swarm agents */
24161
+ buildSwarmTools(role, _workspace) {
24162
+ const autoresearchDir = join36(this.repoRoot, ".oa", "autoresearch");
24163
+ const taskComplete = this.createSwarmTaskCompleteTool(role);
24164
+ switch (role) {
24165
+ case "researcher": {
24166
+ const tools = [
24167
+ new FileReadTool(this.repoRoot),
24168
+ new AutoresearchFileEditTool(autoresearchDir),
24169
+ new AutoresearchFileWriteTool(autoresearchDir),
24170
+ new AutoresearchTool(this.repoRoot),
24171
+ new GrepSearchTool(this.repoRoot),
24172
+ new GlobFindTool(this.repoRoot),
24173
+ new MemoryReadTool(this.repoRoot),
24174
+ new MemorySearchTool(this.repoRoot)
24175
+ ];
24176
+ return [...tools.map(adaptTool), taskComplete];
24177
+ }
24178
+ case "monitor": {
24179
+ const tools = [
24180
+ new FileReadTool(this.repoRoot),
24181
+ new DreamShellTool(this.repoRoot),
24182
+ // read-only shell
24183
+ new AutoresearchTool(this.repoRoot)
24184
+ // status-only in prompt
24185
+ ];
24186
+ return [...tools.map(adaptTool), taskComplete];
24187
+ }
24188
+ case "evaluator": {
24189
+ const tools = [
24190
+ new FileReadTool(this.repoRoot),
24191
+ new AutoresearchTool(this.repoRoot),
24192
+ // results/keep/discard
24193
+ new MemoryReadTool(this.repoRoot),
24194
+ new MemorySearchTool(this.repoRoot),
24195
+ new MemoryWriteTool(this.repoRoot),
24196
+ new GrepSearchTool(this.repoRoot)
24197
+ ];
24198
+ return [...tools.map(adaptTool), taskComplete];
24199
+ }
24200
+ case "critic": {
24201
+ const tools = [
24202
+ new FileReadTool(this.repoRoot),
24203
+ new MemoryReadTool(this.repoRoot),
24204
+ new MemorySearchTool(this.repoRoot),
24205
+ new GrepSearchTool(this.repoRoot)
24206
+ ];
24207
+ return [...tools.map(adaptTool), taskComplete];
24208
+ }
24209
+ case "flow_maintainer": {
24210
+ const tools = [
24211
+ new MemoryReadTool(this.repoRoot),
24212
+ new MemoryWriteTool(this.repoRoot),
24213
+ new MemorySearchTool(this.repoRoot)
24214
+ ];
24215
+ return [...tools.map(adaptTool), taskComplete];
24216
+ }
24217
+ }
24218
+ }
24219
+ createSwarmTaskCompleteTool(role) {
24220
+ return {
24221
+ name: "task_complete",
24222
+ description: `Signal that the ${role} swarm agent is done with its task.`,
24223
+ parameters: {
24224
+ type: "object",
24225
+ properties: {
24226
+ summary: { type: "string", description: "JSON summary of findings/results" }
24227
+ },
24228
+ required: ["summary"]
24229
+ },
24230
+ async execute(args) {
24231
+ return { success: true, output: args["summary"] || "{}" };
24232
+ }
24233
+ };
24234
+ }
24235
+ /**
24236
+ * Run the 5-agent autoresearch swarm — the core orchestrator.
24237
+ *
24238
+ * Phase 0: Load memory + GPU check
24239
+ * Phase 1: Parallel critic + flow maintainer to generate/filter hypothesis queue
24240
+ * Phase 2: Sequential experiment loop (up to 3 rounds)
24241
+ * Phase 3: Consolidated summary to memory + dream report
24242
+ */
24243
+ async runAutoresearchSwarm(cycleNum, previousFindings, onEvent) {
24244
+ renderSwarmPhase(0, "Loading autoresearch memory + GPU check");
24245
+ const memoryContext = loadAutoresearchMemory(this.repoRoot);
24246
+ const specs = detectSystemSpecs();
24247
+ const gpuInfo = specs.gpuVramGB > 0 ? `GPU: ${specs.gpuName} (${specs.gpuVramGB.toFixed(0)}GB VRAM)` : "GPU: not detected";
24248
+ onEvent?.({
24249
+ type: "status",
24250
+ content: `Autoresearch swarm activating \u2014 ${gpuInfo}`,
24251
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
24252
+ });
24253
+ const workspace = {
24254
+ hypothesisQueue: [],
24255
+ experimentResults: [],
24256
+ monitor: { status: "idle" },
24257
+ memoryContext,
24258
+ bestValBpb: Infinity,
24259
+ roundsCompleted: 0
24260
+ };
24261
+ const baseContext = `AUTORESEARCH SWARM \u2014 Autonomous ML Experiment Loop
24262
+
24263
+ You are part of a 5-agent swarm that iteratively improves a language model
24264
+ training script through hypothesis \u2192 experiment \u2192 evaluate cycles.
24265
+
24266
+ PREVIOUS DREAM FINDINGS:
24267
+ ${previousFindings}
24268
+ ${memoryContext}
24269
+
24270
+ GPU: ${gpuInfo}
24271
+ Workspace: .oa/autoresearch/`;
24272
+ renderSwarmPhase(1, "Generating hypothesis queue (Critic + Flow Maintainer)");
24273
+ const [criticInit, flowInit] = await Promise.allSettled([
24274
+ this.runSwarmAgent("critic", `${baseContext}
24275
+
24276
+ ROLE: CRITIC \u2014 Hypothesis Generator & Filter
24277
+ You are the adversarial critic. Your job is to:
24278
+ 1. Review the current state of experiments (check autoresearch memory above)
24279
+ 2. Read train.py in .oa/autoresearch/ to understand current architecture
24280
+ 3. Generate 5-8 hypotheses for improving val_bpb (bits per byte)
24281
+ 4. For each hypothesis, assess: feasibility, expected impact, risk of regression
24282
+ 5. Filter out hypotheses that overlap with failed_approaches in memory
24283
+
24284
+ Output JSON with: { "hypotheses": [{ "id": number, "description": string, "rationale": string, "risk": "low"|"medium"|"high", "expected_impact": string }] }
24285
+
24286
+ Call task_complete with your JSON when done.`, workspace, (event) => onEvent?.({ ...event, content: event.type === "status" ? `[critic] ${event.content ?? ""}` : event.content })),
24287
+ this.runSwarmAgent("flow_maintainer", `${baseContext}
24288
+
24289
+ ROLE: FLOW MAINTAINER \u2014 Experiment Planner
24290
+ You are the flow controller. Your job is to:
24291
+ 1. Review autoresearch memory for past experiments and their outcomes
24292
+ 2. Identify the current best val_bpb and what changes produced it
24293
+ 3. Determine if there's a clear direction of improvement (e.g., architecture changes vs hyperparams)
24294
+ 4. Propose an ordering strategy: which types of experiments should run first?
24295
+ 5. Set initial experiment budget: how many rounds (1-3) should we attempt?
24296
+
24297
+ Output JSON with: { "strategy": string, "recommended_rounds": number, "priority_order": string[], "stop_conditions": string[] }
24298
+
24299
+ Call task_complete with your JSON when done.`, workspace, (event) => onEvent?.({ ...event, content: event.type === "status" ? `[flow_maintainer] ${event.content ?? ""}` : event.content }))
24300
+ ]);
24301
+ const criticOutput = criticInit.status === "fulfilled" ? criticInit.value : "{}";
24302
+ const flowOutput = flowInit.status === "fulfilled" ? flowInit.value : "{}";
24303
+ try {
24304
+ const parsed = JSON.parse(criticOutput.match(/\{[\s\S]*\}/)?.[0] ?? "{}");
24305
+ if (Array.isArray(parsed.hypotheses)) {
24306
+ workspace.hypothesisQueue = parsed.hypotheses.map((h) => h.description ?? `Hypothesis ${h.id ?? 0}`);
24307
+ }
24308
+ } catch {
24309
+ }
24310
+ let maxRounds = 3;
24311
+ try {
24312
+ const parsed = JSON.parse(flowOutput.match(/\{[\s\S]*\}/)?.[0] ?? "{}");
24313
+ if (typeof parsed.recommended_rounds === "number") {
24314
+ maxRounds = Math.min(3, Math.max(1, parsed.recommended_rounds));
24315
+ }
24316
+ } catch {
24317
+ }
24318
+ if (workspace.hypothesisQueue.length === 0) {
24319
+ workspace.hypothesisQueue = [
24320
+ "Increase model depth while reducing width to maintain parameter count",
24321
+ "Adjust learning rate schedule with warmup",
24322
+ "Modify attention mechanism (e.g., grouped query attention)"
24323
+ ];
24324
+ }
24325
+ onEvent?.({
24326
+ type: "status",
24327
+ content: `Phase 1 complete: ${workspace.hypothesisQueue.length} hypotheses queued, ${maxRounds} rounds planned`,
24328
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
24329
+ });
24330
+ renderSwarmPhase(2, `Running experiments (up to ${maxRounds} rounds)`);
24331
+ for (let round = 0; round < maxRounds; round++) {
24332
+ if (this.abortController?.signal.aborted)
24333
+ break;
24334
+ if (workspace.hypothesisQueue.length === 0)
24335
+ break;
24336
+ const hypothesis = workspace.hypothesisQueue.shift();
24337
+ renderSwarmExperiment(round + 1, maxRounds, hypothesis);
24338
+ const criticScreen = await this.runSwarmAgent("critic", `${baseContext}
24339
+
24340
+ ROLE: CRITIC \u2014 Pre-screen Hypothesis
24341
+ Quickly evaluate this hypothesis before we spend GPU time on it:
24342
+
24343
+ HYPOTHESIS: ${hypothesis}
24344
+
24345
+ Previous experiment results:
24346
+ ${workspace.experimentResults.map((r) => `- ${r.hypothesis}: val_bpb=${r.valBpb}, verdict=${r.verdict}`).join("\n") || "(none yet)"}
24347
+
24348
+ Questions to answer:
24349
+ 1. Is this hypothesis likely to improve val_bpb given what we know?
24350
+ 2. Does it overlap with any failed approaches?
24351
+ 3. Is it safe to implement (won't corrupt the training script)?
24352
+
24353
+ Output JSON: { "approved": boolean, "reason": string, "modifications": string }
24354
+ If not approved, briefly explain why and we'll skip to the next hypothesis.
24355
+
24356
+ Call task_complete with your JSON.`, workspace, (event) => onEvent?.({ ...event, content: event.type === "status" ? `[critic] ${event.content ?? ""}` : event.content }));
24357
+ let approved = true;
24358
+ try {
24359
+ const parsed = JSON.parse(criticScreen.match(/\{[\s\S]*\}/)?.[0] ?? "{}");
24360
+ if (parsed.approved === false) {
24361
+ approved = false;
24362
+ onEvent?.({
24363
+ type: "status",
24364
+ content: `[critic] Rejected hypothesis: ${parsed.reason ?? "no reason given"}`,
24365
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
24366
+ });
24367
+ }
24368
+ } catch {
24369
+ }
24370
+ if (!approved)
24371
+ continue;
24372
+ workspace.monitor.status = "watching";
24373
+ const [researcherResult, monitorResult] = await Promise.allSettled([
24374
+ this.runSwarmAgent("researcher", `${baseContext}
24375
+
24376
+ ROLE: RESEARCHER \u2014 Implement & Run Experiment
24377
+ You are the researcher. Your hypothesis for this round:
24378
+
24379
+ HYPOTHESIS: ${hypothesis}
24380
+
24381
+ INSTRUCTIONS:
24382
+ 1. Read the current train.py in .oa/autoresearch/ using file_read
24383
+ 2. Modify train.py using file_edit to implement the hypothesis
24384
+ 3. Run the experiment using autoresearch(action="run")
24385
+ 4. Report the results
24386
+
24387
+ Previous experiments:
24388
+ ${workspace.experimentResults.map((r) => `- ${r.hypothesis}: val_bpb=${r.valBpb}, verdict=${r.verdict}`).join("\n") || "(none yet)"}
24389
+
24390
+ Best val_bpb so far: ${workspace.bestValBpb === Infinity ? "N/A (first experiment)" : workspace.bestValBpb.toFixed(6)}
24391
+
24392
+ Be precise with file_edit \u2014 match exact strings from the file. Small, targeted changes are better than large rewrites.
24393
+
24394
+ Call task_complete with JSON: { "val_bpb": number, "changes_made": string, "observations": string }`, workspace, (event) => onEvent?.({ ...event, content: event.type === "status" ? `[researcher] ${event.content ?? ""}` : event.content })),
24395
+ this.runSwarmAgent("monitor", `${baseContext}
24396
+
24397
+ ROLE: MONITOR \u2014 Watch Experiment Status
24398
+ ${workspace.monitor.detachedTask ? `DETACHED TASK: ${workspace.monitor.detachedTask}
24399
+ Complete the detached task, then check experiment status.` : "Watch the experiment status."}
24400
+
24401
+ INSTRUCTIONS:
24402
+ 1. Check autoresearch status: autoresearch(action="status")
24403
+ 2. Report GPU utilization and any concerning patterns
24404
+ 3. If the experiment seems hung, report that
24405
+
24406
+ Call task_complete with JSON: { "status": string, "gpu_usage": string, "concerns": string[] }`, workspace, (event) => onEvent?.({ ...event, content: event.type === "status" ? `[monitor] ${event.content ?? ""}` : event.content }))
24407
+ ]);
24408
+ workspace.monitor.status = "idle";
24409
+ const researcherOutput = researcherResult.status === "fulfilled" ? researcherResult.value : "{}";
24410
+ let experimentValBpb = Infinity;
24411
+ let changesMade = hypothesis;
24412
+ try {
24413
+ const parsed = JSON.parse(researcherOutput.match(/\{[\s\S]*\}/)?.[0] ?? "{}");
24414
+ if (typeof parsed.val_bpb === "number")
24415
+ experimentValBpb = parsed.val_bpb;
24416
+ if (typeof parsed.changes_made === "string")
24417
+ changesMade = parsed.changes_made;
24418
+ } catch {
24419
+ }
24420
+ const evaluatorResult = await this.runSwarmAgent("evaluator", `${baseContext}
24421
+
24422
+ ROLE: EVALUATOR \u2014 Assess Experiment Results
24423
+ Evaluate the experiment that just completed:
24424
+
24425
+ HYPOTHESIS: ${hypothesis}
24426
+ RESULT: val_bpb = ${experimentValBpb === Infinity ? "unknown/failed" : experimentValBpb.toFixed(6)}
24427
+ CHANGES: ${changesMade}
24428
+ BEST SO FAR: ${workspace.bestValBpb === Infinity ? "N/A" : workspace.bestValBpb.toFixed(6)}
24429
+ MONITOR REPORT: ${monitorResult.status === "fulfilled" ? monitorResult.value : "unavailable"}
24430
+
24431
+ INSTRUCTIONS:
24432
+ 1. Check experiment results using autoresearch(action="results")
24433
+ 2. Compare to previous best val_bpb
24434
+ 3. If improved: call autoresearch(action="keep", description="...") and write insights to memory
24435
+ 4. If worse: call autoresearch(action="discard", description="...") and record failed approach
24436
+ 5. Extract architectural insights regardless of outcome
24437
+
24438
+ Use memory_write to save insights to "autoresearch" topic.
24439
+
24440
+ Call task_complete with JSON: { "verdict": "keep"|"discard", "val_bpb": number, "insights": string, "architectural_lesson": string }`, workspace, (event) => onEvent?.({ ...event, content: event.type === "status" ? `[evaluator] ${event.content ?? ""}` : event.content }));
24441
+ let verdict = "discard";
24442
+ let insights = "";
24443
+ try {
24444
+ const parsed = JSON.parse(evaluatorResult.match(/\{[\s\S]*\}/)?.[0] ?? "{}");
24445
+ if (parsed.verdict === "keep")
24446
+ verdict = "keep";
24447
+ if (typeof parsed.val_bpb === "number" && parsed.val_bpb < experimentValBpb) {
24448
+ experimentValBpb = parsed.val_bpb;
24449
+ }
24450
+ if (typeof parsed.insights === "string")
24451
+ insights = parsed.insights;
24452
+ } catch {
24453
+ }
24454
+ if (verdict === "keep" && experimentValBpb < workspace.bestValBpb) {
24455
+ workspace.bestValBpb = experimentValBpb;
24456
+ }
24457
+ workspace.experimentResults.push({
24458
+ hypothesis,
24459
+ valBpb: experimentValBpb,
24460
+ verdict,
24461
+ insights
24462
+ });
24463
+ workspace.roundsCompleted = round + 1;
24464
+ const flowDecision = await this.runSwarmAgent("flow_maintainer", `${baseContext}
24465
+
24466
+ ROLE: FLOW MAINTAINER \u2014 Continue/Stop Decision
24467
+ Round ${round + 1}/${maxRounds} just completed.
24468
+
24469
+ EXPERIMENT HISTORY:
24470
+ ${workspace.experimentResults.map((r, i) => `Round ${i + 1}: ${r.hypothesis} \u2192 val_bpb=${r.valBpb === Infinity ? "failed" : r.valBpb.toFixed(6)} (${r.verdict})`).join("\n")}
24471
+
24472
+ REMAINING HYPOTHESES: ${workspace.hypothesisQueue.length}
24473
+ ${workspace.hypothesisQueue.map((h, i) => ` ${i + 1}. ${h}`).join("\n") || " (none)"}
24474
+
24475
+ DECISIONS:
24476
+ 1. Should we continue to the next round? (consider: are we improving? are hypotheses promising?)
24477
+ 2. Should the monitor be given a detached task between rounds? (e.g., "analyze GPU memory patterns")
24478
+ 3. Any hypotheses to add or remove from the queue?
24479
+
24480
+ Call task_complete with JSON: { "continue": boolean, "reason": string, "monitor_task": string|null, "add_hypotheses": string[], "remove_indices": number[] }`, workspace, (event) => onEvent?.({ ...event, content: event.type === "status" ? `[flow_maintainer] ${event.content ?? ""}` : event.content }));
24481
+ try {
24482
+ const parsed = JSON.parse(flowDecision.match(/\{[\s\S]*\}/)?.[0] ?? "{}");
24483
+ if (typeof parsed.monitor_task === "string" && parsed.monitor_task) {
24484
+ workspace.monitor.status = "detached";
24485
+ workspace.monitor.detachedTask = parsed.monitor_task;
24486
+ }
24487
+ if (Array.isArray(parsed.add_hypotheses)) {
24488
+ workspace.hypothesisQueue.push(...parsed.add_hypotheses.map(String));
24489
+ }
24490
+ if (Array.isArray(parsed.remove_indices)) {
24491
+ const toRemove = new Set(parsed.remove_indices.map(Number));
24492
+ workspace.hypothesisQueue = workspace.hypothesisQueue.filter((_, i) => !toRemove.has(i));
24493
+ }
24494
+ if (parsed.continue === false) {
24495
+ onEvent?.({
24496
+ type: "status",
24497
+ content: `[flow_maintainer] Stopping early: ${parsed.reason ?? "no reason"}`,
24498
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
24499
+ });
24500
+ break;
24501
+ }
24502
+ } catch {
24503
+ }
24504
+ }
24505
+ renderSwarmPhase(3, "Writing consolidated summary");
24506
+ const summaryResult = await this.runSwarmAgent("flow_maintainer", `${baseContext}
24507
+
24508
+ ROLE: FLOW MAINTAINER \u2014 Final Summary
24509
+ All experiment rounds are complete. Write a consolidated summary.
24510
+
24511
+ EXPERIMENT RESULTS:
24512
+ ${workspace.experimentResults.map((r, i) => `Round ${i + 1}: ${r.hypothesis}
24513
+ val_bpb: ${r.valBpb === Infinity ? "failed" : r.valBpb.toFixed(6)}
24514
+ verdict: ${r.verdict}
24515
+ insights: ${r.insights}`).join("\n\n")}
24516
+
24517
+ BEST val_bpb: ${workspace.bestValBpb === Infinity ? "no successful experiments" : workspace.bestValBpb.toFixed(6)}
24518
+
24519
+ INSTRUCTIONS:
24520
+ 1. Write a consolidated summary of all experiments to memory using memory_write:
24521
+ - Topic: "autoresearch"
24522
+ - Include: best_config, experiment_log, architectural_insights, failed_approaches, hypothesis_queue
24523
+ 2. Summarize the key learnings and next steps
24524
+
24525
+ Call task_complete with a human-readable summary of the autoresearch session.`, workspace, onEvent);
24526
+ const reportPath = join36(this.dreamsDir, `cycle-${cycleNum}-autoresearch-report.md`);
24527
+ const report = `# Autoresearch Swarm Report \u2014 Cycle ${cycleNum}
24528
+
24529
+ **Date**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
24530
+ **Rounds completed**: ${workspace.roundsCompleted}
24531
+ **Best val_bpb**: ${workspace.bestValBpb === Infinity ? "N/A" : workspace.bestValBpb.toFixed(6)}
24532
+
24533
+ ## Experiments
24534
+
24535
+ ${workspace.experimentResults.map((r, i) => `### Round ${i + 1}: ${r.hypothesis}
24536
+ - **val_bpb**: ${r.valBpb === Infinity ? "failed" : r.valBpb.toFixed(6)}
24537
+ - **Verdict**: ${r.verdict}
24538
+ - **Insights**: ${r.insights || "none"}`).join("\n\n")}
24539
+
24540
+ ## Summary
24541
+
24542
+ ${summaryResult}
24543
+
24544
+ ---
24545
+ *Generated by open-agents autoresearch swarm*
24546
+ `;
24547
+ try {
24548
+ mkdirSync12(this.dreamsDir, { recursive: true });
24549
+ writeFileSync11(reportPath, report, "utf-8");
24550
+ } catch {
24551
+ }
24552
+ renderSwarmComplete(workspace);
24553
+ return { summary: summaryResult };
24554
+ }
23983
24555
  /** Build tools appropriate for the dream mode */
23984
24556
  buildDreamTools(toolMode) {
23985
24557
  if (toolMode === "full") {
@@ -24596,7 +25168,8 @@ var init_dmn_engine = __esm({
24596
25168
  "browser_action \u2014 headless Chrome automation",
24597
25169
  "scheduler, reminder, agenda \u2014 temporal agency",
24598
25170
  "codebase_map, diagnostic, git_info \u2014 project analysis",
24599
- "sub_agent \u2014 delegate subtasks to independent agents"
25171
+ "sub_agent \u2014 delegate subtasks to independent agents",
25172
+ "autoresearch \u2014 autonomous GPU ML experiment loop (modify architecture/hyperparams, train 5min, keep/discard)"
24600
25173
  ];
24601
25174
  const prompt = buildDMNGatherPrompt(this.recentTaskSummaries, reminders, attention, memoryTopics, capabilities, this.state.competence, this.state.reflectionBuffer);
24602
25175
  const modelTier = getModelTier(this.config.model);
@@ -25019,7 +25592,7 @@ OUTPUT: Call task_complete with JSON:
25019
25592
  task: String(p.task ?? ""),
25020
25593
  rationale: String(p.rationale ?? ""),
25021
25594
  provenance: Array.isArray(p.provenance) ? p.provenance.map(String) : [],
25022
- category: ["directive", "exploration", "capability", "maintenance", "social"].includes(String(p.category)) ? String(p.category) : "exploration",
25595
+ category: ["directive", "exploration", "capability", "maintenance", "social", "autoresearch"].includes(String(p.category)) ? String(p.category) : "exploration",
25023
25596
  confidence: typeof p.confidence === "number" ? p.confidence : 0.5,
25024
25597
  challengeResult: p.challengeResult ? String(p.challengeResult) : void 0
25025
25598
  }));
@@ -25070,7 +25643,7 @@ OUTPUT: Call task_complete with JSON:
25070
25643
  task: String(t.task ?? ""),
25071
25644
  rationale: String(t.rationale ?? ""),
25072
25645
  provenance: Array.isArray(t.provenance) ? t.provenance.map(String) : [],
25073
- category: ["directive", "exploration", "capability", "maintenance", "social"].includes(t.category) ? t.category : "exploration",
25646
+ category: ["directive", "exploration", "capability", "maintenance", "social", "autoresearch"].includes(t.category) ? t.category : "exploration",
25074
25647
  confidence: typeof t.confidence === "number" ? Math.min(1, Math.max(0, t.confidence)) : 0.5,
25075
25648
  challengeResult: t.challengeResult ? String(t.challengeResult) : void 0
25076
25649
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",