Files
hermes-web-ui/docs/workflow.md
ekko f5a292663b [codex] Complete workflow run execution (#1759)
* feat: add workflow builder page

* Add workflow schema and editor controls

* feat: add workflow builder persistence

* feat: add workflow socket and agent skills

* feat: complete workflow run execution

* test: fix workflow ci coverage failures

---------

Co-authored-by: Codex <codex@openai.com>
2026-06-24 12:54:08 +08:00

12 KiB

Workflow Page

This document records the current Workflow page implementation in Hermes Web UI. The page is still a front-end workflow builder plus persistence layer. Workflow execution is intentionally not wired yet.

Entry Points

  • Client route: /hermes/workflow
  • Client view: packages/client/src/views/hermes/WorkflowView.vue
  • Agent node component: packages/client/src/components/hermes/workflow/WorkflowAgentNode.vue
  • Client API helper: packages/client/src/api/hermes/workflows.ts
  • Server routes: packages/server/src/routes/hermes/workflows.ts
  • Server controller: packages/server/src/controllers/hermes/workflows.ts
  • Server service singleton: packages/server/src/services/workflow-manager.ts
  • Server socket: packages/server/src/services/workflow-socket.ts
  • Store: packages/server/src/db/hermes/workflow-store.ts
  • Schema: packages/server/src/db/hermes/schemas.ts

The Workflow page is opened from the same left page sidebar used by chat, history, and group chat. The workflow list also lives in the left sidebar.

Implemented UI

  • Vue Flow is used for the canvas.
  • Users can create, edit, save, delete, and batch delete workflows.
  • The create workflow drawer collects:
    • workflow name
    • profile
    • optional workspace directory
  • If no workspace is selected, the server creates a default workspace: ~/.hermes-web-ui/workflow/<profile>/<workflowId>.
  • The workflow sidebar supports:
    • profile filter
    • batch mode
    • select all
    • batch delete
    • per-row delete
    • empty/loading states
  • Workflow rows show name, profile, node count, and edge count.
  • The canvas toolbar includes:
    • add node
    • save
    • start execution placeholder
  • The start execution button currently only shows a "not connected yet" message.

Node Behavior

Workflow nodes are custom agent nodes. Each node has:

  • title
  • agent selector: hermes, claude-code, codex
  • provider/model selector
  • API mode selector for coding agents
  • skills tag input
  • main input textarea
  • file/image attachments
  • one left input handle
  • one right output handle

The node frame is resizable. The textarea grows with the node height instead of being independently resized.

Attachments use the shared upload endpoint through uploadRuntimeFiles. Images render as thumbnails and open in a full-screen preview overlay. Non-image files render as file chips. The upload button remains last in the attachment flow.

Edge Behavior

Edges are Vue Flow smoothstep edges with dashed styling and arrow markers.

Connections are constrained in two places:

  • During dragging, isValidWorkflowConnection only allows right output to left input.
  • During save, existing edges are validated again so bad loaded data cannot be saved silently.

Valid edge shape:

{
  "id": "agent-1-agent-2",
  "source": "agent-1",
  "target": "agent-2",
  "sourceHandle": "output",
  "targetHandle": "input",
  "type": "smoothstep",
  "animated": true
}

Invalid examples:

  • input to input
  • output to output
  • left input to right output
  • self connection
  • connection to a missing node

Save Validation

Saving a workflow is blocked on the client when any of these checks fail:

  • at least one node is required
  • every node needs a title
  • every node needs a provider
  • every node needs a model
  • coding-agent nodes need an API mode
  • every node needs input text
  • every edge must reference existing nodes
  • every edge must go from output to input
  • with more than one node, no node can be completely disconnected
  • the graph must be one connected workflow, not multiple independent flows
  • the directed graph must not contain a cycle

The graph allows:

  • multiple start nodes
  • multiple terminal nodes
  • branching
  • merging

The graph does not allow parallel disconnected flows. For example, A -> B and C -> D in the same workflow is invalid until the two groups are connected into one workflow graph.

Cycle detection is directed. Connectivity detection is undirected, because it is only used to catch disconnected groups.

Persistence

Workflow definitions are persisted in the workflows table.

CREATE TABLE workflows (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  profile TEXT NOT NULL DEFAULT 'default',
  workspace TEXT,
  nodes_json TEXT NOT NULL DEFAULT '[]',
  edges_json TEXT NOT NULL DEFAULT '[]',
  viewport_json TEXT NOT NULL DEFAULT '{}',
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

The schema sync code adds new workflow columns safely on startup. Existing databases do not need a manual migration for viewport_json.

nodes_json stores the serialized Vue Flow nodes with runtime callbacks removed. edges_json stores Vue Flow edge definitions. viewport_json stores the current canvas viewport:

{
  "x": 80,
  "y": 80,
  "zoom": 0.75
}

The page restores the saved viewport when switching to a workflow or reloading the workflow list. Save sends the current x, y, and zoom together with nodes and edges.

API

Routes:

  • GET /api/hermes/workflows
  • GET /api/hermes/workflows?profile=<profile>
  • POST /api/hermes/workflows
  • GET /api/hermes/workflows/:id
  • PATCH /api/hermes/workflows/:id
  • DELETE /api/hermes/workflows/:id
  • POST /api/hermes/workflows/batch-delete

Socket namespace:

  • /workflow
  • workflows.list returns the same workflow list shape as the HTTP list API.
  • workflow.status.subscribe subscribes to accessible workflow status rooms, or to a single workflow by workflowId.
  • workflow.status.unsubscribe leaves the matching status room.
  • workflow.status.updated is emitted when the server-side workflow manager publishes a runtime status change.

Create body:

{
  "name": "Research Flow",
  "profile": "default",
  "workspace": null,
  "nodes": [],
  "edges": [],
  "viewport": { "x": 80, "y": 80, "zoom": 0.75 }
}

Patch body supports partial updates:

{
  "name": "Updated Flow",
  "workspace": "/path/to/workspace",
  "nodes": [],
  "edges": [],
  "viewport": { "x": -200, "y": 120, "zoom": 0.9 }
}

Batch delete body:

{
  "ids": ["workflow-id-1", "workflow-id-2"]
}

The controller enforces profile access for non-super-admin users. The store persists data in SQLite when available and falls back to the JSON store helpers when no SQLite database is active.

Execution Design

Workflow execution is implemented through the shared chat-run and coding-agent runtime paths.

Implemented

  • The schema includes run-oriented tables:
    • workflow_runs
    • workflow_run_node_sessions
  • Workflow node messages are not duplicated in workflow-specific message tables. They are read from the shared messages table through the node's sessions.id.
  • Chat sessions created for workflow nodes use sessions.source = "workflow" so normal chat and Hermes history lists stay clean.
  • The workflow_run_messages table is no longer used.
  • Workflow runtime status can be pushed over the /workflow socket through workflow.status.updated.
  • Workflow node agent mapping is defined in the server workflow manager:
    • Hermes nodes are workflow sessions but execute through the existing bridge/API-server path because the upstream Hermes bridge does not understand workflow as an execution source.
    • Claude Code and Codex nodes are workflow sessions but execute through the existing coding-agent path with the matching coding_agent_id.
  • POST /api/hermes/workflows/:id/run starts a run asynchronously.
  • packages/server/src/services/workflow-manager.ts owns immediate execution, run status, stop/delete behavior, run snapshots, and node session cleanup.
  • Each run persists a workflow_runs row and a workflow_run_node_sessions row per executed node.
  • The executor schedules nodes from the workflow graph and applies the fan-in failure rule documented below.
  • Each node receives one assembled user message containing upstream outputs, selected skill content, the current node task, and current node images.
  • packages/server/src/lib/llm-prompt.ts injects workflow-specific context when the run source is workflow.
  • Workflow node sessions are hidden from normal chat/history lists by default.
  • Workflow runs can be listed, selected, stopped, deleted, and inspected from the workflow page.
  • Selecting a run shows the workflow snapshot. Nodes in snapshot mode are read-only and can open their associated session transcript.
  • Hermes workflow nodes auto-respond to tool approval requests with the one-time once choice. Normal single-chat approvals still require the user to respond from the chat UI.

Execution Source Model

workflow is a Web UI session source and scheduler marker. It is not a new upstream agent backend.

  • Persisted node sessions use sessions.source = "workflow".
  • Hermes nodes still execute through the existing bridge/API-server path.
  • Claude Code and Codex nodes still execute through the existing coding-agent path.
  • The executor decides the concrete run path from the node's selected agent.

Node User Message Assembly

Each workflow node receives exactly one user message per node execution. That user message is assembled from upstream outputs and the current node's configured input, plus selected skill content when configured.

For a node with upstream dependencies, the format should be stable and debuggable:

[Workflow upstream results]

[Upstream: <node title or node id>]
<last assistant output from that upstream node session>

[Current task]
<current node input>

Rules:

  • Use the last assistant message from each completed upstream node session as that upstream node's output.
  • Include selected skill content in the assembled user message before the current node task.
  • Do not copy upstream messages into the downstream node session as separate messages.
  • Do not forge assistant messages in the downstream node session.
  • Preserve a stable upstream order, preferably by incoming edge order in the workflow snapshot.
  • Node attachments belong to the current node. Upstream attachments are not inherited automatically in the first implementation.

Workflow System Context

Workflow runs inject a small system context when source is workflow. The injection point is packages/server/src/lib/llm-prompt.ts.

The system context tells the model that it is executing one workflow node, that upstream results are context, and that it should focus only on the current node task.

You are executing one node in a workflow.

Focus only on the current node task. Use upstream node results as context, but
do not rerun upstream work. If upstream results conflict, call out the conflict
and proceed with the best supported answer.

Return the result for this node clearly and concisely. Do not describe the
workflow mechanics unless the task asks for it.

Fan-in Execution Rule

When multiple upstream nodes connect into the same downstream node, the downstream node must wait until every upstream node has completed successfully before it starts.

For example:

A ─┐
   ├─> C
B ─┘

Node C starts only after both A and B are completed. If either A or B fails, the entire workflow run is marked as failed and C must not run.

Current Limitations

  • Server-side save validation currently validates request shapes and profile access. Graph semantic validation is implemented in the client save path.
  • Attachments are stored as uploaded file paths on node data. There is no separate workflow attachment table.
  • Skills are stored as names on the node and resolved at run time.
  • Workflow node sessions are persisted through the shared chat/message tables. Coding-agent sessions may only show assistant messages after the underlying run flushes its database writes.

Validation

Relevant checks used while building this feature:

npm run test -- tests/server/workflow-store.test.ts tests/server/schema-sync.test.ts
npm run test -- tests/server/workflow-controller.test.ts tests/server/workflow-manager.test.ts tests/server/run-chat-queued-item.test.ts
npx tsc --noEmit -p packages/server/tsconfig.json
npx vue-tsc -b
npm run build
git diff --check