<!-- Generated from the canonical HTML page. Do not edit separately. -->
# Building Pipeline Agents with the Ankra CLI | Ankra

Canonical: https://ankra.ai/blog/building-pipeline-agents-with-ankra-cli

A practical guide to wiring an infrastructure agent into your CI: review comments on pull requests, deploy verification on merge, and Slack reports that contain an actual root cause instead of a red X.

[Back to Blog](https://ankra.ai/blog)

[AI](https://ankra.ai/blog/tag/ai)  [CI/CD](https://ankra.ai/blog/tag/ci-cd)  [GitOps](https://ankra.ai/blog/tag/gitops)  [Kubernetes](https://ankra.ai/blog/tag/kubernetes)  [DevOps](https://ankra.ai/blog/tag/devops)

# Building Pipeline Agents with the Ankra CLI

[Mark Shine](https://ankra.ai/blog/author/mark-shine)

June 10, 2026

9 min read

### On this page

Most posts about AI agents stop at the architecture diagram. This one walks through three working agents, built from plain CI jobs and the Ankra CLI, that you can copy into a repo today.

What they do:

1. **PR review agent.** When a pull request touches your cluster definition, it validates the change against the live platform and comments on the PR with what will actually change on the cluster and what looks risky.
2. **Deploy watcher.** On merge it applies the change, waits for the rollout to finish, verifies health, and posts the result to Slack. When the deploy fails, it posts a root cause instead of a red X.
3. **Scheduled infra watcher.** Runs every 30 minutes, independent of CI. It stays silent while things are healthy and posts to Slack only when something degrades.

There is no framework here, no orchestration layer, and no vector database. Each agent is a trigger, a few deterministic CLI calls to gather facts, one LLM call for the judgment step, and a write to somewhere humans actually look: a PR thread or a Slack channel.

You also never wire up a model yourself. `ankra chat` is a one-shot, scriptable command, and the AI behind it already has server-side access to your cluster context: logs, events, manifests, stack history, operations, and metrics. You ask a question from CI and it answers against live state. The pipeline never holds a model API key or a kubeconfig.

---

## Prerequisites

You need an Ankra-managed cluster, imported or provisioned, with its definition versioned in the repo the pipelines run in. That is the standard GitOps layout: a `cluster.yaml` plus a `stacks/` directory.

Create an API token for the agent. Do this from a machine where you are logged in:

Terminal window

```
ankra loginankra tokens create ci-agent
```

Store the returned token as `ANKRA_API_TOKEN` in your CI secret store (GitHub Actions secrets, GitLab CI/CD variables). Also add `SLACK_WEBHOOK_URL`, a plain Slack incoming webhook for the channel you want reports in.

The CLI installs in one line on any runner:

Terminal window

```
bash <(curl -sL https://github.com/ankraio/ankra-cli/releases/latest/download/install.sh)
```

One gotcha before you script anything. The CLI resolves its token in this order: the `--token` flag, then a saved login from `ankra login`, then `ANKRA_API_TOKEN`. On a throwaway CI runner the environment variable works fine. On a self-hosted runner where someone once ran `ankra login`, the saved login silently wins over your CI token. If you run on shared machines, pass `--token "$ANKRA_API_TOKEN"` explicitly.

---

## Agent 1: PR review comments

The job triggers when a pull request touches the cluster definition. It does three things: server-side validation, an impact question against the live cluster, and a comment on the PR.

`.github/workflows/infra-review.yml`:

````
name: infra-reviewon:  pull_request:    paths:      - "cluster.yaml"      - "stacks/**"
permissions:  contents: read  pull-requests: write
jobs:  review:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4        with:          fetch-depth: 0
      - name: Install the Ankra CLI        run: bash <(curl -sL https://github.com/ankraio/ankra-cli/releases/latest/download/install.sh)
      - name: Validate against the platform        env:          ANKRA_API_TOKEN: ${{ secrets.ANKRA_API_TOKEN }}        run: |          ankra cluster validate -f cluster.yaml --strict-secrets 2>&1 | tee validation.txt
      - name: Ask what the change does to the live cluster        env:          ANKRA_API_TOKEN: ${{ secrets.ANKRA_API_TOKEN }}        run: |          DIFF=$(git diff "origin/${GITHUB_BASE_REF}"...HEAD -- cluster.yaml stacks/ | head -c 12000)          ankra chat --cluster production "A pull request changes our cluster definition.          Review the diff below against the current state of this cluster.          Answer in three short sections:          1. What changes on the cluster (which stacks, addons, manifests).          2. Which workloads will restart or be replaced.          3. Anything risky given live state right now (capacity, dependencies, ordering).          Be specific. If nothing is risky, say so in one line.
          ${DIFF}" > impact.md
      - name: Comment on the pull request        env:          GH_TOKEN: ${{ github.token }}        run: |          {            echo "## Infra review"            echo            echo "**Validation** (\`ankra cluster validate --strict-secrets\`)"            echo '```'            cat validation.txt            echo '```'            echo            echo "**Impact against live cluster state**"            echo            cat impact.md          } > comment.md          gh pr comment "${{ github.event.pull_request.number }}" --body-file comment.md
````

Why each piece is there:

- `ankra cluster validate -f` runs local structural and dependency checks first, then sends the file to the API for the checks you cannot do offline: whether the referenced charts exist in your connected Helm registries, whether there are plaintext Secrets or unencrypted addon values, and whether `parents` references resolve against the cluster’s deployed resources. `--strict-secrets` turns a plaintext secret from a warning into a failed job, which is exactly what you want on a PR.
- The `ankra chat` step is the only LLM call. The diff is in the prompt; the cluster state is on the server side. That combination is what makes the review better than a generic LLM looking at YAML. It knows the addon you are reparenting is mid-rollout, or that the node group you are scaling down is already at 85% memory.
- The comment is plain `gh pr comment`. No app installation, no webhook receiver. The default `GITHUB_TOKEN` with `pull-requests: write` is enough.

A real comment from this job looks like:

> **Infra review**
>
>
>
> **Validation** (`ankra cluster validate --strict-secrets`)
>
>
>
> ```
> cluster.yaml: OKstacks: payments (2 addons, 3 manifests), monitoring (unchanged)chart payments-api 1.4.2 found in registry harbor-internalno plaintext secrets detected
> ```
>
>
>
> **Impact against live cluster state**
>
>
>
> 1. Bumps the `payments-api` chart from 1.4.1 to 1.4.2 and raises its memory limit from 256Mi to 512Mi.
> 2. The `payments-api` Deployment (3 replicas, namespace `payments`) rolls. Nothing else restarts.
> 3. Worker group `default` has 1.1Gi allocatable headroom; three new 512Mi pods fit, but only just. If you also merge PR #214 (raises `checkout` limits), schedule them apart.

That last point, the cross-reference against live headroom, is the check a human reviewer skips at 5pm on a Friday.

### The same agent on GitLab

The structure is identical. The one piece of friction is that `CI_JOB_TOKEN` cannot post notes on merge requests. Create a project access token with the `api` scope and Reporter role, store it as `GITLAB_AGENT_TOKEN`, and post to the notes API:

````
infra-review:  stage: review  image: alpine:3.20  rules:    - if: $CI_PIPELINE_SOURCE == "merge_request_event"      changes:        - cluster.yaml        - stacks/**/*  script:    - apk add --no-cache bash curl git jq    - bash <(curl -sL https://github.com/ankraio/ankra-cli/releases/latest/download/install.sh)    - ankra cluster validate -f cluster.yaml --strict-secrets 2>&1 | tee validation.txt    - |      DIFF=$(git diff "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"...HEAD -- cluster.yaml stacks/ | head -c 12000)      ankra chat --cluster production "A merge request changes our cluster definition. Review the diff against live state. What changes, what restarts, what is risky right now? ${DIFF}" > impact.md    - |      BODY=$(printf '## Infra review\n\n```\n%s\n```\n\n%s\n' "$(cat validation.txt)" "$(cat impact.md)")      curl -sS --request POST \        --header "PRIVATE-TOKEN: ${GITLAB_AGENT_TOKEN}" \        --data-urlencode "body=${BODY}" \        "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
````

---

## Agent 2: merge, deploy, verify, report to Slack

On merge to `main`, this agent applies the cluster definition, blocks until the platform finishes, verifies health, and reports. The interesting part is the failure path: instead of “pipeline failed, go look”, the Slack message contains the failed operation and a root cause pulled from live state.

`.github/workflows/deploy-watch.yml`:

```
name: deploy-watchon:  push:    branches: [main]    paths:      - "cluster.yaml"      - "stacks/**"
permissions:  contents: read  pull-requests: write
jobs:  deploy:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4
      - name: Install the Ankra CLI        run: bash <(curl -sL https://github.com/ankraio/ankra-cli/releases/latest/download/install.sh)
      - name: Select the target cluster        env:          ANKRA_API_TOKEN: ${{ secrets.ANKRA_API_TOKEN }}        run: ankra cluster select production
      - name: Apply and wait        id: apply        env:          ANKRA_API_TOKEN: ${{ secrets.ANKRA_API_TOKEN }}        run: |          set +e          ankra cluster apply -f cluster.yaml --wait --timeout 15m 2>&1 | tee apply.txt          echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
      - name: Verify health after deploy        if: steps.apply.outputs.exit_code == '0'        env:          ANKRA_API_TOKEN: ${{ secrets.ANKRA_API_TOKEN }}        run: |          ankra chat health > report.md          ankra cluster metrics query \            'sum(rate(http_requests_total{code=~"5.."}[5m]))' \            -o json > error-rate.json
      - name: Investigate failure        if: steps.apply.outputs.exit_code != '0'        env:          ANKRA_API_TOKEN: ${{ secrets.ANKRA_API_TOKEN }}        run: |          ankra cluster operations list 2>&1 | head -n 20 > operations.txt          ankra chat --cluster production "The apply for commit ${GITHUB_SHA::12} failed or timed out.          Find the most recent failed operation on this cluster, look at its jobs,          and tell me: the exact resource that failed, the error, and whether a          retry is likely to succeed or the change itself is broken.          Apply output for reference: $(tail -c 4000 apply.txt)" > report.md
      - name: Report to Slack        if: always()        env:          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}        run: |          STATUS="DEPLOYED"          if [ "${{ steps.apply.outputs.exit_code }}" != "0" ]; then STATUS="FAILED"; fi          jq -n \            --arg status "$STATUS" \            --arg commit "${GITHUB_SHA::12}" \            --arg url "${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" \            --rawfile report report.md \            '{text: "[\($status)] production <\($url)|\($commit)>\n\($report)"}' \            | curl -sS -X POST -H 'Content-type: application/json' --data @- "$SLACK_WEBHOOK_URL"
      - name: Comment on the merged PR        if: always()        env:          GH_TOKEN: ${{ github.token }}        run: |          PR=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls" --jq '.[0].number' || true)          if [ -n "$PR" ]; then            gh pr comment "$PR" --body-file report.md          fi
```

Details worth knowing:

- `ankra cluster apply` is **asynchronous by default**. Without `--wait` the command exits 0 the moment the platform accepts the change, while the actual rollout continues in the background. For a deploy watcher that exit code is useless on its own. Always pass `--wait` with a `--timeout` that matches your slowest stack.
- `ankra cluster select production` is run once, by name, non-interactively. Bare `ankra cluster select` opens an interactive picker and will hang a CI job until the timeout kills it. The same applies to every command with an interactive mode: in CI, always pass names.
- The failure path does not parse error strings or grep logs. It collects the apply output and the recent operations as raw facts and hands the diagnosis to `ankra chat`, which can also see the operation’s jobs, pod events, and container logs on the server side. The one thing the prompt insists on is a verdict on “retry or broken change”, because that is the decision the on-call human has to make. Make the agent answer it.
- The last step closes the loop: the verdict lands back on the PR that caused it. Whoever merged gets the outcome in the same thread as the review, without watching the pipeline.

The Slack message on a bad day reads like this:

> **\[FAILED\] production** `a3f8c91b02d4`
>
>
>
> Operation `op-7c21` failed at job `helm-upgrade payments-api`. The new pod is in `CrashLoopBackOff`: the 1.4.2 image reads `PAYMENTS_DB_POOL_SIZE` at startup and the variable is not set in this environment. Rolled-back replicas from 1.4.1 are still serving; no user impact. A retry will fail the same way because the change itself is broken. Add the variable to the stack, or revert the image bump, before re-merging.

Compare that to what a failed pipeline normally gives you: a red X and twenty minutes of `kubectl describe`.

---

## Agent 3: a scheduled watcher that only speaks when something is wrong

CI-triggered agents miss everything that breaks between deploys: node pressure, a certificate that expired, an addon that drifted. This one runs on a schedule and posts to Slack only on a degraded verdict.

```
name: infra-watchon:  schedule:    - cron: "*/30 * * * *"
jobs:  watch:    runs-on: ubuntu-latest    steps:      - name: Install the Ankra CLI        run: bash <(curl -sL https://github.com/ankraio/ankra-cli/releases/latest/download/install.sh)
      - name: Check cluster health        env:          ANKRA_API_TOKEN: ${{ secrets.ANKRA_API_TOKEN }}        run: |          ankra cluster select production          ankra chat --cluster production "Check this cluster now: failed or stuck operations          in the last 30 minutes, pods not Running or Succeeded, nodes not Ready,          and anything alarming in recent events. The FIRST LINE of your answer          must be exactly OK or DEGRADED. Then the details." > verdict.md
      - name: Post to Slack only when degraded        env:          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}        run: |          if ! head -n 1 verdict.md | grep -qx "OK"; then            jq -n --rawfile v verdict.md '{text: "[infra-watch] production\n\($v)"}' \              | curl -sS -X POST -H 'Content-type: application/json' --data @- "$SLACK_WEBHOOK_URL"          fi
```

The verdict-first-line trick is the load-bearing part. You cannot branch a shell script on prose, so force the model to emit a machine-checkable first line and treat anything that is not exactly `OK` as degraded. If the model ignores the format, the worst case is a spurious Slack message rather than a missed incident. That is the right direction to fail in.

If this overlaps with alerting you already have, keep the alerts. The watcher’s value is the paragraph under the verdict: which operation, which pod, what the events say. Ankra’s built-in  [alerts and webhooks](https://docs.ankra.ai/essentials/webhooks)  cover the push-based version of this with AI analysis attached; the scheduled agent is for the checks you want to phrase yourself.

---

## The rules that keep this safe

**The agents are read-only by design.** Everything above validates, asks, and reports. The only mutation is `ankra cluster apply` on merge, which is the deploy your GitOps flow was going to run anyway, gated by the PR review. If you want an agent to propose fixes, use `ankra cluster draft -f`. It stages every stack in the file as reviewable drafts in Ankra instead of deploying, and a human ships them from the stack builder. Never give a pipeline agent a path to mutate the cluster outside Git.

**Treat the model output as untrusted text.** It goes into a PR comment or a Slack message and stops there. Never pipe `ankra chat` output into a shell, an `apply`, or anything that executes. The diff you feed in came from a PR author; the output inherits that trust level.

**Scope the credentials.** The CI token is created with `ankra tokens create` under a real account, so use a dedicated machine user with the minimum org role rather than your own login. The Slack webhook posts to one channel and can do nothing else. The GitHub side runs on the default `GITHUB_TOKEN` with one extra permission. There is no kubeconfig and no cloud credential anywhere in the pipeline.

**Forked PRs do not get secrets.** On GitHub, `pull_request` runs from forks without access to repository secrets, so the review agent simply does not run there. That is the behavior you want, since untrusted diffs should not reach your `ankra chat` context anyway. Run it on internal branches and have maintainers re-trigger for external contributions.

**Control the noise.** One comment per PR update, one Slack message per merge, silence from the watcher when healthy. The fastest way to kill an agent like this is to let it chat. If nobody would act on the message, do not send it.

---

## Where this goes next

Everything here flows one way, from pipeline to humans. The natural next step is letting humans answer back: replying in the Slack thread with “scale it down” or “show me the logs” and having an agent act on it. That takes a long-running process rather than a CI job. The  [OpenClaw integration](https://docs.ankra.ai/integrations/openclaw)  covers deploying a chat-native agent on your cluster with the Ankra CLI installed as a skill, using the same token model as above.

Start with the pipeline agents, though. They are three YAML files, one token, and an afternoon of work, and they put live-cluster context into the two places your team already looks: the PR thread and the Slack channel.

---

Get started:  [Create a free account on Ankra](https://platform.ankra.app/auth/signup) .

Join our community:  [**Slack**](https://join.slack.com/t/ankra-community/shared_invite/zt-3a5rem8f8-cUho4epX2MoLT83bFf~VSA)

Follow us on:  [LinkedIn](https://www.linkedin.com/company/ankra)  |  [GitHub](https://github.com/ankraio)

Contact us:  [hello@ankra.ai](mailto:hello@ankra.ai)

Share  [X](https://x.com/intent/post?url=https%3A%2F%2Fankra.ai%2Fblog%2Fbuilding-pipeline-agents-with-ankra-cli&text=Building%20Pipeline%20Agents%20with%20the%20Ankra%20CLI)  [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fankra.ai%2Fblog%2Fbuilding-pipeline-agents-with-ankra-cli)  [HN](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fankra.ai%2Fblog%2Fbuilding-pipeline-agents-with-ankra-cli&t=Building%20Pipeline%20Agents%20with%20the%20Ankra%20CLI)

### Get the next post in your inbox

Written by

 [Mark Shine](https://ankra.ai/blog/author/mark-shine)

Mark writes about Kubernetes operations, CI/CD, AI-driven platform engineering, and the practical economics of running infrastructure - grounded in the day-to-day work of building Ankra.

[All posts](https://ankra.ai/blog/author/mark-shine)  [LinkedIn](https://www.linkedin.com/in/markshine/)

## Related Posts

[Edge Computing](https://ankra.ai/blog/tag/edge-computing)  [AI](https://ankra.ai/blog/tag/ai)  [CI/CD](https://ankra.ai/blog/tag/ci-cd)

[## Kubernetes on the Edge: One Platform to Rule Them All](https://ankra.ai/blog/kubernetes-on-the-edge)

Edge Kubernetes is exploding, but our operational tooling has not kept up. Here is how Ankra delivers a true single pane of glass combining AI-powered troubleshooting, native GitOps, and stack cloning to turn weeks of cluster setup into minutes.

[Mark Shine](https://ankra.ai/blog/author/mark-shine)

March 5, 2026

7 min read

[AI](https://ankra.ai/blog/tag/ai)  [Kubernetes](https://ankra.ai/blog/tag/kubernetes)  [Platform Engineering](https://ankra.ai/blog/tag/platform-engineering)

[## Build a Fleet of AI Agents on UpCloud Kubernetes](https://ankra.ai/blog/ai-agent-fleet-on-upcloud-kubernetes)

From an empty UpCloud account to a Kubernetes cluster running a team of AI agents, each with its own personality, chat channel and keys, all building through GitOps.

[Mark Shine](https://ankra.ai/blog/author/mark-shine)

August 18, 2026

14 min read

[Back to Blog](https://ankra.ai/blog)
