opencode-ralph-rlm 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -228,6 +228,8 @@ Create `.opencode/ralph.json`. All fields are optional — the plugin runs with
228
228
  | `statusVerbosity` | `"normal"` | Supervisor status emission level: `minimal` (warnings/errors), `normal`, or `verbose`. |
229
229
  | `maxAttempts` | `20` | Hard stop after this many failed verify attempts. |
230
230
  | `heartbeatMinutes` | `15` | Warn if active strategist/worker has no progress for this many minutes. |
231
+ | `strategistHandoffMinutes` | `5` | Warn/retry if the strategist does not spawn a worker within this many minutes. |
232
+ | `strategistHandoffMaxRetries` | `2` | Max retries to respawn strategist after a missed handoff. |
231
233
  | `verifyTimeoutMinutes` | `0` | Timeout for verify command in minutes. `0` disables timeouts. |
232
234
  | `verify.command` | - | Shell command to run as an array, e.g. `["bun", "run", "verify"]`. If omitted, verify always returns `unknown`. |
233
235
  | `verify.cwd` | `"."` | Working directory for the verify command, relative to the repo root. |
package/dist/ralph-rlm.js CHANGED
@@ -23417,6 +23417,8 @@ var RalphConfigSchema = exports_Schema.Struct({
23417
23417
  statusVerbosity: exports_Schema.optional(exports_Schema.Union(exports_Schema.Literal("minimal"), exports_Schema.Literal("normal"), exports_Schema.Literal("verbose"))),
23418
23418
  maxAttempts: exports_Schema.optional(exports_Schema.Number),
23419
23419
  heartbeatMinutes: exports_Schema.optional(exports_Schema.Number),
23420
+ strategistHandoffMinutes: exports_Schema.optional(exports_Schema.Number),
23421
+ strategistHandoffMaxRetries: exports_Schema.optional(exports_Schema.Number),
23420
23422
  verifyTimeoutMinutes: exports_Schema.optional(exports_Schema.Number),
23421
23423
  verify: exports_Schema.optional(VerifyConfigSchema),
23422
23424
  gateDestructiveToolsUntilContextLoaded: exports_Schema.optional(exports_Schema.Boolean),
@@ -23456,6 +23458,8 @@ var CONFIG_DEFAULTS = {
23456
23458
  statusVerbosity: "normal",
23457
23459
  maxAttempts: 20,
23458
23460
  heartbeatMinutes: 15,
23461
+ strategistHandoffMinutes: 5,
23462
+ strategistHandoffMaxRetries: 2,
23459
23463
  verifyTimeoutMinutes: 0,
23460
23464
  gateDestructiveToolsUntilContextLoaded: true,
23461
23465
  maxRlmSliceLines: 200,
@@ -23482,6 +23486,8 @@ function resolveConfig(raw) {
23482
23486
  statusVerbosity: raw.statusVerbosity ?? CONFIG_DEFAULTS.statusVerbosity,
23483
23487
  maxAttempts: toBoundedInt(raw.maxAttempts, CONFIG_DEFAULTS.maxAttempts, 1, 500),
23484
23488
  heartbeatMinutes: toBoundedInt(raw.heartbeatMinutes, CONFIG_DEFAULTS.heartbeatMinutes, 1, 240),
23489
+ strategistHandoffMinutes: toBoundedInt(raw.strategistHandoffMinutes, CONFIG_DEFAULTS.strategistHandoffMinutes, 1, 60),
23490
+ strategistHandoffMaxRetries: toBoundedInt(raw.strategistHandoffMaxRetries, CONFIG_DEFAULTS.strategistHandoffMaxRetries, 0, 10),
23485
23491
  verifyTimeoutMinutes: toBoundedInt(raw.verifyTimeoutMinutes, CONFIG_DEFAULTS.verifyTimeoutMinutes, 0, 240),
23486
23492
  ...verify !== undefined ? { verify } : {},
23487
23493
  gateDestructiveToolsUntilContextLoaded: raw.gateDestructiveToolsUntilContextLoaded ?? CONFIG_DEFAULTS.gateDestructiveToolsUntilContextLoaded,
@@ -23514,6 +23520,7 @@ var DEFAULT_TEMPLATES = {
23514
23520
  " When you receive one, call ralph_respond(id, answer) to unblock the session.",
23515
23521
  "- Use ralph_doctor() to check setup, ralph_bootstrap_plan() to generate PLAN/TODOS,",
23516
23522
  " ralph_create_supervisor_session() to bind/start explicitly, ralph_pause_supervision()/ralph_resume_supervision() to control execution, and ralph_end_supervision() to stop.",
23523
+ "- Only call loop-control tools (spawn, pause, resume, end) after the supervisor session is bound via ralph_create_supervisor_session().",
23517
23524
  "- End supervision when verification has passed and the user confirms they are done, or when the user explicitly asks to stop the loop.",
23518
23525
  "- Optional reviewer flow: worker marks readiness with ralph_request_review(); supervisor runs ralph_run_reviewer().",
23519
23526
  "- Monitor progress in SUPERVISOR_LOG.md, CONVERSATION.md, or via toast notifications.",
@@ -23700,6 +23707,7 @@ var DEFAULT_TEMPLATES = {
23700
23707
  "- You do NOT write code yourself; you are not the RLM worker.",
23701
23708
  "- After reviewing state and optionally updating PLAN.md / RLM_INSTRUCTIONS.md,",
23702
23709
  " call ralph_spawn_worker() to hand off to the RLM worker for this attempt.",
23710
+ "- You MUST call ralph_spawn_worker() exactly once per attempt.",
23703
23711
  "- Then STOP. The plugin verifies independently and will spawn the next Ralph session if needed.",
23704
23712
  "",
23705
23713
  "Role boundaries:",
@@ -23720,7 +23728,7 @@ var DEFAULT_TEMPLATES = {
23720
23728
  " guidance for the next worker based on patterns in the failures.",
23721
23729
  "5. Optionally call ralph_set_status('running', 'strategy finalized').",
23722
23730
  "6. Call ralph_report() summarizing strategy changes for this attempt.",
23723
- "7. Call ralph_spawn_worker() to delegate the coding work to a fresh RLM worker.",
23731
+ "7. Call ralph_spawn_worker() to delegate the coding work to a fresh RLM worker (required).",
23724
23732
  "8. STOP \u2014 the plugin handles verification and will spawn attempt {{nextAttempt}} if needed.",
23725
23733
  "",
23726
23734
  "You do not write code. Your value is strategic context adjustment between attempts.",
@@ -23728,7 +23736,10 @@ var DEFAULT_TEMPLATES = {
23728
23736
  "Tool meaning:",
23729
23737
  "- ralph_update_plan / ralph_update_rlm_instructions = durable strategy changes",
23730
23738
  "- ralph_spawn_worker = handoff to implementation session",
23731
- "- ralph_report = visible summary for the supervisor"
23739
+ "- ralph_report = visible summary for the supervisor",
23740
+ "",
23741
+ "Example flow:",
23742
+ '- ralph_load_context() \u2192 ralph_report("Strategy: update PLAN.md with constraint X") \u2192 ralph_spawn_worker() \u2192 STOP'
23732
23743
  ].join(`
23733
23744
  `)
23734
23745
  };
@@ -25733,6 +25744,8 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25733
25744
  mutateSession(sessionID, (s) => {
25734
25745
  s.workerSpawned = true;
25735
25746
  });
25747
+ supervisor.ralphHandoffRetries = 0;
25748
+ supervisor.ralphHandoffAttempt = st.attempt;
25736
25749
  await notifySupervisor(`ralph/attempt-${st.attempt}`, `Delegated coding to worker session ${workerId}.`, "info", true, sessionID);
25737
25750
  return JSON.stringify({ ok: true, workerSessionId: workerId, attempt: st.attempt }, null, 2);
25738
25751
  };
@@ -25741,6 +25754,10 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25741
25754
  body: { title: `ralph-strategist-attempt-${attempt}` }
25742
25755
  });
25743
25756
  const ralphId = result.data?.id ?? `ralph-${Date.now()}`;
25757
+ if (supervisor.ralphHandoffAttempt !== attempt) {
25758
+ supervisor.ralphHandoffAttempt = attempt;
25759
+ supervisor.ralphHandoffRetries = 0;
25760
+ }
25744
25761
  supervisor.currentRalphSessionId = ralphId;
25745
25762
  sessionMap.set(ralphId, freshSession("ralph", attempt));
25746
25763
  mutateSession(ralphId, (s) => {
@@ -25762,7 +25779,7 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25762
25779
  clearTimeout(supervisor.ralphHandoffTimer);
25763
25780
  }
25764
25781
  const cfg = await run(getConfig());
25765
- const timeoutMs = Math.max(cfg.heartbeatMinutes, 1) * 60000;
25782
+ const timeoutMs = Math.max(cfg.strategistHandoffMinutes, 1) * 60000;
25766
25783
  supervisor.ralphHandoffTimer = setTimeout(async () => {
25767
25784
  if (supervisor.done || supervisor.paused)
25768
25785
  return;
@@ -25771,7 +25788,17 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25771
25788
  const st = sessionMap.get(ralphId);
25772
25789
  if (st?.workerSpawned)
25773
25790
  return;
25774
- await notifySupervisor(`ralph/attempt-${attempt}`, "Strategist did not hand off to a worker within the heartbeat window. Re-check prompt delivery or restart supervision.", "warning", true, ralphId);
25791
+ const retries = supervisor.ralphHandoffRetries ?? 0;
25792
+ if (retries < cfg.strategistHandoffMaxRetries) {
25793
+ supervisor.ralphHandoffRetries = retries + 1;
25794
+ await notifySupervisor(`ralph/attempt-${attempt}`, `Strategist did not hand off; retrying strategist (${retries + 1}/${cfg.strategistHandoffMaxRetries}).`, "warning", true, ralphId);
25795
+ supervisor.currentRalphSessionId = undefined;
25796
+ await client.session.abort({ path: { id: ralphId } }).catch(() => {});
25797
+ await spawnRalphSession(attempt);
25798
+ return;
25799
+ }
25800
+ await notifySupervisor(`ralph/attempt-${attempt}`, "Strategist did not hand off after retries; supervision paused. Use ralph_create_supervisor_session(restart_if_done=true) to retry.", "error", true, ralphId);
25801
+ supervisor.paused = true;
25775
25802
  }, timeoutMs);
25776
25803
  await notifySupervisor(`supervisor/attempt-${attempt}`, `Spawned Ralph strategist session ${ralphId}.`, "info", true);
25777
25804
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ralph-rlm",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "OpenCode plugin: Ralph outer loop + RLM inner loop. Iterative AI development with file-first discipline and sub-agent support.",
5
5
  "type": "module",
6
6
  "main": "./dist/ralph-rlm.js",