Skip to content

Latest commit

 

History

History
2339 lines (1816 loc) · 50.4 KB

File metadata and controls

2339 lines (1816 loc) · 50.4 KB

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:

  • view — View workflow definitions and runs
  • create — Create new workflows
  • edit — Update workflow definitions
  • delete — Delete workflows (owner only)
  • execute — Start workflow runs, approve gates

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:

  • 200 OK — Success

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": "{{task.git.worktreePath}}",
    "test_command": "npm test"
  }
}

Status Codes:

  • 200 OK — Success
  • 404 Not Found — Workflow not found
  • 403 Forbidden — No view permission

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:

  • 200 OK — Access and provenance resolved
  • 403 Forbidden — No view permission
  • 404 Not Found — Workflow not found

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:

  • 201 Created — Workflow created successfully
  • 400 Bad Request — Validation error (missing required fields, invalid references)
  • 409 Conflict — Workflow ID already exists

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

Validation:

  • id: Required, alphanumeric + dashes, max 100 characters
  • name: Required, max 200 characters
  • version: Required, integer ≥ 0
  • description: Required, max 2000 characters
  • agents: Required, 1-20 agents
  • steps: Required, 1-50 steps
  • All step.agent references must match an agents.id
  • All on_fail.retry_step references must match a steps.id

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:

  • 200 OK — Workflow updated successfully
  • 400 Bad Request — Validation error or ID mismatch
  • 404 Not Found — Workflow not found
  • 403 Forbidden — No edit permission
  • 409 Conflict — Workflow has changed since the supplied If-Match revision

Permissions: Requires edit permission.

Notes:

  • Version is auto-incremented (ignore version in request body)
  • Workflow version is also the optimistic-concurrency revision. Read the workflow ETag, then send it back with If-Match on update.
  • Workflow definitions include createdBy, updatedBy, createdAt, and updatedAt when saved through the API.
  • Active runs continue with their snapshotted version (no interruption)
  • Changes are logged to .veritas-kanban/workflows/.audit.jsonl

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:

  • 204 No Content — Workflow deleted successfully
  • 404 Not Found — Workflow not found
  • 403 Forbidden — Not owner (only owners can delete)

Permissions: Requires delete permission (owner only).

Notes:

  • Deletes workflow YAML file
  • Does NOT delete historical run data (runs remain accessible)
  • Audit event logged

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:

  • 201 Created — Run started successfully
  • 400 Bad Request — Validation error
  • 404 Not Found — Workflow not found
  • 403 Forbidden — No execute permission

Permissions: Requires execute permission.

Notes:

  • Workflow execution begins immediately (asynchronous)
  • Monitor progress via WebSocket or polling /api/workflow-runs/:id
  • Run state persisted to .veritas-kanban/workflow-runs/{runId}/run.json

GET /api/workflow-runs

List workflow runs with optional filters.

Query Parameters:

  • workflowId (string, optional) — Filter by workflow ID
  • taskId (string, optional) — Filter by task ID
  • status (string, optional) — Filter by status: pending, running, blocked, completed, failed

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:

  • 200 OK — Success

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:

  • 200 OK — Success
  • 404 Not Found — Run not found
  • 403 Forbidden — No view permission

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:

  • 200 OK — Success

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:

  • period (string, optional) — Period for stats: 24h, 7d, 30d (default: 7d)

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:

  • avgDuration — Average duration in milliseconds
  • successRate — Decimal (0.0 to 1.0) representing percentage
  • perWorkflow — Per-workflow breakdown

Status Codes:

  • 200 OK — Success
  • 400 Bad Request — Invalid period value

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:

  • 200 OK — Run resumed successfully
  • 400 Bad Request — Run not blocked (current status: running/completed/failed)
  • 404 Not Found — Run not found
  • 403 Forbidden — No execute permission

Permissions: Requires execute permission on the workflow.

Notes:

  • Only runs with status blocked can be resumed
  • Workflow execution continues from where it left off
  • Context can be updated during resume

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:

  • 200 OK — Gate approved, run resumed
  • 400 Bad Request — Step not awaiting approval or not a gate step
  • 404 Not Found — Run or step not found
  • 403 Forbidden — No execute permission

Permissions: Requires execute permission on the workflow.

Notes:

  • Only works for steps with type: gate and status failed
  • Approval context is added to run context
  • Workflow continues execution

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:

  • 200 OK — Gate rejected, run marked as failed
  • 400 Bad Request — Step not awaiting approval or not a gate step
  • 404 Not Found — Run or step not found
  • 403 Forbidden — No execute permission

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:

  • 200 OK — Success
  • 404 Not Found — Run or step not found
  • 403 Forbidden — No view permission

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:

  • 200 OK — Success

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:

  • 200 OK — Success
  • 404 Not Found — Policy not found

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:

  • 201 Created — Policy created successfully
  • 400 Bad Request — Validation error
  • 409 Conflict — Role already exists

Permissions: Any authenticated user can create custom policies.

Validation:

  • role: Required, alphanumeric + dash/underscore, max 50 characters
  • allowed: Required array, max 100 tools
  • denied: Required array (can be empty), max 100 tools
  • description: Required, max 500 characters
  • Cannot use reserved role names (planner, developer, reviewer, tester, deployer)

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:

  • 200 OK — Policy updated successfully
  • 400 Bad Request — Validation error or role mismatch
  • 404 Not Found — Policy not found

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:

  • 204 No Content — Policy deleted successfully
  • 400 Bad Request — Cannot delete default policy
  • 404 Not Found — Policy not found

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:

  • 200 OK — Success
  • 404 Not Found — Policy not found

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:

  • Tool policies decide which Veritas/OpenClaw tools a workflow role may use.
  • Sandbox policies decide what the underlying provider process may access at launch time: filesystem paths, network egress, environment variables, and credentials.

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:

  • totalTokens, inputTokens, and outputTokens
  • costUsd
  • toolCalls
  • runtimeSeconds and idleRuntimeSeconds
  • retries
  • fanOut

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:

  • pause or require-approval blocks the workflow run for operator review.
  • downgrade records a routed decision and applies downgradeModel to Codex workflow steps.
  • cancel fails the run immediately.

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:

  • 200 OK — Success
  • 404 Not Found — Task not found
  • 400 Bad Request — Circular dependency detected

Notes:

  • Returns recursive tree with all upstream (depends_on) and downstream (blocks) dependencies
  • Cycle detection prevents infinite loops
  • Batch-loaded for performance (no N+1 queries)

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:

  • 200 OK — Dependency added
  • 400 Bad Request — Would create circular dependency
  • 404 Not Found — Task not found

Notes:

  • depends_on: This task depends on the specified task
  • blocks: This task blocks the specified task
  • Validates for cycles before adding

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:

  • direction (required): depends_on or blocks

Status Codes:

  • 200 OK — Dependency removed
  • 404 Not Found — Task or dependency not found

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:

  • 200 OK — Checkpoint saved
  • 400 Bad Request — State exceeds 1MB limit
  • 404 Not Found — Task not found

Notes:

  • Auto-sanitizes 20+ secret patterns (API keys, tokens, passwords, etc.)
  • 1MB size limit enforced
  • 24h expiry with automatic cleanup
  • Secrets are sanitized in response but preserved in file for resume

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:

  • 200 OK — Checkpoint retrieved (increments resumeCount)
  • 404 Not Found — Task or checkpoint not found
  • 410 Gone — Checkpoint expired

Notes:

  • Each GET increments the resumeCount
  • Secrets are sanitized in response
  • Use for sub-agent context injection

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:

  • 200 OK — Checkpoint cleared
  • 404 Not Found — Task or checkpoint not found

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:

  • 201 Created — Observation added
  • 400 Bad Request — Invalid type or importance score
  • 404 Not Found — Task not found

Notes:

  • Content is XSS-sanitized (strips script tags, dangerous attributes)
  • Activity log entry created automatically

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:

  • 200 OK — Success
  • 404 Not Found — Task not found

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:

  • query (required): search terms (full-text search)
  • limit (optional): max results per page (default: 50, max: 200)
  • offset (optional): pagination offset (default: 0)

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:

  • 200 OK — Success
  • 400 Bad Request — Missing query or invalid limit/offset

Notes:

  • Searches across all tasks
  • Results include task title for context
  • Max 200 results per page

DELETE /api/observations/:id

Delete an observation.

Request:

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

Status Codes:

  • 200 OK — Observation deleted
  • 404 Not Found — Observation not found

Notes:

  • Activity log entry created automatically

Agent Filter

GET /api/tasks?agent=:name

Filter tasks by assigned agent name.

Request:

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

Query Parameters:

  • agent (optional): agent name (trimmed, max 100 chars)
  • status (optional): filter by status (todo, in-progress, blocked, done)
  • limit (optional): max results (default: 100)
  • offset (optional): pagination offset (default: 0)

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:

  • 200 OK — Success

Notes:

  • Agent name is case-insensitive and trimmed
  • Works with existing pagination and filters
  • Returns tasks where the agent is in the agents[] array

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