Frazer CIon Frazer Runners

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

Frazer CI plans every push to Fir 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.

Linux on amd64 and arm64GPU tasksyour servers or the Frazer fleetAPI, 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, explainedFig. 1 · product UI, demo data
Fig. 1: a run page in the Frazer console, product UI with demo data.
A
The whole graph, known up frontEvery task and its key are in the plan before anything runs, and the critical path starts first.
B
Hits start nothingA task whose key already has a result is done: no machine, no checkout, no charge. Running keys are joined.
C
Why a task ranEach one names the input that changed its key, so nothing runs on a hunch.
D
Who wrote each resultResults 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 todayFrazer CI
TrustDecided by the event’s name and where the workflow file sitsDecided by who wrote the code, the paths’ merge policy and where the definition came from
SecretsReadable by any workflow in the repositoryBound to one task, branch class and lane; nothing readable before merge
Shared stepsThird-party code by a mutable tag, running with the job’s tokenFir Actions loaded by content ID with a lock; images and toolchains by digest
CacheKeys chosen by each workflow; any writer can pushKeys computed from inputs; only the host writes, and signs
ConfigurationYAML with an expression language spliced into shellsFir Actions in typed Starlark, evaluated before anything runs; no templating
First stepCheck out the repositoryRead only the declared inputs; a hit starts nothing
Graph sizeOne graph per file, capped by syntaxOne plan per push, bounded by quota
BillingEvery job rounded up to a minutePer second from command start, no minimum
HistoryChecks and runs expire with a retention settingVerdicts and provenance last as long as the repository

Moving from GitHub Actions

  1. InventoryA lint lists every git call and version stamp in your workflows and build scripts, with its replacement.
  2. DraftA converter reads your workflows once and an agent drafts Fir Actions as an ordinary pull request you review.
  3. ShadowFor 14 days and at least 500 commits, both systems run side by side while Fir follows your repository. Deploys run dry.
  4. SwitchWhen every difference is explained, the repository moves to Fir with native checks in its merge policy.
02 · The task graphFig. 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. 2What goes into a key, and the three answers a lookup givesfrom 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 shareFig. 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. 3Who writes a result, and who may read itfrom the plan
Hermeticity levels: network access, who reuses the result, and typical tasks
LevelNetworkResult reused byTypical tasks
openby pool policynobody: never cacheddeploy hooks, tests against outside services
declareddependency proxies only, each fetch loggedthe same writer class, while fetches resolve the samebuilds that resolve packages at run time
sealednoneevery requester in the projectcompiles, unit tests, codegen, linters, image builds
reproduciblenoneas sealed, after an independent rebuild matchesrelease artifacts
04 · VerifiedFig. 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. 4Finding a culprit by key, and telling a flake from a failuredemo data
05 · Built for agentsFig. 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
Fig. 5A structured failure for an agent, failure clusters and a session budgetproduct 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 · DeliveryFig. 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 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. 6Built, written, synced, rolled out, verifiedfrom the plan
07 · Frazer RunnersFig. 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 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. 7One node: task microVMs, the host daemon, Fir’s store and the cachefrom 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
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
MetricTargetHow
Plan ready after a pushp50 ≤ 500 ms · p99 ≤ 2 stargetTree IDs instead of file reads; plans cached as data
Task running, from a warm poolp50 ≤ 1 s · p99 ≤ 3 stargetA booted microVM is claimed; no runner registration
Inputs readable inside the taskp50 ≤ 200 ms · p99 ≤ 1 stargetA projection is smaller than a checkout, prefetched when queued
First failing test reportedp50 ≤ 60 s · p90 ≤ 3 mintargetLikely failures first; results stream
Verdict when every task is a hitp50 ≤ 5 s · p99 ≤ 15 stargetNo machine starts
Merge to main@verifiedthe affected critical path + 2 mintargetVerify lanes with floors; one run per batch
CI runs per merged pull request, at 25× today’s load≤ 2.0targetKeys, and re-checks only where paths overlap
Wrong results in audit re-runs0 in 10,000 a monthtargetOne 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
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
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: 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

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