workflow-tool-steps.md 5.8 KB

Workflow Tool Steps

This note documents the Tool_* workflow extension implemented in VLCode-Lite on top of the JS VL-Workflow-Engine vendor copy.

Canonical spec note:

  • DocCenter Path 3 is now VL Workflow Spec 3.18
  • Current VLCode-Lite runtime still emits/accepts workflow JSON version: "3.16" during the transition
  • Tool_*, tool_message, clientRunToken, and checkpoint rerun semantics are documented as part of the 3.17 extension profile
  • Subflow_* is supported as an editor/runtime alias that lowers to WorkflowRun without introducing a separate execution primitive

What It Adds

  • Direct workflow access to the local VLCode tool registry
  • Optional Subflow_* node alias for child-workflow orchestration
  • Tool_* node rendering and editing in public/workflow-editor.html
  • Runtime events: tool_start, tool_message, tool_done, tool_error
  • Support in normal execution, WorkflowRun, pause/resume, and executeFrom() rerun
  • Automatic run attribution for tool events through runID and clientRunToken

Step Shape

{
  "id": "Tool_010_ReadSpec",
  "tool": "ReadFile",
  "input": {
    "file_path": "=\"docs/spec.md\""
  },
  "out": {
    "$specText": "=_result",
    "$rawTool": "=_toolResult"
  },
  "next": "Stop_End"
}

Supported fields:

  • tool or toolName: explicit tool name
  • input or in: tool arguments
  • timeout: forwarded into tool input when the input object does not already carry timeout
  • allowError or continueOnError: continue after tool failure
  • out: normal workflow output mapping

If tool is omitted, VLCode derives it from the step id:

  • Tool_010_ReadFileReadFile
  • Tool_120_VLCompileVLCompile

Runtime Variables

During Tool_* output mapping, two temporary variables are available:

  • _result: normalized primary tool payload
  • _toolResult: full raw tool envelope

Normalization rule:

  • If a tool returns { result: ... }, _result is auto-unwrapped to that inner payload
  • Use _toolResult when the workflow needs metadata like diff, undoId, or the original nested return shape

Example:

{
  "id": "Tool_020_EditFile",
  "tool": "EditFile",
  "input": {
    "file_path": "=\"Apps/Main.vx\"",
    "old_string": "=\"old\"",
    "new_string": "=\"new\""
  },
  "out": {
    "$editSummary": "=_result",
    "$editDiff": "=_toolResult.diff",
    "$undoId": "=_toolResult.undoId"
  }
}

Tool Runtime Hooks

VLCode tools may accept a second runtime argument:

execute: async (input, runtime = {}) => {
  runtime.info?.('Reading spec', { file_path: input.file_path });
  runtime.progress?.('Loaded first chunk', { bytes: 4096 });
  return { result: '...' };
}

Available helpers:

  • runtime.emit(type, payload)
  • runtime.text(message, meta)
  • runtime.info(message, data)
  • runtime.warn(message, data)
  • runtime.error(message, data)
  • runtime.progress(message, data)

These are surfaced by the workflow runtime as tool_message events, so the outer window, detail log, SSE clients, and workflow-editor.html can display them while the tool is still running.

Error Handling

Default behavior:

  • tool failure emits tool_error
  • the step fails
  • workflow execution stops unless a parent parallel strategy absorbs it

With allowError: true:

{
  "id": "Tool_030_Compile",
  "tool": "VLCompile",
  "allowError": true,
  "input": {},
  "out": {
    "$compileOk": "=_result.ok",
    "$compileError": "=_result.error"
  },
  "next": "Branch_040_HandleCompile"
}

In this mode:

  • tool_error still emits
  • _result becomes { "ok": false, "error": "...", "tool": "VLCompile" }
  • the step reaches step_done

Events

Tool_* steps emit:

step_start
  → tool_start
  → tool_message(0..N)
  → tool_done | tool_error
  → var_changed(0..N)
  → step_done | step_error

tool_message payload shape:

{
  "type": "tool_message",
  "stepId": "Tool_010_ReadSpec",
  "name": "ReadFile",
  "level": "info",
  "message": "Reading file",
  "data": {
    "file_path": "docs/spec.md"
  },
  "runID": "wf_1770000000000",
  "clientRunToken": "client:1770000000000:1"
}

Server-side broadcasts now include runID and clientRunToken for all tool events, so parallel runs can be disambiguated in the editor and in the main UI.

Editor Support

The workflow editor now treats Tool as a first-class node type:

  • icon: TL
  • dedicated node body renderer
  • modal editor for tool, input, timeout, allowError
  • selected-run event panel shows tool_message, checkpoint, pause/resume, rerun, and other non-token events
  • works with the multi-run session bar and rerun-from-step flow

Workflow-of-Workflows Pattern

WorkflowRun now supports mode: "sync" for supervisor-style orchestration:

{
  "id": "Tool_010_RunWorker",
  "tool": "WorkflowRun",
  "input": {
    "mode": "sync",
    "workflow_path": "=\"examples/workflows/vlclaw-worker-spec.json\"",
    "params": {
      "goal": "=goal",
      "artifactPath": "=$specPath"
    }
  },
  "out": {
    "$workerRun": "=_result"
  },
  "next": "RETURN"
}

sync mode returns a structured object with:

  • status
  • runID
  • filesWritten
  • variables
  • checkpoint
  • completedSteps
  • events

This is the recommended primitive for “workflow managing child workflows”, which is the most practical landing path for a VLClaw-style AI engineer manager.

Current Limits

  • Tool_* is a VLCode extension, not a pure core-spec node
  • the target tool must already be registered in ToolRegistry
  • pause is still run-level, not branch-level
  • rerun from a downstream node still depends on checkpoint completeness for variables, loop progress, and completed branches

Verified Paths

Validated locally on 2026-03-14:

  • node test-workflow-executor.js
  • node test-workflow-run-tool-step.js
  • node test-workflow-editor.js
  • node test-workflow-parallel-codegen.js