{"slug": "stepyard-local-yaml-pipelines-with-a-python-escape-hatch-no-server-needed", "title": "Stepyard – local YAML pipelines with a Python escape hatch, no server needed", "summary": "Stepyard, a local-first automation runner for developers, has been released. It enables Git-versioned YAML workflows with Python plugins and private LLM automations without requiring a server. The tool runs flows in isolated OS subprocesses and includes a built-in scheduler for cron, interval, or startup triggers.", "body_md": "**Stepyard is a local-first automation runner for developers who want Git-versioned workflows, Python plugins, and private LLM automations - without running a workflow server.**\n\n```\npip install stepyard\nstepyard init my-automations && cd my-automations\nstepyard run hello\n```\n\nBuild a container, smoke-test it, summarise the result with an LLM, and post to Slack. One YAML file, no glue scripts.\n\n```\nname: deploy\ndescription: Build the image, smoke-test it, and post an AI summary to Slack.\n\nsteps:\n  - id: build\n    uses: shell.run\n    with:\n      command: docker build -t myapp:${{ env.GIT_SHA }} .\n\n  - id: smoke_test\n    uses: http.request\n    with:\n      method: GET\n      url: https://staging.myapp.com/healthz\n\n  - id: summary\n    uses: llm.generate            # built-in - reads OPENAI_API_KEY from the env\n    with:\n      model: gpt-4o-mini\n      prompt: |\n        Write a one-line Slack message about this deploy.\n        Build exit code: ${{ steps.build.output.code }}\n        Health check HTTP status: ${{ steps.smoke_test.output.status }}\n\n  - id: notify\n    uses: http.request\n    with:\n      method: POST\n      url: ${{ env.SLACK_WEBHOOK }}\n      json_body:\n        text: ${{ steps.summary.output.output }}\n```\n\nRun it:\n\n```\nGIT_SHA=$(git rev-parse --short HEAD) stepyard run deploy\n✓  build         12.4s\n✓  smoke_test     0.3s\n✓  summary        0.9s\n✓  notify         0.2s\n\nFlow completed in 13.8s\n```\n\nReading step outputs.Each node has a documented output shape, referenced as`${{ steps.<id>.output... }}`\n\n:\n\nNode `output`\n\nshape`shell.run`\n\n`{ stdout, stderr, code }`\n\n`http.request`\n\n`{ status, headers, body }`\n\n`llm.generate`\n\n`{ output, usage, model, provider }`\n\n(use`${{ steps.<id>.output.output }}`\n\nfor text)Full reference:\n\n[.]`docs/nodes/builtin.md`\n\n**Flows are YAML files in your repo.** Steps, conditions, loops, and retries are plain keys. Version-control them alongside your code; no proprietary DSL to learn.**Extend with plain Python.** One`@node`\n\ndecorator turns any function into a reusable step. Inputs are type-validated automatically; plugin dependencies run in an isolated virtualenv, so they never clash with Stepyard's own.**Nothing leaves your machine.** State is stored in a local SQLite database. Data only goes out if a step in your flow explicitly sends it.**Every run is its own process.** Each flow executes in a dedicated OS subprocess, so a crash, timeout, or`sys.exit`\n\nin one run cannot take down the scheduler or a sibling run. See[Execution model](#execution-model).**Built-in scheduler, no hosted service.** Add a`trigger:`\n\nblock with`cron`\n\n,`interval`\n\n, or`startup`\n\n, run`stepyard service start`\n\n, and flows execute on schedule without a control plane.\n\n```\n# from PyPI\npip install stepyard\n\n# or, for development, with uv\ngit clone https://github.com/rorlikowski/stepyard && cd stepyard\nuv pip install -e \".[dev,docs]\"\nuv run stepyard doctor      # verify the install\n```\n\nRequires Python 3.10+. Works on macOS, Linux, and Windows (WSL).\n\n```\nstepyard init my-automations   # scaffold flows/ + .gitignore + .stepyard/\ncd my-automations\n\nstepyard run hello             # run a flow now (in its own subprocess)\nstepyard status               # see every flow and its latest run\nstepyard show <run-id>        # drill into the steps of one run\nstepyard logs <run-id>        # stream the captured logs\nstepyard validate --all       # type-check your YAML without running it\n```\n\nSchedule it instead of running by hand - add a trigger and start the daemon:\n\n```\nname: nightly_backup\ntrigger:\n  uses: cron\n  with:\n    schedule: \"0 3 * * *\"      # every day at 03:00\n\nsteps:\n  - id: dump\n    uses: shell.run\n    with:\n      command: pg_dump ${{ env.DATABASE_URL }} | gzip > /tmp/backup.sql.gz\nstepyard service start          # run the scheduler in the background\nstepyard service status\n```\n\nStepyard is deliberately process-isolated:\n\n(`stepyard run <flow>`\n\nspawns a fresh subprocess`python -m stepyard.engine.runner`\n\n) for that single run. Its stdout/stderr are captured to`.stepyard/logs/`\n\n.**The scheduler daemon**(`stepyard service start`\n\n) runs separately. It evaluates triggers, enqueues runs in SQLite, and a worker spawns one subprocess per run (bounded by`STEPYARD_MAX_CONCURRENT_FLOWS`\n\n, default`4`\n\n).**Inside a run**, steps execute sequentially. Built-in nodes run in-process; plugin nodes installed into an** isolated virtualenv**run in a*second*short-lived subprocess that talks JSON over stdin/stdout - so a plugin's dependencies can never clash with Stepyard's own.\n\nThe practical upshot: one misbehaving flow or plugin cannot corrupt the scheduler or sibling runs.\n\n``` python\n# my_plugin/nodes.py\nfrom stepyard.sdk import node, NodeResult, NodeStatus\n\n@node(name=\"math.add\")\ndef add(a: int, b: int) -> int:\n    return a + b\n\n@node(name=\"files.archive\")\ndef archive(path: str) -> NodeResult:\n    if not path:\n        return NodeResult(status=NodeStatus.FAILED, error=\"path is required\")\n    # ... do the work ...\n    return NodeResult(status=NodeStatus.SUCCESS, output={\"archived\": path})\n```\n\nRegister it via an entry point and it becomes available as `uses: math.add`\n\nin\nevery flow. See [ docs/plugins/creating.md](/rorlikowski/stepyard/blob/main/docs/plugins/creating.md).\n\n| Section | What's inside |\n|---|---|\n|\n\n[Core Concepts](/rorlikowski/stepyard/blob/main/docs/concepts/index.md)[How-to Guides](/rorlikowski/stepyard/blob/main/docs/how-to/index.md)[Built-in Nodes](/rorlikowski/stepyard/blob/main/docs/nodes/builtin.md)[Plugin Development](/rorlikowski/stepyard/blob/main/docs/plugins/creating.md)[CLI Reference](/rorlikowski/stepyard/blob/main/docs/cli/reference.md)The full documentation is published at ** rorlikowski.github.io/stepyard**.\n\nBrowse the docs locally with `uv run mkdocs serve`\n\n.\n\nMIT - see [LICENSE](/rorlikowski/stepyard/blob/main/LICENSE).", "url": "https://wpnews.pro/news/stepyard-local-yaml-pipelines-with-a-python-escape-hatch-no-server-needed", "canonical_source": "https://github.com/rorlikowski/stepyard", "published_at": "2026-06-16 10:49:31+00:00", "updated_at": "2026-06-16 11:19:26.096241+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "large-language-models"], "entities": ["Stepyard", "OpenAI", "Python", "YAML", "SQLite", "Docker", "Slack", "Git"], "alternates": {"html": "https://wpnews.pro/news/stepyard-local-yaml-pipelines-with-a-python-escape-hatch-no-server-needed", "markdown": "https://wpnews.pro/news/stepyard-local-yaml-pipelines-with-a-python-escape-hatch-no-server-needed.md", "text": "https://wpnews.pro/news/stepyard-local-yaml-pipelines-with-a-python-escape-hatch-no-server-needed.txt", "jsonld": "https://wpnews.pro/news/stepyard-local-yaml-pipelines-with-a-python-escape-hatch-no-server-needed.jsonld"}}