Skip to main content

Executing Canvas Workflows

How to run workflows, handle streaming events, and manage approval gates.

SDK execution (streaming)

import { CanvasClient, AuthManager } from 'rickydata';

const auth = new AuthManager('https://agents.rickydata.org', 'mcpwt_...');
const canvas = new CanvasClient({ auth });

for await (const event of canvas.executeWorkflow({
nodes: [
{
id: 'input',
type: 'textInputNode',
data: { value: 'Summarize the MCP protocol' },
},
{
id: 'agent',
type: 'agentNode',
data: { prompt: 'Summarize the following input', model: 'haiku' },
},
{
id: 'output',
type: 'resultsNode',
data: {},
},
],
connections: [
{ source: 'input', target: 'agent' },
{ source: 'agent', target: 'output' },
],
})) {
switch (event.type) {
case 'node_started':
console.log(`Starting: ${event.data.nodeId}`);
break;
case 'node_completed':
console.log(`Done: ${event.data.nodeId}`);
break;
case 'node_error':
console.error(`Error at ${event.data.nodeId}: ${event.data.error}`);
break;
case 'approval_required':
console.log(`Approval needed: ${event.data.approvalId}`);
// See "Handling approval gates" below
break;
case 'run_completed':
console.log('Results:', event.data.results);
break;
}
}

SDK execution (synchronous)

Wait for the complete result:

const result = await canvas.executeWorkflowSync({
nodes: [...],
connections: [...],
}, {
timeout: 60000, // 60 second timeout
});

console.log(result.status); // 'completed' | 'failed' | 'approval_pending'
console.log(result.results);

CLI execution

# Execute by entity ID (streams progress)
rickydata canvas execute <entity-id> --verbose

# Execute a local file
rickydata canvas execute ./workflow.canvas.json --verbose

The --verbose flag shows node-level progress in real time.

Handling approval gates

When a workflow hits an approval node, execution pauses and emits an approval_required event:

case 'approval_required':
const { approvalId, nodeId, description } = event.data;

// Present to user, then approve or reject
const approved = await askUser(description);

await canvas.approveGate(runId, approvalId, {
decision: approved ? 'approve' : 'reject',
reason: 'Reviewed and approved',
});
break;

Why approval gates exist

Write-capable nodes (GitHub pushes, data mutations) require an upstream approval gate in the workflow graph. The canvas runtime enforces this topological constraint — if a write node has no approval gate in its upstream path, the workflow is rejected at validation time.

Event types

The SSE stream emits these event types:

EventWhenData
run_startedWorkflow execution begins{ runId }
node_startedA node begins execution{ nodeId, nodeType }
node_logProgress update from a node{ nodeId, message }
node_completedA node finishes successfully{ nodeId, output }
node_errorA node fails{ nodeId, error }
approval_requiredApproval gate reached{ approvalId, nodeId, description }
run_completedAll nodes finished{ status, results }
run_failedWorkflow failed{ error }

Managing workflows

List saved workflows

const workflows = await canvas.listWorkflows();
workflows.forEach(w => console.log(w.id, w.name));

Save a workflow

await canvas.saveWorkflow({
name: 'Research and summarize',
nodes: [...],
connections: [...],
});

List runs

const runs = await canvas.listRuns();
runs.forEach(r => console.log(r.id, r.status, r.startedAt));

Get run details

const run = await canvas.getRun('run-id');
console.log(run.status, run.results);

API reference

CanvasClient

MethodDescription
executeWorkflow(request, signal?)SSE streaming execution (async generator)
executeWorkflowSync(request, options?)Wait for complete result
listWorkflows()List saved workflows
saveWorkflow(workflow)Save a new workflow
listRuns()List execution runs
getRun(runId)Get run details
approveGate(runId, approvalId, decision)Approve/reject an approval gate

Example: research workflow

A workflow that searches for papers, summarizes them, and creates a GitHub issue:

const workflow = {
nodes: [
{ id: 'topic', type: 'textInputNode', data: { value: 'Zero-knowledge proofs in MCP' } },
{ id: 'search', type: 'mcpToolNode', data: {
serverId: 'arxiv-mcp-server', tool: 'search_papers', args: { max_results: 5 }
}},
{ id: 'summarize', type: 'agentNode', data: {
prompt: 'Summarize these papers into a brief report', model: 'sonnet'
}},
{ id: 'approve', type: 'approvalNode', data: {
description: 'Review summary before creating GitHub issue'
}},
{ id: 'github', type: 'githubNode', data: {
action: 'create_issue', repo: 'my-org/research', title: 'Research: ZK + MCP'
}},
{ id: 'output', type: 'resultsNode', data: {} },
],
connections: [
{ source: 'topic', target: 'search' },
{ source: 'search', target: 'summarize' },
{ source: 'summarize', target: 'approve' },
{ source: 'approve', target: 'github' },
{ source: 'github', target: 'output' },
],
};

Next steps