GitHub - shiehn/DeclarAgent: Declarative runbook executor for AI agents. Validate, dry-run, and safely run real CLI workflows.

8 min read Original article ↗

DeclarAgent

Declarative runbook executor for AI agents. Validate, dry-run, and safely execute multi-step YAML workflows from any LLM tool-use loop.


Why DeclarAgent?

LLM agents are great at reasoning but dangerous when executing. DeclarAgent gives agents a structured, auditable, and safe way to run real workflows:

  • YAML plans — human-readable, version-controllable runbooks
  • Three step types — shell commands (run), built-in actions (action), and HTTP requests (http)
  • Dry-run & explain — inspect exactly what will happen before it does
  • Destructive-step gating — steps marked destructive: true require explicit --approve
  • Structured JSON results — every run returns machine-readable output with typed errors
  • Built-in actions — file I/O, JSON manipulation, and env access without shell gymnastics
  • Template engine — reference outputs from prior steps with ${{steps.<id>.outputs.<key>}}
  • MCP server — expose plans as directly callable tools over the Model Context Protocol
  • Plan-as-tool — drop YAML files in a directory, each becomes an MCP tool automatically

Installation

Requires Go. This project is currently only tested on macOS.

go install github.com/shiehn/declaragent@latest

# Verify
declaragent --help

Platform note: Linux should work out of the box (the codebase uses sh -c with no OS-specific code). Windows requires WSL or a similar POSIX-compatible shell.

Quick Start

# Validate a plan
declaragent validate plan.yaml

# See what it would do
declaragent explain plan.yaml --input branch=main

# Dry-run (resolves templates, shows commands)
declaragent dry-run plan.yaml --input branch=main

# Execute for real
declaragent run plan.yaml --input branch=main

# Execute with destructive steps allowed
declaragent run plan.yaml --input branch=main --approve

# Start MCP server with a plans directory
declaragent mcp --plans ./plans

Plan Schema

Plan Schema

Plans are YAML files with a simple structure. Each step does exactly one thing: run a shell command, call a built-in action, or send an HTTP request.

name: deploy-service
description: Build, test, and deploy the service
inputs:
  env:
    required: true
    description: Target environment
  tag:
    default: latest
steps:
  - id: test
    run: go test ./...
    outputs:
      result: stdout

  - id: build
    run: docker build -t myapp:${{inputs.tag}} .

  - id: check_health
    http:
      url: "https://${{inputs.env}}.example.com/health"
      method: GET
    outputs:
      status: stdout

  - id: deploy
    run: kubectl apply -f k8s/${{inputs.env}}.yaml
    destructive: true

Step Types

Type Field Description
Shell run Runs a shell command via sh -c. Captures stdout/stderr.
Action action Calls a built-in action (file I/O, JSON, env).
HTTP http Sends an HTTP request. Response body captured as stdout.

Each step must have exactly one of run, action, or http.

HTTP Step Fields

- id: call_api
  http:
    url: "https://api.example.com/data"    # required
    method: POST                            # default: GET
    headers:
      Authorization: "Bearer ${{inputs.token}}"
    body: '{"key": "value"}'               # template-resolved string
  outputs:
    response: stdout                        # response body

Key Fields

Field Description
name Plan identifier
inputs Named parameters with required, description, and default
steps[].id Unique step identifier
steps[].run Shell command to execute
steps[].action Built-in action (alternative to run)
steps[].http HTTP request (alternative to run and action)
steps[].with Parameters passed to built-in actions
steps[].outputs Capture step output (e.g., stdout)
steps[].destructive If true, blocked unless --approve is passed

CLI Commands

Command Description
validate <plan.yaml> Check plan structure and references
explain <plan.yaml> Show resolved steps without executing
dry-run <plan.yaml> Simulate execution, resolve templates
run <plan.yaml> Execute the plan
mcp [--plans DIR] Start MCP stdio server
skill [--plans DIR] Generate a Claude Code Skill (SKILL.md)

All commands accept --json for machine-readable output and --input key=value for plan inputs.

Built-in Actions

Action Params Description
file.write path, content Write content to a file
file.append path, content Append content to a file
json.get file, path Read a value from a JSON file
json.set file, path, value Set a value in a JSON file
env.get name Read an environment variable

Structured Results

Structured Results

Every execution returns a JSON result:

{
  "run_id": "a1b2c3",
  "success": true,
  "steps": [
    {"id": "test", "status": "success", "exit_code": 0, "duration": "1.2s"}
  ],
  "outputs": {"test.result": "ok"},
  "artifacts": [".declaragent/a1b2c3/test/stdout"],
  "errors": []
}

Errors are typed for agent decision-making:

Error Type Retryable Meaning
VALIDATION_ERROR No Bad plan or missing inputs
STEP_FAILED No A command returned non-zero
PERMISSION_DENIED No Destructive step without --approve
SIDE_EFFECT_BLOCKED No Destructive step blocked in dry-run
TRANSIENT Yes Temporary failure, safe to retry
TIMEOUT Yes Step exceeded time limit
TOOL_NOT_FOUND No Unknown action referenced in step
PRECONDITION_FAILED No Step precondition not met
CANCELLED No Execution was cancelled

MCP Integration

MCP Integration

Run declaragent mcp to start a Model Context Protocol stdio server.

Plan-as-Tool

When you pass --plans <directory>, every YAML plan in that directory becomes a directly callable MCP tool:

declaragent mcp --plans ./plans
  • Tool name = plan name
  • Tool description = plan description
  • Tool inputSchema = derived from plan inputs
  • Calling the tool = executing the plan with the provided inputs

This means an LLM agent can discover and invoke your plans without knowing anything about DeclarAgent's internal plan format.

Integrations

Claude Code

Add to .claude/settings.json:

{
  "mcpServers": {
    "declaragent": {
      "command": "/path/to/declaragent",
      "args": ["mcp", "--plans", "/path/to/your/plans"]
    }
  }
}

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "declaragent": {
      "command": "/path/to/declaragent",
      "args": ["mcp", "--plans", "/path/to/your/plans"]
    }
  }
}

Cursor

Add to .cursor/mcp.json in your project root:

{
  "mcpServers": {
    "declaragent": {
      "command": "/path/to/declaragent",
      "args": ["mcp", "--plans", "/path/to/your/plans"]
    }
  }
}

Windsurf

Add to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "declaragent": {
      "command": "/path/to/declaragent",
      "args": ["mcp", "--plans", "/path/to/your/plans"]
    }
  }
}

GitHub Copilot (VS Code)

Add to .vscode/mcp.json in your project root. Note: Copilot uses "servers" instead of "mcpServers":

{
  "servers": {
    "declaragent": {
      "command": "/path/to/declaragent",
      "args": ["mcp", "--plans", "/path/to/your/plans"]
    }
  }
}

Amazon Q Developer

Add to ~/.aws/amazonq/mcp.json:

{
  "mcpServers": {
    "declaragent": {
      "command": "/path/to/declaragent",
      "args": ["mcp", "--plans", "/path/to/your/plans"]
    }
  }
}

ChatGPT

ChatGPT requires a remotely accessible SSE endpoint. See SSE Transport below, then add the public URL in ChatGPT's MCP settings.

SSE Transport

For remote hosting or HTTP-based clients (e.g., ChatGPT), start the MCP server with SSE transport:

declaragent mcp --transport sse --port 19100 --plans ./plans

The server will be available at http://localhost:19100/sse. Expose this URL publicly (e.g., via a reverse proxy or tunnel) for clients that require a remote endpoint.

Built-in MCP Tools

These meta-tools are always available regardless of --plans:

Tool Description
plan.validate Validate a plan YAML file
plan.explain Explain a plan without executing
plan.dry_run Dry-run a plan
plan.run Execute a plan
plan.schema Return the plan YAML schema

Claude Code Skills

As an alternative to MCP, you can generate a Claude Code Skill that teaches Claude Code how to use the DeclarAgent CLI directly. Skills are simpler and more token-efficient than MCP since they work through the CLI rather than a server.

# Generate a skill with a plan catalog
declaragent skill --plans ./plans

# Custom output directory and binary name
declaragent skill --plans ./plans --output .claude/skills/declaragent --binary declaragent

This creates .claude/skills/declaragent/SKILL.md containing CLI usage instructions and a catalog of your available plans. Claude Code automatically discovers skills in .claude/skills/, so once generated, any user (or agent) can invoke it via /declaragent.

Flag Default Description
--plans (none) Directory containing plan YAML files to catalog
--output .claude/skills/declaragent Output directory for SKILL.md
--binary declaragent Binary name used in generated instructions

Docs: 3 Example Plans You Can Copy and Run

Copy these into a plans/ directory and start the MCP server:

mkdir -p plans
# (paste each YAML below into the corresponding file)
declaragent mcp --plans ./plans

Or run any plan directly:

declaragent run plans/hello.yaml --input name=Alice

Plan 1: plans/hello.yaml — Simple Shell Command

The simplest possible plan. Runs echo and captures the output.

name: hello
description: Say hello to someone
inputs:
  name:
    description: Who to greet
    default: World
steps:
  - id: greet
    run: echo "Hello, ${{inputs.name}}!"
    outputs:
      message: stdout

Run it:

declaragent run plans/hello.yaml --input name=Alice

Result:

Plan "hello" completed successfully.
Run ID: <uuid>

With --json:

{
  "run_id": "...",
  "success": true,
  "steps": [{"id": "greet", "status": "success", "stdout_ref": "Hello, Alice!\n"}]
}

Plan 2: plans/ip_lookup.yaml — HTTP Request

Fetches your public IP address from a free API.

name: ip-lookup
description: Look up your public IP address
steps:
  - id: fetch_ip
    http:
      url: "https://httpbin.org/ip"
      method: GET
    outputs:
      response: stdout

Run it:

declaragent run plans/ip_lookup.yaml --json

Result:

{
  "success": true,
  "steps": [{
    "id": "fetch_ip",
    "status": "success",
    "stdout_ref": "{\"origin\": \"203.0.113.42\"}"
  }]
}

Plan 3: plans/build_and_notify.yaml — Multi-Step with Chaining

Runs a build, then POSTs the result to a webhook. Demonstrates step chaining — the HTTP step uses the shell step's output via ${{steps.build.outputs.result}}.

name: build-and-notify
description: Run a build command and POST the result to a webhook
inputs:
  webhook_url:
    required: true
    description: URL to POST the build result to
steps:
  - id: build
    run: echo "build-ok"
    outputs:
      result: stdout

  - id: notify
    http:
      url: "${{inputs.webhook_url}}"
      method: POST
      headers:
        Content-Type: application/json
      body: '{"event": "build_complete", "output": "${{steps.build.outputs.result}}"}'
    outputs:
      response: stdout

Run it:

declaragent run plans/build_and_notify.yaml \
  --input webhook_url=https://httpbin.org/post --json

This runs echo "build-ok", captures the output, then POSTs it to the webhook URL.


Testing

CI / Releases

All pushes to main and pull requests run tests automatically via GitHub Actions.

To create a new release, tag a commit and push the tag:

git tag v0.2.0
git push origin v0.2.0

This triggers the release workflow which:

  1. Runs the full test suite
  2. Builds binaries for macOS (arm64, amd64) and Linux (arm64, amd64)
  3. Creates a GitHub Release with the binaries attached and auto-generated release notes

License

See LICENSE.