feat(FN-4153): complete Step 3 — wire scheduler permanent-agent assignment toggle
Fusion-Task-Id: FN-4153 Fusion-Task-Lineage: 6ec09467-c7a0-4a9d-9a09-16d5ef6f7a86
This commit is contained in:
@@ -262,6 +262,18 @@ export class InProcessRuntime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5a. Initialize AgentStore (required for scheduler assignment, reflection service, and heartbeat monitoring)
|
||||||
|
let agentStoreForReflection: import("@fusion/core").AgentStore | undefined;
|
||||||
|
try {
|
||||||
|
const { AgentStore: AgentStoreClass } = await import("@fusion/core");
|
||||||
|
agentStoreForReflection = new AgentStoreClass({ rootDir: this.taskStore.getFusionDir(), taskStore: this.taskStore });
|
||||||
|
await agentStoreForReflection.init();
|
||||||
|
runtimeLog.log("AgentStore initialized for reflection service");
|
||||||
|
} catch (agentErr) {
|
||||||
|
runtimeLog.warn(`AgentStore initialization failed (reflection service will be unavailable):`, agentErr instanceof Error ? agentErr.message : agentErr);
|
||||||
|
}
|
||||||
|
this.agentStore = agentStoreForReflection;
|
||||||
|
|
||||||
// 5. Initialize Scheduler
|
// 5. Initialize Scheduler
|
||||||
const missionStore = this.taskStore.getMissionStore();
|
const missionStore = this.taskStore.getMissionStore();
|
||||||
this.missionAutopilot = missionStore
|
this.missionAutopilot = missionStore
|
||||||
@@ -337,18 +349,6 @@ export class InProcessRuntime
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5a. Initialize AgentStore (required for reflection service and heartbeat monitoring)
|
|
||||||
let agentStoreForReflection: import("@fusion/core").AgentStore | undefined;
|
|
||||||
try {
|
|
||||||
const { AgentStore: AgentStoreClass } = await import("@fusion/core");
|
|
||||||
agentStoreForReflection = new AgentStoreClass({ rootDir: this.taskStore.getFusionDir(), taskStore: this.taskStore });
|
|
||||||
await agentStoreForReflection.init();
|
|
||||||
runtimeLog.log("AgentStore initialized for reflection service");
|
|
||||||
} catch (agentErr) {
|
|
||||||
runtimeLog.warn(`AgentStore initialization failed (reflection service will be unavailable):`, agentErr instanceof Error ? agentErr.message : agentErr);
|
|
||||||
}
|
|
||||||
this.agentStore = agentStoreForReflection;
|
|
||||||
|
|
||||||
// 5b. Initialize ReflectionStore for agent reflections
|
// 5b. Initialize ReflectionStore for agent reflections
|
||||||
let reflectionStoreForService: import("@fusion/core").ReflectionStore | undefined;
|
let reflectionStoreForService: import("@fusion/core").ReflectionStore | undefined;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { resolveEffectiveNode } from "./effective-node.js";
|
|||||||
import { applyUnavailableNodePolicy } from "./node-routing-policy.js";
|
import { applyUnavailableNodePolicy } from "./node-routing-policy.js";
|
||||||
import type { NodeDispatchValidationResult } from "./node-dispatch-validation.js";
|
import type { NodeDispatchValidationResult } from "./node-dispatch-validation.js";
|
||||||
import type { MeshLeaseManager } from "./mesh-lease-manager.js";
|
import type { MeshLeaseManager } from "./mesh-lease-manager.js";
|
||||||
|
import { selectPermanentAgentForTask } from "./agent-assignment.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether two sets of file scope paths overlap.
|
* Check whether two sets of file scope paths overlap.
|
||||||
@@ -179,6 +180,8 @@ export class Scheduler {
|
|||||||
private wasNodeBlocked = new Set<string>();
|
private wasNodeBlocked = new Set<string>();
|
||||||
/** Tracks tasks blocked by missing project-node mapping to deduplicate block log entries. */
|
/** Tracks tasks blocked by missing project-node mapping to deduplicate block log entries. */
|
||||||
private wasNodeDispatchValidationBlocked = new Set<string>();
|
private wasNodeDispatchValidationBlocked = new Set<string>();
|
||||||
|
/** Tracks tasks queued due to missing permanent executors when ephemeral workers are disabled. */
|
||||||
|
private wasPermanentAgentUnavailable = new Set<string>();
|
||||||
/** Tracks dispatch-queued reason signatures to avoid per-tick log spam. */
|
/** Tracks dispatch-queued reason signatures to avoid per-tick log spam. */
|
||||||
private wasDispatchQueuedReasonLogged = new Set<string>();
|
private wasDispatchQueuedReasonLogged = new Set<string>();
|
||||||
|
|
||||||
@@ -465,6 +468,7 @@ export class Scheduler {
|
|||||||
this.failedTaskIds.clear();
|
this.failedTaskIds.clear();
|
||||||
this.wasNodeBlocked.clear();
|
this.wasNodeBlocked.clear();
|
||||||
this.wasNodeDispatchValidationBlocked.clear();
|
this.wasNodeDispatchValidationBlocked.clear();
|
||||||
|
this.wasPermanentAgentUnavailable.clear();
|
||||||
this.wasDispatchQueuedReasonLogged.clear();
|
this.wasDispatchQueuedReasonLogged.clear();
|
||||||
schedulerLog.log("Stopped");
|
schedulerLog.log("Stopped");
|
||||||
}
|
}
|
||||||
@@ -772,6 +776,7 @@ export class Scheduler {
|
|||||||
// Resolve dependency order among todo tasks
|
// Resolve dependency order among todo tasks
|
||||||
const ordered = resolveDependencyOrder(todo);
|
const ordered = resolveDependencyOrder(todo);
|
||||||
let started = 0;
|
let started = 0;
|
||||||
|
let loggedMissingAgentStoreThisPass = false;
|
||||||
|
|
||||||
for (const taskId of ordered) {
|
for (const taskId of ordered) {
|
||||||
const task = tasks.find((t) => t.id === taskId)!;
|
const task = tasks.find((t) => t.id === taskId)!;
|
||||||
@@ -954,6 +959,42 @@ export class Scheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (latestSettings.ephemeralAgentsEnabled === false && !freshTask.assignedAgentId) {
|
||||||
|
if (!this.options.agentStore) {
|
||||||
|
if (!loggedMissingAgentStoreThisPass) {
|
||||||
|
loggedMissingAgentStoreThisPass = true;
|
||||||
|
schedulerLog.warn("ephemeralAgentsEnabled=false but scheduler has no agentStore; falling back to legacy dispatch behavior");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const selectedAgent = await selectPermanentAgentForTask({
|
||||||
|
task: freshTask,
|
||||||
|
agentStore: this.options.agentStore,
|
||||||
|
taskStore: this.store,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!selectedAgent) {
|
||||||
|
await this.store.updateTask(task.id, { status: "queued" });
|
||||||
|
if (!this.wasPermanentAgentUnavailable.has(task.id)) {
|
||||||
|
await this.logDispatchQueuedReason(
|
||||||
|
task.id,
|
||||||
|
"queued — no permanent executor available (ephemeral agents disabled)",
|
||||||
|
);
|
||||||
|
this.wasPermanentAgentUnavailable.add(task.id);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.store.updateTask(task.id, { assignedAgentId: selectedAgent.id });
|
||||||
|
await this.store.logEntry(
|
||||||
|
task.id,
|
||||||
|
`Auto-assigned to permanent agent ${selectedAgent.id} (ephemeral agents disabled)`,
|
||||||
|
);
|
||||||
|
this.wasPermanentAgentUnavailable.delete(task.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.wasPermanentAgentUnavailable.delete(task.id);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear status, reserve worktree path, and then move to in-progress.
|
// Clear status, reserve worktree path, and then move to in-progress.
|
||||||
// Reset mergeRetries so a fresh execution gets a fresh merge budget —
|
// Reset mergeRetries so a fresh execution gets a fresh merge budget —
|
||||||
// otherwise a task whose previous run exhausted its 3 retries (e.g.
|
// otherwise a task whose previous run exhausted its 3 retries (e.g.
|
||||||
@@ -976,6 +1017,7 @@ export class Scheduler {
|
|||||||
});
|
});
|
||||||
this.wasNodeBlocked.delete(task.id);
|
this.wasNodeBlocked.delete(task.id);
|
||||||
this.wasNodeDispatchValidationBlocked.delete(task.id);
|
this.wasNodeDispatchValidationBlocked.delete(task.id);
|
||||||
|
this.wasPermanentAgentUnavailable.delete(task.id);
|
||||||
this.clearDispatchQueuedReasonMemo(task.id);
|
this.clearDispatchQueuedReasonMemo(task.id);
|
||||||
await this.store.logEntry(task.id, `Node routing resolved: ${effectiveNode.nodeId ?? "local"} (source: ${effectiveNode.source})`);
|
await this.store.logEntry(task.id, `Node routing resolved: ${effectiveNode.nodeId ?? "local"} (source: ${effectiveNode.source})`);
|
||||||
this.options.onSchedule?.(task);
|
this.options.onSchedule?.(task);
|
||||||
|
|||||||
Reference in New Issue
Block a user