Skip to content
37 changes: 34 additions & 3 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,27 +243,58 @@ project/

**Purpose**: Self-referential development loop that runs until task completion

**Named after**: Anthropic's Ralph Wiggum plugin
**Named after**: [The Ralph Wiggum Loop](https://ghuntley.com/ralph/) - a technique for keeping LLMs in the "smart zone" by using fresh context per iteration

**Usage**:
```
/ralph-loop "Build a REST API with authentication"
/ralph-loop "Refactor the payment module" --max-iterations=50
/ralph-loop "Add test coverage" --strategy=continue
```

**Flags**:
| Flag | Description |
|------|-------------|
| `--max-iterations=N` | Maximum loop iterations (default: 100) |
| `--completion-promise=TEXT` | Custom completion tag (default: "DONE") |
| `--strategy=reset\|continue` | Override default strategy for this loop |

**Behavior**:
- Agent works continuously toward the goal
- Detects `<promise>DONE</promise>` to know when complete
- Auto-continues if agent stops without completion
- Ends when: completion detected, max iterations reached (default 100), or `/cancel-ralph`

**Configure**: `{ "ralph_loop": { "enabled": true, "default_max_iterations": 100 } }`
**Configure**:
```json
{
"ralph_loop": {
"enabled": true,
"default_max_iterations": 100,
"default_strategy": "reset"
}
}
```

**Strategy Options**:
| Strategy | Description |
|----------|-------------|
| `"reset"` (default) | Create a fresh session with clean context for each iteration - keeps LLM in "smart zone" |
| `"continue"` | Keep same session, accumulates context - may enter "dumb zone" on long loops |

Use `"reset"` (default) to prevent context overflow on long-running loops. This matches how the original bash-loop Ralph works where each iteration gets a fresh context window. Use `"continue"` only for short tasks where you need conversation history preserved.

### Command: /ulw-loop

**Purpose**: Same as ralph-loop but with ultrawork mode active

Everything runs at maximum intensity - parallel agents, background tasks, aggressive exploration.
**Usage**:
```
/ulw-loop "Build complex feature"
/ulw-loop "Major refactor" --strategy=reset --max-iterations=50
```

Everything runs at maximum intensity - parallel agents, background tasks, aggressive exploration. Supports the same flags as `/ralph-loop`.

### Command: /refactor

Expand Down
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {
SisyphusAgentConfigSchema,
ExperimentalConfigSchema,
RalphLoopConfigSchema,
ContextStrategySchema,
TmuxConfigSchema,
TmuxLayoutSchema,
} from "./schema"
Expand All @@ -25,6 +26,7 @@ export type {
ExperimentalConfig,
DynamicContextPruningConfig,
RalphLoopConfig,
ContextStrategy,
TmuxConfig,
TmuxLayout,
SisyphusConfig,
Expand Down
10 changes: 10 additions & 0 deletions src/config/schema/ralph-loop.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { z } from "zod"

export const ContextStrategySchema = z.enum(["reset", "continue"])

export const RalphLoopConfigSchema = z.object({
/** Enable ralph loop functionality (default: false - opt-in feature) */
enabled: z.boolean().default(false),
/** Default max iterations if not specified in command (default: 100) */
default_max_iterations: z.number().min(1).max(1000).default(100),
/** Custom state file directory relative to project root (default: .opencode/) */
state_dir: z.string().optional(),
/**
* Default context management strategy between loop iterations (default: "reset")
* Can be overridden per-loop with --strategy flag
* - "reset": Create a new session with fresh context for each iteration (recommended)
* - "continue": Keep same session and accumulate context across iterations
*/
default_strategy: ContextStrategySchema.optional(),
})

export type RalphLoopConfig = z.infer<typeof RalphLoopConfigSchema>
export type ContextStrategy = z.infer<typeof ContextStrategySchema>
4 changes: 2 additions & 2 deletions src/features/builtin-commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ ${RALPH_LOOP_TEMPLATE}
<user-task>
$ARGUMENTS
</user-task>`,
argumentHint: '"task description" [--completion-promise=TEXT] [--max-iterations=N]',
argumentHint: '"task description" [--completion-promise=TEXT] [--max-iterations=N] [--strategy=reset|continue]',
},
"ulw-loop": {
description: "(builtin) Start ultrawork loop - continues until completion with ultrawork mode",
Expand All @@ -39,7 +39,7 @@ ${RALPH_LOOP_TEMPLATE}
<user-task>
$ARGUMENTS
</user-task>`,
argumentHint: '"task description" [--completion-promise=TEXT] [--max-iterations=N]',
argumentHint: '"task description" [--completion-promise=TEXT] [--max-iterations=N] [--strategy=reset|continue]',
},
"cancel-ralph": {
description: "(builtin) Cancel active Ralph Loop",
Expand Down
6 changes: 4 additions & 2 deletions src/features/builtin-commands/templates/ralph-loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ export const RALPH_LOOP_TEMPLATE = `You are starting a Ralph Loop - a self-refer
## Your Task

Parse the arguments below and begin working on the task. The format is:
\`"task description" [--completion-promise=TEXT] [--max-iterations=N]\`
\`"task description" [--completion-promise=TEXT] [--max-iterations=N] [--strategy=reset|continue]\`

Default completion promise is "DONE" and default max iterations is 100.`
Default completion promise is "DONE", default max iterations is 100, default strategy is "reset".
- **reset**: Create a new session with fresh context for each iteration (recommended for long tasks)
- **continue**: Keep same session and accumulate context across iterations`

export const CANCEL_RALPH_TEMPLATE = `Cancel the currently active Ralph Loop.

Expand Down
3 changes: 3 additions & 0 deletions src/hooks/ralph-loop/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { ContextStrategy } from "../../config"

export const HOOK_NAME = "ralph-loop"
export const DEFAULT_STATE_FILE = ".sisyphus/ralph-loop.local.md"
export const COMPLETION_TAG_PATTERN = /<promise>(.*?)<\/promise>/is
export const DEFAULT_MAX_ITERATIONS = 100
export const DEFAULT_COMPLETION_PROMISE = "DONE"
export const DEFAULT_STRATEGY: ContextStrategy = "reset"
49 changes: 39 additions & 10 deletions src/hooks/ralph-loop/continuation-prompt-builder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { SYSTEM_DIRECTIVE_PREFIX } from "../../shared/system-directive"
import { RALPH_LOOP_TEMPLATE } from "../../features/builtin-commands/templates/ralph-loop"
import type { RalphLoopState } from "./types"
import { DEFAULT_STRATEGY } from "./constants"

const CONTINUATION_PROMPT = `${SYSTEM_DIRECTIVE_PREFIX} - RALPH LOOP {{ITERATION}}/{{MAX}}]

Your previous attempt did not output the completion promise. Continue working on the task.
const CONTINUE_STRATEGY_PROMPT = `Your previous attempt did not output the completion promise. Continue working on the task.

IMPORTANT:
- Review your progress so far
Expand All @@ -14,14 +14,43 @@ IMPORTANT:
Original task:
{{PROMPT}}`

function getResetStrategyPrompt(prompt: string): string {
return `<command-instruction>
${RALPH_LOOP_TEMPLATE}
</command-instruction>

<user-task>
${prompt}
</user-task>`
}

function getIterationPrompt(
iteration: number,
max: number,
strategy: "reset" | "continue",
prompt: string,
): string {
if (strategy === "continue") {
const prefix = `${SYSTEM_DIRECTIVE_PREFIX} - RALPH LOOP ${iteration}/${max}]`
return `${prefix}\n\n${CONTINUE_STRATEGY_PROMPT}`
.replace("{{PROMPT}}", prompt)
}
const prefix = `[RALPH LOOP - Iteration ${iteration}/${max}]`
return `${prefix}\n\n${getResetStrategyPrompt(prompt)}`
}

export function buildContinuationPrompt(state: RalphLoopState): string {
const continuationPrompt = CONTINUATION_PROMPT.replace(
"{{ITERATION}}",
String(state.iteration),
const strategy = state.strategy ?? DEFAULT_STRATEGY
let iterationPrompt = getIterationPrompt(
state.iteration,
state.max_iterations,
strategy,
state.prompt,
)
.replace("{{MAX}}", String(state.max_iterations))
.replace("{{PROMISE}}", state.completion_promise)
.replace("{{PROMPT}}", state.prompt)

return state.ultrawork ? `ultrawork ${continuationPrompt}` : continuationPrompt
if (strategy === "continue") {
iterationPrompt = iterationPrompt.replace("{{PROMISE}}", state.completion_promise)
}

return state.ultrawork ? `ultrawork ${iterationPrompt}` : iterationPrompt
}
Loading