Files
letta-code/src/ralph/mode.ts
Charles Packer c96a5204eb feat: add Ralph Wiggum mode (#484)
Co-authored-by: Letta <noreply@letta.com>
2026-01-06 21:13:50 -08:00

162 lines
4.4 KiB
TypeScript

// src/ralph/mode.ts
// Ralph Wiggum mode state management
// Singleton pattern matching src/permissions/mode.ts
// Default completion promise inspired by The_Whole_Daisy's recommendations
// Source: X post 2008625420741341355
export const DEFAULT_COMPLETION_PROMISE =
"The task is complete. All requirements have been implemented and verified working. " +
"Any tests that were relevant have been run and are passing. The implementation is " +
"clean and production-ready. I have not taken any shortcuts or faked anything to " +
"meet these requirements.";
export type RalphState = {
isActive: boolean;
isYolo: boolean;
originalPrompt: string;
completionPromise: string | null; // null = no promise check (Claude Code style)
maxIterations: number; // 0 = unlimited
currentIteration: number;
};
// Use globalThis to ensure singleton across bundle
const RALPH_KEY = Symbol.for("@letta/ralphMode");
type GlobalWithRalph = typeof globalThis & {
[RALPH_KEY]: RalphState;
};
function getDefaultState(): RalphState {
return {
isActive: false,
isYolo: false,
originalPrompt: "",
completionPromise: null,
maxIterations: 0,
currentIteration: 0,
};
}
function getGlobalState(): RalphState {
const global = globalThis as GlobalWithRalph;
if (!global[RALPH_KEY]) {
global[RALPH_KEY] = getDefaultState();
}
return global[RALPH_KEY];
}
function setGlobalState(state: RalphState): void {
const global = globalThis as GlobalWithRalph;
global[RALPH_KEY] = state;
}
/**
* Ralph Wiggum mode state manager.
* Implements iterative development loops where the agent keeps working
* until it outputs a completion promise.
*/
class RalphModeManager {
/**
* Activate Ralph mode with the given configuration.
* @param prompt - The task prompt
* @param completionPromise - Promise text to check for (null = no check, uses default if undefined)
* @param maxIterations - Max iterations before auto-stop (0 = unlimited)
* @param isYolo - Whether to bypass permissions
*/
activate(
prompt: string,
completionPromise: string | null | undefined,
maxIterations: number,
isYolo: boolean,
): void {
// If completionPromise is undefined, use default
// If it's null or empty string, that means "no promise check" (Claude Code style)
let resolvedPromise: string | null;
if (completionPromise === undefined) {
resolvedPromise = DEFAULT_COMPLETION_PROMISE;
} else if (
completionPromise === null ||
completionPromise === "" ||
completionPromise.toLowerCase() === "none"
) {
resolvedPromise = null;
} else {
resolvedPromise = completionPromise;
}
setGlobalState({
isActive: true,
isYolo,
originalPrompt: prompt,
completionPromise: resolvedPromise,
maxIterations,
currentIteration: 1,
});
}
/**
* Deactivate Ralph mode and reset state.
*/
deactivate(): void {
setGlobalState(getDefaultState());
}
/**
* Get current Ralph mode state.
*/
getState(): RalphState {
return getGlobalState();
}
/**
* Increment the iteration counter.
*/
incrementIteration(): void {
const state = getGlobalState();
setGlobalState({
...state,
currentIteration: state.currentIteration + 1,
});
}
/**
* Check if the assistant's output contains the completion promise.
* Uses regex to find <promise>...</promise> tags.
* @param text - The assistant's output text
* @returns true if promise was found and matches
*/
checkForPromise(text: string): boolean {
const state = getGlobalState();
if (!state.completionPromise) return false;
// Match <promise>...</promise> tags (case insensitive, handles multiline)
const match = text.match(/<promise>([\s\S]*?)<\/promise>/i);
if (!match || match[1] === undefined) return false;
// Normalize whitespace and compare
const promiseText = match[1].trim().replace(/\s+/g, " ");
const expected = state.completionPromise.trim().replace(/\s+/g, " ");
return promiseText === expected;
}
/**
* Check if the loop should continue.
* @returns true if active and under iteration limit
*/
shouldContinue(): boolean {
const state = getGlobalState();
if (!state.isActive) return false;
if (
state.maxIterations > 0 &&
state.currentIteration >= state.maxIterations
) {
return false;
}
return true;
}
}
// Singleton instance
export const ralphMode = new RalphModeManager();