| name | yaw |
| description | Manage scheduled workflows and cron jobs via the yaw (yet another workflow) CLI. Use when the user wants to list, create, inspect, run, enable, disable, or debug recurring scheduled tasks or multi-step workflows. Also use when the user asks about cron jobs, timed tasks, scheduled commands, periodic scripts, recurring agent tasks, or workflow orchestration. Triggers on "cron", "scheduled tasks", "cron jobs", "timed tasks", "periodic", "recurring", "yaw", "workflow", or any request to schedule, list, or manage recurring commands.
|
| metadata | {"author":"tizee","version":"0.4.0"} |
yaw -- yet another workflow
Declarative workflow engine. Schedules any command (scripts, agent CLIs, pipelines) via system crontab with support for multi-step DAG workflows.
Invocation
Always use yaw directly. It should be installed on PATH (via uv or pip).
yaw list
If yaw is not on PATH, run from the project directory:
uv run yaw list
Default output is human-readable tables. Add --json for machine-readable JSON output (on list, show, status).
Configuration
- Config dir:
~/.yaw/ (created by yaw init)
- Workflow definitions:
~/.yaw/jobs/<name>.yaml
- Execution logs:
~/.yaw/logs/<name>/
- Runtime state:
~/.yaw/state/<name>.json
- Global config:
~/.yaw/config.yaml
- Override config dir:
--dir <path>
Commands
init
Create ~/.yaw/ directory structure and default config.
yaw init
list
List all registered workflows with schedule, next run time (accounting for jitter), enabled status, jitter, and last run result.
yaw list
yaw list --json
status
Summary of all workflows: last run time, run/failure counts, current status.
yaw status
yaw status --json
show
Show full workflow definition, state, and recent log paths for a specific workflow.
yaw show <workflow-name>
yaw show <workflow-name> --json
run
Execute a workflow immediately (manual trigger, useful for testing).
yaw run <workflow-name>
logs
View the latest execution log for a workflow.
yaw logs <workflow-name>
yaw logs <workflow-name> --tail 20
add / remove
Register or remove a workflow. Automatically syncs crontab.
yaw add <file.yaml>
yaw add <file.yaml> --force # overwrite existing job with same name
yaw remove <workflow-name>
update
Update individual fields of an existing job in-place. Uses --set key=value syntax. Automatically syncs crontab.
Updatable fields: command, description, enabled, timeout, cwd, env_file, jitter.
yaw update <workflow-name> --set timeout=600
yaw update <workflow-name> --set enabled=false --set jitter=30
enable / disable
Toggle a workflow without removing its definition. Automatically syncs crontab.
yaw enable <workflow-name>
yaw disable <workflow-name>
validate
Validate a workflow definition file without registering it.
yaw validate <file.yaml>
install / uninstall
Sync all enabled workflows to system crontab, or remove all yaw entries from crontab.
yaw install
yaw uninstall
version
Show version info (version, commit, build time).
yaw version
Workflow Definition Format
Workflows are YAML files. A single WorkflowDefinition model handles all cases.
Simple scheduled job (command shorthand)
name: my-job
trigger:
- cron: "0 9 * * *"
command: echo "hello world"
Legacy schedule shorthand
The schedule field is accepted as shorthand for a single cron trigger:
name: my-job
schedule: "0 9 * * *"
command: echo "hello world"
This desugars to trigger: [{cron: "0 9 * * *"}] internally. schedule and trigger are mutually exclusive.
Multi-step DAG workflow
Use steps (a dict of name -> step) with optional needs and condition:
name: ci-pipeline
trigger:
- cron: "0 9 * * 1-5"
- manual: true
steps:
lint:
command: ruff check src/
test:
command: pytest
build:
needs: [lint, test]
command: python -m build
deploy:
needs: [build]
command: ./scripts/deploy.sh
condition: "${{ workflow.trigger == 'manual' }}"
Jitter (thundering herd mitigation)
The jitter field (int, default 0) adds a deterministic delay in seconds before command execution. The delay is computed via MD5 hash of the job name, producing a stable offset in [0, jitter]. Same job name always yields the same delay — predictable and debuggable.
Use jitter when multiple jobs share the same cron schedule to spread load:
name: check-api-1
trigger:
- cron: "0 * * * *"
command: ~/scripts/check-api.sh
jitter: 60
The NEXT RUN column in yaw list shows the effective fire time (cron time + jitter offset). The show command also displays the jitter value.
Validation rules
command and steps are mutually exclusive
trigger list must be non-empty
- Step names must match
^[a-zA-Z0-9][a-zA-Z0-9_-]*$
needs must reference existing step names
- Step dependency graph must be acyclic (DAG)
condition must use ${{ ... }} syntax (validated at parse time)
For full schema and examples, read references/examples.md.
Agent Workflows
Creating a new workflow
- Write a YAML workflow definition file to
/tmp/<name>.yaml
- Validate:
yaw validate /tmp/<name>.yaml
- Register:
yaw add /tmp/<name>.yaml
Diagnosing a failed workflow
- Check status:
yaw status
- Find the failing workflow and read logs:
yaw logs <name> --tail 50
- Inspect definition:
yaw show <name>
- Fix the underlying issue, then test:
yaw run <name>
Common failure: exit 127 means command not found — the command is not on cron's minimal PATH. Fix by using absolute paths in the workflow command (see "Scheduling a headless agent task" above).
Scheduling a headless agent task
Important: use absolute paths for commands. Cron runs with a minimal PATH (typically /usr/bin:/bin). Commands installed in user directories (e.g. ~/.local/bin/llms, ~/.cargo/bin/, Homebrew paths) will fail with exit 127 (command not found) unless referenced by absolute path.
Find the absolute path with which <command>, then use it in the workflow definition:
name: daily-digest
trigger:
- cron: "0 9 * * *"
command: >
/Users/<user>/.local/bin/llms agent -p "Check RSS feeds and send digest to telegram"
--tools "Bash,Read,WebFetch"
For complex agent invocations, use a script wrapper:
name: daily-digest
trigger:
- cron: "0 9 * * *"
command: ~/scripts/daily-digest.sh
Alternatively, set PATH in the job's env field to make multiple commands available:
name: daily-digest
trigger:
- cron: "0 9 * * *"
env:
PATH: "/Users/<user>/.local/bin:/usr/local/bin:/usr/bin:/bin"
command: llms agent -p "..." --tools "Bash,Read,WebFetch"
Self-healing with failure hooks
Workflows can run a notify command on failure:
on_failure:
notify: >
llms agent -p "Job '${YAW_JOB_NAME}' failed (exit ${YAW_EXIT_CODE}).
Diagnose from ${YAW_LOG_FILE}." --tools "Bash,Read,Glob,Grep"
Environment variables injected into the notify command: YAW_JOB_NAME, YAW_EXIT_CODE, YAW_LOG_FILE, YAW_ATTEMPT, YAW_DURATION.
Setup
If yaw is not on PATH, ask the user to install it. Requires Python 3.12+ and uv.
cd /path/to/yaw && uv pip install -e .