feat: Make /init an interactive flow conducted by the primary agent [LET-7891] (#1356)

This commit is contained in:
Devansh Jain
2026-03-11 16:59:25 -07:00
committed by GitHub
parent d9eaa74ad6
commit a5c407eade
10 changed files with 93 additions and 488 deletions

View File

@@ -224,11 +224,9 @@ import {
import { formatCompact } from "./helpers/format";
import { parsePatchOperations } from "./helpers/formatArgsDisplay";
import {
buildLegacyInitMessage,
buildMemoryInitRuntimePrompt,
buildInitMessage,
fireAutoInit,
gatherGitContext,
hasActiveInitSubagent,
} from "./helpers/initCommand";
import {
getReflectionSettings,
@@ -1711,27 +1709,12 @@ export default function App({
const [showExitStats, setShowExitStats] = useState(false);
const sharedReminderStateRef = useRef(createSharedReminderState());
// Per-agent init progression — survives agent/conversation switches unlike SharedReminderState.
const initProgressByAgentRef = useRef(
new Map<string, { shallowCompleted: boolean; deepFired: boolean }>(),
);
const systemPromptRecompileByConversationRef = useRef(
new Map<string, Promise<void>>(),
);
const queuedSystemPromptRecompileByConversationRef = useRef(
new Set<string>(),
);
const updateInitProgress = (
forAgentId: string,
update: Partial<{ shallowCompleted: boolean; deepFired: boolean }>,
) => {
const progress = initProgressByAgentRef.current.get(forAgentId) ?? {
shallowCompleted: false,
deepFired: false,
};
Object.assign(progress, update);
initProgressByAgentRef.current.set(forAgentId, progress);
};
// Track if we've set the conversation summary for this new conversation
// Initialized to true for resumed conversations (they already have context)
@@ -9261,7 +9244,6 @@ export default function App({
if (trimmed === "/init") {
const cmd = commandRunner.start(msg, "Gathering project context...");
// Check for pending approvals before either path
const approvalCheck = await checkPendingApprovalsForSlashCommand();
if (approvalCheck.blocked) {
cmd.fail(
@@ -9270,112 +9252,38 @@ export default function App({
return { submitted: false };
}
const gitContext = gatherGitContext();
// Interactive init: the primary agent conducts the flow,
// asks the user questions, and runs the initializing-memory skill.
autoInitPendingAgentIdsRef.current.delete(agentId);
setCommandRunning(true);
try {
cmd.finish(
"Building your memory palace... Start a new conversation with `letta --new` to work in parallel.",
true,
);
if (settingsManager.isMemfsEnabled(agentId)) {
// MemFS path: background subagent
if (hasActiveInitSubagent()) {
cmd.fail(
"Memory initialization is already running in the background.",
);
return { submitted: true };
}
const gitContext = gatherGitContext();
const memoryDir = settingsManager.isMemfsEnabled(agentId)
? getMemoryFilesystemRoot(agentId)
: undefined;
try {
const initPrompt = buildMemoryInitRuntimePrompt({
agentId,
workingDirectory: process.cwd(),
memoryDir: getMemoryFilesystemRoot(agentId),
gitContext,
depth: "deep",
});
const initMessage = buildInitMessage({
gitContext,
memoryDir,
});
const { spawnBackgroundSubagentTask } = await import(
"../tools/impl/Task"
);
spawnBackgroundSubagentTask({
subagentType: "init",
prompt: initPrompt,
description: "Initializing memory",
silentCompletion: true,
onComplete: async ({ success, error }) => {
const msg = await handleMemorySubagentCompletion(
{
agentId,
conversationId: conversationIdRef.current,
subagentType: "init",
initDepth: "deep",
success,
error,
},
{
recompileByConversation:
systemPromptRecompileByConversationRef.current,
recompileQueuedByConversation:
queuedSystemPromptRecompileByConversationRef.current,
updateInitProgress,
logRecompileFailure: (message) =>
debugWarn("memory", message),
},
);
appendTaskNotificationEvents([msg]);
},
});
// Clear pending auto-init only after spawn succeeded
autoInitPendingAgentIdsRef.current.delete(agentId);
cmd.finish(
"Learning about you and your codebase in the background. You'll be notified when ready.",
true,
);
// TODO: Remove this hack once commandRunner supports a
// "silent" finish that skips the reminder callback.
// Currently cmd.finish() always enqueues a command-IO
// reminder, which leaks the /init context into the
// primary agent's next turn and causes it to invoke the
// initializing-memory skill itself.
const reminders =
sharedReminderStateRef.current.pendingCommandIoReminders;
const idx = reminders.findIndex((r) => r.input === "/init");
if (idx !== -1) {
reminders.splice(idx, 1);
}
} catch (error) {
const errorDetails = formatErrorDetails(error, agentId);
cmd.fail(
`Failed to start memory initialization: ${errorDetails}`,
);
}
} else {
// Legacy path: primary agent processConversation
autoInitPendingAgentIdsRef.current.delete(agentId);
setCommandRunning(true);
try {
cmd.finish(
"Assimilating project context and defragmenting memories...",
true,
);
const initMessage = buildLegacyInitMessage({
gitContext,
memfsSection: "",
});
await processConversation([
{
type: "message",
role: "user",
content: buildTextParts(initMessage),
},
]);
} catch (error) {
const errorDetails = formatErrorDetails(error, agentId);
cmd.fail(`Failed: ${errorDetails}`);
} finally {
setCommandRunning(false);
}
await processConversation([
{
type: "message",
role: "user",
content: buildTextParts(initMessage),
},
]);
} catch (error) {
const errorDetails = formatErrorDetails(error, agentId);
cmd.fail(`Failed: ${errorDetails}`);
} finally {
setCommandRunning(false);
}
return { submitted: true };
}
@@ -9493,7 +9401,6 @@ export default function App({
agentId,
conversationId: conversationIdRef.current,
subagentType: "init",
initDepth: "shallow",
success,
error,
},
@@ -9502,7 +9409,6 @@ export default function App({
systemPromptRecompileByConversationRef.current,
recompileQueuedByConversation:
queuedSystemPromptRecompileByConversationRef.current,
updateInitProgress,
logRecompileFailure: (message) =>
debugWarn("memory", message),
},
@@ -9632,7 +9538,6 @@ ${SYSTEM_REMINDER_CLOSE}
systemPromptRecompileByConversationRef.current,
recompileQueuedByConversation:
queuedSystemPromptRecompileByConversationRef.current,
updateInitProgress,
logRecompileFailure: (message) =>
debugWarn("memory", message),
},
@@ -9655,72 +9560,10 @@ ${SYSTEM_REMINDER_CLOSE}
return false;
}
};
const maybeLaunchDeepInitSubagent = async () => {
if (!memfsEnabledForAgent) return false;
if (hasActiveInitSubagent()) return false;
try {
const gitContext = gatherGitContext();
const initPrompt = buildMemoryInitRuntimePrompt({
agentId,
workingDirectory: process.cwd(),
memoryDir: getMemoryFilesystemRoot(agentId),
gitContext,
depth: "deep",
});
const { spawnBackgroundSubagentTask } = await import(
"../tools/impl/Task"
);
spawnBackgroundSubagentTask({
subagentType: "init",
prompt: initPrompt,
description: "Deep memory initialization",
silentCompletion: true,
onComplete: async ({ success, error }) => {
const msg = await handleMemorySubagentCompletion(
{
agentId,
conversationId: conversationIdRef.current,
subagentType: "init",
initDepth: "deep",
success,
error,
},
{
recompileByConversation:
systemPromptRecompileByConversationRef.current,
recompileQueuedByConversation:
queuedSystemPromptRecompileByConversationRef.current,
updateInitProgress,
logRecompileFailure: (message) =>
debugWarn("memory", message),
},
);
appendTaskNotificationEvents([msg]);
},
});
debugLog("memory", "Auto-launched deep init subagent");
return true;
} catch (error) {
debugWarn(
"memory",
`Failed to auto-launch deep init subagent: ${
error instanceof Error ? error.message : String(error)
}`,
);
return false;
}
};
syncReminderStateFromContextTracker(
sharedReminderStateRef.current,
contextTrackerRef.current,
);
// Hydrate init progression from the per-agent map into the shared state
// so the deep-init provider sees the correct flags for the current agent.
const initProgress = initProgressByAgentRef.current.get(agentId);
sharedReminderStateRef.current.shallowInitCompleted =
initProgress?.shallowCompleted ?? false;
sharedReminderStateRef.current.deepInitFired =
initProgress?.deepFired ?? false;
const { getSkillSources } = await import("../agent/context");
const { parts: sharedReminderParts } = await buildSharedReminderParts({
mode: "interactive",
@@ -9736,7 +9579,6 @@ ${SYSTEM_REMINDER_CLOSE}
skillSources: getSkillSources(),
resolvePlanModeReminder: getPlanModeReminder,
maybeLaunchReflectionSubagent,
maybeLaunchDeepInitSubagent,
});
for (const part of sharedReminderParts) {
reminderParts.push(part);