feat(FN-4080): complete Step 2 — route thinking persistence by agent kind

Fusion-Task-Id: FN-4080
Fusion-Task-Lineage: 086863ba-7100-41ab-9cfc-197301fbcab0
This commit is contained in:
Fusion
2026-05-13 05:41:53 -07:00
committed by gsxdsm
parent 8c7fb1f893
commit 758d213d9c
7 changed files with 89 additions and 16 deletions

View File

@@ -0,0 +1,57 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import { isEphemeralAgent, resolvePersistAgentThinkingLog, type GlobalSettings } from "@fusion/core";
import { AgentLogger } from "../agent-logger.js";
vi.mock("../agent-logger.js", () => ({
AgentLogger: vi.fn(),
}));
function createLoggerForAgent(agent: { metadata?: Record<string, unknown> }, settings: Partial<GlobalSettings>) {
const ephemeral = isEphemeralAgent(agent);
return new AgentLogger({
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral }),
});
}
describe("thinking persistence routing", () => {
beforeEach(() => {
vi.clearAllMocks();
});
const permanentAgent = { metadata: { type: "permanent" } };
const ephemeralAgent = { metadata: { agentKind: "task-worker" } };
it.each([
{
name: "both off",
settings: { persistAgentThinkingLogPermanent: false, persistAgentThinkingLogEphemeral: false },
expectedPermanent: false,
expectedEphemeral: false,
},
{
name: "permanent only",
settings: { persistAgentThinkingLogPermanent: true, persistAgentThinkingLogEphemeral: false },
expectedPermanent: true,
expectedEphemeral: false,
},
{
name: "ephemeral only",
settings: { persistAgentThinkingLogPermanent: false, persistAgentThinkingLogEphemeral: true },
expectedPermanent: false,
expectedEphemeral: true,
},
{
name: "legacy fallback",
settings: { persistAgentThinkingLog: true },
expectedPermanent: true,
expectedEphemeral: true,
},
])("routes %s", ({ settings, expectedPermanent, expectedEphemeral }) => {
createLoggerForAgent(permanentAgent, settings);
createLoggerForAgent(ephemeralAgent, settings);
const calls = vi.mocked(AgentLogger).mock.calls;
expect(calls[0]?.[0]).toMatchObject({ persistAgentThinkingLog: expectedPermanent });
expect(calls[1]?.[0]).toMatchObject({ persistAgentThinkingLog: expectedEphemeral });
});
});

View File

@@ -18,7 +18,7 @@
*/
import type { AgentStore, AgentHeartbeatRun, HeartbeatInvocationSource, AgentHeartbeatConfig, AgentBudgetStatus, Message, MessageStore, TaskStore, TaskDetail, AgentRole, Agent, InboxTask, RunMutationContext, Settings, AgentConfigRevision, ReflectionStore, ChatStore, ChatRoom, ChatRoomMessage } from "@fusion/core";
import { ApprovalRequestStore, buildExecutionMemoryInstructions, isEphemeralAgent, hasAgentIdentity, resolveEffectiveAgentPermissionPolicy, canAgentTakeImplementationTask, canAgentTakeImplementationTaskForExplicitRouting } from "@fusion/core";
import { ApprovalRequestStore, buildExecutionMemoryInstructions, isEphemeralAgent, hasAgentIdentity, resolveEffectiveAgentPermissionPolicy, canAgentTakeImplementationTask, canAgentTakeImplementationTaskForExplicitRouting, resolvePersistAgentThinkingLog } from "@fusion/core";
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
import { Type, type Static } from "@mariozechner/pi-ai";
import { createHash } from "node:crypto";
@@ -2029,7 +2029,7 @@ export class HeartbeatMonitor {
appendLog: (entry) => this.store.appendRunLog(agentId, run.id, entry),
agent: agent.role as AgentRole,
persistAgentToolOutput: memorySettings?.persistAgentToolOutput,
persistAgentThinkingLog: memorySettings?.persistAgentThinkingLog,
persistAgentThinkingLog: resolvePersistAgentThinkingLog(memorySettings, { ephemeral: isAgentEphemeral }),
});
} else if (taskId) {
agentLogger = new AgentLogger({
@@ -2038,7 +2038,7 @@ export class HeartbeatMonitor {
agent: agent.role as AgentRole,
appendLog: (entry) => this.store.appendRunLog(agentId, run.id, entry),
persistAgentToolOutput: memorySettings?.persistAgentToolOutput,
persistAgentThinkingLog: memorySettings?.persistAgentThinkingLog,
persistAgentThinkingLog: resolvePersistAgentThinkingLog(memorySettings, { ephemeral: isAgentEphemeral }),
});
}

View File

@@ -12,6 +12,7 @@ import {
getTaskMergeBlocker,
isEphemeralAgent,
resolveAgentPrompt,
resolvePersistAgentThinkingLog,
resolveEffectiveAgentPermissionPolicy,
resolveProjectDefaultModel,
type RunCommandResult,
@@ -3160,7 +3161,8 @@ export class TaskExecutor {
taskId: task.id,
agent: "executor",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Executor sessions are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: (taskId, delta) => {
lastAssistantText += delta;
stuckDetector?.recordActivity(taskId);
@@ -5290,7 +5292,8 @@ ${feedback}
taskId: task.id,
agent: "executor",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Executor sessions are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: this.options.onAgentText,
onAgentTool: this.options.onAgentTool,
});
@@ -6100,7 +6103,8 @@ and show an appropriate message to the user.\`
taskId: task.id,
agent: "reviewer",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Review-in-executor sessions are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: (taskId, delta) => {
this.options.onAgentText?.(taskId, delta);
},

View File

@@ -39,6 +39,7 @@ import {
resolveTaskMergeTarget,
resolveTitleSummarizerSettingsModel,
resolveAgentPrompt,
resolvePersistAgentThinkingLog,
summarizeCommitBody,
summarizeCommitSubject,
summarizeMergeCommit,
@@ -1006,7 +1007,8 @@ async function attemptInMergeVerificationFix(
taskId,
agent: "merger",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Merger agents are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: options.onAgentText,
onAgentTool: options.onAgentTool,
});
@@ -2116,7 +2118,8 @@ async function runAiAgentForAutostashConflict(params: {
taskId,
agent: "merger",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Merger agents are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: options.onAgentText
? (_id: string, delta: string) => options.onAgentText!(delta)
: undefined,
@@ -2486,7 +2489,8 @@ async function runAiAgentForAutostashHardFail(params: {
taskId,
agent: "merger",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Merger agents are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: options.onAgentText
? (_id: string, delta: string) => options.onAgentText!(delta)
: undefined,
@@ -4931,7 +4935,8 @@ You are assisting with a paused \`git pull --rebase\`.
taskId,
agent: "merger",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Merger agents are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: options?.onAgentText
? (_id, delta) => options.onAgentText?.(delta)
: undefined,
@@ -7878,7 +7883,8 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
taskId,
agent: "merger",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Merger agents are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: options.onAgentText
? (_id, delta) => options.onAgentText!(delta)
: undefined,
@@ -8492,7 +8498,8 @@ If issues are found that need attention, describe them clearly and include concr
taskId,
agent: "merger",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Merger agents are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
});
try {

View File

@@ -9,7 +9,7 @@
*/
import type { TaskStore, TaskComment, AgentPromptsConfig, Settings } from "@fusion/core";
import { buildReviewerMemoryInstructions, resolveAgentPrompt } from "@fusion/core";
import { buildReviewerMemoryInstructions, resolveAgentPrompt, resolvePersistAgentThinkingLog } from "@fusion/core";
import { describeModel, promptWithFallback } from "./pi.js";
import { isContextLimitError } from "./context-limit-detector.js";
import { createResolvedAgentSession, extractRuntimeHint } from "./agent-session-helpers.js";
@@ -350,7 +350,8 @@ export async function reviewStep(
? (_id, delta) => options.onText!(delta)
: undefined,
persistAgentToolOutput: liveSettings?.persistAgentToolOutput,
persistAgentThinkingLog: liveSettings?.persistAgentThinkingLog,
// Reviewer sessions are task-scoped ephemeral workers.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(liveSettings, { ephemeral: true }),
})
: null;

View File

@@ -18,6 +18,7 @@ import { existsSync } from "node:fs";
import { join } from "node:path";
import type { AgentSession } from "@mariozechner/pi-coding-agent";
import type { AgentStore, MessageStore, PermanentAgentGatingContext, TaskDetail, Settings, TaskStore } from "@fusion/core";
import { resolvePersistAgentThinkingLog } from "@fusion/core";
import {
createResolvedAgentSession,
@@ -890,7 +891,8 @@ export class StepSessionExecutor {
taskId: taskDetail.id,
agent: "executor",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Step-session workers are task-scoped ephemeral agents.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
});
let session: AgentSession | null = null;

View File

@@ -9,6 +9,7 @@ import type {
import {
buildTriageMemoryInstructions,
resolveAgentPrompt,
resolvePersistAgentThinkingLog,
sortTasksByPriorityThenAgeAndId,
} from "@fusion/core";
import type { ImageContent } from "@mariozechner/pi-ai";
@@ -928,7 +929,8 @@ export class TriageProcessor {
taskId: task.id,
agent: "triage",
persistAgentToolOutput: settings.persistAgentToolOutput,
persistAgentThinkingLog: settings.persistAgentThinkingLog,
// Triage runs in a task-scoped ephemeral worker session.
persistAgentThinkingLog: resolvePersistAgentThinkingLog(settings, { ephemeral: true }),
onAgentText: (id, delta) => {
stuckDetector?.recordActivity(task.id);
this.options.onAgentText?.(id, delta);