Documentation
¶
Overview ¶
Package workflow implements a server-side, n8n-style workflow execution engine: a DAG of typed nodes (triggers, AI agents, logic, actions, data) wired by ports, executed with join-gated scheduling, cancellation, and per-node run state suitable for a live executions inspector.
The package is pure: it imports no other internal package. All side effects (running an agent, HTTP, filesystem, shell, persistence, event emission) are injected via Deps so the engine is unit-testable and reusable. The wire model (Workflow/WorkflowNode/WorkflowEdge/WorkflowNodeConfig) mirrors the Angular desktop `workflow.models.ts` byte-for-byte so definitions round-trip 1:1.
Index ¶
- Constants
- func IsTriggerType(t string) bool
- type AgentReq
- type AgentResult
- type ApprovalReq
- type ChannelReq
- type Deps
- type EmailReq
- type FSOps
- type FormField
- type KBIngestReq
- type LLMReq
- type NodeResult
- type NodeRunState
- type NodeStatus
- type RunOptions
- type RunStatus
- type SQLReq
- type SQLResult
- type ShellReq
- type ShellResult
- type Snapshot
- type SwitchCase
- type Workflow
- type WorkflowEdge
- type WorkflowNode
- type WorkflowNodeConfig
- type WorkflowRun
- type WorkflowVar
Constants ¶
const ( TypeTriggerManual = "trigger_manual" TypeTriggerSchedule = "trigger_schedule" TypeTriggerWebhook = "trigger_webhook" TypeTriggerEvent = "trigger_event" TypeTriggerForm = "trigger_form" TypeAgent = "agent" TypeAgentHandoff = "agent_handoff" TypeCondition = "condition" TypeSwitch = "switch" TypeHTTPRequest = "http_request" TypeCodeTransform = "code_transform" TypeWorkspaceOp = "workspace_op" TypeShellCommand = "shell_command" TypeWait = "wait" TypeLoop = "loop" TypeMerge = "merge" TypeSetVariable = "set_variable" TypeEmailSend = "email_send" TypeTriggerEmail = "trigger_email" TypeSendChannel = "send_channel" TypeLLMPrompt = "llm_prompt" TypeWeb = "web" TypeKnowledgeBase = "knowledge_base" TypeSQLQuery = "sql_query" TypeFilter = "filter" TypeTemplate = "template" TypeApproval = "approval" TypeSubWorkflow = "sub_workflow" )
Node type identifiers (must match the TS WorkflowNodeType union).
const DefaultOutputPort = "output"
DefaultOutputPort is the port id the web UI assigns to a node's single default output. Standard nodes fire their default port as "" internally (see defaultPort in executor_nodes.go), so an edge saved from the UI's "output" port must be treated as that default when matching fired ports. Explicit branch ports (true/false/each/done/approved/rejected/default/case_*) carry their own ids on both sides and are returned unchanged.
Variables ¶
This section is empty.
Functions ¶
func IsTriggerType ¶
IsTriggerType reports whether a node type is a trigger (a run's seed node).
Types ¶
type AgentResult ¶
type ApprovalReq ¶
type ChannelReq ¶
type Deps ¶
type Deps struct {
// RunAgent executes an AI agent node and returns its final text + session id.
RunAgent func(ctx context.Context, req AgentReq) (AgentResult, error)
// RunShell executes a shell command node.
RunShell func(ctx context.Context, req ShellReq) (ShellResult, error)
// SendEmail delivers an email_send node's message via the caller's SMTP sender
// (e.g. the org's configured SMTP settings). When nil, email_send nodes error.
SendEmail func(ctx context.Context, req EmailReq) error
// SendChannel delivers a send_channel node's message through a configured
// messaging channel (Slack/Telegram/Discord/WhatsApp). When nil, the node errors.
SendChannel func(ctx context.Context, req ChannelReq) error
// RunLLM performs a single, no-tools LLM completion for the llm_prompt node.
RunLLM func(ctx context.Context, req LLMReq) (string, error)
// WebSearch / WebFetch back the web node (search the web, fetch a URL as text).
WebSearch func(ctx context.Context, query string, maxResults int) (string, error)
WebFetch func(ctx context.Context, url string) (string, error)
// KBSearch / KBIngest back the knowledge_base node (vector search / ingest).
KBSearch func(ctx context.Context, query string, topK int) (string, error)
KBIngest func(ctx context.Context, req KBIngestReq) (string, error)
// SQLQuery runs the sql_query node against an external database.
SQLQuery func(ctx context.Context, req SQLReq) (SQLResult, error)
// RunSubWorkflow runs another workflow to completion and returns its outputs
// (sub_workflow node). The caller enforces a recursion-depth guard.
RunSubWorkflow func(ctx context.Context, workflowID string, payload map[string]any) (map[string]any, error)
// RequestApproval blocks on a human decision for the approval node.
RequestApproval func(ctx context.Context, req ApprovalReq) (bool, error)
// FS performs workspace-confined file operations.
FS FSOps
// HTTPClient is used by http_request nodes.
HTTPClient *http.Client
// RequestPerm asks the caller to approve a shell command (allow, allowAll).
RequestPerm func(ctx context.Context, nodeID, command string) (allow, allowAll bool)
// Emit publishes a run event to the bus (run_started/node_started/... ).
Emit func(ev map[string]any)
// PersistNode upserts a node run-state row (executions inspector).
PersistNode func(ns NodeRunState)
// Suspend durably records a long-wait suspension so a scheduler can resume the
// run at resumeAt. When nil, wait nodes fall back to an in-process timer.
Suspend func(nodeID string, resumeAt time.Time, snap Snapshot)
// Workspace is the resolved filesystem root for fs/shell nodes.
Workspace string
// MaxConcurrency bounds parallel node execution per run (default 8).
MaxConcurrency int
}
Deps injects all side effects the engine needs so the package imports no other internal package. It is constructed per-run by the caller (the selfhost handler) so RunAgent/RequestPerm/Emit close over the run id and event bus.
type FSOps ¶
type FSOps interface {
Read(path string) (string, error)
Write(path, content string) error
List(path string) ([]string, error)
Search(query string) ([]string, error)
}
FSOps is the minimal workspace-confined filesystem surface for workspace_op.
type KBIngestReq ¶
type NodeResult ¶
type NodeResult struct {
Output any // becomes $json for downstream nodes
FiredPorts []string // output ports to follow ("" = default)
AgentSessionID string // set by agent nodes for inspector deep-linking
Suspend bool // wait node: run suspended, resume scheduled durably
}
NodeResult is what a node body produces.
type NodeRunState ¶
type NodeRunState struct {
RunID string `json:"runId"`
NodeID string `json:"nodeId"`
Status NodeStatus `json:"status"`
Input any `json:"input,omitempty"`
Output any `json:"output,omitempty"`
Error string `json:"error,omitempty"`
AgentSessionID string `json:"agentSessionId,omitempty"`
Attempts int `json:"attempts,omitempty"`
StartedAt *time.Time `json:"startedAt,omitempty"`
CompletedAt *time.Time `json:"completedAt,omitempty"`
}
type NodeStatus ¶
type NodeStatus string
const ( NodePending NodeStatus = "pending" NodeRunning NodeStatus = "running" NodeDone NodeStatus = "done" NodeError NodeStatus = "error" NodeSkipped NodeStatus = "skipped" NodeWaiting NodeStatus = "waiting" )
type RunOptions ¶
type RunOptions struct {
// TriggerNodeID, when set, seeds only that trigger node (the trigger that
// started this run). Empty seeds all trigger/indegree-0 nodes.
TriggerNodeID string
// StartNodeID begins a partial ("run from here") execution at this node.
StartNodeID string
// SingleNode runs only StartNodeID and does not follow its output edges.
SingleNode bool
// SeedOutputs pre-populates upstream node outputs (partial execution) so the
// start node's input can be assembled without re-running upstream nodes.
SeedOutputs map[string]any
// SeedVars pre-populates workflow variables (used when resuming a suspended run).
SeedVars map[string]any
// Resume marks this as the continuation of a suspended run; the wait node at
// StartNodeID passes through instead of waiting again.
Resume bool
// Test honors the workflow's pinned data: a node with pinned output is not
// executed; its pinned value is used instead.
Test bool
}
RunOptions controls how a run is seeded (manual, trigger, or partial execution).
type RunStatus ¶
type RunStatus string
const ( RunPending RunStatus = "pending" RunRunning RunStatus = "running" RunDone RunStatus = "done" RunError RunStatus = "error" RunCanceled RunStatus = "canceled" // RunWaiting means the run suspended on a long wait node and will be resumed // by the scheduler at the persisted resume time (survives restarts). RunWaiting RunStatus = "waiting" )
func Run ¶
func Run(ctx context.Context, wf *Workflow, run *WorkflowRun, deps Deps, opts RunOptions) RunStatus
Run executes wf, mutating run (status, timing) and emitting events via deps. It blocks until the run finishes or ctx is cancelled, returning the final status.
type ShellResult ¶
type Snapshot ¶
Snapshot captures a run's accumulated state at a suspend point so the run can be durably resumed from a wait node.
type SwitchCase ¶
type SwitchCase struct {
Expression string `json:"expression"`
Label string `json:"label,omitempty"`
PortID string `json:"portId"`
}
SwitchCase mirrors the desktop { expression, label, portId } shape. The engine evaluates Expression as a boolean; the first truthy case fires its PortID.
type Workflow ¶
type Workflow struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
WorkspacePath string `json:"workspacePath,omitempty"`
Nodes []WorkflowNode `json:"nodes"`
Edges []WorkflowEdge `json:"edges"`
Active bool `json:"active"`
Tags []string `json:"tags,omitempty"`
Variables []WorkflowVar `json:"variables,omitempty"`
// PinnedData maps nodeID -> pinned output used during test runs.
PinnedData map[string]any `json:"pinnedData,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type WorkflowEdge ¶
type WorkflowEdge struct {
ID string `json:"id"`
SourceNodeID string `json:"sourceNodeId"`
SourcePort string `json:"sourcePort,omitempty"`
TargetNodeID string `json:"targetNodeId"`
TargetPort string `json:"targetPort,omitempty"`
Label string `json:"label,omitempty"`
Style string `json:"style,omitempty"`
}
type WorkflowNode ¶
type WorkflowNodeConfig ¶
type WorkflowNodeConfig struct {
// agent / agent_handoff
AgentID string `json:"agentId,omitempty"`
TaskPrompt string `json:"taskPrompt,omitempty"`
WorkspacePath string `json:"workspacePath,omitempty"`
WaitForComplete *bool `json:"waitForCompletion,omitempty"`
Timeout int `json:"timeout,omitempty"`
// trigger_schedule
Schedule string `json:"schedule,omitempty"`
// trigger_webhook
WebhookPath string `json:"webhookPath,omitempty"`
// trigger_event
EventType string `json:"eventType,omitempty"`
// trigger_form
FormFields []FormField `json:"formFields,omitempty"`
// condition
ConditionExpression string `json:"conditionExpression,omitempty"`
TrueLabel string `json:"trueLabel,omitempty"`
FalseLabel string `json:"falseLabel,omitempty"`
// switch
Cases []SwitchCase `json:"cases,omitempty"`
// http_request
URL string `json:"url,omitempty"`
Method string `json:"method,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
AuthType string `json:"authType,omitempty"`
AuthValue string `json:"authValue,omitempty"`
// code_transform
Code string `json:"code,omitempty"`
Language string `json:"language,omitempty"`
// wait
DelayValue int `json:"delayValue,omitempty"`
DelayUnit string `json:"delayUnit,omitempty"`
// loop
IterateOver string `json:"iterateOver,omitempty"`
LoopVar string `json:"loopVar,omitempty"`
// set_variable
Variables []WorkflowVar `json:"variables,omitempty"`
// workspace_op
Operation string `json:"operation,omitempty"`
FilePath string `json:"filePath,omitempty"`
Content string `json:"content,omitempty"`
ContentSource string `json:"contentSource,omitempty"`
SearchQuery string `json:"searchQuery,omitempty"`
// shell_command
ShellCommand string `json:"shellCommand,omitempty"`
ShellCwd string `json:"shellCwd,omitempty"`
ShellAutoApprove bool `json:"shellAutoApprove,omitempty"`
// email_send (the email body reuses Body / "body" above)
To string `json:"to,omitempty"`
Cc string `json:"cc,omitempty"`
Bcc string `json:"bcc,omitempty"`
Subject string `json:"subject,omitempty"`
EmailHTML bool `json:"emailHtml,omitempty"`
FromName string `json:"fromName,omitempty"`
// trigger_email (inbound IMAP poll)
EmailFolder string `json:"emailFolder,omitempty"` // default INBOX
// send_channel (the message text reuses Body / "body" above)
ChannelID string `json:"channelId,omitempty"` // configured channel to send through
ChannelTarget string `json:"channelTarget,omitempty"` // chat/channel id, or phone for whatsapp
// llm_prompt (the user prompt reuses TaskPrompt / "taskPrompt" above)
SystemPrompt string `json:"systemPrompt,omitempty"`
LLMJSON bool `json:"llmJson,omitempty"` // parse the model's reply as JSON
// web (operation reuses Operation: "search" | "fetch"; url reuses URL; query reuses SearchQuery)
MaxResults int `json:"maxResults,omitempty"`
// knowledge_base (operation reuses Operation: "search" | "ingest";
// search: SearchQuery + TopK; ingest file: FilePath; ingest text: Content/ContentSource + KBSourceName)
TopK int `json:"topK,omitempty"`
KBSourceName string `json:"kbSourceName,omitempty"`
// sql_query
SQLDriver string `json:"sqlDriver,omitempty"` // currently: postgres
SQLDSN string `json:"sqlDsn,omitempty"` // connection string / DSN
SQLQuery string `json:"sqlQuery,omitempty"`
// filter (keep the array items for which FilterExpression is truthy)
FilterExpression string `json:"filterExpression,omitempty"`
// template (render a {{ }} template; optionally wrap the result under TemplateField)
Template string `json:"template,omitempty"`
TemplateField string `json:"templateField,omitempty"`
// approval (human-in-the-loop gate)
ApprovalTitle string `json:"approvalTitle,omitempty"`
ApprovalMessage string `json:"approvalMessage,omitempty"`
ApprovalTimeout int `json:"approvalTimeout,omitempty"` // minutes; 0 = default 60
ApprovalOnTimeout string `json:"approvalOnTimeout,omitempty"` // "approve" | "reject" (default reject)
// sub_workflow
SubWorkflowID string `json:"subWorkflowId,omitempty"`
// error handling (all executable nodes)
OnError string `json:"onError,omitempty"` // stop|continue|retry (default stop)
RetryCount int `json:"retryCount,omitempty"`
RetryDelayMs int `json:"retryDelayMs,omitempty"`
// output binding
OutputVar string `json:"outputVar,omitempty"`
}
WorkflowNodeConfig is a wide optional-field struct; omitempty keeps the JSON round-tripping 1:1 with the TS optional-field object.
type WorkflowRun ¶
type WorkflowRun struct {
ID string `json:"id"`
WorkflowID string `json:"workflowId"`
Status RunStatus `json:"status"`
TriggerType string `json:"triggerType"`
TriggerPayload map[string]any `json:"triggerPayload,omitempty"`
Error string `json:"error,omitempty"`
StartedAt time.Time `json:"startedAt"`
CompletedAt *time.Time `json:"completedAt,omitempty"`
NodeStates []NodeRunState `json:"nodeStates,omitempty"`
}
type WorkflowVar ¶
type WorkflowVar struct {
Name string `json:"name"`
Value string `json:"value"`
Description string `json:"description,omitempty"`
}
WorkflowVar matches the desktop { name, value, description? } shape.