Release - v2.0.0

This commit is contained in:
Mikhail Larchanka
2026-04-14 19:27:24 +02:00
committed by GitHub
15 changed files with 851 additions and 1069 deletions

View File

@@ -91,7 +91,7 @@ AI-Agent/
### Agent layer
- **Planner**: Listens for `plan.create`; uses Lemonade + Model Router to produce a DAG; validates with `validateGraph`; responds with plan.
- **Executor**: Listens for `plan.execute`; computes ready nodes (parallel batch, concurrency limit); dispatches `node.execute` to `node.service` (model-router, rag-service, critic-agent, tool-host); waits for response by `correlationId`; updates Task Memory; after DAG, optional reflection loop (Critic → REVISE → re-run generation, max 3); aggregates result and completes task.
- **Executor**: Listens for `plan.execute`; traverses the DAG and manages **Autonomous Agent Loops**; provides an agent system prompt and core tools; handles **Dynamic Skill Loading**; after DAG, optional reflection loop.
- **Critic**: Listens for `reflection.evaluate`; uses Lemonade with Critic prompt; returns structured `{ decision: PASS|REVISE, feedback, score }`.
### Service layer
@@ -121,7 +121,7 @@ AI-Agent/
3. Core → Planner: `plan.create` (goal); Planner → Core: plan (DAG).
4. Core → Task Memory: `task.create` (taskId, goal, nodes, edges).
5. Core → Executor: `plan.execute` (taskId, plan, goal).
6. Executor runs DAG: `node.execute` to model-router, rag-service, critic-agent, tool-host; Task Memory updates; optional Critic revision loop.
6. Executor runs DAG: executes specialized **Agents** with instructions; Agents use tools and dynamically call `load_skill`; Task Memory updates; optional Critic revision loop.
7. Executor → Core: response with aggregated result.
8. Core → Telegram Adapter: `telegram.send` (chatId, text).
9. User sees reply in Telegram.

View File

@@ -13,7 +13,7 @@ A multi-process AI platform with type-safe IPC and capability-graph execution. U
## Features
- **Multi-agent pipeline**: Planner → Task Memory → Executor → Critic (optional revision loop)
- **Capability graph (DAG)**: Nodes for `generate_text`, `semantic_search`, `reflect`, `tool`; parallel execution where dependencies allow
- **Capability graph (DAG)**: Nodes for specialized **Agents** and tool-agnostic LLM generation; parallel execution where dependencies allow
- **Type-safe IPC**: JSONL over stdin/stdout with Zod-validated envelopes
- **Conversation Memory**: Short-term memory (last 5 tasks) is injected into the Planner for immediate session context; `/new` resets the session and archives the conversation.
- **Session-Scoped RAG**: Memory searches are session-scoped by default to prevent context leakage after `/new`, with an optional `global` scope.

View File

@@ -47,13 +47,14 @@ Prevents chaotic reasoning loops.
## 4. Capability Graph Pattern
Planner produces a Directed Acyclic Graph (DAG):
Planner produces a Directed Acyclic Graph (DAG) consisting of independent **Agents**:
Example:
semantic_search → sql_query → generate_text → reflect
research_agent → coding_agent → testing_agent → analysis_agent
Executor processes nodes sequentially or parallel when possible.
Nodes are now specialized autonomous agents that can dynamically load instructions (Skills) as needed.
Executor processes these agent nodes sequentially or parallel when possible.
---

View File

@@ -1,43 +1,41 @@
# CAPABILITY GRAPH JSON FORMAT
## Execution Plan Structure
```
```json
{
"taskId": "uuid",
"complexity": "medium",
"reflectionMode": "NORMAL",
"nodes": [
{
"id": "node1",
"type": "semantic_search",
"service": "rag-service",
"id": "research-01",
"type": "agent",
"service": "executor",
"input": {
"query": "scalable API architecture"
"name": "Research Agent",
"instructions": "Search for the latest F1 results using http_search."
}
},
{
"id": "node2",
"id": "summary-01",
"type": "agent",
"service": "executor",
"input": {
"name": "Summary Agent",
"instructions": "Summarize the research from {{research-01}} and load the 'email' skill to prepare a draft."
},
"dependsOn": ["research-01"]
},
{
"id": "node-final",
"type": "generate_text",
"service": "model-router",
"input": {
"modelClass": "medium",
"promptTemplate": "architecture_template",
"dependsOn": ["node1"]
}
},
{
"id": "node3",
"type": "reflect",
"service": "critic-agent",
"input": {
"dependsOn": ["node2"]
"prompt": "Construct final Telegram response: {{summary-01}}",
"system_prompt": "analyzer"
}
}
],
"edges": [
{ "from": "node1", "to": "node2" },
{ "from": "node2", "to": "node3" }
{ "from": "research-01", "to": "summary-01" },
{ "from": "summary-01", "to": "node-final" }
]
}
```
@@ -65,8 +63,9 @@ interface CapabilityNode {
## Node types (model-router / Generator)
- **generate_text** — LLM generation; input: `modelClass`, optional `prompt`, context from dependencies.
- **summarize** — Memory extraction from chat history; input: `chatHistory` (text). Uses dedicated summarizer system prompt. Used by Orchestrator for conversation archiving.
- **agent** — Autonomous LLM loop; input: `name`, `instructions`. High-level strategic node that can use tools and dynamically call `load_skill`.
- **generate_text** — Simple LLM generation; input: `prompt`, context from dependencies. Used for final consolidation.
- **summarize** — Memory extraction from chat history; input: `chatHistory`.
## Graph Rules

View File

@@ -26,8 +26,8 @@ Responsibilities:
Responsibilities:
- Intent analysis
- Capability determination
- Execution graph creation
- Model complexity selection
- Creates an **Agent-based Execution Graph** (DAG)
- Assigns specialized roles to agents (input: `name`, `instructions`)
Input:
- User message
@@ -41,10 +41,10 @@ Output:
### 3. Executor Agent
Responsibilities:
- Execute DAG nodes
- Call services
- Aggregate intermediate results
- Update task memory
- Traverses the DAG and manages **Autonomous Agent Loops**
- Provides core tools (shell, browser, search) to agents
- Handles **Dynamic Skill Loading** via `load_skill` tool
- Aggregates results and updates task memory
---

1374
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@
"better-sqlite3": "^12.6.2",
"ffmpeg-static": "^5.3.0",
"node-cron": "^4.2.1",
"node-telegram-bot-api": "^0.67.0",
"node-telegram-bot-api": "^0.63.0",
"nodejs-whisper": "^0.2.9",
"pino": "^9.5.0",
"playwright": "^1.48.0",
@@ -43,9 +43,9 @@
"@types/node-cron": "^3.0.11",
"@types/node-telegram-bot-api": "^0.64.13",
"@types/turndown": "^5.0.5",
"@vitest/coverage-v8": "^4.0.18",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"vitest": "^4.0.18",
"@vitest/coverage-v8": "^4.0.18"
"vitest": "^4.0.18"
}
}

View File

@@ -71,6 +71,20 @@ const SKILL_TOOLS: any[] = [
required: ["local_path"]
}
}
},
{
type: "function",
function: {
name: "load_skill",
description: "Load detailed instructions for a specific skill. Use this if you need more information on how to use a skill from the available list.",
parameters: {
type: "object",
properties: {
skillName: { type: "string", description: "The name of the skill to load" }
},
required: ["skillName"]
}
}
}
];
@@ -89,6 +103,7 @@ import { responsePayloadSchema } from "../shared/protocol.js";
import { getConfig } from "../shared/config.js";
import { SkillManager } from "../services/skill-manager.js";
import { parseTimeExpression } from "../services/time-parser.js";
import { buildAgentPrompt } from "./prompts/agent-node.js";
const PLAN_EXECUTE = "plan.execute";
const NODE_EXECUTE = "node.execute";
@@ -102,7 +117,8 @@ const TYPE_TO_SERVICE: Record<string, string> = {
generate_text: "model-router",
generate: "model-router",
summarize: "model-router",
skill: "model-router", // skills are handled by generation with custom system prompt
skill: "model-router", // legacy skill support
agent: "model-router", // new agent node support
tool: "tool-host",
semantic_search: "rag-service",
reflect: "critic-agent",
@@ -482,10 +498,11 @@ export class ExecutorAgent extends BaseProcess {
...(Object.keys(context).length > 0 && { context }),
};
// Handle skill nodes by swapping prompt and injecting skill system prompt
if (node.type === "skill") {
// Handle agent/skill nodes by running an LLM loop with tool access
if (node.type === "agent" || node.type === "skill") {
const isAgent = node.type === "agent";
const skillName = (node.input?.skillName ?? node.input?.skill) as string;
let task = (node.input?.task ?? node.input?.prompt ?? "") as string;
let task = (node.input?.task ?? node.input?.prompt ?? node.input?.instructions ?? "") as string;
// Replace placeholders from context if present (e.g. {{nodeId}})
for (const [key, value] of Object.entries(context)) {
@@ -498,26 +515,39 @@ export class ExecutorAgent extends BaseProcess {
}
}
const skillPrompt = this.skillManager.getSkillPrompt(skillName);
if (!skillPrompt) {
reject(new Error(`Skill prompt not found: ${skillName}`));
return;
}
// Active Skill loop
// Active Agent/Skill loop
(async () => {
try {
const messages: any[] = [
{ role: "system", content: skillPrompt },
{ role: "user", content: task }
];
const messages: any[] = [];
if (isAgent) {
const skillsList = this.skillManager.listSkills();
const skillsDescription = skillsList.map(s => `- ${s.name}: ${s.description}`).join("\n");
const agentPrompt = buildAgentPrompt(
(node.input?.name as string) || "Task Agent",
task,
skillsDescription,
new Date().toISOString()
);
messages.push({ role: "system", content: agentPrompt });
messages.push({ role: "user", content: "Proceed with the task." });
} else {
// Legacy skill handling
const skillPrompt = this.skillManager.getSkillPrompt(skillName);
if (!skillPrompt) {
reject(new Error(`Skill prompt not found: ${skillName}`));
return;
}
messages.push({ role: "system", content: skillPrompt });
messages.push({ role: "user", content: task });
}
let turnCount = 0;
while (turnCount < MAX_SKILL_TURNS) {
const genResponse = await this.callModelRouter(taskId, node.id, {
type: "generate_text",
input: {
prompt: task, // Fallback, messages are used if present
prompt: task,
messages,
tools: SKILL_TOOLS
},
@@ -532,35 +562,41 @@ export class ExecutorAgent extends BaseProcess {
// Execute each tool call
for (const tc of result.tool_calls) {
const toolName = tc.function.name;
let args = tc.function.arguments;
if (typeof args === "string") {
try {
args = JSON.parse(args);
} catch (e) {
// fallback to raw string if not JSON
}
} catch (e) { }
}
const toolResult = await this.callTool(taskId, node.id, tc.function.name, args, context);
let toolResult: any;
if (toolName === "load_skill") {
const sn = args.skillName;
const content = this.skillManager.getSkillPrompt(sn);
toolResult = content ? `Skill '${sn}' loaded:\n${content}` : `Skill '${sn}' not found.`;
} else {
toolResult = await this.callTool(taskId, node.id, toolName, args, context);
}
let content = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult);
// Safety truncation for large web pages or shell outputs to stay within context limits
if (content.length > 30000) {
content = content.substring(0, 30000) + "\n\n...[TRUNCATED DUE TO LENGTH]...";
content = content.substring(0, 30000) + "\n\n...[TRUNCATED]...";
}
messages.push({
role: "tool",
tool_call_id: tc.id,
name: tc.function.name,
name: toolName,
content
});
}
turnCount++;
} else {
// No more tool calls, we are done
resolve(result);
return;
}
}
reject(new Error(`Skill exceeded maximum turns (${MAX_SKILL_TURNS})`));
reject(new Error(`Agent/Skill exceeded maximum turns (${MAX_SKILL_TURNS})`));
} catch (err) {
reject(err);
}

View File

@@ -0,0 +1,57 @@
/**
* System prompt for the unified Agent node.
*/
export const AGENT_NODE_SYSTEM_PROMPT = `
<role>Specialized Task Agent: {{name}}</role>
<context>
Your specific task: {{instructions}}
Available skills (overview):
{{skillsDescription}}
</context>
<current_time>
ONLY USE THIS FOR DATE/TIME
{{currentTime}}
</current_time>
<instructions>
## 1. DYNAMIC SKILL LOADING
You have access to many specialized skills, but you only see their names and descriptions initially.
- If a skill in the list fits your task, you **MUST** call \`load_skill(skillName: "...")\` to get the full instructions and API details for that skill.
- After loading a skill, the system will provide the full instructions in the next turn.
## 2. TOOL ACCESS
You have access to the following core tools:
- **"shell"**: For terminal commands (ls, cat, mkdir, etc.).
- **"http_get"**: For fetching web pages.
- **"http_search"**: For searching the web.
- **"send_file"**: For sharing files with the user.
- **"schedule_reminder"**: For setting cron-based reminders.
## 3. OUTPUT FORMATTING
- Provide your final answer in a clear, concise format.
- IF you were given a specific goal in the context, ensure your output directly addresses it.
- Use Telegram-supported HTML tags for formatting if the output is intended for the user.
</instructions>
<available_tools>
- load_skill(skillName: string)
- shell(command: string)
- http_get(url: string, useBrowser: boolean)
- http_search(query: string)
- send_file(local_path: string, brief_file_description: string, chatId?: number)
- schedule_reminder(time: string, message: string, isAction: boolean)
</available_tools>
MISSION: COMPLETE THE TASK. USE TOOLS. LOAD SKILLS IF NEEDED.
`;
export function buildAgentPrompt(name: string, instructions: string, skillsDescription: string, currentTime: string): string {
return AGENT_NODE_SYSTEM_PROMPT
.replace("{{name}}", name)
.replace("{{instructions}}", instructions)
.replace("{{skillsDescription}}", skillsDescription)
.replace("{{currentTime}}", currentTime);
}

View File

@@ -10,8 +10,6 @@ Your name is \`🧬 ManBot\`. You are a Professional Data Analyst and Assistant.
Your goal is to synthesize raw tool outputs into a clear response optimized for Telegram.
</role>
<current_date_iso>${new Date().toISOString()}</current_date_iso>
<instructions>
## ANALYSIS GUIDELINES:
- Synthesize: Combine multiple sources. Identify patterns or contradictions.
@@ -19,17 +17,19 @@ Your goal is to synthesize raw tool outputs into a clear response optimized for
- Tone: Friendly, direct, and conversational. Avoid "As an AI..." or "Here is the data...".
</instructions>
<format_constraint>
<response_format>
${TELEGRAM_HTML_FORMAT_INSTRUCTION}
Output: Telegram HTML only. NEVER use Markdown (replace with allowed tags or remove). NEVER use raw JSON.
</format_constraint>`;
</response_format>
MISSION: COMPLETE THE TASK. REPLY WITH TELEGRAM HTML FORMAT.`;
/**
* Builds the analyzer prompt.
*/
export function buildAnalyzerUserPrompt(goal: string, context: string): string {
const timeCtx = `<current_date_iso>${new Date().toISOString()}</current_date_iso>\n\n`;
if (!context || !context.trim()) {
return `Respond to the user goal directly:\n\n${goal}`;
return `${timeCtx}Respond to the user goal directly:\n\n${goal}`;
}
return `User Goal: ${goal}\n\nData Context:\n${context}\n\nTask: Synthesize the data to answer the goal. Use Telegram HTML formatting (no markdown, no tables).`;
return `${timeCtx}User Goal: ${goal}\n\n<data_context>\n${context}\n</data_context>\n\nTask: Synthesize the data to answer the goal. Use Telegram HTML formatting (no markdown, no tables).`;
}

View File

@@ -52,9 +52,9 @@ Return ONLY a raw JSON object. No markdown wrappers.
* Builds the critic prompt with injection protection.
*/
export function buildCriticPrompt(goal: string, draftOutput: string): string {
// Basic sanitization to prevent tag-breaking injection
const safeGoal = goal.replace(/<\/?[^>]+(>|$)/g, "");
const safeDraft = draftOutput.replace(/<\/?[^>]+(>|$)/g, "");
// Wrap in CDATA to prevent XML structure breakage while preserving original tags
const safeGoal = `<![CDATA[\n${goal.replace(/\]\]>/g, ']]]]><![CDATA[>')}\n]]>`;
const safeDraft = `<![CDATA[\n${draftOutput.replace(/\]\]>/g, ']]]]><![CDATA[>')}\n]]>`;
return `<audit_request>
<user_goal>

View File

@@ -8,9 +8,9 @@ export const PLANNER_SYSTEM_PROMPT = `<role>Strategic Execution Planner</role>
<logic_gate>
IF you can fulfill the user's goal using ONLY your internal knowledge (e.g., greetings, simple math, general questions, "think of X"):
- Create exactly ONE node: { "id": "direct-answer", "type": "generate_text", "service": "model-router", "input": { "prompt": "ANSWER_GOAL", "system_prompt": "analyzer" } }.
- DO NOT use any tools.
- DO NOT use any agents.
ELSE:
- Proceed with creating a Capability Graph.
- Proceed with creating a Capability Graph consisting of specialized Agents.
</logic_gate>
<file_context_awareness>
@@ -20,38 +20,31 @@ The user's goal may contain pre-processed file content injected by the system:
- "[Audio transcript: ...]" prefix: speech-to-text transcript of a voice/audio message.
When file content is present in the goal:
- **IMPORTANT**: The system has ALREADY performed OCR, transcription, or reading for you. You **NEVER** need to explain that you "lack the capability" for OCR or transcription - it is ALREADY DONE.
- **DO NOT** look for tools (shell, etc.) to read these files. They are provided as part of the instruction.
- Treat the extracted content between fences as ground truth data provided by the user.
- If the content says "Warning: No OCR text extracted" or similar, it simply means the model couldn't find text in that specific file; acknowledge this to the user, but still perform any other requested actions.
- If asked to analyse/summarise/translate the content, use it directly in a generate_text node — no extra tools needed.
- UNLESS explicitly asked for something beyond the provided text (like searching the web about it), do not use tools.
- If asked about a file that was indexed (too large to inline), add a "memory.semantic.search" step first.
- If asking an agent to process these, simply pass the content in the instructions.
- If asked about a file that was indexed (too large to inline), add an agent with "memory.semantic.search" capability first.
</file_context_awareness>
<instructions>
## 1. SKILLS FIRST (ABSOLUTE PRIORITY)
Before using raw tools, scan <available_skills>.
- If a skill matches the goal, you **MUST** use \`type: "skill"\`.
- Manual "shell" or "http" chains are a last resort when no skill fits.
## 1. AGENTS FIRST
Break down complex tasks into specialized Agents. Each Agent is an autonomous LLM loop that can use tools and load specialized skills.
## 2. TOOL CONSTRAINTS
The "tool-host" service supports ONLY these 4 names in the "tool" field:
- **"shell"**: For ALL terminal commands. (Example: \`"tool": "shell", "arguments": { "command": "cat file.txt" }\`)
- **"http_get"**: For rendering a specific URL (Playwright).
- **"http_search"**: For finding information on the web.
- **"send_file"** (service: "core"): For sharing files produced or found in the sandbox with the user via Telegram. (Input: \`local_path\`, \`brief_file_description\`).
## 2. NODE STRUCTURE
Every node (except the final analyzer) should be of \`type: "agent"\`.
- \`id\`: Unique identifier.
- \`type\`: "agent".
- \`service\`: "executor".
- \`input\`:
- \`name\`: Descriptive role (e.g., "Research Agent", "Coding Agent").
- \`instructions\`: Specific, detailed task for this agent. Use {{nodeId}} to reference output from previous nodes.
## 3. GRAPH ARCHITECTURE RULES
- **Synthesis**: Every research/tool-heavy plan **MUST** end with a "model-router" node (\`system_prompt: "analyzer"\`).
## 3. SKILL USAGE
Scan <available_skills>. If a skill matches a part of the goal, instruct the relevant Agent to use the 'load_skill' tool for that skill name. Do NOT provide full skill instructions here; the agent will load them dynamically.
## 4. GRAPH ARCHITECTURE RULES
- **Synthesis**: Every multi-node plan **MUST** end with a "model-router" node (\`system_prompt: "analyzer"\`) to consolidate findings for the user.
- **Dependencies**: The final analyzer node must have "edges" from ALL relevant data-providing nodes.
- **Acyclic**: Ensure no circular dependencies.
- **Start Node**: At least one node must have no "from" edges.
## 4. VALIDATION CHECKLIST
- Is the JSON syntax perfect?
- Is every "tool" name valid (not 'ls' or 'google')?
- Are all node IDs unique?
- Does the "to" in edges point to an existing "id"?
</instructions>
<output_format>
@@ -62,71 +55,29 @@ Required complexity levels: "small" | "medium" | "large".
export const PLANNER_FEW_SHOT_EXAMPLES = `
<examples>
## Example: System Operation
User: "create folder 'logs' and list permissions"
## Example: Research and Summarize
User: "Who won the F1 race today and why?"
{
"taskId": "task-sys-01",
"complexity": "small",
"reflectionMode": "OFF",
"nodes": [
{
"id": "op-shell",
"type": "tool",
"service": "tool-host",
"input": {
"tool": "shell",
"arguments": { "command": "mkdir -p logs && ls -ld logs" }
}
}
],
"edges": []
}
## Example: Research Task
User: "who won the F1 race today?"
{
"taskId": "task-f1",
"taskId": "task-f1-01",
"complexity": "medium",
"reflectionMode": "OFF",
"nodes": [
{
"id": "f1-search",
"type": "tool",
"service": "tool-host",
"id": "research-agent",
"type": "agent",
"service": "executor",
"input": {
"tool": "http_search",
"arguments": { "query": "F1 race results today" }
"name": "Research Agent",
"instructions": "Find the results of today's F1 race. Use http_search to get the winner and key race events."
}
},
{
"id": "f1-report",
"type": "generate_text",
"service": "model-router",
"input": {
"prompt": "Identify the winner and summarize the podium based on results.",
"system_prompt": "analyzer"
}
}
],
"edges": [
{ "from": "f1-search", "to": "f1-report" }
]
}
## Example: Deep Research
User: "Deep dive into the current status of the RISC-V ecosystem."
{
"taskId": "task-riscv",
"complexity": "large",
"reflectionMode": "OFF",
"nodes": [
{
"id": "research-eco",
"type": "skill",
"id": "summary-agent",
"type": "agent",
"service": "executor",
"input": {
"skillName": "research",
"task": "Investigate RISC-V hardware, software support, and corporate adoption in 2024. Use search first, then follow key documentation links."
"name": "Summary Agent",
"instructions": "Based on the research findings: {{research-agent}}, provide a concise summary of the winner and the main reasons for their victory."
}
},
{
@@ -134,139 +85,65 @@ User: "Deep dive into the current status of the RISC-V ecosystem."
"type": "generate_text",
"service": "model-router",
"input": {
"prompt": "Consolidate the RISC-V research into a comprehensive report.",
"prompt": "Construct the final Telegram message based on: {{summary-agent}}",
"system_prompt": "analyzer"
}
}
],
"edges": [
{ "from": "research-eco", "to": "final-report" }
{ "from": "research-agent", "to": "summary-agent" },
{ "from": "summary-agent", "to": "final-report" }
]
}
## Example: Image with OCR Warning
User: "--- image: receipt.jpg ---\nWarning: No OCR text extracted from the image.\n---"
{
"taskId": "task-img-warn",
"complexity": "small",
"reflectionMode": "OFF",
"nodes": [
{
"id": "direct-answer",
"type": "generate_text",
"service": "model-router",
"input": {
"prompt": "The user provided an image but no text could be extracted. Formulate a polite response asking if they wanted a visual description or if they can send a clearer photo.",
"system_prompt": "analyzer"
}
}
],
"edges": []
}
## Example: Reminder
User: "remind me to drink water in 2 hrs"
## Example: Skill Usage (Reminder)
User: "remind me to check my crypto at 9pm"
{
"taskId": "task-rem-01",
"complexity": "small",
"reflectionMode": "OFF",
"nodes": [
{
"id": "rem-node",
"type": "skill",
"id": "rem-agent",
"type": "agent",
"service": "executor",
"input": {
"skillName": "reminder",
"task": "remind me to drink water in 2 hrs"
"name": "Scheduler Agent",
"instructions": "Use the 'load_skill' tool for 'reminder' to see how to schedule this: 'remind me to check my crypto at 9pm'"
}
}
],
"edges": []
}
## Example: Generate and Save
User: "think of a 3-day workout plan and save it to my notes"
## Example: Complex Coding/File Task
User: "Create a python script that fetches btc price and save it to btc.py"
{
"taskId": "task-workout",
"taskId": "task-btc-01",
"complexity": "medium",
"reflectionMode": "OFF",
"nodes": [
{
"id": "gen-plan",
"id": "coder-agent",
"type": "agent",
"service": "executor",
"input": {
"name": "Python Developer",
"instructions": "Write a python script that uses an public API to fetch the current BTC price. Save the code to 'btc.py' using the shell tool."
}
},
{
"id": "final-report",
"type": "generate_text",
"service": "model-router",
"input": { "prompt": "Create a 3-day workout plan for a beginner." }
},
{
"id": "save-notes",
"type": "skill",
"service": "executor",
"input": {
"skillName": "apple-notes",
"task": "Save this workout plan to my notes: {{gen-plan}}"
"prompt": "Tell the user that the script btc.py has been created successfully. Mention the code: {{coder-agent}}",
"system_prompt": "analyzer"
}
}
],
"edges": [
{ "from": "gen-plan", "to": "save-notes" }
]
}
## Example: Email/Calendar
User: "check my inbox for unread messages"
{
"taskId": "task-gog-01",
"complexity": "small",
"reflectionMode": "OFF",
"nodes": [
{
"id": "check-mail",
"type": "skill",
"service": "executor",
"input": {
"skillName": "gog",
"task": "check my inbox for unread messages"
}
}
],
"edges": []
}
## Example: Generate and Send File
User: "search for the latest price of Gold and create a simple text report, then send it to me"
{
"taskId": "task-gold-01",
"complexity": "medium",
"reflectionMode": "OFF",
"nodes": [
{
"id": "gold-search",
"type": "tool",
"service": "tool-host",
"input": {
"tool": "http_search",
"arguments": { "query": "current gold price" }
}
},
{
"id": "create-report",
"type": "tool",
"service": "tool-host",
"input": {
"tool": "shell",
"arguments": { "command": "echo \\"Latest Gold Price: $(grep -oE '[0-9,]+\\.[0-9]+' gold_results.txt | head -1)\\" > gold_report.txt && realpath gold_report.txt" }
}
},
{
"id": "send-report",
"type": "send_file",
"service": "core",
"input": {
"local_path": "{{create-report}}",
"brief_file_description": "Here is the gold price report you requested."
}
}
],
"edges": [
{ "from": "gold-search", "to": "create-report" },
{ "from": "create-report", "to": "send-report" }
{ "from": "coder-agent", "to": "final-report" }
]
}
</examples>`;
@@ -304,7 +181,16 @@ ${Object.entries(process.env)
"service": "executor",
"input": { "skillName": "NAME", "task": "INSTRUCTION" }
}
</skill_node_template>`;
</skill_node_template>
<agent_node_template>
{
"id": "agent-node",
"type": "agent",
"service": "executor",
"input": { "name": "ROLE_NAME", "instructions": "DETAILED_INSTRUCTIONS" }
}
</agent_node_template>`;
}
const now = new Date().toISOString().split('T')[0];

View File

@@ -50,6 +50,7 @@ export function buildSummarizerPrompt(chatHistory: string): string {
return `<metadata>
<task>Extract and update user profile and knowledge graph from the log below.</task>
<timestamp>${new Date().toISOString()}</timestamp>
</metadata>
<conversation_log>

View File

@@ -565,6 +565,9 @@ export class Orchestrator {
if (details.originalErrorMessage != null) parts.push(String(details.originalErrorMessage));
lastError = parts.join(" ");
}
this.sendAndWait(taskMemory, "task.fail", { taskId, reason: lastError }).catch(() => { });
if (attempt === Orchestrator.MAX_PLAN_RETRIES) {
this.sendToTelegram(chatId, lastError);
return;
@@ -576,6 +579,9 @@ export class Orchestrator {
const plan = planPayload.result as { nodes: unknown[]; edges?: unknown[]; complexity?: string } | undefined;
if (!plan?.nodes || !Array.isArray(plan.nodes)) {
lastError = "Invalid plan from planner: missing or invalid nodes.";
this.sendAndWait(taskMemory, "task.fail", { taskId, reason: lastError }).catch(() => { });
if (attempt === Orchestrator.MAX_PLAN_RETRIES) {
this.sendToTelegram(chatId, lastError);
return;
@@ -616,6 +622,9 @@ export class Orchestrator {
if (details.originalErrorMessage != null) parts.push(String(details.originalErrorMessage));
lastError = parts.join(". ");
}
this.sendAndWait(taskMemory, "task.fail", { taskId, reason: lastError }).catch(() => { });
if (attempt === Orchestrator.MAX_PLAN_RETRIES) {
this.sendToTelegram(chatId, lastError);
return;
@@ -682,6 +691,30 @@ export class Orchestrator {
return;
}
const taskId = randomUUID();
// Create placeholder task in memory so it shows up in dashboard during processing
const taskMemory = this.children.get("task-memory");
if (taskMemory?.stdin.writable) {
this.send({
id: randomUUID(),
timestamp: Date.now(),
from: "core",
to: "task-memory",
type: "task.create",
version: "1.0",
payload: {
taskId,
userId: String(userId),
conversationId: conversationId ?? String(chatId),
goal: caption || "Processing uploaded files...",
status: "pending",
complexity: "unknown",
nodes: [],
edges: []
}
});
}
// Notify user processing has started
const fileWord = files.length === 1 ? "file" : "files";
this.sendToTelegram(chatId, `⏳ Processing ${files.length} ${fileWord}...`, true);
@@ -799,7 +832,7 @@ export class Orchestrator {
// Run the task pipeline with the enriched goal
ConsoleLogger.debug("core", `Running task pipeline with enriched goal (length: ${enrichedGoal.length})`);
await this.runTaskPipeline(chatId, userId, enrichedGoal, conversationId);
await this.runTaskPipeline(chatId, userId, enrichedGoal, conversationId, taskId);
}
// ---------------------------------------------------------------------------

View File

@@ -266,11 +266,16 @@ async function updateDashboard() {
const activeIndicator = ['planning', 'running'].includes(n.status) ? '<div class="pulse"></div>' : '';
const typeLabel = n.type.split('.').pop().toUpperCase();
let chipAttr = '';
if (n.type === 'skill' && n.input) {
if ((n.type === 'skill' || n.type === 'agent') && n.input) {
try {
const input = typeof n.input === 'string' ? JSON.parse(n.input) : n.input;
const skillName = input.skillName || input.skill;
if (skillName) chipAttr = ` data-title="Skill: ${skillName}"`;
if (n.type === 'skill') {
const skillName = input.skillName || input.skill;
if (skillName) chipAttr = ` data-title="Skill: ${skillName}"`;
} else if (n.type === 'agent') {
const agentName = input.name || "Task Agent";
chipAttr = ` data-title="Agent: ${agentName}"`;
}
} catch (e) { }
}
return `<div class="node-chip ${n.status}"${chipAttr}>${activeIndicator}${typeLabel}</div>`;