Spec: Workflows

  • Status: Draft
  • Last amended: 2026-05-26 (ADR-0022 — tool intersection operates against root agent's tool surface)
  • Constrained by: ADR-0006, ADR-0011, ADR-0012, ADR-0015, ADR-0019, ADR-0022
  • Implements: packages/dsl/, packages/harness/, packages/daemon/

Purpose

This spec defines the workflows system: operator-authored, parameterised agent recipes declared in the project DSL. Workflows provide a constrained, prompt-bound way for operators and guests to execute specific tasks with structured inputs, strictly limited tools, and a defined invocation lifecycle.

This document is normative for:

  • The workflows: block schema in the project DSL.
  • The input parameter types and validation rules.
  • The two-stage file upload protocol for workflow inputs.
  • The invocation lifecycle from selection to audit.
  • The composition of system prompts and tool allowlists for workflow runs.

It is not normative for:

  • Guest principal management (that's guests.md).
  • The low-level agent execution details (that's agent.md).
  • The PTY-based task runner (that's task-runner.md).

Constraints (from ADRs)

Constraint Source
Workflows are declared as a named-object map in the YAML DSL ADR-0006, ADR-0019
Portable across hosts; paths use project-relative URI prefixes ADR-0011, ADR-0015
Compiled into Mastra Agent (v1) or Workflow (v1.x) primitives ADR-0012, ADR-0019
Supports federated configuration: local overrides and nullification ADR-0015
Operator-authored recipes; guests invoke but do not define ADR-0019
Tool allowlist must be a strict subset of the root agent's tool surface ADR-0019, ADR-0022

WorkflowDefinition Schema

The workflows: block is a named-object map where each entry is a WorkflowDefinition.

Field Type Required Meaning
description string yes Human-readable description. Max 280 chars.
system_prompt path yes Project-relative path to the workflow's system prompt fragment.
inputs object yes Named-object map of WorkflowInput schemas.
tools.allow string[] yes Tool names the workflow is permitted to use.
tools.deny string[] no Explicit denials to narrow allowlist patterns.
confirm_required bool no Show confirmation step to guests. Default false.
invokable_by enum[] no ["operator", "guest"]. Default ["operator"].
timeout_seconds int no Max wall-clock for invocation. Default 600.
cage_overrides object no Reserved for v1.x cage tightening.

Input Schema Reference

Each input parameter follows the WorkflowInput schema.

Field Type Required Meaning
type enum yes string, integer, number, boolean, file, url.
required bool no Default true.
description string no Field label in the invocation form.
max_length / min_length int no For string.
min / max number no For integer / number.
pattern string no Regex (re2) for string and url.
accept string[] no MIME types for file.
max_size_kb int no Size limit for file.
enum string[] no Restricted set of allowed values.
default any no Default value when not supplied.

File Upload Protocol

Workflows with file inputs use a two-stage protocol:

  1. Upload: Client POSTs file to /api/v1/projects/:id/workflows/:name/upload.
  2. Staging: Daemon validates MIME/size and streams to a temporary staging area.
  3. Token: Daemon returns a JSON response with an upload_token.
  4. Invoke: Client includes the upload_token in the invoke payload.
  5. Cleanup:
    • Abandoned: Files deleted after a 1-hour TTL if no invocation occurs.
    • Completion: Files deleted immediately after the workflow run completes.

Invocation Lifecycle

An invocation follows these eight steps:

  1. Select: Invoker (operator or guest) selects a workflow.
  2. Form: UI renders an input form from the workflow's inputs schema.
  3. Submit & Validate: Client submits inputs; daemon validates against schema.
  4. Create Workflow Run: Daemon creates a session record with kind: "workflow".
  5. Harness Compose: Harness composes instructions and intersects tool sets.
  6. Primary Runs: Primary agent executes the recipe; output streams to UI.
  7. Completion: Run ends as succeeded or failed.
  8. Audit: AuditProcessor logs the run with the invoker's identity.

Prompt Composition

The effective system prompt is composed by the harness:

[Project Primary System Prompt]

---

### Workflow: <name>

[Workflow System Prompt Fragment]

### Workflow inputs

<name>: <value>
...

The composition is fully visible in the run's debug view and recorded in audit logs.

Federated Config Composition

project.local.yaml follows ADR-0015 for workflows:

  • Override: Merging fields (e.g., narrowing tools or changing timeout_seconds).
  • Nullification: Setting a workflow to null to disable it on a specific host.

Mastra Integration

  • Constrained Agent Path (v1): Compiled into a Mastra Agent with composed instructions and tool intersection.
  • Mastra Workflow (v1.x): Reserved for complex control flow (suspend/resume).

Run Record Schema

Stored as a session with kind: "workflow" and extended metadata:

Field Type Meaning
workflow_name string Name of the invoked workflow.
inputs object Frozen input values used for the run.
invoking_principal object Type (operator

Tool Intersection

  • The effective tool set is (Root Agent Tools ∩ Workflow Allowlist) - Workflow Denylist.
  • Per ADR-0022, tools are per-agent — there is no project-level tools: block. Workflows compose against the root agent's resolved tool surface (built-in defaults → role-based defaults → root agent's tools: override → cage filter). The root agent is the agent at ProjectDsl.primary.
  • Validation occurs at session-start; workflows cannot expand the root agent's tool set.
  • kaged.issue.* and kaged.workflow.* tools are available to workflows by default (they are root-agent defaults), but can be excluded via tools.deny if the workflow should not interact with issues or trigger other workflows.

Validation Timing

  1. DSL Parse: Schema validation (Zod).
  2. Project Load: Prompt-file existence check.
  3. Session Start: Tool intersection validation.
  4. Invocation: Input value validation against schema.

Audit Events

  • workflow.invoked
  • workflow.completed
  • workflow.failed
  • workflow.upload
  • workflow.upload_expired

API Endpoints

  • GET /api/v1/projects/:id/workflows — List available workflows.
  • POST /api/v1/projects/:id/workflows/:name/invoke — Invoke with { inputs }.
  • POST /api/v1/projects/:id/workflows/:name/upload — Upload file for file inputs.
  • GET /api/v1/projects/:id/workflows/:name/runs — List historical runs.
  • GET /api/v1/projects/:id/workflows/:name/runs/:rid — Get run detail/logs.

Zod-style Type Definitions

const WorkflowInput = z.object({
  type: z.enum(["string", "integer", "number", "boolean", "file", "url"]),
  required: z.boolean().default(true),
  description: z.string().optional(),
  max_length: z.number().int().optional(),
  min_length: z.number().int().optional(),
  min: z.number().optional(),
  max: z.number().optional(),
  pattern: z.string().optional(),
  accept: z.array(z.string()).optional(),
  max_size_kb: z.number().int().optional(),
  enum: z.array(z.string()).optional(),
  default: z.any().optional(),
});

const WorkflowDefinition = z.object({
  description: z.string().max(280),
  system_prompt: z.string(),
  inputs: z.record(WorkflowInput),
  tools: z.object({
    allow: z.array(z.string()),
    deny: z.array(z.string()).optional(),
  }),
  confirm_required: z.boolean().default(false),
  invokable_by: z.array(z.enum(["operator", "guest"])).default(["operator"]),
  timeout_seconds: z.number().int().default(600),
  cage_overrides: z.record(z.any()).optional(), // Reserved
});

const WorkflowRun = z.object({
  kind: z.literal("workflow"),
  workflow_name: z.string(),
  inputs: z.record(z.any()),
  invoking_principal: z.object({
    type: z.enum(["operator", "guest"]),
    id: z.string(),
  }),
});

Failure Modes

  • Input Validation Error: Daemon rejects invocation (422).
  • Tool Intersection Failure: Logged as critical error; invocation blocked.
  • Upload Timeout: Staged files purged after 1 hour.
  • Agent Timeout: Run aborted after timeout_seconds.

Testing Notes

  • Verify Zod schema enforces all constraints (max_length, pattern, etc.).
  • Test tool intersection with both glob and explicit tools.
  • Verify file staging cleanup after successful and failed runs.
  • Test federated merge (nullification and deep merge).

Open Questions

  • Should workflows declare a result_schema for v1.x?
  • Mapping to Mastra.Workflow for branching logic in v1.x.

References


Amendments

2026-05-26 — ADR-0022: tool intersection operates against root agent's tool surface

ADR-0022 removes the project-level tools: block and moves tool configuration to each AgentSpec. This changes the workflow tool intersection logic:

  1. Intersection target changed. The effective tool set formula is now (Root Agent Tools ∩ Workflow Allowlist) - Workflow Denylist, where "Root Agent Tools" is the resolved tool surface of the agent at ProjectDsl.primary. Previously the intersection was against the project-level tools: block, which no longer exists.
  2. Root-agent defaults available. The root agent gets kaged.issue.* and kaged.workflow.* by default (ADR-0022 rule 5). These are available to workflows unless explicitly denied. A workflow that should not interact with issues can add "kaged.issue.*" to tools.deny.
  3. Constraint table updated. The "Tool allowlist must be a strict subset" constraint now references ADR-0022 alongside ADR-0019.
  4. Validation timing unchanged. Tool intersection still occurs at session-start (step 3 of validation timing).