Skip to content

Workflow generation (Feature 015 US10) ​

Agentweaver can generate a complete workflow definition from a plain-language description. A user clicks Generate workflow on the project Workflows page, describes the pipeline they need, and the server returns a validated WorkflowDefinition YAML draft that opens in the workflow editor for review and an explicit save. Nothing is written to .agentweaver/workflows/ until the user saves.

For the shared describe → validate → review → save journey, see Generate from description. This page keeps the server contract rather than introducing a second authoring diagram.

This document covers the server-side generation capability behind POST /api/projects/{id}/workflows/generate (FR-056–FR-061).

Components ​

PieceResponsibility
IWorkflowGeneratorThe seam: GenerateAsync(WorkflowGenerationRequest) → WorkflowGenerationResult. Returns a draft; never persists.
CopilotWorkflowGeneratorBuilds the prompt, resolves the effective generation provider via GenerationModelProviderExecutor, calls IAgentRunner, validates, and runs one correction pass.
WorkflowDefinitionLoaderValidates the model output with the same schema/structural rules the runtime loader enforces.
RunWorkflowGraphBinder.ValidateBindableDry-runs runtime binding after schema validation; rejects loadable but unrunnable node/edge combinations.
WorkflowDefinitionEndpointsAccepts durable generation jobs, resolves the project's cast roles for the request, and exposes authorized status, result, cancellation, and retry endpoints.
BlueprintGenerationJobWorkerRuns both Blueprint and workflow generation through the existing leased durable-job queue, binds generated worker nodes to confirmed cast members, and persists exactly one artifact only after binding succeeds.

All prompt construction, schema context, and LLM invocation live server-side (FR-057). The client sends a description plus project target-repository context when available, then renders the returned YAML.

Endpoint ​

POST /api/projects/{id}/workflows/generate
Idempotency-Key: <caller retry key>
Body: { "description": "string" }
→ 202 { "job_id": string, "status": "queued", "status_url": string, "result_url": string, ... }
→ 400 { "error": string }   // description or Idempotency-Key missing
→ 409 { "error": "idempotency_key_conflict" }
→ 404                       // project not found
→ 403                       // caller is not the project owner

Poll status_url; a completed job exposes immutable YAML, workflow ID, version, and graph at result_url. The job can be cancelled while queued/running and retried after cancellation or a retryable failure. Reusing the same Idempotency-Key with the identical request returns the same job; using it for different input returns 409. A provider timeout becomes the canonical retryable workflow_provider_timeout failure instead of a disconnected request with ambiguous progress. Before artifact persistence, generated worker and peer-review nodes bind to confirmed cast members. A missing or unreadable team, or an unmapped role, fails the job with workflow_team_binding_required and structured unresolved_roles; result_url returns the same requirements with 422, and no artifact is created. The returned YAML remains an unsaved draft — the MCP server and Web UI use the same server-side generation contract (FR-059), and project workspace persistence still requires an explicit save.

The production provider can be Copilot or BYOK; the class name is not a provider guarantee. Prepare the workflow_generation AI execution context and send its execution_key in If-Model-Provider-Key for the guarded acceptance request. The accepted job snapshots that provider binding and restores it in the worker. For GitHub-backed projects, the server also passes the project's source repository into the generation prompt so generated node prompts keep acting against that repo.

Prompt design (FR-057) ​

The generation prompt is assembled in CopilotWorkflowGenerator.BuildPrompt and contains:

  1. Schema description — the top-level keys (id, name, description, version, triggers, start, nodes, edges) and their required-ness. Legacy singular trigger input remains supported.
  2. Node-type vocabulary with runtime semantics — prompt, peer_review, build_test, check, and terminal. The prompt explains platform-owned merge/scribe but tells the model not to author them. It explicitly forbids serial because ordinary edges express sequential execution, and forbids fan_out, fan_in, and coordinator_composed because they load but cannot bind.
  3. Validation rules — required fields, edge/start node-reference integrity, check nodes needing branches: with a matching outgoing edge per verdict, and the binder's supported runtime topology. The review-transition matrix is rendered directly from WorkflowTransitionContract, the same allowlist used by runtime binding. Schema acceptance alone is insufficient.
  4. Available roles — the project's actual cast roles when a team exists, otherwise the full catalog (FR-061). Constraining the agent/role fields to castable roles keeps the generated workflow immediately runnable without role-not-found errors at build time.
  5. Few-shot examples — the library workflows, preferring the canonical software-delivery and bug-fix patterns from CatalogConformanceSnapshot. If neither is present, the generator takes up to three valid non-default library workflows. Current YAML lives in packages/Agentweaver.Squad/Catalog/Resources/workflows/; agent-evaluation is sequential prompt work, not a parallel fan-out example.
  6. Target repository context — fenced as untrusted data (<<<TARGET_REPOSITORY>>> … <<<END_TARGET_REPOSITORY>>>). The generator receives the project source repository and also extracts GitHub URLs from the description so workflows keep repository/issue targets instead of dropping them.
  7. The user's description — fenced as untrusted data (<<<DESCRIPTION>>> … <<<END_DESCRIPTION>>>) with an instruction to treat it as data, never as instructions to follow (prompt-injection hardening).
  8. Output instruction — "Return ONLY valid YAML for a WorkflowDefinition. No markdown fences. No commentary."

Correction pass (FR-060) ​

The generator validates with WorkflowDefinitionLoader, then RunWorkflowGraphBinder.ValidateBindable. Editing a built-in workflow must also produce a project-owned copy with a new id. On the first failure it makes exactly one more model call:

<original prompt>

Your previous attempt produced YAML that FAILED validation. Fix it.

PREVIOUS YAML:
<failed yaml>

VALIDATION ERROR:
<the loader's file-scoped error message>

Fix the YAML and return only the corrected YAML.
  • If the corrected output validates → it is returned with wasCorrected = true.
  • If it is still invalid → the generator throws WorkflowGenerationException, which the endpoint maps to a structured 400 with error, message, validation_errors, and transition_issues. Each transition issue identifies the rejected edge/kinds/condition and lists supported outgoing alternatives. The mechanism never loops or retries indefinitely.

An edit request with unbindable base_yaml is rejected with the same transition details before the AI execution context is activated, so no model call or persistence occurs for a topology the runtime cannot execute.

Output cleanup and id generation ​

  • Markdown fences — despite the "no fences" instruction, models sometimes wrap output in ```yaml. StripFences extracts the fenced content (or strips stray markers) before validation.
  • Missing id — if the model omits a top-level id: (or leaves it blank), the generator derives a kebab-case slug from the description (lowercased, non-alphanumerics collapsed to hyphens, max 40 chars) and injects it, so the draft always carries a stable id.

Blueprint-driven generation: process fit, FR-063 fallback, and bespoke roles ​

Workflow generation is also reachable indirectly through blueprint generation (BlueprintService.GenerateAsync, behind POST /api/blueprints/generate). The blueprint generator picks library workflows on process fit and falls back to generating a custom workflow only when nothing fits.

  • Library-first, process-fit matching (FR-062). CopilotBlueprintGenerator instructs the model to select library workflows only when the PROCESS they define matches what the team will actually do — never on name similarity or domain-word overlap. For operational/domain-specific work that matches no library workflow's process, the model returns an empty workflows array. An empty array is the correct answer when nothing fits — it is the sentinel for FR-063.
  • Generate-when-none-fits (FR-063). When the model returns no library match, BlueprintService invokes IWorkflowGenerator (the same generator documented above) to produce a custom workflow draft, returned in BlueprintGenerationResult. On ApplyAsync, that YAML is parsed, written to the project's .agentweaver/workflows/ directory, and the registry is synced so the new workflow is immediately coordinator-selectable.
  • Bespoke roles. A generated blueprint may roster roles that have no catalog match. Each such id must also appear in a bespoke_roles array, where every entry carries id, title, and an inline charter (2–4 sentences). BlueprintService.ValidateAsync enforces that every non-catalog roster id has a matching bespoke definition, that bespoke ids don't collide with catalog roles, and that each bespoke role is actually rostered. On apply, bespoke roles are materialized into the casting pipeline so the team is cast with the inline charters. Bespoke roles are a last resort — the generator prefers catalog roles, which ship with pre-built charters and are immediately runnable.

The matching process-fit selection at run time (which library workflow a coordinator picks per task) is documented separately in workflow-selection.md.

Testing ​

tests/Agentweaver.Tests/Workflows/WorkflowGeneratorTests.cs covers:

  • A valid model response → parsed workflow, wasCorrected = false.
  • Markdown-fenced valid output → cleaned and parsed.
  • An invalid response → correction pass triggered, corrected draft returned with wasCorrected = true.
  • Both passes invalid → WorkflowGenerationException.
  • Missing id → derived from the description slug.
  • A peer-review approval that continues to a report-producing agent turn → a runnable draft without a correction pass.
  • The endpoint returns 200 with yaml + workflowId (driven through a stub IWorkflowGenerator), and 400 for a missing description.

Unit tests drive CopilotWorkflowGenerator with a scripted IAgentRunner so the prompt → validate → correction pipeline runs without the live model.

Diagram details and constraints
ElementContract
titleWorkflow authoring
takeawayGenerate a draft. Review it. Save deliberately.
generation-boundary1 GENERATE + REVIEW / No workflow file is saved
persistence-boundary2 EXPLICIT SAVE / Project workspace + registry
Authorize requestAuthorize request
Authorize requestProject ownership + AI execution plan
Authorize requestPOST …/workflows/generate
Authorize requestDescription required
Prompt contextPrompt context
Prompt contextRoles, schema, examples
Prompt contextProject model override
Prompt contextCatalog fallback
Generate candidateGenerate candidate
Generate candidateCopilotWorkflowGenerator
Generate candidateModel returns YAML, not a saved file
Generate candidateCreate or edit
Validate candidateValidate candidate
Validate candidateWorkflowDefinitionLoader + binder dry-run
Validate candidateStructure AND runtime bindability
Validate candidateStrip fences; ensure id
One correctionOne correction
One correctionFailed YAML + error
One correctionRe-run same checks
One correctionNo third attempt
Explicit errorExplicit error
Explicit errorSecond invalid result
Explicit error400 · not persisted
Review & edit draftReview & edit draft
Review & edit draftHuman edits YAML or the visual graph
Review & edit draftValid draft stays unsaved
Save: validate againSave: validate again
Save: validate againParse + structure + route id + binder
Save: validate againPUT …/workflows/{workflowId}
Save: validate againOwnership required
Reject saveReject save
Reject saveParse / id / bind error
Reject save400 or 422 · no write
Reject saveFix the draft
Write project YAMLWrite project YAML
Write project YAMLResolve the path inside the workspace
Write project YAML.agentweaver/workflows/{id}.yaml
Write project YAMLContained-path guard
Write can failWrite can fail
Write can failPath guard or file I/O
Write can fail400 / 500 · stop here
Write can failNo success response
Sync → definitionSync → definition
Sync → definitionExtend allowed set if needed; reload
Sync → definitionReturn saved detail on success
Reload failureReload failure
Reload failureWritten, not available
Reload failure422 / 500 · file may exist
e01permitted
e02grounds prompt
e03candidate YAML
e04valid; unsaved
e05first invalid
e06one repair
e07invalid again
e08explicit Save
e09invalid
e10checks pass
e11failure
e12write succeeded

Visual model ​

Workflow authoring ​

Flowchart showing one model correction pass, human editing of an unsaved workflow draft, save-time validation, project YAML persistence, registry activation, and distinct pre-write and post-write failures.

Structured source · Editable draw.io