veritas-kanban

Veritas Kanban Workflow Engine — API Reference

Version: v3.3
Last Updated: 2026-02-15
Base URL: http://localhost:3001/api


Table of Contents

  1. Authentication
  2. Workflow CRUD
  3. Workflow Runs
  4. Gate Operations
  5. Tool Policies
  6. Sandbox Policies
  7. Task Dependencies (NEW — v3.3)
  8. Crash-Recovery Checkpointing (NEW — v3.3)
  9. Observational Memory (NEW — v3.3)
  10. Agent Filter (NEW — v3.3)
  11. WebSocket Events
  12. TypeScript Interfaces
  13. Error Responses

Authentication

All API endpoints currently accept an optional authentication key via x-api-key header. RBAC permissions are enforced based on workflow ACLs (Access Control Lists).

Header:

x-api-key: <your-api-key>

Permissions:


Workflow CRUD

GET /api/workflows

List all workflows (metadata only, filtered by user permissions).

Request:

curl http://localhost:3001/api/workflows

Response:

[
  {
    "id": "feature-dev",
    "name": "Feature Development Workflow",
    "version": 2,
    "description": "End-to-end feature development pipeline"
  },
  {
    "id": "security-audit",
    "name": "Security Audit & Remediation",
    "version": 1,
    "description": "Scan, prioritize, and fix security issues"
  }
]

Status Codes:

Permissions: Any authenticated user can list workflows they have view permission for.


GET /api/workflows/:id

Get a specific workflow definition (full YAML content).

Request:

curl http://localhost:3001/api/workflows/feature-dev

Response:

{
  "id": "feature-dev",
  "name": "Feature Development Workflow",
  "version": 2,
  "description": "End-to-end feature development pipeline",
  "config": {
    "timeout": 7200,
    "fresh_session_default": true,
    "progress_file": "progress.md",
    "telemetry_tags": ["workflow", "feature-dev"]
  },
  "agents": [
    {
      "id": "planner",
      "name": "Planner",
      "role": "planner",
      "model": "github-copilot/claude-opus-4.6",
      "description": "Decomposes tasks into user stories"
    },
    {
      "id": "developer",
      "name": "Developer",
      "role": "developer",
      "model": "github-copilot/claude-sonnet-4.5",
      "description": "Implements features"
    }
  ],
  "steps": [
    {
      "id": "plan",
      "name": "Plan: Decompose into stories",
      "type": "agent",
      "agent": "planner",
      "input": "Decompose this task into stories...",
      "output": {
        "file": "plan.yml"
      },
      "acceptance_criteria": ["stories:"],
      "on_fail": {
        "retry": 2,
        "escalate_to": "human"
      },
      "timeout": 600
    }
  ],
  "variables": {
    "repo_path": "",
    "test_command": "npm test"
  }
}

Status Codes:

Headers:

ETag: "workflow:feature-dev:2"
X-Resource-Revision: 2

Permissions: Requires view permission.


GET /api/workflows/:id/access

Resolve workflow-specific actions and provenance for the current identity. Clients use this server-owned result to distinguish editable user workflows from built-in or shared read-only definitions.

Request:

curl http://localhost:3001/api/workflows/feature-dev/access

Response:

{
  "workflowId": "feature-dev",
  "canView": true,
  "canEdit": false,
  "canExecute": true,
  "canDuplicate": true,
  "readOnlyReason": "Built-in workflows are read-only. Duplicate this workflow to customize it.",
  "provenance": {
    "kind": "built-in",
    "owner": "system",
    "createdBy": "system",
    "updatedBy": "system",
    "createdAt": "2026-02-09T12:00:00Z",
    "updatedAt": "2026-02-09T14:30:00Z"
  }
}

provenance.kind is built-in, user-owned, or shared. canEdit and canExecute combine the authenticated request permissions with the workflow ACL decision. canDuplicate reflects whether the authenticated request may create workflows. A read-only response includes an actionable reason suitable for the workflow browser.

Status Codes:

Permissions: Requires workflow:read and workflow-level view permission.


POST /api/workflows

Create a new workflow.

Request:

curl -X POST http://localhost:3001/api/workflows \
  -H "Content-Type: application/json" \
  -d '{
    "id": "hello-world",
    "name": "Hello World Workflow",
    "version": 1,
    "description": "A simple test workflow",
    "agents": [
      {
        "id": "writer",
        "name": "Writer",
        "role": "developer",
        "model": "github-copilot/claude-sonnet-4.5",
        "description": "Writes messages"
      }
    ],
    "steps": [
      {
        "id": "greet",
        "name": "Greet user",
        "type": "agent",
        "agent": "writer",
        "input": "Write a hello message",
        "output": {
          "file": "greeting.md"
        }
      }
    ]
  }'

Response:

{
  "success": true,
  "workflowId": "hello-world"
}

Status Codes:

Permissions: Any authenticated user can create workflows (becomes owner).

Validation:


PUT /api/workflows/:id

Update an existing workflow (auto-increments version).

Request:

curl -X PUT http://localhost:3001/api/workflows/hello-world \
  -H "Content-Type: application/json" \
  -H 'If-Match: "workflow:hello-world:1"' \
  -d '{
    "id": "hello-world",
    "name": "Hello World Workflow v2",
    "version": 1,
    "description": "Updated workflow with farewell step",
    "agents": [
      {
        "id": "writer",
        "name": "Writer",
        "role": "developer",
        "model": "github-copilot/claude-sonnet-4.5",
        "description": "Writes messages"
      }
    ],
    "steps": [
      {
        "id": "greet",
        "name": "Greet user",
        "type": "agent",
        "agent": "writer",
        "input": "Write a hello message",
        "output": {
          "file": "greeting.md"
        }
      },
      {
        "id": "farewell",
        "name": "Say goodbye",
        "type": "agent",
        "agent": "writer",
        "input": "Write a goodbye message",
        "output": {
          "file": "farewell.md"
        }
      }
    ]
  }'

Response:

{
  "success": true,
  "version": 2
}

Status Codes:

Permissions: Requires edit permission.

Notes:

Conflict response:

{
  "code": "CONFLICT",
  "message": "workflow hello-world has changed since it was loaded. Reload and retry with the latest revision.",
  "details": {
    "resourceType": "workflow",
    "resourceId": "hello-world",
    "expectedRevision": 1,
    "currentRevision": 2,
    "current": {
      "id": "hello-world",
      "version": 2,
      "description": "Latest workflow body"
    }
  }
}

DELETE /api/workflows/:id

Delete a workflow.

Request:

curl -X DELETE http://localhost:3001/api/workflows/hello-world

Response:

(Empty body, 204 status)

Status Codes:

Permissions: Requires delete permission (owner only).

Notes:


Workflow Authoring

POST /api/workflows/authoring/dry-run

Validate an unsaved workflow without starting a run. The response includes lint messages, executable checks, and a skill audit for referenced shared skills.

Request:

curl -X POST http://localhost:3001/api/workflows/authoring/dry-run \
  -H "Content-Type: application/json" \
  -d '{
    "workflow": {
      "id": "release-helper",
      "name": "Release Helper",
      "version": 1,
      "description": "Uses a shared release skill",
      "agents": [
        {
          "id": "runner",
          "name": "Runner",
          "role": "developer",
          "tools": ["Read", "skill:release-helper"]
        }
      ],
      "steps": [{ "id": "run", "name": "Run", "type": "agent", "agent": "runner" }]
    },
    "context": { "clientMode": "remote" }
  }'

Response excerpt:

{
  "status": "blocked",
  "canRun": false,
  "checks": [{ "id": "skill", "label": "Skill audit", "status": "fail" }],
  "messages": [
    {
      "category": "skill",
      "severity": "error",
      "message": "Skill Release Helper has no persisted scan and cannot run in remote mode."
    }
  ],
  "skillAudit": {
    "status": "fail",
    "mode": "remote",
    "references": [
      {
        "reference": "release-helper",
        "skillId": "release-helper",
        "status": "blocked",
        "message": "Skill Release Helper has no persisted scan and cannot run in remote mode."
      }
    ]
  }
}

The skill audit recognizes skill:<id> and skill/<id> references in agents, tools, steps, variables, inputs, and descriptions. Local mode warns on unscanned skills. Remote and cloud modes fail missing, unscanned, or blocked skills unless the skill has an active reviewed exception.

Dry-run responses may include pipelineSummary when a workflow declares pipeline. Pipeline lint validates the parent agent, subagent role references, deliverable contracts, verification steps, dependencies, and whether each role is used by a workflow step or parallel substep.

GET /api/workflows/recipes

Returns reusable workflow recipes. v5 recipes include .openclaw Audit, which materializes an orchestrated pipeline with config, storage, security, docs, and follow-up task subagent roles.

POST /api/workflows/recipes/:recipeId/materialize

Materializes a recipe into workflow JSON/YAML and a preview. When a recipe has a pipeline, preview.pipeline contains role, status, scope, deliverable, dependency, verification, and telemetry-budget metadata for the authoring UI.

POST /api/workflows/:id/dry-run

Dry-run a saved workflow definition with the same response shape and context rules as /api/workflows/authoring/dry-run.


Workflow Runs

POST /api/workflows/:id/runs

Start a new workflow run.

Request:

curl -X POST http://localhost:3001/api/workflows/feature-dev/runs \
  -H "Content-Type: application/json" \
  -d '{
  "taskId": "US-42",
  "context": {
    "clientMode": "remote",
    "priority": "high",
    "deadline": "2026-02-15"
  }
}'

Request Body:

{
  taskId?: string;           // Optional: VK task ID to associate with run
  context?: {
    clientMode?: "local" | "remote" | "cloud"; // Optional: workflow skill gate mode
    [key: string]: unknown;
  };
  budget?: {
    enabled?: boolean;
    limits?: {
      totalTokens?: number;
      costUsd?: number;
      toolCalls?: number;
      runtimeSeconds?: number;
      idleRuntimeSeconds?: number;
      retries?: number;
      fanOut?: number;
    };
    softThresholdPercent?: number;
    hardAction?: "pause" | "require-approval" | "downgrade" | "cancel";
    downgradeModel?: string;
  };
}

Before a run starts, the server dry-runs the saved workflow and blocks remote or cloud execution when a referenced shared skill is missing, unscanned, or blocked. The run context includes the resulting skillAudit summary when execution is allowed. Workflows with pipeline metadata also include context.pipeline, which rolls subagent role status and time/token telemetry into the run record.

Run budgets are merged with workspace, workflow, and workflow-agent defaults using the strictest positive limit. Soft thresholds write budget-policy governance traces. Hard thresholds pause/block, require approval, downgrade the model route, or cancel according to the effective policy.

The workflow root reserves durable admission capacity before the run becomes active. Every provider-backed step then obtains a child reservation against its resolved provider and selected host before its attempt becomes running. The response exposes the root binding and the latest step decision without including prompts or credentials. Use /api/admission?workflowRunId=<run-id> or vk admission list --workflow-run <run-id> for current lease and limiting policy details.

Response:

{
  "id": "run_20260209_abc123",
  "workflowId": "feature-dev",
  "workflowVersion": 2,
  "taskId": "US-42",
  "status": "running",
  "currentStep": "plan",
  "context": {
    "task": {
      "id": "US-42",
      "title": "Implement user registration",
      "description": "Add registration endpoint with email validation"
    },
    "priority": "high",
    "deadline": "2026-02-15"
  },
  "startedAt": "2026-02-09T12:00:00Z",
  "steps": [
    {
      "stepId": "plan",
      "status": "running",
      "agent": "planner",
      "startedAt": "2026-02-09T12:00:00Z",
      "retries": 0
    },
    {
      "stepId": "implement",
      "status": "pending",
      "retries": 0
    }
  ]
}

Status Codes:

Permissions: Requires execute permission.

Notes:


GET /api/workflow-runs

List workflow runs with optional filters.

Query Parameters:

Request:

# All runs
curl http://localhost:3001/api/workflow-runs

# Runs for a specific workflow
curl "http://localhost:3001/api/workflow-runs?workflowId=feature-dev"

# Runs for a specific task
curl "http://localhost:3001/api/workflow-runs?taskId=US-42"

# Failed runs only
curl "http://localhost:3001/api/workflow-runs?status=failed"

Response:

[
  {
    "id": "run_20260209_abc123",
    "workflowId": "feature-dev",
    "workflowVersion": 2,
    "taskId": "US-42",
    "status": "completed",
    "startedAt": "2026-02-09T12:00:00Z",
    "completedAt": "2026-02-09T12:45:00Z",
    "duration": 2700,
    "stepsCompleted": 7,
    "stepsTotal": 7
  },
  {
    "id": "run_20260209_def456",
    "workflowId": "security-audit",
    "workflowVersion": 1,
    "status": "running",
    "currentStep": "fix",
    "startedAt": "2026-02-09T13:00:00Z",
    "stepsCompleted": 2,
    "stepsTotal": 5
  }
]

Status Codes:

Permissions: Filtered by workflow view permissions.


GET /api/workflow-runs/:id

Get full details of a specific workflow run.

Request:

curl http://localhost:3001/api/workflow-runs/run_20260209_abc123

Response:

{
  "id": "run_20260209_abc123",
  "workflowId": "feature-dev",
  "workflowVersion": 2,
  "taskId": "US-42",
  "status": "completed",
  "currentStep": null,
  "context": {
    "task": { "id": "US-42", "title": "..." },
    "plan": { "stories": [...] },
    "implement": { "changes": "..." }
  },
  "startedAt": "2026-02-09T12:00:00Z",
  "completedAt": "2026-02-09T12:45:00Z",
  "lastCheckpoint": "2026-02-09T12:45:00Z",
  "steps": [
    {
      "stepId": "plan",
      "status": "completed",
      "agent": "planner",
      "sessionKey": "session_xyz",
      "startedAt": "2026-02-09T12:00:00Z",
      "completedAt": "2026-02-09T12:10:00Z",
      "duration": 600,
      "retries": 0,
      "output": ".veritas-kanban/workflow-runs/run_20260209_abc123/step-outputs/plan.yml"
    },
    {
      "stepId": "implement",
      "status": "completed",
      "agent": "developer",
      "startedAt": "2026-02-09T12:10:00Z",
      "completedAt": "2026-02-09T12:35:00Z",
      "duration": 1500,
      "retries": 1,
      "output": ".veritas-kanban/workflow-runs/run_20260209_abc123/step-outputs/implement-0.md",
      "loopState": {
        "totalIterations": 5,
        "currentIteration": 5,
        "completedIterations": 5,
        "failedIterations": 0
      }
    }
  ]
}

Status Codes:

Permissions: Requires view permission on the workflow.


GET /api/workflow-runs/active

Get currently running workflow runs only.

Request:

curl http://localhost:3001/api/workflow-runs/active

Response:

[
  {
    "id": "run_20260209_def456",
    "workflowId": "security-audit",
    "workflowVersion": 1,
    "status": "running",
    "currentStep": "fix",
    "startedAt": "2026-02-09T13:00:00Z",
    "stepsCompleted": 2,
    "stepsTotal": 5
  }
]

Status Codes:

Permissions: Filtered by workflow view permissions.

Notes: Returns metadata only (not full run state).


GET /api/workflow-runs/stats

Get aggregated workflow statistics for a given period.

Query Parameters:

Request:

curl "http://localhost:3001/api/workflow-runs/stats?period=7d"

Response:

{
  "period": "7d",
  "totalWorkflows": 5,
  "activeRuns": 2,
  "completedRuns": 42,
  "failedRuns": 8,
  "avgDuration": 1800000,
  "successRate": 0.84,
  "perWorkflow": [
    {
      "workflowId": "feature-dev",
      "workflowName": "Feature Development Workflow",
      "runs": 25,
      "completed": 20,
      "failed": 5,
      "successRate": 0.8,
      "avgDuration": 1800000
    },
    {
      "workflowId": "security-audit",
      "workflowName": "Security Audit & Remediation",
      "runs": 17,
      "completed": 15,
      "failed": 2,
      "successRate": 0.88,
      "avgDuration": 900000
    }
  ]
}

Field Descriptions:

Status Codes:

Permissions: Filtered by workflow view permissions.


POST /api/workflow-runs/:id/resume

Resume a blocked workflow run (after human approval or escalation).

Request:

curl -X POST http://localhost:3001/api/workflow-runs/run_20260209_abc123/resume \
  -H "Content-Type: application/json" \
  -d '{
    "context": {
      "reviewerComments": "Looks good, proceed"
    }
  }'

Request Body:

{
  context?: Record<string, unknown>;  // Optional: Additional context for resume
}

Response:

{
  "id": "run_20260209_abc123",
  "workflowId": "feature-dev",
  "status": "running",
  "currentStep": "deploy",
  ...
}

Status Codes:

Permissions: Requires execute permission on the workflow.

Notes:


Gate Operations

POST /api/workflow-runs/:runId/steps/:stepId/approve

Approve a gate step (allows workflow to continue).

Request:

curl -X POST http://localhost:3001/api/workflow-runs/run_20260209_abc123/steps/quality-gate/approve

Response:

{
  "id": "run_20260209_abc123",
  "workflowId": "feature-dev",
  "status": "running",
  "context": {
    "_gateApproval": {
      "stepId": "quality-gate",
      "approved": true,
      "approvedBy": "user-123",
      "approvedAt": "2026-02-09T14:00:00Z"
    }
  },
  ...
}

Status Codes:

Permissions: Requires execute permission on the workflow.

Notes:


POST /api/workflow-runs/:runId/steps/:stepId/reject

Reject a gate step (marks workflow as failed).

Request:

curl -X POST http://localhost:3001/api/workflow-runs/run_20260209_abc123/steps/quality-gate/reject

Response:

{
  "id": "run_20260209_abc123",
  "workflowId": "feature-dev",
  "status": "failed",
  "error": "Step quality-gate rejected by user-123",
  "completedAt": "2026-02-09T14:00:00Z",
  ...
}

Status Codes:

Permissions: Requires execute permission on the workflow.


GET /api/workflow-runs/:runId/steps/:stepId/status

Get detailed status of a specific step (useful for parallel sub-steps).

Request:

curl http://localhost:3001/api/workflow-runs/run_20260209_abc123/steps/implement/status

Response:

{
  "stepId": "implement",
  "status": "completed",
  "agent": "developer",
  "sessionKey": "session_xyz",
  "startedAt": "2026-02-09T12:10:00Z",
  "completedAt": "2026-02-09T12:35:00Z",
  "duration": 1500,
  "retries": 1,
  "output": ".veritas-kanban/workflow-runs/run_20260209_abc123/step-outputs/implement-0.md",
  "loopState": {
    "totalIterations": 5,
    "currentIteration": 5,
    "completedIterations": 5,
    "failedIterations": 0
  }
}

Status Codes:

Permissions: Requires view permission on the workflow.


Tool Policies

GET /api/tool-policies

List all tool policies (default + custom).

Request:

curl http://localhost:3001/api/tool-policies

Response:

[
  {
    "role": "planner",
    "allowed": ["read", "web_search", "web_fetch", "browser", "image", "nodes"],
    "denied": ["write", "edit", "exec", "message"],
    "description": "Analysis and planning — read-only access"
  },
  {
    "role": "developer",
    "allowed": ["*"],
    "denied": [],
    "description": "Feature implementation — full access"
  },
  {
    "role": "custom-auditor",
    "allowed": ["read", "web_search"],
    "denied": ["exec", "write", "edit"],
    "description": "Security auditor — read-only with web access"
  }
]

Status Codes:

Permissions: Public (no authentication required).


GET /api/tool-policies/:role

Get a specific tool policy by role.

Request:

curl http://localhost:3001/api/tool-policies/planner

Response:

{
  "role": "planner",
  "allowed": ["read", "web_search", "web_fetch", "browser", "image", "nodes"],
  "denied": ["write", "edit", "exec", "message"],
  "description": "Analysis and planning — read-only access"
}

Status Codes:

Permissions: Public.


POST /api/tool-policies

Create a new custom tool policy.

Request:

curl -X POST http://localhost:3001/api/tool-policies \
  -H "Content-Type: application/json" \
  -d '{
    "role": "custom-auditor",
    "allowed": ["read", "web_search", "web_fetch", "browser"],
    "denied": ["exec", "write", "edit"],
    "description": "Security auditor — read-only with web access"
  }'

Request Body:

{
  role: string;              // Required: role name (alphanumeric + dash/underscore, max 50 chars)
  allowed: string[];         // Required: tool names (use '*' for all tools)
  denied: string[];          // Required: tool names (takes precedence over allowed)
  description: string;       // Required: what this role does (max 500 chars)
}

Response:

{
  "success": true,
  "role": "custom-auditor"
}

Status Codes:

Permissions: Any authenticated user can create custom policies.

Validation:


PUT /api/tool-policies/:role

Update an existing tool policy (including defaults).

Request:

curl -X PUT http://localhost:3001/api/tool-policies/custom-auditor \
  -H "Content-Type: application/json" \
  -d '{
    "role": "custom-auditor",
    "allowed": ["read", "web_search"],
    "denied": ["exec", "write", "edit", "message"],
    "description": "Updated auditor policy"
  }'

Response:

{
  "success": true,
  "role": "custom-auditor"
}

Status Codes:

Permissions: Any authenticated user can update policies.

Notes: Default policies can be edited but not deleted.


DELETE /api/tool-policies/:role

Delete a custom tool policy.

Request:

curl -X DELETE http://localhost:3001/api/tool-policies/custom-auditor

Response:

(Empty body, 204 status)

Status Codes:

Permissions: Any authenticated user can delete custom policies.

Notes: Default policies (planner, developer, reviewer, tester, deployer) cannot be deleted.


POST /api/tool-policies/:role/validate

Validate if a specific tool is allowed for a role.

Request:

curl -X POST http://localhost:3001/api/tool-policies/planner/validate \
  -H "Content-Type: application/json" \
  -d '{
    "tool": "exec"
  }'

Request Body:

{
  tool: string; // Required: tool name to validate
}

Response:

{
  "role": "planner",
  "tool": "exec",
  "allowed": false,
  "reason": "Tool 'exec' is in the denied list"
}

Status Codes:

Permissions: Public.


Sandbox Policies

Workflow agents can set sandboxPresetId to select a sandbox policy preset for that role. The workflow executor dry-runs the preset against the selected provider before launching the step. Required unsupported controls block the step before execution and write a sandbox-policy governance trace; advisory unsupported controls continue with warnings.

Use /api/sandbox-policies/validate to preflight a workflow agent’s preset in the authoring UI or custom automation. Public preflights must include a providerRuntimeManifestDigest currently registered by a connected agent host; the API rejects stale, disconnected, unknown, or provider-mismatched manifests. Presets can also be assigned visually in the workflow authoring panel.

Sandbox policies are complementary to tool policies:


Budget Policies

Workflow definitions can set config.budget for workflow-wide defaults and agents[].budget for stricter role-specific caps. Launch callers can also pass a stricter budget override to POST /api/workflows/:id/runs.

Supported budget limits:

Budget evaluation is policy enforcement, not dashboard-only analytics. Soft thresholds create visible warnings and budget-policy governance traces. Hard thresholds enforce the configured action:

The run record includes budget.usage, budget.thresholdEvents, budget.traceIds, and budget.modelOverride so run detail, timelines, and completion packets can show exactly what happened.


Task Dependencies

GET /api/tasks/:id/dependencies

Get the full dependency graph for a task (recursive tree traversal).

Request:

curl http://localhost:3001/api/tasks/US-42/dependencies

Response:

{
  "task": "US-42",
  "depends_on": [
    {
      "id": "US-40",
      "title": "Create database schema",
      "status": "done",
      "depends_on": []
    },
    {
      "id": "US-41",
      "title": "Implement auth middleware",
      "status": "in-progress",
      "depends_on": [
        {
          "id": "US-39",
          "title": "Setup JWT library",
          "status": "done",
          "depends_on": []
        }
      ]
    }
  ],
  "blocks": [
    {
      "id": "US-43",
      "title": "Add user permissions",
      "status": "todo",
      "blocks": []
    }
  ]
}

Status Codes:

Notes:


POST /api/tasks/:id/dependencies

Add a dependency to a task.

Request:

curl -X POST http://localhost:3001/api/tasks/US-42/dependencies \
  -H "Content-Type: application/json" \
  -d '{
    "dependsOn": "US-40",
    "direction": "depends_on"
  }'

Request Body:

{
  dependsOn: string; // Required: task ID of the dependency
  direction: 'depends_on' | 'blocks'; // Required: direction of dependency
}

Status Codes:

Notes:


DELETE /api/tasks/:id/dependencies/:dependencyId

Remove a dependency from a task.

Request:

curl -X DELETE http://localhost:3001/api/tasks/US-42/dependencies/US-40?direction=depends_on

Query Parameters:

Status Codes:


Crash-Recovery Checkpointing

POST /api/tasks/:id/checkpoint

Save checkpoint state for a task.

Request:

curl -X POST http://localhost:3001/api/tasks/US-42/checkpoint \
  -H "Content-Type: application/json" \
  -d '{
    "state": {
      "current_step": 3,
      "completed": ["step1", "step2"],
      "api_key": "sk-1234567890",
      "context": "Working on user authentication"
    }
  }'

Request Body:

{
  state: any; // Required: checkpoint state (auto-sanitized for secrets)
}

Response:

{
  "success": true,
  "checkpoint": {
    "taskId": "US-42",
    "state": {
      "current_step": 3,
      "completed": ["step1", "step2"],
      "api_key": "[REDACTED]",
      "context": "Working on user authentication"
    },
    "createdAt": "2026-02-15T12:00:00Z",
    "expiresAt": "2026-02-16T12:00:00Z",
    "resumeCount": 0
  }
}

Status Codes:

Notes:


GET /api/tasks/:id/checkpoint

Resume checkpoint state for a task.

Request:

curl http://localhost:3001/api/tasks/US-42/checkpoint

Response:

{
  "success": true,
  "checkpoint": {
    "taskId": "US-42",
    "state": {
      "current_step": 3,
      "completed": ["step1", "step2"],
      "api_key": "[REDACTED]",
      "context": "Working on user authentication"
    },
    "createdAt": "2026-02-15T12:00:00Z",
    "expiresAt": "2026-02-16T12:00:00Z",
    "resumeCount": 1
  }
}

Status Codes:

Notes:


DELETE /api/tasks/:id/checkpoint

Clear checkpoint state for a task.

Request:

curl -X DELETE http://localhost:3001/api/tasks/US-42/checkpoint

Status Codes:


Observational Memory

POST /api/observations

Add an observation to a task.

Request:

curl -X POST http://localhost:3001/api/observations \
  -H "Content-Type: application/json" \
  -d '{
    "taskId": "US-42",
    "type": "decision",
    "content": "Chose React Query over Redux for simpler data fetching and better caching",
    "importance": 8
  }'

Request Body:

{
  taskId: string; // Required: task ID
  type: 'decision' | 'blocker' | 'insight' | 'context'; // Required
  content: string; // Required: observation text (XSS-sanitized)
  importance: number; // Required: 1-10 (1-3: low, 4-7: medium, 8-10: high)
}

Response:

{
  "success": true,
  "observation": {
    "id": "obs_abc123",
    "taskId": "US-42",
    "type": "decision",
    "content": "Chose React Query over Redux for simpler data fetching and better caching",
    "importance": 8,
    "createdAt": "2026-02-15T12:00:00Z",
    "createdBy": "veritas"
  }
}

Status Codes:

Notes:


GET /api/tasks/:id/observations

Get all observations for a task.

Request:

curl http://localhost:3001/api/tasks/US-42/observations

Response:

{
  "success": true,
  "observations": [
    {
      "id": "obs_abc123",
      "taskId": "US-42",
      "type": "decision",
      "content": "Chose React Query over Redux",
      "importance": 8,
      "createdAt": "2026-02-15T12:00:00Z",
      "createdBy": "veritas"
    },
    {
      "id": "obs_def456",
      "taskId": "US-42",
      "type": "blocker",
      "content": "Waiting on API key from ops team",
      "importance": 6,
      "createdAt": "2026-02-15T13:00:00Z",
      "createdBy": "codex"
    }
  ]
}

Status Codes:


GET /api/observations/search

Full-text search across all observations for all tasks.

Request:

curl "http://localhost:3001/api/observations/search?query=react+query&limit=10&offset=0"

Query Parameters:

Response:

{
  "success": true,
  "results": [
    {
      "id": "obs_abc123",
      "taskId": "US-42",
      "taskTitle": "Implement user authentication",
      "type": "decision",
      "content": "Chose React Query over Redux for simpler data fetching",
      "importance": 8,
      "createdAt": "2026-02-15T12:00:00Z",
      "createdBy": "veritas"
    }
  ],
  "total": 1,
  "limit": 10,
  "offset": 0
}

Status Codes:

Notes:


DELETE /api/observations/:id

Delete an observation.

Request:

curl -X DELETE http://localhost:3001/api/observations/obs_abc123

Status Codes:

Notes:


Agent Filter

GET /api/tasks?agent=:name

Filter tasks by assigned agent name.

Request:

curl "http://localhost:3001/api/tasks?agent=codex"

Query Parameters:

Response:

{
  "success": true,
  "tasks": [
    {
      "id": "US-42",
      "title": "Implement user authentication",
      "status": "in-progress",
      "agents": ["codex"],
      "priority": "high",
      "type": "feature"
    },
    {
      "id": "US-45",
      "title": "Add input validation",
      "status": "todo",
      "agents": ["codex", "veritas"],
      "priority": "medium",
      "type": "code"
    }
  ],
  "total": 2
}

Status Codes:

Notes:


WebSocket Events

All workflow state changes are broadcast via WebSocket for real-time UI updates.

Connection:

const ws = new WebSocket('ws://localhost:3001/ws');

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  console.log('Event:', message.type, message.data);
};

workflow:started

Emitted when a workflow run starts.

Payload:

{
  "type": "workflow:started",
  "data": {
    "id": "run_20260209_abc123",
    "workflowId": "feature-dev",
    "status": "running",
    "startedAt": "2026-02-09T12:00:00Z",
    ...
  }
}

workflow:step:started

Emitted when a step begins execution.

Payload:

{
  "type": "workflow:step:started",
  "data": {
    "runId": "run_20260209_abc123",
    "stepId": "plan",
    "status": "running",
    "startedAt": "2026-02-09T12:00:00Z"
  }
}

workflow:step:completed

Emitted when a step completes successfully.

Payload:

{
  "type": "workflow:step:completed",
  "data": {
    "runId": "run_20260209_abc123",
    "stepId": "plan",
    "status": "completed",
    "completedAt": "2026-02-09T12:10:00Z",
    "duration": 600
  }
}

workflow:step:failed

Emitted when a step fails.

Payload:

{
  "type": "workflow:step:failed",
  "data": {
    "runId": "run_20260209_abc123",
    "stepId": "plan",
    "status": "failed",
    "error": "Acceptance criteria not met",
    "completedAt": "2026-02-09T12:10:00Z"
  }
}

workflow:completed

Emitted when a workflow run completes (all steps succeeded).

Payload:

{
  "type": "workflow:completed",
  "data": {
    "id": "run_20260209_abc123",
    "workflowId": "feature-dev",
    "status": "completed",
    "completedAt": "2026-02-09T12:45:00Z",
    "duration": 2700
  }
}

workflow:failed

Emitted when a workflow run fails (step failed with no retry policy).

Payload:

{
  "type": "workflow:failed",
  "data": {
    "id": "run_20260209_abc123",
    "workflowId": "feature-dev",
    "status": "failed",
    "error": "Step 'test' failed after 2 retries",
    "completedAt": "2026-02-09T12:30:00Z"
  }
}

workflow:blocked

Emitted when a workflow run is blocked (waiting for human approval or gate).

Payload:

{
  "type": "workflow:blocked",
  "data": {
    "id": "run_20260209_abc123",
    "workflowId": "feature-dev",
    "status": "blocked",
    "currentStep": "quality-gate",
    "error": "Quality gate failed — manual review required"
  }
}

task:changed

Emitted when a task associated with a workflow run is updated (triggers counter/metrics refresh).

Payload:

{
  "type": "task:changed",
  "data": {
    "taskId": "US-42",
    "status": "done",
    "workflowRunId": "run_20260209_abc123"
  }
}

agent:status

Emitted when an agent’s status changes (for multi-agent coordination).

Payload:

{
  "type": "agent:status",
  "data": {
    "agent": "developer",
    "status": "working",
    "taskTitle": "Implement feature X",
    "workflowRunId": "run_20260209_abc123"
  }
}

TypeScript Interfaces

WorkflowDefinition

export interface WorkflowDefinition {
  id: string;
  name: string;
  version: number;
  description: string;
  config?: WorkflowConfig;
  agents: WorkflowAgent[];
  steps: WorkflowStep[];
  variables?: Record<string, unknown>;
  schemas?: Record<string, unknown>;
}

WorkflowConfig

export interface WorkflowConfig {
  timeout?: number; // seconds
  fresh_session_default?: boolean;
  progress_file?: string;
  telemetry_tags?: string[];
  budget?: AgentBudgetPolicy;
}

WorkflowAgent

export interface WorkflowAgent {
  id: string;
  name: string;
  role: string; // maps to tool policy
  sandboxPresetId?: string; // maps to a sandbox policy preset
  budget?: AgentBudgetPolicy; // stricter workflow-agent budget
  model?: string; // default model for this agent
  description: string;
  tools?: string[]; // tool restrictions (overrides role policy)
}

WorkflowStep

export type StepType = 'agent' | 'loop' | 'gate' | 'parallel';

export interface WorkflowStep {
  id: string;
  name: string;
  agent?: string; // agent ID (required for agent/loop steps)
  type: StepType;
  fresh_session?: boolean; // legacy: use session config instead
  session?: StepSessionConfig;
  input?: string; // template for agent prompt
  output?: StepOutput;
  acceptance_criteria?: string[];
  on_fail?: FailurePolicy;
  timeout?: number;

  // Loop-specific config
  loop?: LoopConfig;

  // Gate-specific config
  condition?: string; // expression evaluating to boolean
  on_false?: EscalationPolicy;

  // Parallel-specific config
  parallel?: ParallelConfig;
}

StepOutput

export interface StepOutput {
  file: string; // filename in step-outputs/
  schema?: string; // schema ID for validation
}

FailurePolicy

export interface FailurePolicy {
  retry?: number;
  retry_delay_ms?: number; // delay between retries
  retry_step?: string; // retry a different step ID
  escalate_to?: 'human' | `agent:${string}` | 'skip';
  escalate_message?: string;
  on_exhausted?: EscalationPolicy;
}

EscalationPolicy

export interface EscalationPolicy {
  escalate_to: 'human' | `agent:${string}` | 'skip';
  escalate_message?: string;
}

LoopConfig

export interface LoopConfig {
  over: string; // expression returning array
  item_var?: string; // variable name for current item
  index_var?: string; // variable name for loop index
  completion: 'all_done' | 'any_done' | 'first_success';
  fresh_session_per_iteration?: boolean;
  verify_each?: boolean;
  verify_step?: string; // step ID to run after each iteration
  max_iterations?: number;
  continue_on_error?: boolean; // if true, failed iterations don't fail the loop
}

GateStepConfig

export interface GateStepConfig {
  condition: string; // expression evaluating to boolean
  on_false: EscalationPolicy;
}

ParallelConfig

export interface ParallelConfig {
  steps: ParallelSubStep[]; // sub-steps to execute in parallel
  completion: 'all' | 'any' | number; // wait for all, any, or N sub-steps
  fail_fast?: boolean; // if true, abort others when one fails
  timeout?: number; // max time to wait (seconds)
}

export interface ParallelSubStep {
  id: string;
  agent: string;
  input: string; // template for sub-step input
  output?: StepOutput;
  timeout?: number;
}

WorkflowRun

export type WorkflowRunStatus = 'pending' | 'running' | 'blocked' | 'completed' | 'failed';
export type WorkflowAdmissionState = 'waiting' | 'dispatching' | 'active' | 'terminal';

export interface WorkflowRun {
  id: string; // run_<timestamp>_<nanoid>
  workflowId: string;
  workflowVersion: number;
  taskId?: string; // optional task association
  admission?: WorkflowRootAdmissionBinding; // durable root reservation or queue identity
  status: WorkflowRunStatus;
  currentStep?: string; // current step ID
  context: Record<string, unknown>; // shared context across steps
  budget?: AgentBudgetState; // effective budget, usage, threshold events, traces
  startedAt: string;
  completedAt?: string;
  lastCheckpoint?: string; // last state persistence timestamp
  error?: string;
  steps: StepRun[];
}

StepRun

export type StepRunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';

export interface StepRun {
  stepId: string;
  status: StepRunStatus;
  agent?: string;
  sessionKey?: string; // OpenClaw session key
  startedAt?: string;
  completedAt?: string;
  duration?: number; // seconds
  retries: number;
  output?: string; // path to output file
  error?: string;
  admission?: WorkflowStepAdmissionBinding; // latest executable attempt or queue decision

  // Loop-specific state
  loopState?: {
    totalIterations: number;
    currentIteration: number;
    completedIterations: number;
    failedIterations: number;
  };
}

When root or step capacity is temporarily unavailable, the corresponding admission binding has state: "waiting" and a durable queueEntryId. The run and step remain pending; provider execution is not marked active. A claimed entry briefly uses dispatching while Veritas transfers durable ownership to workflow recovery, then becomes active before provider execution.

ToolPolicy

export interface ToolPolicy {
  role: string;
  allowed: string[]; // tool names (use '*' for all)
  denied: string[]; // tool names (takes precedence)
  description: string;
}

StepSessionConfig

export interface StepSessionConfig {
  mode: 'fresh' | 'reuse'; // fresh = new session, reuse = continue existing
  context: 'minimal' | 'full' | 'custom'; // how much context to pass
  cleanup: 'delete' | 'keep'; // delete session after step or keep
  timeout: number; // session timeout in seconds
  includeOutputsFrom?: string[]; // step names for 'custom' context
}

WorkflowACL

export type WorkflowPermission = 'view' | 'create' | 'edit' | 'delete' | 'execute';

export interface WorkflowACL {
  workflowId: string;
  owner: string; // user ID or 'system'
  editors: string[]; // users who can edit
  viewers: string[]; // users who can view
  executors: string[]; // users who can trigger runs
  isPublic: boolean; // anyone can view/execute
}

WorkflowAuditEvent

export interface WorkflowAuditEvent {
  timestamp: string;
  userId: string;
  action: 'create' | 'edit' | 'delete' | 'run';
  workflowId: string;
  workflowVersion?: number;
  changes?: Array<{ field: string; oldValue: unknown; newValue: unknown }>;
  runId?: string;
}

Error Responses

All errors follow this structure:

{
  "error": {
    "message": "Human-readable error message",
    "code": "ERROR_CODE",
    "details": {}
  }
}

Status Codes

Code Name Description
400 Bad Request Validation error, missing required fields, invalid input
401 Unauthorized Missing or invalid authentication
403 Forbidden No permission to perform action
404 Not Found Resource not found
409 Conflict Resource already exists (e.g., workflow ID collision)
500 Internal Server Error Unexpected server error

Common Errors

Validation Error

{
  "error": {
    "message": "Workflow must define at least one agent",
    "code": "VALIDATION_ERROR",
    "details": {
      "field": "agents"
    }
  }
}

Not Found

{
  "error": {
    "message": "Workflow feature-dev not found",
    "code": "NOT_FOUND",
    "details": {
      "workflowId": "feature-dev"
    }
  }
}

Permission Denied

{
  "error": {
    "message": "No edit permission for workflow feature-dev",
    "code": "PERMISSION_DENIED",
    "details": {
      "workflowId": "feature-dev",
      "requiredPermission": "edit",
      "userId": "user-123"
    }
  }
}

Conflict

{
  "error": {
    "message": "Workflow hello-world already exists",
    "code": "CONFLICT",
    "details": {
      "workflowId": "hello-world"
    }
  }
}

End of API Reference