How it works

What happens between ciresk ci check and the exit code: the manifest, the tree, the cell, the worker and the summary.

The path of one check

  1. The manifest names the work. .ciresk/ci.yaml declares tasks (check, test, build, lint): a command, its inputs, its outputs, its toolchains and whether its result may be cached. The runner executes what the manifest declares and knows no language. See Manifest.
  2. The working tree travels as digests. The CLI hashes the tree, asks the cell which blobs it lacks, uploads only those, and submits the job with the tree's root digest. Build output never travels. See Caching and tree sync.
  3. The cell derives the trust tier and deduplicates. It derives the trust tier from who submitted and which ref, and deduplicates against identical jobs already queued or running. With no targets it computes the affected set from your diff; a task with a targets_arg runs on those targets, any other task runs its run line unchanged. See Targets.
  4. A worker leases the task. A worker your organization owns (or a Ciresk-run pool instance) takes the task, provisions the pinned toolchains, materializes the tree into its workspace, runs the command as an unprivileged user with CPU and memory caps behind its egress proxy, streams the log and reports the result. See Workers and trust.
  5. You get a verdict. The exit code is the result, the summary is the evidence (verdict, failing tests, the tail of the first failed task, cache counts, cost), and the last line is the exact next command.

The contract

Every surface, the CLI, the MCP endpoint and the API, is built from the agent's side:

NeedWhat you get
A check you can runciresk ci check; exit 0 means pass, 1 means fail, everything else is a reason. See Exit codes.
Evidence you can showThe job id and the summary: verdict, up to 20 failing tests with file:line and the first 5 message lines, the last 50 lines of the first failed task, cache hits, cost.
A bounded resultHuman output under 120 lines; summary under 2,000 tokens (hard cap 8,000); logs failed-only, 200 lines, 64 KiB by default, 256 KiB at most per call; every truncation stated in band.
The next stepThe next line (last line of human output, next field in --json) is the exact command for the obvious drill-down.
No double spendingIdentical requests from two agents are one job (deduplicated: true); a retry after a timeout returns the same job.
A parseable form--json on every command: {schema_version, command, request_id, data, budget, next, truncated}, one document on stdout, progress on stderr.
An instant startWith ciresk ci watch running for the tree, ciresk ci check uploads nothing and is one request to queued.

Reading a summary

Terminal
ciresk ci check --json | jq '.data | {verdict, first_failed_task, failing_tests_total, failing_tests: .failing_tests[:3], next: .next}'
FieldContent
verdictsucceeded, failed, cancelled, timed_out, infra_error, running
failing_testsUp to 20 of {name, file, line, status, message, task, shard}; failing_tests_total is the full count
first_failed_task, tail, tail_taskThe first task that failed and its last 50 lines
cache{hits, misses, writes}
tasksThe task table with state, exit code, duration, cache outcome, cache_note (why a task was neither answered from the cache nor recorded in it) and worker
nextThe exact follow-up command

Rules of thumb:

SituationDo
A test failedciresk ci tests <job> before any log; it lists failing tests first with file:line.
The tail is not enoughciresk ci logs <job> --task <task> --grep '<first failing assertion>' -C 5.
More than 20 failing testsFix the first few, re-run; the next summary is smaller.
A task you expected cached ranciresk ci explain-miss <job>: the reason (--no-cache, a run that changed a tracked file, a first run, a failure) or the key items and input files that differ from the last recorded run.
The summary says deduplicated: trueAnother agent or person asked the same question; the result is theirs too.
The run exceeds a tool's timeoutciresk ci run test --json, note data.id, then ciresk ci wait <job> in a later call.

Do not claim success without a succeeded verdict. Quote the job id so a person can open it.

Deduplication

A job is keyed by the organization, the repository, the base commit, the diff digest, the tree root, the kind, the targets, full and the toolchains. An identical key already queued or running returns the existing job with deduplicated: true, and the budget is charged once, to the first submitter. Separately, every submission carries an idempotency key, so a retry after a timeout returns the same job rather than a second one. --no-dedup opts out of the first; nothing opts out of the second.

The MCP endpoint

Agents use the same surface through MCP at https://ci.eu.ciresk.com/v1/ci/mcp (Streamable HTTP, OAuth for people, bearer keys for machines). The endpoint publishes nine tools; their descriptions refer to each other with a ci__ prefix (ci__job_summary is the tool job_summary):

ToolWhen to callReturns
runStart check, test, build, lint or a named task on a tree already uploaded (tree_root: the data.tree.root that ciresk ci run <kind> --json prints) or on a ref the cell can fetch without credentials; wait_s (at most 55) returns the summary once the job is terminalJob or Summary
job_summaryFirst call after any jobSummary; response_format: concise or detailed
job_logsOnly when the summary tail is not enough: failed-only, tail, grep with context, byte pagingLogSlice with next_byte_offset and truncated
job_testsFailing tests first, cursor paged{tests, total, failed_total, next_cursor}
job_warningsThe warnings a job's tasks printed, as data{warnings, total, unique, by_kind, next_cursor}
job_artifactsNames, sizes, time-limited URLs, an optional text excerpt{artifacts, excerpt}
job_waitBlock until a terminal state, 45 s by default and 55 s at most per callSummary or {state, poll_after_ms}
job_cancelCooperative cancel; marked destructive, asks for confirmation{id, state, cancelled}
jobs_listRecent jobs for a repository, ref, actor or state; compact rows, never logs{jobs, next_cursor}

Every result carries _meta["ciresk/budget"] and _meta["ciresk/principal"]. Errors are isError: true text that names the fix. Read-only MCP is not a security boundary when the agent also has a shell; the token's scopes are. The CLI is the fallback surface whenever MCP is unreachable, with the same contract. Client set-up is in Claude Code, Codex and Cursor.

Budgets

LimitScopeWhen exceeded
Soft budget (cost units)Per actor, per session and per dayThe job still runs, demoted to the lowest priority.
Hard cap (cost units)Per actor, per session and per daybudget_exceeded, exit 5, the reset time in the message.
ConcurrencyPer actor and per organizationconcurrency_limited until a job finishes.
--budget <units>One jobThe server stops the job when it passes the ceiling.

A cost unit is one vCPU-second of the executing worker. A task is charged for the time it ran, at most the time its lease was held, and nothing when it ends infra_error (a fault on Ciresk's side) or is a cache audit the cell started itself. Every response carries budget: {session, day, concurrent, resets_at}; the CLI prints it as one stderr line and ciresk ci budget shows the whole picture. Deduplication charges the first submitter once. An agent whose remaining is 0 should stop and report rather than retry.

Attribution is automatic: in a Claude Code shell the job carries actor: agent:claude-code and the session id; in Codex agent:codex; in GitHub Actions ci:github-actions:<actor>; --actor and CIRESK_ACTOR override. Tokens, prompts and transcripts are not sent.