Skip to content
23 changes: 23 additions & 0 deletions packages/core/src/core/prompts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,29 @@ describe('Core System Prompt (prompts.ts)', () => {
expect(prompt).toMatchSnapshot();
});
});

it('should include YOLO mode instructions in interactive mode', () => {
vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.YOLO);
vi.mocked(mockConfig.isInteractive).mockReturnValue(true);
const prompt = getCoreSystemPrompt(mockConfig);
expect(prompt).toContain('# Autonomous Mode (YOLO)');
expect(prompt).toContain('Only use the `ask_user` tool if');
});

it('should NOT include YOLO mode instructions in non-interactive mode', () => {
vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.YOLO);
vi.mocked(mockConfig.isInteractive).mockReturnValue(false);
const prompt = getCoreSystemPrompt(mockConfig);
expect(prompt).not.toContain('# Autonomous Mode (YOLO)');
});

it('should NOT include YOLO mode instructions for DEFAULT mode', () => {
vi.mocked(mockConfig.getApprovalMode).mockReturnValue(
ApprovalMode.DEFAULT,
);
const prompt = getCoreSystemPrompt(mockConfig);
expect(prompt).not.toContain('# Autonomous Mode (YOLO)');
});
});

describe('Platform-specific and Background Process instructions', () => {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/policy/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,8 @@ describe('createPolicyEngineConfig', () => {
(r) => r.decision === PolicyDecision.ALLOW && !r.toolName,
);
expect(rule).toBeDefined();
// Priority 999 in default tier → 1.999
expect(rule?.priority).toBeCloseTo(1.999, 5);
// Priority 998 in default tier → 1.998 (999 reserved for ask_user exception)
expect(rule?.priority).toBeCloseTo(1.998, 5);
});

it('should allow edit tool in AUTO_EDIT mode', async () => {
Expand Down Expand Up @@ -582,8 +582,8 @@ describe('createPolicyEngineConfig', () => {
(r) => !r.toolName && r.decision === PolicyDecision.ALLOW,
);
expect(wildcardRule).toBeDefined();
// Priority 999 in default tier → 1.999
expect(wildcardRule?.priority).toBeCloseTo(1.999, 5);
// Priority 998 in default tier → 1.998 (999 reserved for ask_user exception)
expect(wildcardRule?.priority).toBeCloseTo(1.998, 5);

// Write tool ASK_USER rules are present (from write.toml)
const writeToolRules = config.rules?.filter(
Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/policy/policies/yolo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,21 @@
# 10: Write tools default to ASK_USER (becomes 1.010 in default tier)
# 15: Auto-edit tool override (becomes 1.015 in default tier)
# 50: Read-only tools (becomes 1.050 in default tier)
# 999: YOLO mode allow-all (becomes 1.999 in default tier)
# 998: YOLO mode allow-all (becomes 1.998 in default tier)
# 999: Ask-user tool (becomes 1.999 in default tier)

# Ask-user tool always requires user interaction, even in YOLO mode.
# This ensures the model can gather user preferences/decisions when needed.
# Note: In non-interactive mode, this decision is converted to DENY by the policy engine.
[[rule]]
decision = "allow"
toolName = "ask_user"
decision = "ask_user"
priority = 999
modes = ["yolo"]

# Allow everything else in YOLO mode
[[rule]]
decision = "allow"
priority = 998
modes = ["yolo"]
allow_redirection = true
56 changes: 56 additions & 0 deletions packages/core/src/policy/policy-engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2030,4 +2030,60 @@ describe('PolicyEngine', () => {
expect(result.decision).toBe(PolicyDecision.DENY);
});
});

describe('YOLO mode with ask_user tool', () => {
it('should return ASK_USER for ask_user tool even in YOLO mode', async () => {
const rules: PolicyRule[] = [
{
toolName: 'ask_user',
decision: PolicyDecision.ASK_USER,
priority: 999,
modes: [ApprovalMode.YOLO],
},
{
decision: PolicyDecision.ALLOW,
priority: 998,
modes: [ApprovalMode.YOLO],
},
];

engine = new PolicyEngine({
rules,
approvalMode: ApprovalMode.YOLO,
});

const result = await engine.check(
{ name: 'ask_user', args: {} },
undefined,
);
expect(result.decision).toBe(PolicyDecision.ASK_USER);
});

it('should return ALLOW for other tools in YOLO mode', async () => {
const rules: PolicyRule[] = [
{
toolName: 'ask_user',
decision: PolicyDecision.ASK_USER,
priority: 999,
modes: [ApprovalMode.YOLO],
},
{
decision: PolicyDecision.ALLOW,
priority: 998,
modes: [ApprovalMode.YOLO],
},
];

engine = new PolicyEngine({
rules,
approvalMode: ApprovalMode.YOLO,
});

const result = await engine.check(
{ name: 'run_shell_command', args: { command: 'ls' } },
undefined,
);
expect(result.decision).toBe(PolicyDecision.ALLOW);
});
});
});
6 changes: 6 additions & 0 deletions packages/core/src/prompts/promptProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class PromptProvider {
const interactiveMode = interactiveOverride ?? config.isInteractive();
const approvalMode = config.getApprovalMode?.() ?? ApprovalMode.DEFAULT;
const isPlanMode = approvalMode === ApprovalMode.PLAN;
const isYoloMode = approvalMode === ApprovalMode.YOLO;
const skills = config.getSkillManager().getSkills();
const toolNames = config.getToolRegistry().getAllToolNames();
const enabledToolNames = new Set(toolNames);
Expand Down Expand Up @@ -183,6 +184,11 @@ export class PromptProvider {
}),
),
sandbox: this.withSection('sandbox', () => getSandboxMode()),
interactiveYoloMode: this.withSection(
'interactiveYoloMode',
() => true,
isYoloMode && interactiveMode,
),
gitRepo: this.withSection(
'git',
() => ({ interactive: interactiveMode }),
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/prompts/snippets.legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface SystemPromptOptions {
planningWorkflow?: PlanningWorkflowOptions;
operationalGuidelines?: OperationalGuidelinesOptions;
sandbox?: SandboxMode;
interactiveYoloMode?: boolean;
gitRepo?: GitRepoOptions;
finalReminder?: FinalReminderOptions;
}
Expand Down Expand Up @@ -114,6 +115,8 @@ ${

${renderOperationalGuidelines(options.operationalGuidelines)}

${renderInteractiveYoloMode(options.interactiveYoloMode)}

${renderSandbox(options.sandbox)}

${renderGitRepo(options.gitRepo)}
Expand Down Expand Up @@ -293,6 +296,25 @@ You are running outside of a sandbox container, directly on the user's system. F
}
}

export function renderInteractiveYoloMode(enabled?: boolean): string {
if (!enabled) return '';
return `
# Autonomous Mode (YOLO)

You are operating in **autonomous mode**. The user has requested minimal interruption.

**Only use the \`${ASK_USER_TOOL_NAME}\` tool if:**
- A wrong decision would cause significant re-work
- The request is fundamentally ambiguous with no reasonable default
- The user explicitly asks you to confirm or ask questions

**Otherwise, work autonomously:**
- Make reasonable decisions based on context and existing code patterns
- Follow established project conventions
- If multiple valid approaches exist, choose the most robust option
`.trim();
}

export function renderGitRepo(options?: GitRepoOptions): string {
if (!options) return '';
return `
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/prompts/snippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface SystemPromptOptions {
planningWorkflow?: PlanningWorkflowOptions;
operationalGuidelines?: OperationalGuidelinesOptions;
sandbox?: SandboxMode;
interactiveYoloMode?: boolean;
gitRepo?: GitRepoOptions;
}

Expand Down Expand Up @@ -111,6 +112,8 @@ ${

${renderOperationalGuidelines(options.operationalGuidelines)}

${renderInteractiveYoloMode(options.interactiveYoloMode)}

${renderSandbox(options.sandbox)}

${renderGitRepo(options.gitRepo)}
Expand Down Expand Up @@ -312,6 +315,25 @@ export function renderSandbox(mode?: SandboxMode): string {
return '';
}

export function renderInteractiveYoloMode(enabled?: boolean): string {
if (!enabled) return '';
return `
# Autonomous Mode (YOLO)

You are operating in **autonomous mode**. The user has requested minimal interruption.
Copy link
Member

@gundermanc gundermanc Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be great to have 1-3 evals to cover the major scenarios.

  • "Significant rework"
  • "Ambiguous request"
  • "Explicitly asked"

These can be a bit of a pain to implement so I think it's also fine if you just wanted to add one.


**Only use the \`${ASK_USER_TOOL_NAME}\` tool if:**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may find we get better behavior with at least one example.

- A wrong decision would cause significant re-work
- The request is fundamentally ambiguous with no reasonable default
- The user explicitly asks you to confirm or ask questions

**Otherwise, work autonomously:**
- Make reasonable decisions based on context and existing code patterns
- Follow established project conventions
- If multiple valid approaches exist, choose the most robust option
`.trim();
}

export function renderGitRepo(options?: GitRepoOptions): string {
if (!options) return '';
return `
Expand Down
Loading