Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@

### Fixed

- Stop appending Ultracode capability tutorials on every turn. Announce session-mode transitions after user input, reuse unchanged state, and restore necessary reminders when context is compacted or replaced.
- Recognize `/exit` as an alias for `/quit`, including while the agent is running or compacting.
- Recognize `search_files`, `find_files`, and `list_directory` when selecting default system-prompt file-exploration guidance.
31 changes: 21 additions & 10 deletions docs/orchestration-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,27 @@ prompt's `+500k` or `+1.5m` directive supplies a default token budget for its
workflow calls. Saved workflow invocations and skills can also authorize
workflow use.

Capability discovery is separate from consent. When workflows are registered,
the model receives guidance that maps both names to `workflow` and states the
current session mode and tool-profile availability. `find_tools` only searches
tools; it does not activate session mode or expand tool access. Registration,
welcome tips, and discovery guidance do not authorize workflow use. The guidance
does not replace the dynamic system prompt, so active-tool guidance can refresh
when active tools change. The `ultraloop-opt-in` reminder remains tied to a per-turn or
session signal. Opt-in is a model-guidance and journaling contract; off-consent
calls are recorded, not rejected by a hard permission gate. Tool permissions
and model thinking settings remain separate from this mode.
Capability discovery is separate from consent. Welcome tips, slash completion,
and command help expose the controls to users. The workflow tool description
explains both names and the opt-in rules to the model; ordinary opted-out turns
do not append a capability tutorial. `find_tools` only searches tools; it does
not activate session mode or expand tool access.

Session-mode reminders follow the last runtime-authored state in the active
context: entering the mode adds an on reminder, unchanged turns add nothing,
and leaving an announced mode adds an off reminder that revokes standing consent.
A new per-turn opt-in still gets its own scoped reminder. The real user message
comes first, followed by the hidden runtime reminder. Internally these are custom
messages; the provider conversion represents them as `user` messages.

Compaction, live-context editing, or switching branches may remove a previous
notice. The runtime reconciles the currently visible context and supplies a
request-local mode reminder when needed, including an off transition during
streaming. These fallback notices do not accumulate in stored session history.
The dynamic system prompt still refreshes when active tools change. Opt-in is a
model-guidance and journaling contract; off-consent calls are recorded, not
rejected by a hard permission gate. Tool permissions and model thinking settings
remain separate from this mode.

A product run may contain multiple low-level attempts because of retries or
context compaction. The opt-in and prompt budget survive those attempts and
Expand Down
16 changes: 8 additions & 8 deletions packages/coding-agent/src/features/step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,14 @@ export function createStepExtension(options: StepExtensionOptions = {}): Extensi
nativeRetryOverridden = false;
}
if (autoResume) {
if (!nativeRetryOverridden && ctx.autoRetryEnabled !== true) {
nativeRetryOverridden = true;
}
try {
ctx.setAutoRetryEnabled(true);
} catch {
// Retry is a product convenience. A host that cannot persist settings
// must still be able to execute the current turn.
if (ctx.autoRetryEnabled !== true) {
if (!nativeRetryOverridden) nativeRetryOverridden = true;
try {
ctx.setAutoRetryEnabled(true);
} catch {
// Retry is a product convenience. A host that cannot persist settings
// must still be able to execute the current turn.
}
}
} else if (nativeRetryOverridden) {
try {
Expand Down
4 changes: 3 additions & 1 deletion packages/coding-agent/src/features/workflow/step-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ export function createStepWorkflowExtension(options: StepWorkflowExtensionOption
renderResult: renderWorkflowResult,
description: `Run an isolated JavaScript workflow that coordinates many agents through phase(), parallel(), pipeline(), agent(), and the HoH iterate() loop. Scripts run in a sandboxed VM (no process, require, network, wall clock, or randomness); every agent call is journaled under .stepcode/workflows/runs so runs can resume. Provide exactly one of script (inline), scriptPath, or name (saved workflow); /workflows lists saved workflows and recent runs.

OPT-IN REQUIRED. Use this tool only when the user has opted in via one of: the keyword "ultraloop" (or Claude Code's spelling "ultracode") in the current message; an explicit request such as "use a workflow", "run a workflow", "fan out agents", or "orchestrate this with subagents"; ultraloop turned on for the session (a system-reminder confirms it — see the Ultraloop section below); a saved workflow invoked by name; or a skill that instructs it. The harness attaches a customType:"ultraloop-opt-in" system-reminder to the opting turn — per-turn signals produce a "the user included ..." reminder, session-standing mode produces an "Ultraloop is on for the session ..." reminder that repeats every turn until /ultraloop off. Saved-workflow or skill invocations are valid opt-in even without a reminder. Never infer opt-in from task shape — a large or parallel-looking task is not consent, per-turn opt-in from an earlier turn does not carry forward, and this tool being registered is an environment capability, not consent. Without any signal, prefer targeted tools: a single subagent (Agent tool), search_files, or read_file; for calendar-based deferral prefer cron_create; for a multi-turn objective prefer create_goal.
Ultracode and Ultraloop name this same multi-agent capability. Interactive controls are /ultracode on, off, status, and help; /ultraloop is an alias. For a single task use ultracode: or ultraloop:. When asked about these modes, explain the controls without starting a workflow. These controls do not expand the active tool profile.

OPT-IN REQUIRED. Use this tool only when the user has opted in via one of: the keyword "ultraloop" (or "ultracode") in the current message; an explicit request such as "use a workflow", "run a workflow", "fan out agents", or "orchestrate this with subagents"; ultraloop turned on for the session (a system-reminder confirms it — see the Ultraloop section below); a saved workflow invoked by name; or a skill that instructs it. The harness attaches a customType:"ultraloop-opt-in" system-reminder after the user message on an opting turn. Session-mode reminders announce changes: the latest on/off state in the current context is authoritative; an unchanged state is not repeated, and an off reminder revokes earlier standing consent. Saved-workflow or skill invocations are valid opt-in even without a reminder. Never infer opt-in from task shape — a large or parallel-looking task is not consent, per-turn opt-in from an earlier turn does not carry forward, and this tool being registered is an environment capability, not consent. Without any signal, prefer targeted tools: a single subagent (Agent tool), search_files, or read_file; for calendar-based deferral prefer cron_create; for a multi-turn objective prefer create_goal.

Ultraloop session-standing mode. When a system-reminder confirms ultraloop is on for the session, the opt-in is standing until /ultraloop off: author and run a workflow for every substantive task by default, the goal is the most exhaustive, correct answer you can produce, and token cost is not a constraint. For multi-phase work (understand → design → implement → review), that often means several workflows in sequence — one per phase — so you stay in the loop between them. Lean toward orchestrating with workflows and adversarially verifying findings unless the work is already verified. Solo only on conversational turns or trivial mechanical edits. When session mode is off, revert to the per-turn opt-in rule above.

Expand Down
112 changes: 69 additions & 43 deletions packages/coding-agent/src/features/workflow/ultraloop-opt-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
*
* 2) session-standing mode: the user turns on ultraloop for the whole
* session via /ultraloop on. A session-scoped system-reminder is
* attached to every subsequent turn until /ultraloop off, or the
* session ends. Mirrors Claude Code's "Ultracode is on for the session"
* opt-in.
* attached when the mode differs from the last state in the active
* context. Unchanged turns reuse it; turning off revokes standing consent.
*
* Soft gate only — off-consent workflow calls are journaled via appendEntry,
* never blocked (no silent behavior; saved-workflow by name and skill-driven
Expand Down Expand Up @@ -64,28 +63,58 @@ export function detectUltraloopBudget(prompt: string): number | undefined {
return Number.isFinite(value) && value > 0 ? value : undefined;
}

export function buildUltraloopReminder(token: string): string {
export function buildUltraloopReminder(token: string, workflowAvailable = true): string {
const lower = token.toLowerCase();
const signal = lower === "ultraloop" || lower === "ultracode" ? `the keyword "${lower}"` : `the phrase "${token}"`;
if (!workflowAvailable) {
return `<system-reminder>The user included ${signal}, requesting multi-agent orchestration for this turn. The workflow tool is not active in the current tool profile. Explain this limitation instead of attempting to call it; the session switch and find_tools do not expand tool access.</system-reminder>`;
}
return `<system-reminder>The user included ${signal}, opting this turn into multi-agent orchestration — use the workflow tool to fulfill the request.</system-reminder>`;
}

export function buildUltraloopSessionReminder(): string {
return "<system-reminder>Ultraloop is on for the session — the user has authorized multi-agent orchestration until they turn it off with /ultraloop off. Author and run a workflow for every substantive task by default; the goal is the most exhaustive, correct answer you can produce, and token cost is not a constraint. For multi-phase work (understand → design → implement → review), run several workflows in sequence — one per phase — so you stay in the loop between them. Lean toward orchestrating with workflows and adversarially verifying findings unless the work is already verified. Solo only on conversational turns or trivial mechanical edits.</system-reminder>";
}

/** Explain the mode and its current scope without changing the active tool profile. */
function buildUltraloopDiscovery(activeTools: readonly string[], sessionMode: boolean): string {
const route = activeTools.includes("workflow")
? "The workflow tool is active; use it when the user requests orchestration."
: "The workflow tool is not active in the current tool profile. Explain that limitation; the session switch does not change tool access.";
return [
"<system-reminder>Ultracode and Ultraloop are names for the same multi-agent workflow capability.",
`Current session mode: ${sessionMode ? "on" : "off"}.`,
"Interactive controls: /ultracode on, /ultracode off, /ultracode status, /ultracode help; /ultraloop is an alias. For one turn, prefix the task with ultracode: or ultraloop:.",
route,
"This capability notice is not consent to start a workflow or enable session mode. For questions about these modes, explain the controls; running a workflow is unnecessary.</system-reminder>",
].join("\n");
function buildUltraloopSessionOffReminder(): string {
return "<system-reminder>Ultracode session mode is off. Previous session-wide workflow authorization no longer applies. Use workflow only for a new explicit per-turn request, a saved workflow invoked by name, or a skill instruction. Do not carry earlier per-turn authorization forward.</system-reminder>";
}

interface ReminderRecord {
customType: string;
content: unknown;
details?: unknown;
}

/** Inspect only runtime-authored entries in the selected context, not user text. */
function lastSessionMode(records: readonly ReminderRecord[]): boolean | undefined {
for (let index = records.length - 1; index >= 0; index--) {
const record = records[index]!;
if (record.customType !== "ultraloop-opt-in" && record.customType !== "ultraloop-discovery") continue;
const details = record.details;
if (!details || typeof details !== "object") continue;
if ("sessionMode" in details && typeof details.sessionMode === "boolean") return details.sessionMode;
// Read reminders from sessions written before transition-based notices.
if ("source" in details && details.source === "session") return true;
if (record.customType === "ultraloop-discovery" && typeof record.content === "string") {
if (record.content.includes("Current session mode: off.")) return false;
if (record.content.includes("Current session mode: on.")) return true;
}
}
return undefined;
}

function sessionModeReminder(sessionMode: boolean, previous: boolean | undefined) {
if (sessionMode ? previous === true : previous !== true) return undefined;
const content = sessionMode ? buildUltraloopSessionReminder() : buildUltraloopSessionOffReminder();
return {
customType: "ultraloop-opt-in",
// Standing consent can outlive a tool-profile change. State the constraint
// conditionally rather than persisting a snapshot of tool availability.
content: `${content}\nIf the workflow tool is not active in the current tool profile, explain the limitation instead of attempting to call it. Consult the current active tools; session mode does not expand tool access.`,
display: false,
details: { source: "session", sessionMode },
};
}

/**
Expand Down Expand Up @@ -130,48 +159,45 @@ export function createUltraloopOptInExtension(options: UltraloopOptInExtensionOp
ctx.ui.setStatus("ultracode", undefined);
});

// Fires after the user submits a prompt, before the agent loop; the returned
// message lands in exactly this turn's context (BeforeAgentStartEventResult.message).
pi.on("before_agent_start", (event) => {
// BeforeAgentStartEventResult.message is appended after the real user input
// and retained in session history. Read the selected branch before deciding
// whether it needs a new state announcement; do not cache an emitted flag.
pi.on("before_agent_start", (event, ctx) => {
const token = detectUltraloopOptIn(event.prompt);
optedInThisTurn = token !== undefined;
setTurnBudget(detectUltraloopBudget(event.prompt));
const discovery = buildUltraloopDiscovery(pi.getActiveTools(), sessionMode);
const previous = lastSessionMode(
ctx.sessionManager.buildContextEntries().filter((entry) => entry.type === "custom_message"),
);
const modeReminder = sessionModeReminder(sessionMode, previous);

// Session-standing wins when both signals fire: the LLM already knows
// workflow is authorized for the whole session; a per-turn reminder
// on top is redundant noise.
if (sessionMode) {
return {
message: {
customType: "ultraloop-opt-in",
content: `${buildUltraloopSessionReminder()}\n\n${discovery}`,
display: false,
details: { source: "session" },
},
};
}

if (token === undefined) {
return {
message: {
customType: "ultraloop-discovery",
content: discovery,
display: false,
details: { source: "discovery" },
},
};
}
if (sessionMode || token === undefined) return modeReminder ? { message: modeReminder } : undefined;
return {
message: {
customType: "ultraloop-opt-in",
content: `${buildUltraloopReminder(token)}\n\n${discovery}`,
content: [modeReminder?.content, buildUltraloopReminder(token, pi.getActiveTools().includes("workflow"))]
.filter(Boolean)
.join("\n\n"),
display: false,
details: { source: "turn", token },
details: { source: "turn", token, ...(modeReminder ? { sessionMode: false } : {}) },
},
};
});

// Compaction/live-context edits can remove an earlier announcement, and
// commands can toggle the mode during streaming without before_agent_start.
// Reconcile against the actual provider context in those cases. This fallback
// is request-local: it does not append duplicate entries to session history.
pi.on("context", (event) => {
const previous = lastSessionMode(event.messages.filter((message) => message.role === "custom"));
const message = sessionModeReminder(sessionMode, previous);
if (!message) return;
return { messages: [...event.messages, { role: "custom" as const, ...message, timestamp: Date.now() }] };
});

// Soft-gate telemetry: journal off-consent workflow calls; never block.
// Either signal counts as consent — saved-workflow by name and skill-driven
// calls without a detectable token remain valid per the static guidance.
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/step/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export function buildStepSystemPromptAppendix(
? [
"# Workflow orchestration",
[
'- workflow is opt-in: reach for it only when the user\'s current message contains the keyword "ultraloop" (or its Claude Code spelling "ultracode") or an explicit request such as "use a workflow", "run a workflow", "fan out agents", or "orchestrate this with subagents"; ultraloop is on for the session (a system-reminder confirms it and repeats every turn until /ultraloop off); the user invokes a saved workflow by name; or a skill instructs it. A system-reminder confirms per-turn and session-standing opt-ins (saved-workflow and skill invocations are valid without one); never infer opt-in from task shape, and never carry a per-turn opt-in forward.',
'- workflow is opt-in: reach for it only when the user\'s current message contains the keyword "ultraloop" (or "ultracode") or an explicit request such as "use a workflow", "run a workflow", "fan out agents", or "orchestrate this with subagents"; ultraloop is on for the session (the latest on/off system-reminder in the current context reports its state; unchanged state is not repeated); the user invokes a saved workflow by name; or a skill instructs it. A system-reminder confirms per-turn and session-standing opt-ins (saved-workflow and skill invocations are valid without one); never infer opt-in from task shape, and never carry a per-turn opt-in forward.',
"- When ultraloop is on for the session that opt-in is standing: author and run a workflow for every substantive task by default, treat token cost as unconstrained, and aim for the most exhaustive correct answer. Multi-phase work often means several workflows in sequence — one per phase — so you stay in the loop between them. Lean toward adversarially verifying findings unless the work is already verified. Solo only on conversational turns or trivial mechanical edits. When session mode is off, revert to the per-turn opt-in rule.",
"- Without any signal, prefer targeted tools — search_files, read_file, or a single subagent — and never reach for workflow on single-file edits, one-shot lookups, or exploration that a few targeted queries can answer.",
"- With opt-in, workflow fits broad multi-subsystem audits, mechanical migrations across many sites, multi-way parallel review with adversarial verification, research sweeps, and HoH convergence on a spec.",
Expand Down
27 changes: 27 additions & 0 deletions packages/coding-agent/test/step-extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,33 @@ describe("Step extension", () => {
}
},
);
test("does not persist retry when automatic mode finds it already enabled", () => {
const on = vi.fn();
createStepExtension({
permission: { env: {}, initialPreset: "approve-for-me" },
stepSettings: () => ({
getStepSettings: () => ({}),
setEffectiveStepSettings: vi.fn(),
}),
})({
on,
sendUserMessage: vi.fn(),
registerProvider: vi.fn(),
registerCommand: vi.fn(),
} as unknown as ExtensionAPI);
const sessionStart = on.mock.calls.find(([event]) => event === "session_start")![1];
const setAutoRetryEnabled = vi.fn();
sessionStart(
{},
{
hasUI: false,
ui: { notify: vi.fn(), setStatus: vi.fn() },
autoRetryEnabled: true,
setAutoRetryEnabled,
},
);
expect(setAutoRetryEnabled).not.toHaveBeenCalled();
});
test.each([
{ name: "selected non-Bash interpreter", shellPath: process.execPath, prefix: undefined },
{ name: "executed command prefix", shellPath: undefined, prefix: "rm -rf ./build" },
Expand Down
Loading
Loading