taskforce-aiagent
Version:
TaskForce is a modular, open-source, production-ready TypeScript agent framework for orchestrating AI agents, LLM-powered autonomous agents, task pipelines, dynamic toolchains, RAG workflows and memory/retrieval systems.
802 lines (797 loc) โข 40.1 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskForce = void 0;
const fs_1 = __importDefault(require("fs"));
const chalk_1 = __importDefault(require("chalk"));
const task_js_1 = require("../tasks/task.js");
const agent_helper_js_1 = require("../agents/agent.helper.js");
const smartManagerAgent_js_1 = require("../agents/smartManagerAgent.js");
const agentRegistry_js_1 = require("../agents/agentRegistry.js");
const log_helper_js_1 = require("../helpers/log.helper.js");
const helper_js_1 = require("../helpers/helper.js");
const memoryFactory_js_1 = require("../memory/memoryFactory.js");
const enum_js_1 = require("../configs/enum.js");
const readline_sync_1 = __importDefault(require("readline-sync"));
const openai_1 = require("openai");
const events_1 = require("events");
const path_1 = __importDefault(require("path"));
const dotenv_1 = __importDefault(require("dotenv"));
const delegation_guard_js_1 = require("../agents/delegation.guard.js");
const summarize_helper_js_1 = require("../helpers/summarize.helper.js");
dotenv_1.default.config();
const openai = new openai_1.OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const emitting = process.env.EMITTING;
class TaskForce extends events_1.EventEmitter {
constructor(config) {
super();
this.globalReplanAttemptCount = 0;
this.maxGlobalReplanLimit = 2;
this.agents = config.agents;
this.tasks = config.tasks;
this.verbose = config.verbose ?? false;
this.memory = config.memory ?? false;
this.executionMode = config.executionMode ?? enum_js_1.ExecutionMode.Sequential;
this.allowParallelProcesses = config.allowParallelProcesses ?? false;
this.maxRetryPerTask = config.maxRetryPerTask ?? 5;
this.maxDelegatePerTask = config.maxDelegatePerTask ?? 3;
this.enableReplanning = config.enableReplanning ?? true;
this.enableAIPlanning = config.enableAIPlanning ?? true;
this.retriever = config.retriever;
// Validate task dependencies
const allTaskIds = new Set(this.tasks.map((t) => t.id));
for (const task of this.tasks) {
if (task.inputFromTask && !allTaskIds.has(task.inputFromTask)) {
throw new Error(`[TaskForce] Task '${task.name}' references missing inputFromTask ID '${task.inputFromTask}'`);
}
}
this.agents.forEach((agent) => {
agent.setVerbose(this.verbose);
agentRegistry_js_1.AgentRegistry.register(agent);
if (agent.toolExecutor?.setTaskForce) {
agent.toolExecutor.setTaskForce(this);
}
});
// Attach memory to agents if global memory is enabled
if (this.memory) {
for (const agent of this.agents) {
if (!agent.memoryScope || agent.memoryScope === enum_js_1.MemoryScope.None)
continue;
// Attach memory only if not externally set
if (!agent.memoryProvider && !agent.memoryInitialized) {
if (agent.memoryScope === enum_js_1.MemoryScope.Short) {
agent.memoryProvider = (0, memoryFactory_js_1.MemoryProvider)(agent.name, agent.memoryScope);
}
else if (agent.memoryScope === enum_js_1.MemoryScope.Long) {
const mode = agent.memoryMode ?? enum_js_1.MemoryMode.Same;
agent.memoryProvider = (0, memoryFactory_js_1.MemoryProvider)(agent.name, agent.memoryScope, mode);
}
}
}
}
if (this.executionMode === enum_js_1.ExecutionMode.Hierarchical ||
this.executionMode === enum_js_1.ExecutionMode.AiDriven) {
this.managerAgent = new smartManagerAgent_js_1.SmartManagerAgent({
name: "Smart Manager",
role: "Autonomous Task Coordinator",
goal: "Dynamically plan, assign, and evaluate tasks based on input context.",
backstory: "You are responsible for delegating and validating the entire task pipeline.",
model: config.managerModel || process.env.DEFAULT_MANAGER_MODEL,
allowDelegation: false,
taskForce: this,
});
this.managerAgent.setVerbose(this.verbose);
}
}
emitStep(payload) {
this.emit("step", {
...payload,
timestamp: Date.now(),
});
}
async run(inputs) {
(0, log_helper_js_1.initializeTaskforceLogFile)();
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ [Task Force] Running with ${this.agents
.map((a) => a.name)
.join(", ")} agents and ${this.tasks
.map((t) => t.name)
.join(", ")} tasks`, chalk_1.default.yellow);
}
let finalContext;
let executedTaskIds = [];
if (this.executionMode === enum_js_1.ExecutionMode.Hierarchical) {
if (!this.managerAgent) {
throw new Error("Manager agent not set for hierarchical execution.");
}
if (this.tasks.length === 1 && this.enableAIPlanning) {
const mainTask = this.tasks[0];
const subtasks = await this.decomposeMainTask(mainTask);
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`[TaskForce] Decomposed main task '${mainTask.name}' into ${subtasks.length} subtasks.`, chalk_1.default.green);
}
this.tasks = subtasks;
}
const { taskPlan, context } = await this.createTaskPlan(inputs);
executedTaskIds = taskPlan.map((t) => t.id);
if (this.allowParallelProcesses) {
finalContext = await this.runParallelHierarchicalFiltered(taskPlan, context);
}
else {
finalContext = await this.runHierarchicalFiltered(taskPlan, context);
}
finalContext = await this.reviewFinalOutput(finalContext, this.allowParallelProcesses);
}
else if (this.executionMode === enum_js_1.ExecutionMode.AiDriven) {
const plan = await (0, agent_helper_js_1.generateDynamicPlan)({
inputs,
agents: this.agents,
model: this.managerAgent?.model ?? enum_js_1.SupportedModel.GPT_4O_MINI,
verbose: this.verbose,
});
this.tasks = plan.tasks;
this.agents = plan.agents;
switch (plan.executionMode) {
case "parallel":
finalContext = await this.runParallelHierarchicalFiltered(this.tasks, inputs);
break;
case "hierarchical":
finalContext = await this.runHierarchicalFiltered(this.tasks, inputs);
break;
default:
finalContext = await this.runSequential(inputs);
}
}
else {
executedTaskIds = this.tasks.map((t) => t.id);
finalContext = await this.runSequential(inputs);
}
finalContext = (0, helper_js_1.cleanFinalContext)(finalContext);
return {
result: finalContext,
executedTaskIds,
};
}
async reviewFinalOutput(finalContext, isParallel) {
if (!this.enableReplanning) {
return finalContext;
}
for (const [taskId, output] of Object.entries(finalContext)) {
if (typeof output === "string") {
if (output.includes("please provide") || output.includes("DELEGATE(")) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ System Check rejected final output due to incomplete or delegated result in task '${taskId}'`, chalk_1.default.cyan);
}
finalContext.__replanReason__ = `System Check: Unresolved output in task '${taskId}'`;
return this.replanRun(finalContext, isParallel);
}
try {
const parsed = JSON.parse(output);
if (parsed?.__delegate__) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ System Check found unresolved __delegate__ in task '${taskId}'`, chalk_1.default.cyan);
}
finalContext.__replanReason__ = `System Check: __delegate__ found in task '${taskId}'`;
return this.replanRun(finalContext, isParallel);
}
}
catch {
// ignore
}
}
}
// General review by the Manager Agent
const review = await this.managerAgent.reviewFinalOutput(finalContext);
if (review.action === "replan") {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ [Manager Agent] rejected final output: ${review.reason}`, chalk_1.default.magenta);
}
finalContext.__replanReason__ = `Manager Agent: ${review.reason}`;
return this.replanRun(finalContext, isParallel);
}
if (review.action === "accept" && this.verbose) {
(0, log_helper_js_1.TFLog)(`โ
[Manager Agent] Final output accepted.`, chalk_1.default.greenBright);
}
return finalContext;
}
computeReplanTasks(failedTasks, taskList) {
const replanTasks = new Set(failedTasks);
const taskMap = new Map(taskList.map((t) => [t.id, t]));
let added = true;
while (added) {
added = false;
for (const task of taskList) {
if (task.inputFromTask &&
replanTasks.has(task.inputFromTask) &&
!replanTasks.has(task.id)) {
replanTasks.add(task.id);
added = true;
}
}
}
return replanTasks;
}
async replanRun(inputs, isParallel) {
if ((inputs.__replanCount__ || 0) >= this.maxGlobalReplanLimit ||
this.globalReplanAttemptCount >= this.maxGlobalReplanLimit) {
(0, log_helper_js_1.TFLog)(`๐ซ Replan limit (${this.maxGlobalReplanLimit}) reached. Aborting.`, chalk_1.default.red);
return inputs;
}
this.globalReplanAttemptCount++;
inputs.__replanCount__ = (inputs.__replanCount__ || 0) + 1;
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ Re-running full task pipeline [attempt ${inputs.__replanCount__}] due to: ${inputs.__replanReason__}`, chalk_1.default.cyan);
}
const { taskPlan, context } = await this.createTaskPlan(inputs);
const failedTasks = Object.entries(inputs)
.filter(([_, output]) => {
if (typeof output !== "string")
return false;
if (output.trim() === "")
return true;
if (output.includes("please provide") || output.includes("DELEGATE(")) {
return true;
}
try {
const parsed = JSON.parse(output);
return typeof parsed === "object" && parsed?.__delegate__;
}
catch {
return false;
}
})
.map(([taskId]) => taskId);
const replanSet = this.computeReplanTasks(failedTasks, taskPlan);
const filteredTaskPlan = taskPlan.filter((t) => replanSet.has(t.id));
const filteredContext = { ...context };
// Save the outputs of successful tasks
for (const [key, value] of Object.entries(inputs)) {
if (!replanSet.has(key)) {
filteredContext[key] = value;
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ [Replan] Keeping output for task '${key}'`, chalk_1.default.cyan);
}
}
}
return isParallel
? await this.runParallelHierarchicalFiltered(filteredTaskPlan, filteredContext)
: await this.runHierarchicalFiltered(filteredTaskPlan, filteredContext);
}
async runSequential(inputs) {
let result = "";
let taskIndex = 0;
let results = {};
while (taskIndex < this.tasks.length) {
const task = this.tasks[taskIndex];
const agent = this.agents.find((a) => a.name === task.agent);
if (!agent) {
taskIndex++;
continue;
}
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ [Task] Starting task '${task.name}' assigned to '${agent.name}'`, chalk_1.default.yellow);
}
const output = await this.runTask(agent, task, inputs, task.description, task.outputFormat, result, undefined, undefined, enum_js_1.ExecutionMode.Sequential, this.retriever);
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`โ
[Task] Completed '${task.name}'`, chalk_1.default.yellow);
(0, log_helper_js_1.TFLog)(`๐งพ Output:\n${output}\n`, chalk_1.default.white);
}
const delegation = await (0, agent_helper_js_1.delegateTo)(agent, output, inputs);
if (delegation) {
const delegateAgent = this.agents.find((a) => a.name === delegation.delegateTo);
if (!delegateAgent)
throw new Error(`โ Agent '${delegation.delegateTo}' not found`);
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐งญ ${agent.name} delegates to ${delegateAgent.name}`, chalk_1.default.red);
}
const delegateResult = await delegateAgent.runTask(inputs, delegation.task, undefined, output, undefined, undefined, undefined, undefined, this.retriever);
result = delegateResult;
// restart chain from the delegated agent
taskIndex = this.tasks.findIndex((t) => t.agent === agent.name) + 1;
continue;
}
results[task.id] = output;
result = output;
taskIndex++;
}
return results;
}
async runHierarchicalFiltered(taskPlan, context) {
if (!this.managerAgent) {
throw new Error("Hierarchical mode requires a managerAgent");
}
for (const task of taskPlan) {
let assignedAgent;
if (typeof task.agent === "string") {
const agent = this.agents.find((a) => a.name === task.agent);
if (!agent)
throw new Error(`Agent '${task.agent}' not found`);
assignedAgent = agent;
}
else if (task.agent) {
assignedAgent = task.agent;
}
else {
assignedAgent = await this.managerAgent.assignAgent(task, this.agents);
if (!assignedAgent) {
throw new Error(`Failed to assign agent for task '${task.name}'`);
}
}
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐งฉ [Task] Running '${task.name}' with '${assignedAgent.name}'`, chalk_1.default.yellow);
}
let inputFromPreviousTask = this.getChainedInput(task, context);
let finalOutput = await this.runTask(assignedAgent, task, context, task.description, task.outputFormat, inputFromPreviousTask, undefined, undefined, undefined, this.retriever);
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`โ
[Task] Completed '${task.name}'`, chalk_1.default.yellow);
(0, log_helper_js_1.TFLog)(`๐งพ Output:\n${finalOutput}\n`, chalk_1.default.white);
}
finalOutput = await this.evaluateTaskLoop(task, assignedAgent, context, finalOutput);
if (finalOutput.includes("DELEGATE(")) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`โ ๏ธ [Task '${task.name}'] unresolved delegation output remains`, chalk_1.default.red);
}
}
if (!finalOutput.includes("DELEGATE(")) {
context[task.id] = finalOutput;
}
}
return context;
}
async runParallelHierarchicalFiltered(taskPlan, context) {
// taskId โ list of resolve functions (multiple tasks can wait for same input)
const contextReady = new Map();
const taskPromises = taskPlan.map((task) => {
return new Promise(async (resolve) => {
const taskKey = task.inputFromTask;
if (taskKey && !context[taskKey]) {
await new Promise((waitForInput) => {
if (!contextReady.has(taskKey)) {
contextReady.set(taskKey, []);
}
contextReady.get(taskKey).push(waitForInput);
});
}
const assignedAgent = this.agents.find((a) => a.name === task.agent);
if (!assignedAgent)
throw new Error(`Agent '${task.agent}' not found`);
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`โก [Parallel Task] Running '${task.name}' with '${assignedAgent.name}'`, chalk_1.default.cyan);
}
const input = this.getChainedInput(task, context);
const output = await this.runTask(assignedAgent, task, context, task.description, task.outputFormat, input, undefined, undefined, undefined, this.retriever);
const finalOutput = await this.evaluateTaskLoop(task, assignedAgent, context, output);
if (finalOutput.includes("DELEGATE(")) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`โ ๏ธ [Task '${task.name}'] unresolved delegation output remains`, chalk_1.default.red);
}
}
if (!finalOutput.includes("DELEGATE(")) {
context[task.id] = finalOutput;
}
if (contextReady.has(task.id)) {
for (const resolveFn of contextReady.get(task.id)) {
resolveFn();
}
contextReady.delete(task.id);
}
resolve({ key: task.id, value: finalOutput });
});
});
const results = await Promise.all(taskPromises);
const finalContext = { ...context };
for (const { key, value } of results) {
finalContext[key] = value;
}
return finalContext;
}
async handleDelegationAndToolOutput(task, output, agent, context) {
// Handle DELEGATE(...)
const delegateMatch = output.match(/^DELEGATE\((.+?),\s*"(.*)"\)$/);
if (delegateMatch) {
const delegateAgentName = delegateMatch[1].trim();
const delegateTaskDesc = delegateMatch[2].trim();
const delegateAgent = this.agents.find((a) => a.name === delegateAgentName);
if (!delegateAgent)
throw new Error(`Agent '${delegateAgentName}' not found`);
if (delegateAgent.name === agent.name) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`โ ๏ธ Skipping self-delegation: ${agent.name}`, chalk_1.default.red);
}
return output;
}
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ค DELEGATE() triggered in task '${task.name}': ${agent.name} โ ${delegateAgent.name}`, chalk_1.default.magenta);
}
if (emitting === "true") {
this.emitStep({
agent: agent.name,
task: task.name,
action: "delegated",
to: delegateAgent.name,
reason: delegateTaskDesc,
});
}
const delegateResult = await this.runTask(delegateAgent, task, context, delegateTaskDesc, task.outputFormat, output, undefined, undefined, undefined, this.retriever);
// context[task.id] = delegateResult;
(0, log_helper_js_1.logTaskChaining)(agent.name, delegateAgent.name, delegateResult);
return delegateResult;
}
// Handle TOOL(...)
const toolMatch = output.match(/^TOOL\((.+?),\s*(\{.*\})\)$/);
if (toolMatch) {
const toolName = toolMatch[1].trim();
const toolInputRaw = toolMatch[2];
const toolExecutor = this.managerAgent?.toolExecutor;
const tools = toolExecutor?.getTools();
const tool = tools?.find((t) => t.name === toolName);
if (!tool)
throw new Error(`Tool '${toolName}' not found`);
const toolInput = JSON.parse(toolInputRaw);
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ง Running tool '${toolName}' for task '${task.name}' by agent '${agent.name}' with input ${toolInputRaw}`, chalk_1.default.cyan);
}
const toolResult = await tool.handler(toolInput);
let summarized = toolResult;
if (typeof toolResult === "string" && toolResult.length > 1000) {
try {
summarized = await (0, summarize_helper_js_1.generateMemorySummary)(toolResult);
if (this.verbose) {
(0, log_helper_js_1.TFLog)("๐ Tool result summarized for memory efficiency.", chalk_1.default.gray);
}
}
catch (err) {
(0, log_helper_js_1.TFLog)(`โ ๏ธ Failed to summarize tool output: ${err}`, chalk_1.default.red);
summarized = toolResult; // fallback
}
}
const rerunResult = await this.runTask(agent, task, context, task.description, task.outputFormat, toolResult, undefined, "Output was tool result", undefined, this.retriever);
context[task.id] = rerunResult;
return rerunResult;
}
return output;
}
topologicalSortTasks(tasks) {
const sorted = [];
const visited = new Set();
const taskMap = new Map();
for (const task of tasks) {
taskMap.set(task.id, task);
}
function visit(task) {
if (visited.has(task.id))
return;
if (task.inputFromTask && taskMap.has(task.inputFromTask)) {
visit(taskMap.get(task.inputFromTask));
}
visited.add(task.id);
sorted.push(task);
}
for (const task of tasks) {
visit(task);
}
return sorted;
}
async createTaskPlan(inputData
//executeInParallel: boolean
) {
const context = { ...inputData };
let taskPlan = await this.managerAgent.planTasks(this.tasks, context);
taskPlan = this.topologicalSortTasks(taskPlan);
// Reset internal replan flags
for (const task of taskPlan) {
if ("__replanReasonUsed" in task) {
delete task.__replanReasonUsed;
}
}
return { taskPlan, context };
}
async decomposeMainTask(mainTask) {
if (!this.managerAgent) {
throw new Error("ManagerAgent not initialized");
}
const subtasksData = await this.managerAgent.decomposeTask(mainTask, this.agents, this.verbose);
const subtasks = subtasksData.map((st) => {
const taskData = {
id: st.id,
name: st.name,
description: st.description,
outputFormat: "text",
agent: st.agent,
};
return new task_js_1.Task(taskData);
});
return subtasks;
}
getChainedInput(task, context) {
const sourceTaskId = task.inputFromTask;
if (!sourceTaskId) {
return "";
}
const sourceTaskOutput = context[sourceTaskId];
if (sourceTaskOutput === undefined)
return "";
const sourceTask = this.tasks.find((t) => t.id === sourceTaskId);
(0, log_helper_js_1.logTaskChaining)(sourceTask?.name || sourceTaskId, task.name, sourceTaskOutput);
const rawInput = task.inputMapper
? task.inputMapper(sourceTaskOutput)
: sourceTaskOutput;
return typeof rawInput === "string" && rawInput.length > 3000
? rawInput.slice(0, 3000) + "\n\n[...truncated]"
: rawInput;
}
async evaluateTaskLoop(task, agent, context, initialOutput) {
const guardCheck = (0, delegation_guard_js_1.checkDelegationValidity)(agent, task);
if (!guardCheck.canDelegate) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ซ Delegation blocked for '${task.name}': ${guardCheck.reason}`, chalk_1.default.red);
}
return `โ ๏ธ Delegation loop or hop limit reached. Accepting output as-is.\n\n${initialOutput}`;
}
(0, delegation_guard_js_1.updateDelegationChain)(task, agent);
let finalOutput = initialOutput;
let needsEvaluation = true;
let retryCount = 0;
let retryReasons = [];
while (needsEvaluation) {
const review = await this.managerAgent.evaluateTaskOutput(task, finalOutput, this.agents);
// ๐ฉ Retry loop detected (same agent)
if (review.action === "retry" && review.retryWith?.name === task.agent) {
retryCount++;
if (retryCount >= this.maxRetryPerTask) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ซ Max retry count (${this.maxRetryPerTask}) reached for task '${task.name}' due to self-retry. Proceeding with last output.`, chalk_1.default.red);
}
needsEvaluation = false;
break;
}
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`โ ๏ธ [Retry Loop] '${task.name}' retrying on same agent '${agent.name}'. Count: ${retryCount}`, chalk_1.default.red);
}
if (review.reason) {
retryReasons.push(review.reason);
}
finalOutput = await this.runTask(agent, task, context, task.description, task.outputFormat, finalOutput, review.reason, undefined, undefined, this.retriever);
continue;
}
if (review.action === "delegate" &&
(review.delegateTo.name === task.agent ||
finalOutput ===
`DELEGATE(${review.delegateTo.name}, "${task.description}")`)) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`โ ๏ธ [Delegation Loop Detected] '${task.name}' tried to delegate back to itself. Forcing accept.`, chalk_1.default.red);
}
needsEvaluation = false;
break;
}
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`[Manager Agent] Evaluation decision for task '${task.name}' by agent '${agent.name}': ${JSON.stringify(review, (0, helper_js_1.getSafeReplacer)())}`, chalk_1.default.blue);
}
if (review.action === "accept") {
const delegationScore = (0, delegation_guard_js_1.checkDelegationScore)(finalOutput);
if (delegationScore.isWeak) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ [DelegationScore Metrics] Task '${task.name}' by '${agent.name}' scored ${delegationScore.score}/10 โ Reason: ${delegationScore.reason}`, chalk_1.default.red);
}
if (emitting === "true") {
this.emitStep({
agent: agent.name,
task: task.name,
action: "delegationScore",
score: delegationScore.score,
reason: delegationScore.reason,
});
}
retryCount++;
if (retryCount >= this.maxRetryPerTask) {
(0, log_helper_js_1.TFLog)(`๐ซ Max retry count reached despite weak delegation score.`, chalk_1.default.red);
break;
}
finalOutput = await this.runTask(agent, task, context, task.description, task.outputFormat, finalOutput, delegationScore.reason, undefined, undefined, this.retriever);
continue;
}
if (agent.memoryScope !== enum_js_1.MemoryScope.None && agent.memoryProvider) {
await agent.memoryProvider.storeMemory({
taskId: task.description,
input: JSON.stringify(context),
output: finalOutput,
metadata: { agent: agent.name },
});
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ง [MemoryStore] Saved memory for ${task.name} by ${agent.name}`, chalk_1.default.gray);
}
}
if (this.verbose && retryReasons.length > 0) {
(0, log_helper_js_1.TFLog)(`๐ [Retry Trace] Task '${task.name}' accepted after ${retryCount} retries. Reasons: ${retryReasons.join(" | ")}`, chalk_1.default.gray);
}
// delegasyon & tool handling
let delegateDepth = 0;
let delegated = true;
while (delegated && delegateDepth < this.maxDelegatePerTask) {
const processed = await this.handleDelegationAndToolOutput(task, finalOutput, agent, context);
if (processed === finalOutput || processed.includes("DELEGATE(")) {
if (this.verbose && processed.includes("DELEGATE(")) {
(0, log_helper_js_1.TFLog)(`โ ๏ธ Unresolved DELEGATE() remains after delegation depth check`, chalk_1.default.red);
}
delegated = false;
}
else {
finalOutput = processed;
delegateDepth++;
}
}
if (delegateDepth >= this.maxDelegatePerTask) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ซ Max delegation depth for '${task.name}'. Forcing execution by '${agent.name}'.`, chalk_1.default.red);
}
agent.setDelegationDisallowed(true);
finalOutput = await this.runTask(agent, task, context, task.description, task.outputFormat, finalOutput, undefined, "Delegation blocked. Forced to execute.", undefined, this.retriever);
}
needsEvaluation = false;
if (emitting === "true") {
this.emitStep({
agent: agent.name,
task: task.name,
action: "completed",
output: finalOutput,
});
}
}
else if (review.action === "retry") {
retryCount++;
if (retryCount >= this.maxRetryPerTask) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ซ Max retry count (${this.maxRetryPerTask}) reached for task '${task.name}'. Proceeding with last output.`, chalk_1.default.red);
}
needsEvaluation = false;
break;
}
const retryAgent = review.retryWith
? review.retryWith
: await this.managerAgent.assignAgent(task, this.agents);
finalOutput = await this.runTask(retryAgent, task, context, task.description, task.outputFormat, finalOutput, review.reason, undefined, undefined, this.retriever);
}
else if (review.action === "delegate") {
finalOutput = await this.runTask(review.delegateTo, task, context, task.description, task.outputFormat, finalOutput, undefined, review.reason, undefined, this.retriever);
if (finalOutput.includes("DELEGATE(")) {
if (this.verbose) {
(0, log_helper_js_1.TFLog)(`๐ซ [DELEGATION Validation] Output still contains unresolved DELEGATE(...) for task '${task.name}'`, chalk_1.default.red);
}
// If can retry
retryCount++;
if (retryCount < this.maxRetryPerTask) {
finalOutput = await this.runTask(agent, task, context, task.description, task.outputFormat, finalOutput, "Unresolved DELEGATE after handling attempt", undefined, undefined, this.retriever);
continue; // evaluateTaskLoop goto top
}
// If can't retry, fallback to original output
finalOutput = `โ ๏ธ Unresolved DELEGATE() in final output. Original content:\n${finalOutput}`;
}
}
}
return finalOutput;
}
async runTask(agent, task, inputs, taskDescription, outputFormat, inputFromPrevious, retryReason, delegateReason, executionMode, retriever) {
if (emitting === "true") {
this.emitStep({
agent: agent.name,
task: task.name,
action: "start",
});
}
let replanReason = inputs.__replanReason__ && !task.__replanReasonUsed
? inputs.__replanReason__
: undefined;
const resolvedDescription = (0, helper_js_1.interpolateTemplate)(taskDescription, inputs);
if (this.verbose && agent.trained) {
(0, log_helper_js_1.TFLog)(`๐ [Training Enriched] Agent "${agent.name}" used training suggestions during this run.`, chalk_1.default.cyan);
}
const output = await agent.runTask(inputs, resolvedDescription, outputFormat, inputFromPrevious, retryReason, delegateReason, replanReason, executionMode, retriever);
if (replanReason) {
task.__replanReasonUsed = true;
}
if (replanReason && this.verbose) {
(0, log_helper_js_1.TFLog)(`[Manager Agent]๐ Replan Reason: ${replanReason}`, chalk_1.default.magenta);
}
return output;
}
async train(iterations, globalInputs = {}) {
for (const agent of this.agents) {
const task = this.tasks.find((t) => t.agent === agent.name);
if (!task) {
console.warn(`โ ๏ธ No task found for agent ${agent.name}. Skipping training.`);
continue;
}
const trainingsDir = path_1.default.join(process.cwd(), "trainings");
fs_1.default.mkdirSync(trainingsDir, { recursive: true });
const outPath = path_1.default.join(trainingsDir, `${agent.name.toLowerCase().replace(/\s/g, "_")}_trained.json`);
let previousData = {
suggestions: [],
quality: 0,
final_summary: "",
};
if (fs_1.default.existsSync(outPath)) {
const raw = fs_1.default.readFileSync(outPath, "utf-8");
previousData = JSON.parse(raw);
}
const examples = [];
for (let i = 0; i < iterations; i++) {
console.log(`\n๐ [${agent.name}] Iteration ${i + 1}/${iterations}`);
const initialOutput = await agent.runTask(globalInputs, task.description, task.outputFormat, "", undefined, undefined, undefined, enum_js_1.ExecutionMode.Sequential, this.retriever, true);
console.log(`\n๐ค Initial Output:\n${initialOutput}\n`);
const feedback = readline_sync_1.default
.question(`[${agent.name}] โ๏ธ Your feedback: `)
.trim();
if (!feedback) {
console.log("โ ๏ธ No feedback provided. Skipping iteration.");
continue;
}
const improvedOutput = await agent.runTask(globalInputs, task.description, task.outputFormat, initialOutput + `\n\n[Feedback from user]: ${feedback}`, undefined, undefined, undefined, enum_js_1.ExecutionMode.Sequential, this.retriever, true);
console.log(`\n๐ฅ Improved Output:\n${improvedOutput}\n`);
examples.push({
initialOutput,
humanFeedback: feedback,
improvedOutput,
});
}
const newSuggestions = examples.flatMap((e) => e.humanFeedback ? [`${e.humanFeedback}`] : []);
const mergedSuggestions = [
...(previousData.suggestions || []),
...newSuggestions,
];
const llmSummaryPrompt = `You are a training compression engine. You are given multiple human feedback comments that have been used to improve an AI agent's outputs.
Your job is to extract the **underlying improvement instructions** from the feedback list and compress them into a clear and concrete list of what the agent should do better next time.
Respond in the following JSON format:
{
"final_summary": "Do this, avoid that, include this... style directives that can be reused in future tasks.",
"quality": 0-10
}
Only include distilled guidance in the final_summary. Do NOT write commentary or explanations.
Strip redundant prefixes like 'Improve based on:' or 'You should...'
The goal is to turn the list into reusable prompt directives.
Use simple English.
Keep the tone neutral and instructive.
Here is the feedback list:
${mergedSuggestions
.map((s) => "- " + s.replace(/^Improve based on:\s*/i, ""))
.join("\n")}
`;
const completion = await openai.chat.completions.create({
model: enum_js_1.SupportedModel.GPT_4O_MINI,
messages: [{ role: "user", content: llmSummaryPrompt }],
temperature: 0.7,
});
let final_summary = "N/A";
let quality = 8;
try {
const parsed = JSON.parse(completion.choices[0].message.content || "{}");
final_summary = parsed.final_summary || "No summary returned.";
quality = parsed.quality || 8;
}
catch {
final_summary = "Failed to parse LLM summary.";
}
const result = {
suggestions: mergedSuggestions,
final_summary,
quality,
};
fs_1.default.writeFileSync(outPath, JSON.stringify(result, null, 2), "utf-8");
console.log(`\nโ
Training complete for ${agent.name}. Saved to ${outPath}`);
}
}
getTaskById(id) {
return this.tasks.find((t) => t.id === id);
}
listTaskNames() {
return this.tasks.map((t) => t.name);
}
getAgentByName(name) {
return this.agents.find((a) => a.name === name);
}
listAgentNames() {
return this.agents.map((a) => a.name);
}
}
exports.TaskForce = TaskForce;