feat(FN-5544): emit runtime-resolved audit event across engine lanes

Adds a "session runtime resolved" audit event that flows through the engine's main execution lanes — triage, executor, reviewer, merger, heartbeat, step-session-executor, and mission-execution-loop — with runtime mutation support and test coverage, plus a compile-fix for the merger auditor wiring.

Fusion-Task-Id: FN-5544

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5544
This commit is contained in:
gsxdsm
2026-05-23 12:06:37 -07:00
parent cd09d1925f
commit 7a20b95502
13 changed files with 329 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Add `session:runtime-resolved` run-audit event (FN-5544) emitted from `createResolvedAgentSession` for per-lane provider/runtime/model attribution. Additive surface; existing events unchanged. Replaces the diagnostic-log workaround introduced by FN-5206.

View File

@@ -1061,6 +1061,7 @@ The run-audit system records every mutation performed by the engine across four
- **Git / `merge:no-op-attribution-mismatch-skipped`** — emitted when the FN-5304 source-tip guard cannot run because the source branch ref is unavailable (for example already pruned). `target` is the task ID; metadata includes `reason` (`"source-ref-unavailable"`).
- **Database / `task:auto-recover-misrouted-foreign-commit`** — emitted per dropped misrouted commit during FN-4948 contamination recovery. `target` is the recovering task; metadata carries `{ droppedSha, foreignTaskId, paths }`.
- **Database / `task:orphan-detected-no-action`** — emitted by `recoverOrphanedExecutions` (FN-5337) when row metadata looks orphaned after grace windows; annotation-only event with no lifecycle mutation (`in-progress` task stays put).
- **Database / `session:runtime-resolved`** — emitted once per `createResolvedAgentSession` call with metadata `{ sessionPurpose, runtimeId, wasConfigured, provider, modelId, mockProviderActive, testModeActive, runtimeHint? }` for per-lane runtime/provider attribution.
- **Database / `task:*-no-action` backward-move family (FN-5335)** — backward self-healing sweeps now emit annotation-only events when triple proof fails instead of mutating lifecycle state. New mutation types: `task:reclaim-pr-conflict-no-action`, `task:reclaim-self-owned-branch-conflict-no-action`, `task:auto-rebound-scope-decay-no-action`, `task:finalize-no-op-review-no-action`, `task:stale-incomplete-review-no-action`, `task:ghost-review-no-action`, `task:stuck-merge-deadlock-no-action`, `task:no-progress-no-task-done-no-action`, `task:missing-worktree-review-no-action`, `task:partial-progress-no-task-done-no-action`. See `docs/self-healing-backward-move-audit.md` for per-stage disposition.
- **Filesystem** — file:write, prompt:write, attachment:create, etc.
- **Sandbox** — backend lifecycle events from `SandboxBackend` wiring in executor/merger/routine-runner (`sandbox:prepare`, `sandbox:run`, `sandbox:failure`, `sandbox:fallback`) introduced after FN-4636.

View File

@@ -126,6 +126,97 @@ describe("createResolvedAgentSession", () => {
}),
);
});
it("emits session:runtime-resolved when runAuditor is provided", async () => {
const mockSession = { prompt: vi.fn() } as any;
const createSessionMock = vi.fn().mockResolvedValue({ session: mockSession });
const auditDatabaseMock = vi.fn().mockResolvedValue(undefined);
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
await createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
defaultProvider: "mock",
defaultModelId: "mock-default",
runAuditor: { database: auditDatabaseMock } as any,
settings: { testMode: true } as any,
});
expect(createSessionMock).not.toHaveBeenCalled();
expect(auditDatabaseMock).toHaveBeenCalledTimes(1);
expect(auditDatabaseMock).toHaveBeenCalledWith({
type: "session:runtime-resolved",
target: "mock",
metadata: {
sessionPurpose: "executor",
runtimeId: "mock",
wasConfigured: true,
provider: "mock",
modelId: "mock-default",
mockProviderActive: true,
testModeActive: true,
},
});
});
it("succeeds when runAuditor is omitted", async () => {
const mockSession = { prompt: vi.fn() } as any;
const createSessionMock = vi.fn().mockResolvedValue({
session: mockSession,
sessionFile: "session.json",
});
resolveRuntimeMock.mockResolvedValue({
runtime: {
id: "pi",
name: "Default PI Runtime",
createSession: createSessionMock,
promptWithFallback: vi.fn(),
describeModel: vi.fn(() => "mock/model"),
},
runtimeId: "pi",
wasConfigured: false,
});
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
await expect(createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
})).resolves.toMatchObject({ runtimeId: "pi", wasConfigured: false });
});
it("warns and continues when runAuditor throws", async () => {
const mockSession = { prompt: vi.fn() } as any;
const createSessionMock = vi.fn().mockResolvedValue({ session: mockSession });
resolveRuntimeMock.mockResolvedValue({
runtime: {
id: "pi",
name: "Default PI Runtime",
createSession: createSessionMock,
promptWithFallback: vi.fn(),
describeModel: vi.fn(() => "mock/model"),
},
runtimeId: "pi",
wasConfigured: false,
});
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const { createResolvedAgentSession } = await import("../agent-session-helpers.js");
await expect(createResolvedAgentSession({
sessionPurpose: "executor",
cwd: "/tmp/project",
systemPrompt: "system",
runAuditor: {
database: vi.fn().mockRejectedValue(new Error("audit down")),
} as any,
})).resolves.toMatchObject({ session: mockSession, runtimeId: "pi", wasConfigured: false });
warnSpy.mockRestore();
});
});
describe("resolveMergerSessionModel", () => {

View File

@@ -0,0 +1,76 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunAuditEvent, RunAuditEventFilter, RunAuditEventInput, TaskStore } from "@fusion/core";
import { createResolvedAgentSession } from "../agent-session-helpers.js";
import { createRunAuditor } from "../run-audit.js";
import { MOCK_PROVIDER_ID } from "../providers/mock-provider.js";
const { resolveRuntimeMock } = vi.hoisted(() => ({ resolveRuntimeMock: vi.fn() }));
vi.mock("../runtime-resolution.js", async () => {
const actual = await vi.importActual<typeof import("../runtime-resolution.js")>("../runtime-resolution.js");
return { ...actual, resolveRuntime: resolveRuntimeMock };
});
/** SessionPurpose canonical set: executor | triage | reviewer | merger | heartbeat | validation */
describe("FN-5544 session:runtime-resolved audit event", () => {
let recordedEvents: RunAuditEvent[] = [];
let counter = 0;
let store: TaskStore;
beforeEach(() => {
recordedEvents = [];
counter = 0;
resolveRuntimeMock.mockReset().mockResolvedValue({
runtime: {
id: "pi",
name: "pi",
createSession: vi.fn().mockResolvedValue({ session: { prompt: vi.fn() } }),
promptWithFallback: vi.fn(),
describeModel: vi.fn(),
},
runtimeId: "pi",
wasConfigured: false,
});
store = {
recordRunAuditEvent: vi.fn(async (input: RunAuditEventInput) => {
recordedEvents.push({ ...input, id: `audit-${++counter}`, timestamp: input.timestamp ?? new Date().toISOString() });
}),
getRunAuditEvents: vi.fn((filter?: RunAuditEventFilter) => {
const filtered = recordedEvents.filter((event) => !filter?.mutationType || event.mutationType === filter.mutationType);
return filter?.limit ? filtered.slice(0, filter.limit) : filtered;
}),
} as unknown as TaskStore;
});
it("emits mock-provider runtime-resolved event", async () => {
const auditor = createRunAuditor(store, { runId: "r1", agentId: "a1", taskId: "FN-5544", phase: "execute", source: "executor" });
await createResolvedAgentSession({ sessionPurpose: "executor", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: MOCK_PROVIDER_ID, defaultModelId: "scripted", runAuditor: auditor });
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
expect(events[0]?.target).toBe("mock");
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "executor", runtimeId: "mock", mockProviderActive: true }));
});
it("emits non-mock provider metadata", async () => {
const auditor = createRunAuditor(store, { runId: "r2", agentId: "a1", taskId: "FN-5544", phase: "review", source: "reviewer" });
await createResolvedAgentSession({ sessionPurpose: "reviewer", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: "openai", defaultModelId: "gpt-4.1", runAuditor: auditor, runtimeHint: "pi" });
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "reviewer", provider: "openai", modelId: "gpt-4.1", mockProviderActive: false }));
});
it("records no rows when runAuditor is omitted", async () => {
await createResolvedAgentSession({ sessionPurpose: "validation", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: MOCK_PROVIDER_ID, defaultModelId: "scripted" });
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(0);
});
it("round-trips metadata through getRunAuditEvents", async () => {
const auditor = createRunAuditor(store, { runId: "r4", agentId: "a1", taskId: "FN-5544", phase: "heartbeat", source: "heartbeat" });
await createResolvedAgentSession({ sessionPurpose: "heartbeat", cwd: "/tmp/project", systemPrompt: "system", defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5", runtimeHint: "hermes", runAuditor: auditor, settings: { testMode: true } as any });
const events = store.getRunAuditEvents({ mutationType: "session:runtime-resolved" });
expect(events).toHaveLength(1);
expect(events[0]?.metadata).toEqual(expect.objectContaining({ sessionPurpose: "heartbeat", runtimeHint: "hermes", testModeActive: true }));
});
});

View File

@@ -2542,6 +2542,8 @@ export class HeartbeatMonitor {
defaultModelId: heartbeatSessionModels.defaultModelId,
fallbackProvider: heartbeatSessionModels.fallbackProvider,
fallbackModelId: heartbeatSessionModels.fallbackModelId,
runAuditor: audit,
settings: heartbeatModelSettings,
onText: (delta) => {
outputLength += delta.length;
appendStdoutExcerpt(delta);

View File

@@ -22,6 +22,7 @@ import {
import { resolveRuntime, buildRuntimeResolutionContext, isMockProviderId, type SessionPurpose } from "./runtime-resolution.js";
import { createLogger } from "./logger.js";
import { promptWithFallback, describeModel } from "./pi.js";
import type { RunAuditor } from "./run-audit.js";
import { MockAgentRuntime } from "./providers/mock-provider.js";
/** Logger for agent session helpers */
@@ -48,6 +49,18 @@ export interface ResolvedSessionOptions extends AgentRuntimeOptions {
pluginRunner?: PluginRunner;
/** Optional runtime hint from task/agent configuration */
runtimeHint?: string;
/**
* Optional run-audit emitter; when provided, a `session:runtime-resolved`
* database event is recorded at resolution time. No-ops when omitted to
* preserve backward compatibility for callers that have not yet been wired
* through.
*/
runAuditor?: RunAuditor;
/**
* Optional settings used only to capture `testModeActive` in
* `session:runtime-resolved` metadata.
*/
settings?: Settings;
/**
* `beforeSpawnSession` and `taskEnv` are inherited from
* {@link AgentRuntimeOptions}. Both are forwarded verbatim to
@@ -261,7 +274,7 @@ export function resolveMergerSessionModel(
export async function createResolvedAgentSession(
options: ResolvedSessionOptions,
): Promise<ResolvedSessionResult> {
const { sessionPurpose, pluginRunner, runtimeHint, ...runtimeOptionsRaw } = options;
const { sessionPurpose, pluginRunner, runtimeHint, runAuditor, settings, ...runtimeOptionsRaw } = options;
const skillNamesFromSelection = extractSkillNamesFromSelection(runtimeOptionsRaw.skillSelection);
const mergedSkillNames = runtimeOptionsRaw.skills && runtimeOptionsRaw.skills.length > 0
@@ -296,6 +309,25 @@ export async function createResolvedAgentSession(
`[${sessionPurpose}] Using runtime "${resolved.runtimeId}" (configured=${resolved.wasConfigured})`,
);
try {
await runAuditor?.database({
type: "session:runtime-resolved",
target: resolved.runtimeId,
metadata: {
sessionPurpose,
runtimeId: resolved.runtimeId,
wasConfigured: resolved.wasConfigured,
provider: runtimeOptions.defaultProvider ?? null,
modelId: runtimeOptions.defaultModelId ?? null,
mockProviderActive: isMockProviderId(runtimeOptions.defaultProvider),
testModeActive: settings ? isTestModeActive(settings) : false,
...(runtimeHint ? { runtimeHint } : {}),
},
});
} catch (err) {
sessionLog.warn(`[${sessionPurpose}] failed to record session:runtime-resolved audit: ${String(err)}`);
}
// Forward `beforeSpawnSession` to the runtime so it fires at the true
// latest sync point (just before LLM session instantiation) rather than
// here, before the runtime's own awaited setup work runs. See

View File

@@ -4005,6 +4005,8 @@ export class TaskExecutor {
fallbackProvider: executorFallbackProvider,
fallbackModelId: executorFallbackModelId,
defaultThinkingLevel: executorThinkingLevel,
runAuditor: audit,
settings,
sessionManager,
taskEnv,
// Skill selection: use assigned agent skills if available, otherwise role fallback
@@ -4421,6 +4423,8 @@ export class TaskExecutor {
fallbackProvider: executorFallbackProvider,
fallbackModelId: executorFallbackModelId,
defaultThinkingLevel: executorThinkingLevel,
runAuditor: audit,
settings,
sessionManager: SessionManager.create(worktreePath),
taskEnv,
// Skill selection: use assigned agent skills if available, otherwise role fallback
@@ -6872,6 +6876,8 @@ Do not refactor, rename broadly, or make opportunistic improvements.
defaultProvider: executorProvider,
defaultModelId: executorModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor: createRunAuditor(this.store, this.getRunContextFor(task.id)),
settings,
taskEnv: extraEnv,
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
});
@@ -7961,6 +7967,8 @@ Backward compat fallback: if JSON is unavailable, you may still begin output wit
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor: createRunAuditor(this.store, this.getRunContextFor(task.id)),
settings,
taskEnv,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
@@ -10412,6 +10420,8 @@ Child agent: ${agent.id} (${name})`;
defaultModelId: childExecutorModelId,
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
runAuditor: createRunAuditor(this.store, this.getRunContextFor(taskId)),
settings,
taskEnv,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),

View File

@@ -1664,6 +1664,14 @@ Do not refactor, rename broadly, or make opportunistic improvements.
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor: createRunAuditor(store, {
runId: mergeRunContext?.runId ?? generateSyntheticRunId("merge", taskId),
agentId: mergeRunContext?.agentId ?? "merger",
taskId,
phase: "merge",
source: "merger",
}),
settings,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
taskId,
@@ -2848,6 +2856,14 @@ ${fileList}
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor: createRunAuditor(store, {
runId: generateSyntheticRunId("merge", taskId),
agentId: "merger",
taskId,
phase: "merge",
source: "merger",
}),
settings,
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
taskId,
taskTitle: taskForSkillContext?.title,
@@ -3251,6 +3267,14 @@ ${fileList}
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor: createRunAuditor(store, {
runId: generateSyntheticRunId("merge", taskId),
agentId: "merger",
taskId,
phase: "merge",
source: "merger",
}),
settings,
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
taskId,
taskTitle: taskForSkillContext?.title,
@@ -6521,6 +6545,14 @@ You are assisting with a paused \`git pull --rebase\`.
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor: createRunAuditor(store, {
runId: generateSyntheticRunId("merge", taskId),
agentId: "merger",
taskId,
phase: "merge",
source: "merger",
}),
settings,
taskId,
onFallbackModelUsed: createFallbackModelObserver({
agent: "merger",
@@ -10878,6 +10910,14 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor: createRunAuditor(store, {
runId: generateSyntheticRunId("merge", taskId),
agentId: "merger",
taskId,
phase: "merge",
source: "merger",
}),
settings,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
taskId,
@@ -11511,6 +11551,14 @@ If issues are found that need attention, describe them clearly and include concr
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor: createRunAuditor(store, {
runId: generateSyntheticRunId("merge", taskId),
agentId: "merger",
taskId,
phase: "merge",
source: "merger",
}),
settings,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(postMergeSkillContext?.skillSelectionContext ? { skillSelection: postMergeSkillContext.skillSelectionContext } : {}),
...(readonlyCustomTools.allowed.length > 0 ? { customTools: readonlyCustomTools.allowed } : {}),

View File

@@ -24,6 +24,7 @@ import { createFnAgent, promptWithFallback, type AgentResult } from "./pi.js";
import { createResolvedAgentSession, extractRuntimeHint } from "./agent-session-helpers.js";
import { createLogger } from "./logger.js";
import { createFallbackModelObserver } from "./fallback-model-observer.js";
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
/** Logger for the mission execution loop subsystem. */
export const loopLog = createLogger("mission-loop");
@@ -308,6 +309,13 @@ export class MissionExecutionLoop extends EventEmitter {
try {
// Create validation agent session
const runAuditor = createRunAuditor(this.taskStore, {
runId: generateSyntheticRunId("mission", feature.taskId ?? feature.id),
agentId: "reviewer",
taskId: task?.id,
phase: "mission",
source: "mission-execution-loop",
});
const sessionResult = await createResolvedAgentSession({
sessionPurpose: "validation",
runtimeHint: validationRuntimeHint,
@@ -316,6 +324,7 @@ export class MissionExecutionLoop extends EventEmitter {
systemPrompt: this.buildValidationSystemPrompt(feature, assertions, taskContext),
tools: "readonly",
defaultThinkingLevel: "medium",
runAuditor,
onText: (_delta) => {
// Could stream this to a log entry if needed
},

View File

@@ -24,6 +24,7 @@ import {
} from "./agent-instructions.js";
import { buildPromptLayers, collapsePromptLayers } from "./prompt-layers.js";
import { createFallbackModelObserver } from "./fallback-model-observer.js";
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
import { createMemoryGetTool, createMemorySearchTool, createWebFetchTool } from "./agent-tools.js";
export const REVIEWER_SYSTEM_PROMPT = `You are an independent code and plan reviewer.
@@ -500,6 +501,15 @@ export async function reviewStep(
const createReviewerSession = async (
overrides?: { forceProvider?: string; forceModelId?: string },
): Promise<import("@mariozechner/pi-coding-agent").AgentSession> => {
const runAuditor = options.store
? createRunAuditor(options.store, {
runId: generateSyntheticRunId("reviewer", options.taskId ?? "review"),
agentId: options.agentId ?? "reviewer",
taskId: options.taskId,
phase: "review",
source: "reviewer",
})
: undefined;
const { session } = await createResolvedAgentSession({
sessionPurpose: "reviewer",
runtimeHint: extractRuntimeHint(memoryAgent?.runtimeConfig),
@@ -518,6 +528,8 @@ export async function reviewStep(
fallbackProvider: validatorFallbackProvider,
fallbackModelId: validatorFallbackModelId,
defaultThinkingLevel: options.defaultThinkingLevel,
runAuditor,
settings: options.settings,
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
taskId: options.taskId,
taskTitle: options.taskTitle,

View File

@@ -428,6 +428,26 @@ export type DatabaseMutationType =
| "task:auto-board-stall-unrecovered"
/** Metadata: { errors: string[], lastCheckedAt: string | null, notificationDispatched: boolean } */
| "task:auto-db-corruption-detected"
/**
* Per-lane runtime/provider/model selection telemetry, emitted once per
* `createResolvedAgentSession` call. Target is the resolved runtime id
* (e.g., `"pi"`, `"mock"`, `"hermes"`).
*
* Metadata shape:
* ```ts
* {
* sessionPurpose: SessionPurpose; // canonical lane label
* runtimeId: string; // resolved runtime id (same as target)
* wasConfigured: boolean; // runtime was explicitly configured (vs default fallback)
* provider: string | null; // resolved AI provider id (null when not yet set)
* modelId: string | null; // resolved model id (null when not yet set)
* mockProviderActive: boolean; // isMockProviderId(provider) — convenience flag for test-mode assertions
* testModeActive: boolean; // isTestModeActive(settings) at resolution time
* runtimeHint?: string; // raw runtime hint when present
* }
* ```
*/
| "session:runtime-resolved"
| "task:in-review-stall-deadlock-disposed"
| "task:finalize-unproven-blocked"
| "task:integrity-reconcile-modified-files"

View File

@@ -36,6 +36,7 @@ import { StuckTaskDetector } from "./stuck-task-detector.js";
import { AgentLogger } from "./agent-logger.js";
import { createLogger } from "./logger.js";
import { createFallbackModelObserver } from "./fallback-model-observer.js";
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
import { isContextLimitError } from "./context-limit-detector.js";
import { checkSessionError } from "./usage-limit-detector.js";
import {
@@ -1015,6 +1016,15 @@ Follow instructions precisely and avoid unrelated changes.`,
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: taskDetail.thinkingLevel ?? settings.defaultThinkingLevel,
runAuditor: createRunAuditor(this.store, {
runId: generateSyntheticRunId("workflow-step", taskDetail.id),
agentId: taskDetail.assignedAgentId ?? "executor",
taskId: taskDetail.id,
taskLineageId: taskDetail.lineageId,
phase: "execute",
source: "step-session-executor",
}),
settings,
customTools: [
...pluginTools,
...documentTools,

View File

@@ -1210,6 +1210,14 @@ export class TriageProcessor {
defaultProvider: planningModel.provider,
defaultModelId: planningModel.modelId,
};
const runAuditor = createRunAuditor(this.store, {
runId: generateSyntheticRunId("triage", task.id),
agentId: assignedAgent?.id ?? "triage",
taskId: task.id,
taskLineageId: task.lineageId,
phase: "plan",
source: "triage",
});
let { session } = await createResolvedAgentSession({
sessionPurpose: "triage",
@@ -1232,6 +1240,8 @@ export class TriageProcessor {
? settings.planningFallbackModelId
: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor,
settings,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
taskId: task.id,
@@ -1460,6 +1470,8 @@ export class TriageProcessor {
defaultProvider: planningFallbackProvider,
defaultModelId: planningFallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
runAuditor,
settings,
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
taskId: task.id,
taskTitle: task.title,