Your First Workflow¶
In this tutorial you'll build a custom workflow that runs a linter before every commit. You'll learn how to:
- Write a custom step in Python
- Extend a built-in workflow using hooks
- Run your workflow from Titan
Prerequisites: Titan installed and configured in a project with the git plugin enabled. If not, see Installation.
The scenario¶
The built-in commit-ai workflow commits your changes with an AI-generated message. You want to run ruff (a Python linter) before the commit happens, and skip it gracefully if ruff isn't installed.
You'll inject your linter step into the before_commit hook that commit-ai already exposes.
Step 1 — Write the step function¶
Create the file .titan/steps/run_linter.py in your project:
import subprocess
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Skip, Error
def run_linter(ctx: WorkflowContext) -> WorkflowResult:
"""Run ruff linter. Skip gracefully if ruff is not installed."""
# Check ruff is available
check = subprocess.run(
["ruff", "--version"],
capture_output=True,
)
if check.returncode != 0:
return Skip("ruff not found — skipping lint")
# Run the linter
result = subprocess.run(
["ruff", "check", "."],
capture_output=True,
text=True,
)
if result.returncode != 0:
ctx.textual.text(result.stdout)
return Error("Linting failed — fix errors before committing")
return Success("Linting passed")
A few things to notice:
- The function name (
run_linter) must match thestep:field in the YAML exactly. - We return
Skipwhen ruff isn't available — this lets the workflow continue without failing. - We return
Errorwhen lint fails — by default this stops the workflow (no commit happens). - We return
Successwhen everything is fine.
Step 2 — Create the workflow file¶
Create .titan/workflows/commit-ai.yaml:
name: "Commit with AI + Linter"
description: "Runs ruff before committing with an AI-generated message"
extends: "plugin:git/commit-ai"
hooks:
before_commit:
- id: run-lint
name: "Run Linter"
plugin: project
step: run_linter
on_error: continue # remove this line if you want lint failures to block the commit
What this does:
extends: "plugin:git/commit-ai"— takes the base workflow from the git pluginhooks.before_commit— injects your step at thebefore_commitinjection pointplugin: project— tells Titan to look forrun_linterin.titan/steps/on_error: continue— even if linting fails, the commit step still runs (remove this to make lint failures block commits)
Because this file is named commit-ai.yaml and lives in .titan/workflows/, it overrides the built-in commit-ai workflow for this project. Titan's precedence rules mean project workflows always win.
Step 3 — Run it¶
Select Commit with AI + Linter from the menu. The workflow runs your lint step before generating the commit message.
You'll see each step in the TUI as it executes. If linting fails (and you kept on_error: continue), you'll see the lint errors but the commit step will still run. Remove that line to make lint failures stop the workflow.
What's next¶
You've seen the two core building blocks: a step (Python function) and a workflow (YAML that sequences steps). From here you can:
- Add more steps to
.titan/steps/and wire them into workflows - Create a completely new workflow (no
extends) for project-specific automation - Use
ctx.get()andmetadatato pass data between steps - Add AI calls using
ctx.aifor generated content
Read Workflow Concepts for the full reference on step types, result types, parameters, and the Skip vs Exit distinction.
If you want your custom steps to use Titan's TUI API directly, continue with Your First Textual Step.