# Fail the build when your agent's tool calls drift.

> Record a skill once with any capture agent, commit the SKILL.md, and replay it in CI. A recorded step whose assertion no longer holds turns the check red — a regression gate for agent tool calls, at 0 tokens.

Markdown twin of https://www.reelier.com/for/github-actions — deterministic replay for GitHub Actions. Reelier records what an agent actually did, compiles it into a SKILL.md with an assertion per step, and replays it at 0 LLM tokens with a receipt every run.

## How capture works on GitHub Actions

This page is different from the others: GitHub Actions is a place to RUN a recorded skill, not a place Reelier captures from. `reelier scan` has no CI transcript directory to read — nothing here is recorded. You record the skill locally on one of the capture agents, commit the `.skill.md` file, and this is where you replay it as a gate.

Record first on the agent you actually work in — freeze a Claude Code session from `~/.claude/projects` (see /for/claude-code), or wrap an MCP server with `reelier mcp --wrap` on Cursor, Codex or Windsurf — then commit the compiled SKILL.md to the repo you want to guard.

In CI, the `seldonframe/reelier@v1` action runs `reelier run <skill>`: it re-executes the recorded MCP and tool-call steps, checks each recorded assertion, and exits non-zero when one fails. Drift surfaces as a failed assertion, which fails the job — regression testing for agent tool calls without a model in the loop.

## Wire it up

### .github/workflows/reelier.yml — replay a recorded skill as a gate

```yaml
name: Reelier replay
on:
  pull_request:
    paths: ["my-job.skill.md"]   # gate only when the skill changes
  schedule:
    - cron: "0 8 * * *"          # daily, UTC — a standing dated proof
jobs:
  replay:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: seldonframe/reelier@v1
        with:
          skill: my-job.skill.md
          max-level: "0"          # 0 LLM tokens — pure deterministic replay
          cloud-key: ${{ secrets.REELIER_CLOUD_KEY }}  # optional receipt push
```

The action is a composite step published at `seldonframe/reelier`, tag `@v1`. It installs `reelier@^0.12`, runs `reelier run my-job.skill.md --max-level 0`, writes the receipt as the job summary, and fails the check on the run's exit code. On `pull_request` a drifted step blocks the merge; the `schedule` trigger turns a public repo's green check into a live, dated proof that the replay still passes.

### action inputs — with: keys

```yaml
with:
  skill: my-job.skill.md          # required — path to one .skill.md (one per step)
  max-level: "0"                  # default "0": never calls a model, 0 tokens
  cloud-url: https://www.reelier.com   # default; receipt-ledger base URL
  cloud-key: ${{ secrets.REELIER_CLOUD_KEY }}  # optional — push receipt on pass
  vars: |                         # optional — forwarded as --var name=value
    projectId=497925
```

`skill` is the only required input. `max-level` "1" or "2" enables opt-in self-healing and then needs BYOK LLM credentials in the runner — the action does not set those for you. `cloud-key` only pushes the receipt after a passing run; unset means a local-only replay that still writes the job summary.

### action outputs — steps.<id>.outputs

```yaml
# outputs you can read from a later step:
passed:           # "true" or "false" — the run record's own passed field
run-record-path:  # path to .reelier/runs/<skill>.jsonl written by this run
```

Wire `passed` into a downstream step (comment on the PR, open an issue) — but you don't need to check it to gate: the action already fails the job from `reelier run`'s exit code, so a red check means the replay genuinely drifted.

## First receipt in 60 seconds

1. `reelier from-session <transcript.jsonl> --name my-job` — Record locally first — freeze a real run into `my-job.skill.md` on a capture agent (record on /for/claude-code, or wrap an MCP server on Cursor/Codex/Windsurf). CI never records; it only replays.
2. `git add my-job.skill.md && git commit` — Commit the SKILL.md into the repo you want to guard — it's a plain markdown file with a per-step assertion, versioned alongside your code.
3. Add the `.github/workflows/reelier.yml` above (the `seldonframe/reelier@v1` action) with `skill: my-job.skill.md` and `max-level: "0"`.
4. Open a PR. The action replays the skill; a recorded tool call whose assertion no longer holds fails `reelier run`, and the check turns red before the change can merge.

For a run-to-run comparison instead of a single-run gate, `reelier diff <skill>` reports SAME or DRIFTED between the last two runs and exits 1 on drift — see /learn/tool-call-drift.

## A real receipt

Verbatim output of a portfolio replay, run on 2026-07-21 with the CLI pinned to the version that recorded the seed (`npx -y reelier@0.12.1`, so it reproduces verbatim). The run record confirms passed: true and 0 LLM tokens. Replay it yourself:

```
npx -y reelier@0.12.1 run examples/portfolio/registry-latest.skill.md

=== registry-latest ===
✓ Step 1 — Latest dist-tag from the registry [passed] 152ms

PASSED: 1/1 steps ok, 0 failed, 152ms total
```

## FAQ

**Does the CI replay cost tokens?**

Not at `max-level: "0"`, the action's default — `reelier run` never constructs or calls a model at level 0, so the replay of MCP and tool-call steps is 0 LLM tokens. Levels 1 and 2 add opt-in self-healing and then require BYOK LLM credentials in the runner.

**What does the check actually gate on?**

On the recorded assertions. `reelier run` re-executes each recorded tool call and checks its assertion; if a step that had a clean recorded result now returns something different, that assertion fails, `reelier run` exits non-zero, and the action fails the job. Steps recorded without a clean result stay unchecked rather than being force-asserted — the gate fires on real drift, not on noise.

→ [Regression testing for AI agents](https://www.reelier.com/learn/agent-regression-testing)

**Is the replay read-only in CI?**

Yes — replays are read-only by default. Idempotent write steps only re-fire with `--allow-writes`, and destructive steps need `--yes`; the action passes neither, so a scheduled or PR replay reads and asserts without mutating anything. That's what makes it safe to run on every push.

**Which steps replay in CI, and which don't?**

Reelier deterministically replays MCP tool calls and its own `http.get`/`http.post` builtins. Bash, Read, Edit, Write, Grep and other non-deterministic steps are not replayed and stay unchecked — a session that was mostly file edits has little to gate here. Record the jobs that were sequences of API/MCP calls: data pulls, status checks, report generation, CRUD sequences.

→ [Agent drift and tool-call drift, defined](https://www.reelier.com/learn/tool-call-drift)

**Why would an agent's tool calls drift between commits?**

An upstream API changes a field, a model upgrade shifts how the agent shapes a call, a dependency bump alters a response. A recorded skill replayed in CI catches that the moment it lands — the same regression a model upgrade can introduce silently.

→ [When a model upgrade quietly breaks a working agent](https://www.reelier.com/blog/model-upgrade-regression)

## Links

- [Your OpenClaw cron burns 2,880 LLM calls a month. Here's the same job at zero.](https://www.reelier.com/blog/openclaw-cron-costs)
- [Scheduled agents re-learn the job every run. Replay the part that never changes.](https://www.reelier.com/blog/scheduled-agents-zero-token)
- [Agent cost calculator — what re-reasoning a recurring job costs, with your numbers.](https://www.reelier.com/tools/agent-cost-calculator)
- [seldonframe/reelier — the engine, AGPL-3.0. Fork it, audit it, self-host it.](https://github.com/seldonframe/reelier)

Other agents: [Claude Code](https://www.reelier.com/for/claude-code) · [Cursor](https://www.reelier.com/for/cursor) · [Codex](https://www.reelier.com/for/codex) · [Windsurf](https://www.reelier.com/for/windsurf) · [OpenClaw](https://www.reelier.com/for/openclaw) · [Hermes](https://www.reelier.com/for/hermes)
