---
title: "Frazer CI: CI that knows what changed · Frazer"
description: "Frazer CI plans every push to Fir as a graph of tasks keyed by their inputs, defined in Fir Actions or by zero-config. A task whose inputs did not change is a cache hit that starts no machine; the rest run on Frazer Runners, a fresh microVM per task."
url: https://frazer.ai/ci
index: https://frazer.ai/llms.txt
tokens: 4929
---

> For agents: every page on frazer.ai is indexed at https://frazer.ai/llms.txt.

# Frazer CI: CI that knows what changed · Frazer

**Frazer CI**  on Frazer Runners

# CI that knows what changed.  
Most of it never runs.

Frazer CI plans every push to [Fir](https://frazer.ai/source) as a graph of tasks, each keyed by the hash of its inputs. A task whose inputs did not change is a cache hit: no machine starts, nothing is checked out, nothing is billed. The rest run on Frazer Runners, a fresh microVM per task that reads only what the task declared, and report results the host signs.

[Start building](https://frazer.ai/start?product=ci) [Move from GitHub Actions](#move)

Linux on amd64 and arm64  GPU tasks  your servers or the Frazer fleet  API, CLI and MCP

Definitions

Fir Actions in typed Starlark, in .frazer/ci, or none at all for common layouts

Unit of work

A task with declared inputs, outputs and a key

Results

Written and signed by the host, never by the job

Price

Compute that ran, per second; a cache hit is free

\# .frazer/ci/storefront.star
load("@frazer//go:v1", "go\_test", "go\_binary")
load("@frazer//oci:v1", "image")
GO = toolchain("go@1.27.1")  \# resolved to a content ID
go\_test(
    name = "payments-test",
    packages = \["./services/payments/..."\],
    inputs = \["services/payments/\*\*", "go.mod", "go.sum"\],
    toolchains = \[GO\], level = "sealed", shards = "auto",
)
bin = go\_binary(name = "payments", toolchains = \[GO\],
                main = "./services/payments/cmd/payments")
image(name = "payments-image", files = {"/payments": bin})
check("presubmit", tasks = affected(tests()),
      required\_on = \["main"\])
check("verify", tasks = affected(all()), post\_merge = True)
deliver("payments", artifacts = \["payments-image"\],
        environment = "acme-staging/platform", on = "verified")

$ frazer ci runs plan --workspace ws/s-81f2 --explain
148 tasks · 132 hits · 3 joined · 13 to run
ran because
  test web ×3           upstream output: build checkout-api
  test payments ×4      services/payments/webhook/retry\_test.go
  test checkout-api ×2  upstream output: build payments
joined  3 keys still running for version 1
hits    132 keys with a result: no machine
first   build checkout-api → build web → test web

→ ci\_status   { "pull\_request": 482 }
← presubmit passed at v2 · 13 ran · own server $0
→ ci\_explain  { "task": "test web 2/3" }
← ran: upstream output build checkout-api changed
→ ci\_failures { "pull\_request": 482 }
← 0 failures · 1 flaky: test payments 3/4
  same key failed, then passed · score updated

$ frazer ci runs list --pull-request 482
r-8c2f41  presubmit  v2  passed  3 min 30 s
$ frazer ci tests list --flaky --sort score
go://services/search#TestIndexWarmup  0.93
$ frazer ci tasks repro search-test#2
sandbox with the exact projection, image and toolchain
$ frazer ci workflow-imports create \\
    --repository acme/storefront --from github-actions
draft Fir Actions in a pull request; unmatched steps listed

00 · A run, explained Fig. 1 · product UI, demo data

*A run page in the Frazer console, product UI with demo data: the presubmit run r-8c2f41 for pull request #482 in acme/storefront, version 2, passed in 3 minutes 30 seconds with one flaky annotation. Of 148 tasks, 132 were cache hits that started no machine, 3 joined runs still going for version 1, and 13 ran. The graph shows every task and the critical path; a table names the input that made each task run; side panels show the critical path, where the time went, and which trust tier wrote each result.*

Fig. 1: a run page in the Frazer console, product UI with demo data.

A

**The whole graph, known up front** Every task and its key are in the plan before anything runs, and the critical path starts first.

B

**Hits start nothing** A task whose key already has a result is done: no machine, no checkout, no charge. Running keys are joined.

C

**Why a task ran** Each one names the input that changed its key, so nothing runs on a hunch.

D

**Who wrote each result** Results are signed by the host with its tier; verify re-runs them before anything deploys.

01 · No legacy

## No YAML. No marketplace.  
No checkout step.

Most CI today decides trust by an event’s name, runs other people’s code with your token, and lets any job write the shared cache. Each of those has been the way in for a supply-chain attack.

Frazer CI does not carry them. Nothing comes back through a compatibility layer; a converter reads your workflows once, at the boundary.

Typical CI today compared with Frazer CI

|  | Typical CI today | Frazer CI |
| --- | --- | --- |
| Trust | Decided by the event’s name and where the workflow file sits | Decided by who wrote the code, the paths’ merge policy and where the definition came from |
| Secrets | Readable by any workflow in the repository | Bound to one task, branch class and lane; nothing readable before merge |
| Shared steps | Third-party code by a mutable tag, running with the job’s token | Fir Actions loaded by content ID with a lock; images and toolchains by digest |
| Cache | Keys chosen by each workflow; any writer can push | Keys computed from inputs; only the host writes, and signs |
| Configuration | YAML with an expression language spliced into shells | Fir Actions in typed Starlark, evaluated before anything runs; no templating |
| First step | Check out the repository | Read only the declared inputs; a hit starts nothing |
| Graph size | One graph per file, capped by syntax | One plan per push, bounded by quota |
| Billing | Every job rounded up to a minute | Per second from command start, no minimum |
| History | Checks and runs expire with a retention setting | Verdicts and provenance last as long as the repository |

### Moving from GitHub Actions

1.  **Inventory** A lint lists every git call and version stamp in your workflows and build scripts, with its replacement.
    
2.  **Draft** A converter reads your workflows once and an agent drafts Fir Actions as an ordinary pull request you review.
    
3.  **Shadow** For 14 days and at least 500 commits, both systems run side by side while Fir follows your repository. Deploys run dry.
    
4.  **Switch** When every difference is explained, the repository moves to Fir with native checks in its merge policy.
    

02 · The task graph Fig. 2

## Keys, not triggers.  
Decided before a machine starts.

A task is a command in an image with declared inputs and outputs. Its key hashes everything that can change its result, including the Fir tree IDs of its inputs, so whether a task must run is a lookup, not a checkout.

On a typical agent pull request in a monorepo, most keys already have a result. Those tasks cost nothing, and the rest run on the critical path first.

**Fig. 2** What goes into a key, and the three answers a lookup gives from the plan

-   01
    
    ### Affected tasks only
    
    A task runs when a changed path is in its inputs or an upstream key changed. Everything else is a hit, recorded as one.
    
-   02
    
    ### One execution per key
    
    Two pull requests, two agents or two attempts that need the same key share one execution and its result.
    
-   03
    
    ### Failures first
    
    Tasks likely to fail and the critical path start first, and test results stream as they happen.
    
-   04
    
    ### Zero-config
    
    Go, Rust, JavaScript and Python workspaces, Bazel and Buck2, and Dockerfiles get tasks without a Fir Actions file.
    
-   05
    
    ### Typed, deterministic Fir Actions
    
    Starlark that reads no network, clock or environment, so the plan is data and errors arrive, with a file and line, before anything runs.
    
-   06
    
    ### Graphs that grow
    
    A task can emit more of the graph, still cached by key, with fan-out bounded by quota instead of syntax.
    

03 · Results you can share Fig. 3

## A cache only the host writes.  
Shared by key, trusted by tier.

Job code never holds a cache credential. The host outside the microVM computes the key from what it mounted, hashes the outputs and signs the result, so a job can only ever answer for the inputs it was given.

Agents share every sealed result in the project. What deploys reads only results from hosts that have run nothing but protected work.

**Fig. 3** Who writes a result, and who may read it from the plan

Hermeticity levels: network access, who reuses the result, and typical tasks

| Level | Network | Result reused by | Typical tasks |
| --- | --- | --- | --- |
| open | by pool policy | nobody: never cached | deploy hooks, tests against outside services |
| declared | dependency proxies only, each fetch logged | the same writer class, while fetches resolve the same | builds that resolve packages at run time |
| sealed | none | every requester in the project | compiles, unit tests, codegen, linters, image builds |
| reproducible | none | as sealed, after an independent rebuild matches | release artifacts |

04 · Verified Fig. 4

## main@verified moves on green.  
Culprits found from keys.

After a pull request merges, Frazer CI verifies main on protected hosts and moves main@verified only to commits that passed. Deploys, seeds and new agent work read it, so a red merge never ships.

When a test fails, the candidates are the commits where that test’s key changed, which catches toolchains, images and lockfiles too. A confirmed culprit is reverted automatically, and its pull request reopens with the evidence.

**Fig. 4** Finding a culprit by key, and telling a flake from a failure demo data

05 · Built for agents Fig. 5

## Results an agent can read.  
Not a log to scrape.

Every task result has the same typed shape in the API, the CLI’s JSON and the MCP tools, with no timestamps or random IDs in the parts an agent reads, so the bytes stay friendly to prompt caches.

Agents preflight a workspace before they open a pull request, and a superseded version’s tasks are canceled the moment a new version arrives.

{
  "task": "search-test#2", "pull\_request": 483, "version": 2,
  "status": "FAILED", "failure\_kind": "TEST",
  "first\_error": {
    "path": "services/search/index/segment\_test.go", "line": 41,
    "message": "want 1 load span, got 0" },
  "failing\_tests": \[{ "test\_id": "go://services/search/index#TestSegmentLoad",
                     "flaky\_score": 0.01, "pre\_existing": false }\],
  "excerpt": { "token\_count": 612, "log\_range": "120:180" },
  "repro": { "command": "frazer ci tasks repro search-test#2" },
  "cost": { "vcpu\_seconds": 212, "usd": 0.0027 }
}

→ ci\_failures { "pull\_request": 483, "version": 2 }
← { "failures": \[
    { "task": "search-test#2", "failure\_kind": "TEST",
      "test\_id": "go://services/search/index#TestSegmentLoad",
      "first\_error": "segment\_test.go:41: want 1 load span, got 0",
      "pre\_existing": false } \],
  "pre\_existing": \[
    { "cluster": "TestReserveStock: timed out",
      "since": "main@48196", "sent\_to": "the culprit’s author" } \] }

$ frazer ci tasks logs search-test#2 --failed --lines 120:180 --json
{ "task": "search-test#2", "log\_range": "120:180", "token\_count": 612,
  "lines": \[
    "--- FAIL: TestSegmentLoad (0.04s)",
    "    segment\_test.go:41: want 1 load span, got 0",
    "FAIL services/search/index" \] }
$ frazer ci tasks repro search-test#2
sandbox with the exact projection, image and toolchain

*Failure clusters for the repository acme/storefront: TestReserveStock timed out across 37 pull requests, pre-existing since main@48196 and sent to the culprit’s author rather than to 37 agents; vet unchecked error in 3 pull requests, new, sent to each author. Session s-9c04 has spent $1.12 of a $4.00 budget; the estimate is debited before dispatch, and at 100 % tasks are held, never canceled.*

**Fig. 5** A structured failure for an agent, failure clusters and a session budget product UI, demo data

-   ### Results for models
    
    A typed result per task: the failure kind, the first error, a bounded excerpt with its token count, and the command that reproduces it.
    
-   ### One failure, one fix
    
    Failures are clustered across pull requests, so 200 agents never all “fix” one broken test. Pre-existing failures go to the culprit’s author.
    
-   ### The loop closes itself
    
    A failed task wakes the agent’s session with the result; the next version re-runs only what it changed. Three rounds, then a person.
    
-   ### Budgets that hold
    
    Per session, agent, campaign and project. The estimate is debited before a task starts; at the cap, tasks wait.
    

06 · Delivery Fig. 6

## From a verified build  
to a verified deploy.

CI builds; Delivery deploys. Release tasks run from main@verified, push to Frazer Registry by digest with provenance, and hand off. Delivery writes the digest into your environment repository as a pull request, checks it like any other, and [GitOps](https://frazer.ai/gitops) syncs exactly what was verified.

Production waits for its gates: provenance that verifies, no freeze window, the approvals the environment asks for, and a canary with analysis.

**Fig. 6** Built, written, synced, rolled out, verified from the plan

07 · Frazer Runners Fig. 7

## A microVM per task.  
Never reused.

Every task that has to run gets its own microVM, started warm from a pool, on your servers or the Frazer fleet. A sealed task has no network device at all. Inputs arrive as a read-only projection from the node’s Fir store, so nothing is cloned.

Frazer Runners run only Frazer CI’s tasks. [GitHub runners](https://frazer.ai/runners/github) is the separate product for repositories that stay on GitHub: it runs their GitHub Actions jobs on hosts and pools of its own, at the same price. The two never share a host, a pool or a queue.

**Fig. 7** One node: task microVMs, the host daemon, Fir’s store and the cache from the plan

-   ### Linux · amd64
    
    Every task class, sealed included.
    
-   ### Linux · arm64
    
    Tasks on arm64 servers you bring.
    
-   ### GPU
    
    GPU tasks on your GPU servers or the fleet.
    

[GitHub runners, for GitHub Actions](https://frazer.ai/runners/github)

08 · Performance

## Fast where CI loses time.  
Every number is a target until measured.

These are the targets Frazer CI is built and tested against. Each run page already shows its own breakdown, from the push to the signed result.

A target becomes a claim only with the measurement, the method and the commit behind it, published next to the same jobs on GitHub runners.

Frazer CI performance targets: the metric, the target and how Frazer CI meets it

| Metric | Target | How |
| --- | --- | --- |
| Plan ready after a push | p50 ≤ 500 ms · p99 ≤ 2 starget | Tree IDs instead of file reads; plans cached as data |
| Task running, from a warm pool | p50 ≤ 1 s · p99 ≤ 3 starget | A booted microVM is claimed; no runner registration |
| Inputs readable inside the task | p50 ≤ 200 ms · p99 ≤ 1 starget | A projection is smaller than a checkout, prefetched when queued |
| First failing test reported | p50 ≤ 60 s · p90 ≤ 3 mintarget | Likely failures first; results stream |
| Verdict when every task is a hit | p50 ≤ 5 s · p99 ≤ 15 starget | No machine starts |
| Merge to main@verified | the affected critical path + 2 mintarget | Verify lanes with floors; one run per batch |
| CI runs per merged pull request, at 25× today’s load | ≤ 2.0target | Keys, and re-checks only where paths overlap |
| Wrong results in audit re-runs | 0 in 10,000 a monthtarget | One key implementation; results written by the host |

09 · Pricing

## Pay for compute that ran.  
A cache hit costs nothing.

One rate for Frazer Runners and GitHub runners, metered per second from the moment your command starts. Booting, restoring and waiting in the queue are ours to pay. There are no seats and no fee per pipeline or check. Frazer CI includes 200 minutes a month on a standard 4 vCPU runner, for every personal account and every organization.

Example arithmetic, not a measurementA pull request whose plan has 400 tasks, of which 30 run for 60 seconds on 4 vCPUs: 7,200 vCPU-seconds, or 2 vCPU-hours, is **$0.09** on the fleet. The 370 hits cost nothing.

[See every price](https://frazer.ai/pricing#lines)

Frazer fleet

**$0.045 per vCPU-hour** with 4 GiB per vCPU, per second from command start, no minimum

Free minutes

**200 a month** on a standard 4 vCPU runner, for every personal account and every organization; public repositories draw from the same minutes

Your servers

**$0 per minute**, part of $1.50 per physical core per month

A cache hit

**Free**: no machine ran, no minute used

Planning, results

Test intelligence, insights, MCP and the console: included, within fair use on your servers

Storage

Cache, logs and artifacts: free on your servers; $0.012 per GiB-month above the allowance on the fleet

GPU tasks

The fleet GPU price plus a 4 vCPU runner

Seats, pipelines

**None**, and no fee per check

Questions

## Frazer CI, answered.

[Fir, our version control](https://frazer.ai/source)

Do we have to write Fir Actions?

No. Zero-config reads the tree and proposes tasks for Go modules, Cargo, pnpm and npm workspaces, uv and Poetry projects, Bazel and Buck2 workspaces and Dockerfiles; you override only what you need. When you do write Fir Actions, in typed Starlark under `.frazer/ci`, `frazer ci runs plan` returns typed errors at the file and line, and the plan diff, before anything runs.

Can we keep GitHub Actions?

Yes, on [GitHub runners](https://frazer.ai/runners/github): repositories that stay on GitHub run their Actions jobs there, at the same price, on hosts, pools and queues of their own. Frazer CI comes with Fir and runs Fir Actions, which are native, not a GitHub Actions dialect. When a repository moves, a converter drafts Fir Actions from its workflows once, both run side by side during Fir’s rehearsal, and you switch when every difference is explained.

Are there free minutes?

Yes. Every personal account and every organization gets 200 Frazer CI minutes a month on a standard 4 vCPU runner on the Frazer fleet, and public repositories draw from the same minutes. Cache hits never use them. On your own servers tasks have no per-minute fee at all. GitHub runners have no free minutes.

Can another CI gate merges on Fir?

Yes. Fir asks for checks through an event any CI can consume and accepts check reports from any system with the permission. What only Frazer CI gives you is cache hits without a checkout, one execution per key, and culprits found from keys.

Is a cached result safe for a release?

Results carry the tier of the host that produced them. Agents and people may reuse any sealed result; verification, release, deploy and prebuild accept only results from protected hosts. Release artifacts are also rebuilt without any cache on a second runner class, and the digests must match.

What happens when a budget runs out?

Tasks are held, never canceled, and the session sees the estimated remaining cost. Cache hits keep flowing, because they cost nothing.

## More in Build and ship

-   [**Fir** Version control built for agents](https://frazer.ai/source)
-   [**GitHub runners** GitHub Actions in a microVM](https://frazer.ai/runners/github)
-   [**Cache** One cache for CI and boxes](https://frazer.ai/runners/github#cache)
-   [**Registry** Images and models, signed](https://frazer.ai/registry)
-   [**Environments and GitOps** Connect a repo, roll out](https://frazer.ai/gitops)
-   [**Templates and previews** A preview per pull request](https://frazer.ai/previews)

## Let unchanged work stay done.  
Pay only for what has to run.

[Start building](https://frazer.ai/start?product=ci) [Fir, our version control](https://frazer.ai/source)
