Talooner ships as one image — talooner-runner — that you add as a step to the
pipeline you already have. The job starts OpenTalon and the Talooner plugin inside its
own container, checks your license, reviews the merge request or pull request against
your repository's .talooner/rules.tln, posts a check run and one comment, and exits.
ghcr.io/talooner/talooner-runner:latest
The image holds binaries only — no source, no compiler — on a minimal, non-root base.
What you need
- A license key — from your license page (or get a free demo license).
- A forge token so Talooner can post its review:
GitLab — a project or group access token, Developer role,
apiscope; GitHub Actions — the built-ingithub.token. - Optional: any LLM for AI review — see Any LLM. Without one, Talooner reviews with rules only.
- Optional: a context decision model (
CDM_URL+CDM_TOKEN) — Jev from TypeSafe or SubQ Snap from Subquadratic as a fast first pass: each changed unit gets a typed match / mismatch / unclear with a confidence score, and only what the model can't confirm goes to the full LLM review.
Any LLM
Talooner works with any model behind an OpenAI-compatible API — hosted or on your own servers. Set three variables:
TALOONER_MODEL_BASE_URL | TALOONER_MODEL | |
|---|---|---|
| Qwen (Alibaba Cloud) | https://dashscope-intl.aliyuncs.com/compatible-mode/v1 | qwen3-coder-plus |
| DeepSeek | https://api.deepseek.com/v1 | deepseek-chat |
| OpenRouter | https://openrouter.ai/api/v1 | any model it lists |
| Ollama / vLLM on your servers | http://llm.internal:11434/v1 | e.g. qwen3-coder |
…plus TALOONER_MODEL_API_KEY (any non-empty value for a local server). Anthropic and
OpenAI have shortcuts: ANTHROPIC_API_KEY, or OPENAI_API_KEY with TALOONER_MODEL.
The snippets below use the three variables; swap in a shortcut if you prefer.
GitLab CI
Works on gitlab.com and self-managed GitLab. Add the job to .gitlab-ci.yml, then add
TALOONER_LICENSE_KEY and GITLAB_TOKEN (and optionally your LLM:
TALOONER_MODEL_BASE_URL, TALOONER_MODEL, TALOONER_MODEL_API_KEY) under
Settings → CI/CD → Variables, masked.
talooner:
image:
name: ghcr.io/talooner/talooner-runner:latest
entrypoint: [""]
stage: test
needs: []
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
TALOONER_PROVIDER: gitlab
GIT_STRATEGY: none
script:
- talooner-step
Keep entrypoint: [""] — GitLab runs the job's script: through a shell.
GitHub Actions
Works on github.com and GitHub Enterprise Server. Add TALOONER_LICENSE_KEY (and
optionally your LLM's TALOONER_MODEL_API_KEY) under Settings → Secrets and
variables → Actions, and
allow GitHub Actions to create and approve pull requests under Settings → Actions →
General.
# .github/workflows/talooner.yml
name: talooner
on:
pull_request:
pull_request_review:
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
checks: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: docker://ghcr.io/talooner/talooner-runner:latest
env:
TALOONER_PROVIDER: github
TALOONER_LICENSE_KEY: ${{ secrets.TALOONER_LICENSE_KEY }}
TALOONER_MODEL_BASE_URL: https://dashscope-intl.aliyuncs.com/compatible-mode/v1
TALOONER_MODEL: qwen3-coder-plus
TALOONER_MODEL_API_KEY: ${{ secrets.TALOONER_MODEL_API_KEY }}
GITHUB_TOKEN: ${{ github.token }}
Any other CI
Jenkins, CircleCI, Buildkite, Drone, Woodpecker, TeamCity — anything that can run a container against a GitHub or GitLab repository:
docker run --rm \
-e TALOONER_PROVIDER=gitlab \
-e TALOONER_LICENSE_KEY \
-e TALOONER_MODEL_BASE_URL -e TALOONER_MODEL -e TALOONER_MODEL_API_KEY \
-e GITLAB_TOKEN -e GITLAB_API_URL \
-e CI_PROJECT_ID -e CI_MERGE_REQUEST_IID -e CI_PIPELINE_SOURCE \
ghcr.io/talooner/talooner-runner:latest
The image prints a ready config for your CI:
docker run --rm <image> snippet gitlab (or github, generic).
Gitea/Forgejo, Bitbucket and Azure DevOps are coming soon.
One variable instead of five
Put everything in one YAML file and store it as a single masked variable,
TALOONER_CONFIG_B64:
# talooner.yaml
provider: gitlab
license_key: TLN1....
model:
base_url: https://dashscope-intl.aliyuncs.com/compatible-mode/v1
name: qwen3-coder-plus
api_key: sk-...
gitlab_token: glpat-...
docker run --rm -i ghcr.io/talooner/talooner-runner:latest \
encode-config - < talooner.yaml # → paste the output into TALOONER_CONFIG_B64
A variable set on its own always overrides the same setting in the file.
Settings
| Variable | In talooner.yaml | |
|---|---|---|
TALOONER_LICENSE_KEY | license_key | required |
TALOONER_PROVIDER | provider | gitlab or github — detected in GitLab CI and GitHub Actions |
GITLAB_TOKEN / GITHUB_TOKEN | gitlab_token / github_token | required for the provider |
GITLAB_API_URL | gitlab_url | self-managed GitLab outside GitLab CI |
TALOONER_MODEL_BASE_URL + TALOONER_MODEL_API_KEY + TALOONER_MODEL | model.base_url + model.api_key + model.name | AI review with any LLM behind an OpenAI-compatible API |
ANTHROPIC_API_KEY | model.anthropic_api_key | shortcut for Anthropic; model defaults to claude-sonnet-5-5 |
OPENAI_API_KEY + TALOONER_MODEL | model.openai_api_key + model.name | shortcut for OpenAI |
CDM_URL + CDM_TOKEN | cdm.url + cdm.token | decision-model triage before the full review |
CDM_BACKEND | cdm.backend | jev or subq; taken from the URL when unset (subq.ai → subq, else jev) |
JEV_URL + JEV_TOKEN | jev.url + jev.token | older names, still read when CDM_URL and CDM_TOKEN are unset |
TALOONER_CDM_MIN_CONFIDENCE | cdm.min_confidence | triage threshold, default 0.9 |
TALOONER_LOG_LEVEL | log_level | debug prints OpenTalon's own log |
TALOONER_METRICS_PUSH_URL | metrics.push_url | push talooner_* metrics at the end of each job — see below |
TALOONER_METRICS_PUSH_TOKEN | metrics.push_token | the gateway's bearer token — store it masked / as a secret |
TALOONER_METRICS_PUSH_JOB | metrics.job | the job label, default talooner |
TALOONER_METRICS_LABELS | metrics.labels | optional extra labels to filter by in Grafana, e.g. team=payments when several teams share one Prometheus |
TALOONER_MODEL_COST_INPUT / _OUTPUT | model.cost_input / model.cost_output | your model's price per 1M input / output tokens — fills the LLM cost metrics |
TALOONER_METRICS_PR_LABEL | metrics.pr_label | true adds the PR number and title, for cost per pull request — off by default |
Metrics with Prometheus and Grafana
Every verdict, action, rule hit and AI review is a talooner_* Prometheus metric. A
pipeline job lives for seconds, so Prometheus can't scrape it — instead, each job
pushes its metrics once, when it finishes, to a gateway next to your Prometheus.
Push from your pipeline
The job pushes its metrics with the Prometheus Pushgateway protocol to your gateway — prom-aggregation-gateway, which adds up the counters of every job.
GitLab CI — add two variables under Settings → CI/CD → Variables (or once on the group, for every project):
| Variable | Value | |
|---|---|---|
TALOONER_METRICS_PUSH_URL | https://metrics.acme.dev:9091 | |
TALOONER_METRICS_PUSH_TOKEN | the gateway's token — only if it has one | masked, never in .gitlab-ci.yml |
The URL can also go in the job; the token always stays a masked variable:
talooner:
image:
name: ghcr.io/talooner/talooner-runner:latest
entrypoint: [""]
stage: test
needs: []
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
TALOONER_PROVIDER: gitlab
GIT_STRATEGY: none
TALOONER_METRICS_PUSH_URL: https://metrics.acme.dev:9091
# TALOONER_METRICS_PUSH_TOKEN comes from the masked CI/CD variable
script:
- talooner-step
When the review is done, talooner-step pushes once and the job log shows
talooner-step: metrics: pushed 23 series to https://metrics.acme.dev:9091. The token is sent as
Authorization: Bearer <token>.
GitHub Actions — the same two settings in the step's env: the URL as a repository
variable, the token as a secret:
- uses: docker://ghcr.io/talooner/talooner-runner:latest
env:
TALOONER_PROVIDER: github
TALOONER_LICENSE_KEY: ${{ secrets.TALOONER_LICENSE_KEY }}
GITHUB_TOKEN: ${{ github.token }}
TALOONER_METRICS_PUSH_URL: ${{ vars.TALOONER_METRICS_PUSH_URL }}
TALOONER_METRICS_PUSH_TOKEN: ${{ secrets.TALOONER_METRICS_PUSH_TOKEN }}
The runner must reach the gateway. The push goes out from the machine that runs the job:
- Your own runners (self-hosted GitLab runner or GitHub runner) — use the internal
address, e.g.
http://metrics.internal:9091. Nothing needs to be public. - gitlab.com shared runners or GitHub-hosted runners run on the internet, so the
gateway needs a public HTTPS address. Put it behind a reverse proxy that checks a bearer
token, and store the token as a masked variable or secret,
TALOONER_METRICS_PUSH_TOKEN.
Collect it in Prometheus
docker run -d -p 9091:80 ghcr.io/zapier/prom-aggregation-gateway:latest
# prometheus.yml
scrape_configs:
- job_name: talooner
honor_labels: true # keep the repo / provider labels Talooner sets
static_configs:
- targets: ["metrics.internal:9091"]
Import the dashboard into Grafana (Dashboards → New → Import) — download talooner-dashboard.json, or print the one that matches your image:
docker run --rm ghcr.io/talooner/talooner-runner:latest \
dashboard > talooner-dashboard.json
What you get
| Metric | |
|---|---|
talooner_pr_evaluations_total{repo, verdict} | reviews by verdict — approve, block, none |
talooner_pr_actions_total{verb} | what the rules did: approve, block, route, comment… |
talooner_pr_rules_fired | rules that fired per review (histogram) |
talooner_pr_evaluation_duration_seconds | how long a review took (histogram) |
talooner_llm_reviews_total{result} | AI reviews: match, mismatch, unclear, triage hits… |
talooner_pr_warnings_total{code} | ruleset health warnings |
opentalon_plugin_llm_cost_usd_total{plugin, action, model} | LLM spend per plugin action and model — e.g. talooner · evaluate_pr |
opentalon_plugin_llm_tokens_total{plugin, action, model, direction} | the tokens behind it, input / output |
opentalon_llm_*_tokens_total, opentalon_llm_*_cost_usd_total{model} | all LLM tokens and cost of the run, by model |
Each series also carries repo, provider and pipeline_source, plus your own
TALOONER_METRICS_LABELS. The dashboard shows the auto-approval and needs-a-human rates,
the verdict mix over time, latency, top repositories, AI review outcomes, warnings and
LLM cost — watch the auto-approval rate climb as you tune the rules:
sum(rate(talooner_pr_evaluations_total{verdict="approve"}[1d]))
/ sum(rate(talooner_pr_evaluations_total[1d]))
A push that fails — gateway down, wrong URL — is logged as a warning and never fails the job.
LLM cost
Set your model's price and every review reports what its AI calls cost — per plugin action and model, with your own LLM key:
TALOONER_MODEL_COST_INPUT: "0.8" # price per 1M input tokens
TALOONER_MODEL_COST_OUTPUT: "3.2" # price per 1M output tokens
The dashboard's LLM cost row shows the spend in the selected range, the cost per review, cost by plugin action and by model, and the most expensive repositories:
# cost per review this week
sum(increase(opentalon_plugin_llm_cost_usd_total[7d]))
/ sum(increase(talooner_pr_evaluations_total{mode="execute"}[7d]))
Without a price you still get the token counts; the cost series stay at 0.
Cost per pull request. Set TALOONER_METRICS_PR_LABEL=true and the cost and review
series also carry the PR number (!412 on GitLab, #412 on GitHub); the title and link
travel in a separate talooner_pr_info series. The dashboard's Top pull requests by LLM
cost table then shows each PR's total cost, tokens and review runs — click one to open it.
It's off by default: every PR adds a few series to your Prometheus, and PR titles end up in
your monitoring.
What leaves your pipeline
- Calls to your forge's API.
- Calls to your own LLM provider — only where a rule asks for
llm_review. - Metrics to your own gateway, if you set one — counts, verdicts, cost and repository
names; PR numbers and titles only with
TALOONER_METRICS_PR_LABEL=true. - One license check to us: the key, a nonce and the version.
Never code, repositories or PR data to us.
Each job verifies the license online before it reviews. If the license is revoked or expired, or the license server can't be reached, Talooner posts a "license inactive" check run and comment — and the job still succeeds, so it never blocks your merges.
