Skip to content

Build a Workflow from Scratch

Every Attune workflow follows the same pattern: a Python class with staged execution, a skill file for discovery, and an entry point for registration. This tutorial walks through each layer using our documentation generator as the running example.

By the end you'll know how to build, register, and run your own cost-optimized workflow.

What You'll Build

A three-stage documentation workflow that:

  1. Outlines with Claude Haiku (cheap, fast)
  2. Writes with Claude Sonnet (balanced)
  3. Polishes with Claude Opus (highest quality)

Four files make it work:

File Purpose
src/attune/workflows/doc_gen/workflow.py Workflow class
plugin/skills/docs/SKILL.md Socratic discovery
plugin/commands/attune-docs.md Quick-access command
pyproject.toml Entry point registration

Step 1: Define the Workflow Class

Every workflow extends BaseWorkflow and declares three things: stages, tier_map, and run_stage().

from attune.workflows.base import BaseWorkflow, ModelTier


class DocumentGenerationWorkflow(BaseWorkflow):
    name = "doc-gen"
    description = "Cost-optimized documentation generation"
    stages = ["outline", "write", "polish"]
    tier_map = {
        "outline": ModelTier.CHEAP,
        "write": ModelTier.CAPABLE,
        "polish": ModelTier.PREMIUM,
    }

    async def run_stage(self, stage_name, tier, input_data):
        if stage_name == "outline":
            return await self._outline(input_data, tier)
        if stage_name == "write":
            return await self._write(input_data, tier)
        if stage_name == "polish":
            return await self._polish(input_data, tier)
        raise ValueError(f"Unknown stage: {stage_name}")

Three concepts to understand:

  • stages is an ordered list. The execution engine runs them in sequence, passing each stage's output to the next.
  • tier_map assigns a Claude model to each stage. CHEAP routes to Claude Haiku, CAPABLE to Claude Sonnet, and PREMIUM to Claude Opus.
  • run_stage() is the only method you implement. It receives the stage name, the resolved tier, and the input data from the previous stage.

Why tiers matter

Without tier routing, every stage runs on Claude Opus. With it, you spend Opus tokens only where quality demands it:

Stage Tier Claude Model Input $/1M Output $/1M
outline CHEAP Claude Haiku $0.80 $4.00
write CAPABLE Claude Sonnet $3.00 $15.00
polish PREMIUM Claude Opus $15.00 $75.00

A 10,000-token doc generation job costs roughly $0.38 with tier routing vs $0.90 on Claude Opus alone -- a 58% reduction. Scale that across hundreds of workflow runs and the savings add up fast.

Step 2: Create a Skill Definition

Skills give your workflow Socratic discovery -- instead of memorizing CLI flags, users describe what they need and the skill asks clarifying questions.

Create plugin/skills/docs/SKILL.md:

---
name: documentation
description: "Generate, explain, or audit documentation"
triggers:
  - docs
  - documentation
  - readme
  - changelog
  - api reference
  - explain
---

Below the frontmatter, define the interaction flow:

## Socratic Scoping

Before running, ask:

1. **Type**: "What kind of docs? API reference, README,
   changelog, or a general guide?"
2. **Scope**: "Which path should I document?"
3. **Audience**: "Who's reading -- developers, end users,
   or both?"

## Execution

Call the `document_generation` MCP tool with the scoped
parameters:

document_generation(path="<path>", doc_type="<type>")

## Output Format

Present a summary followed by the generated content:

**Generated:** <doc_type> | **Sections:** N | **Tokens:** X

## Follow-Up

After presenting results, offer:

- "Want me to export this to a file?"
- "Should I refine a specific section?"
- "Want a changelog based on recent commits?"

The triggers array is what connects natural language to your skill. When a user types "generate docs for src/models", the router matches on "docs" and activates this skill.

Pattern reference: plugin/skills/security-audit/SKILL.md

Step 3: Add a Command Shortcut

Commands bypass Socratic discovery for users who know exactly what they want. Create plugin/commands/attune-docs.md:

---
name: attune-docs
description: "Generate documentation for a path"
argument-hint: "<path>"
category: workflows
aliases: [adoc]
tags: [docs, documentation, generate]
version: "3.0.0"
---
# attune-docs

Quick-access command for documentation generation.
Bypasses Socratic discovery when you know the target.

## Execution

1. If a path argument is provided, use it. Otherwise
   ask: "Which path should I document?"
2. Call the `document_generation` MCP tool with the path.
3. Present results using the format from the docs skill.

## Examples

/attune-docs src/attune/models/
/attune-docs src/attune/workflows/
/attune-docs .

Now users have two paths to the same workflow:

  • /attune + "generate docs" -- guided discovery
  • /attune-docs src/ -- direct execution

Pattern reference: plugin/commands/attune-security.md

Step 4: Register the Entry Point

Add your workflow to pyproject.toml so the CLI can discover it:

[project.entry-points."attune.workflows"]
doc-gen = "attune.workflows.document_gen:DocumentGenerationWorkflow"

Verify it's registered:

attune workflow list

You should see doc-gen in the output. Run it:

attune workflow run doc-gen --path src/attune/models/

The CLI loads your class, instantiates it, calls execute(), and formats the result -- including cost savings vs a premium-only baseline.

Step 5: Build Your Own

You now know every layer of the system. To build your own workflow:

  1. Pick your stages. What are the distinct steps? A code reviewer might use scan, analyze, report.
  2. Assign tiers. Which stages need Claude Opus (PREMIUM) vs Claude Haiku speed (CHEAP)?
  3. Implement run_stage(). Each stage receives the previous stage's output.
  4. Add a SKILL.md. Define triggers and Socratic questions.
  5. Register in pyproject.toml. One line.

Browse existing workflows for inspiration:

attune workflow list

Ideas to try

  • Chain workflows: lint, then test, then docs, then commit
  • Use /batch for 50% savings on non-interactive runs
  • Add custom mixins for shared logic across workflows