Release an agent version
This guide makes an agent version live where you choose, checks which version a run gets, rolls back, and gates a release behind your test set.
A run that names no version gets the agent's latest version. Once a new version has done better on your test set, as Compare a version on a test set shows, you can make it live where you choose: the whole tenant, an org, a project, or a segment of a project's runs, such as one company's. Runs there use it from the next run, nothing changes anywhere else, and you can roll back at once. Live versions reference has every rule, refusal and console page.
Before you start
Section titled “Before you start”- An agent with more than one version, registered on a runtime such as
kindgi dev, with Kindgi 0.1's CLI. This guide usesacme.order-desk, with versions 1.0.0, 1.1.0, 1.2.0 and 1.3.0. - A project's id, for a project or a segment:
kindgi projects listlists them. - For a gate: a comparison of the version on your test set, and a second reviewer to approve.
- For the app's half: Node 22.12 or later, or Python 3.11 or later, with
Kindgi 0.1's client, and
KINDGI_API_URLandKINDGI_API_TOKENset.
Make a version live
Section titled “Make a version live”A live version is what a run that names no version gets.
Make 1.0.0 live for the whole tenant:
pnpm exec kindgi agents promote acme.order-desk 1.0.0 --tenant --reason="1.0.0 is the version we checked"uv run kindgi agents promote acme.order-desk 1.0.0 --tenant --reason="1.0.0 is the version we checked"./kindgiw agents promote acme.order-desk 1.0.0 --tenant --reason="1.0.0 is the version we checked"./kindgiw agents promote acme.order-desk 1.0.0 --tenant --reason="1.0.0 is the version we checked"acme.order-desk 1.0.0is the agent and the version.--tenantis the scope: the whole tenant.--reasonis kept on the record.
It prints the promotion:
{ "id": "ea4e7c15-7990-41b6-b153-0847a83f2e9d", "agentId": "acme.order-desk", "scope": { "kind": "tenant" }, "action": "promote", "fromVersion": null, "toVersion": "1.0.0", "requestedBy": { "kind": "user", "id": "5c0a7e11-0000-4000-8000-0000000000aa" }, "reason": "1.0.0 is the version we checked", "createdAt": "2026-10-06T18:46:15.125Z"}From the next run, a run that names no version gets 1.0.0, and says why:
pnpm exec kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is order A-1002?"}'uv run kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is order A-1002?"}'./kindgiw runs start --agent=acme.order-desk --input='{"userMessage":"Where is order A-1002?"}'./kindgiw runs start --agent=acme.order-desk --input='{"userMessage":"Where is order A-1002?"}' "agent": { "id": "acme.order-desk", "version": "1.0.0", "conversationId": "4af092ea-5e8e-42f1-832e-b7ba1ed6e3d8", "via": "live", "liveScope": { "kind": "tenant" } },via: "live" says it got the live version of the scope in liveScope.
How a run gets its version
lists every via. A version only goes live this way: publishing a newer
version doesn't change what a scope with a live version runs.
Make it live for some runs only
Section titled “Make it live for some runs only”A narrower scope tries a version with some runs first. The most specific
live version wins: a segment path in a project, then the project, the
project's org, the tenant, and then the latest version. A segment
path is one or more key:value steps, coarse to fine, that a run is started
with: company:acme, or company:acme then role:counsel.
Try 1.1.0 with acme's runs only:
pnpm exec kindgi agents promote acme.order-desk 1.1.0 --project=<project-id> --segment=company:acme --reason="Trying 1.1.0 with acme first"uv run kindgi agents promote acme.order-desk 1.1.0 --project=<project-id> --segment=company:acme --reason="Trying 1.1.0 with acme first"./kindgiw agents promote acme.order-desk 1.1.0 --project=<project-id> --segment=company:acme --reason="Trying 1.1.0 with acme first"./kindgiw agents promote acme.order-desk 1.1.0 --project=<project-id> --segment=company:acme --reason="Trying 1.1.0 with acme first"--project=<project-id>is the project, where<project-id>is its id.--segment=company:acmeis the segment path in it.--org=<org-id>names an org instead.
It prints the promotion, with the segment as its scope.
A run started with that segment gets 1.1.0:
pnpm exec kindgi runs start --agent=acme.order-desk --project=<project-id> --segment=company:acme --input='{"userMessage":"Where is order A-1002?"}'uv run kindgi runs start --agent=acme.order-desk --project=<project-id> --segment=company:acme --input='{"userMessage":"Where is order A-1002?"}'./kindgiw runs start --agent=acme.order-desk --project=<project-id> --segment=company:acme --input='{"userMessage":"Where is order A-1002?"}'./kindgiw runs start --agent=acme.order-desk --project=<project-id> --segment=company:acme --input='{"userMessage":"Where is order A-1002?"}' "agent": { "id": "acme.order-desk", "version": "1.1.0", "conversationId": "d35be284-99c0-4437-9186-b951386db8b3", "via": "live", "liveScope": { "kind": "segment", "projectId": "367e1657-8067-4a1d-8ec4-bfa3c5fc5865", "path": [{ "key": "company", "value": "acme" }] } },A run with --segment=company:globex has no live version of its own, so it
gets the tenant's, 1.0.0. Without --project, a run is in its agent's own
project, which is Default for what kindgi deploy registers.
See which version a run would get
Section titled “See which version a run would get”kindgi agents live answers without starting a run:
pnpm exec kindgi agents live acme.order-desk --project=<project-id> --segment=company:acmeuv run kindgi agents live acme.order-desk --project=<project-id> --segment=company:acme./kindgiw agents live acme.order-desk --project=<project-id> --segment=company:acme./kindgiw agents live acme.order-desk --project=<project-id> --segment=company:acme{ "agentId": "acme.order-desk", "version": "1.1.0", "via": "live", "liveScope": { "kind": "segment", "projectId": "367e1657-8067-4a1d-8ec4-bfa3c5fc5865", "path": [{ "key": "company", "value": "acme" }] }}kindgi agents live-versions acme.order-desk lists every scope with a live
version, and since when.
From your app
Section titled “From your app”Your app can do the same. Ask your coding agent: "I want to make a new version of my agent live for one company's runs, and check which version a run gets." For reference, the script looks like this, with the project's id as its argument:
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi devconst projectId = process.argv[2];const acme = [{ key: 'company', value: 'acme' }];
// Make 1.1.0 live for acme's runs in this project.await kindgi.agents.promotions.create('acme.order-desk', { version: '1.1.0', scope: { kind: 'segment', projectId, path: acme }, reason: 'Trying 1.1.0 with acme first',});
// Which version would a run there use, and why?const live = await kindgi.agents.live.resolve('acme.order-desk', { projectId, segments: acme });console.log(live.version, live.via, live.liveScope?.kind);
// A run that names no version gets it.const run = await kindgi.runs.start({ agent: 'acme.order-desk', projectId, segments: acme, input: { userMessage: 'Where is order A-1002?' },});console.log(run.agent?.version, run.agent?.via);import sys
from kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi devproject_id = sys.argv[1]acme = [{"key": "company", "value": "acme"}]
# Make 1.1.0 live for acme's runs in this project.kindgi.agents.promotions.create( "acme.order-desk", version="1.1.0", scope={"kind": "segment", "project_id": project_id, "path": acme}, reason="Trying 1.1.0 with acme first",)
# Which version would a run there use, and why?live = kindgi.agents.live.resolve("acme.order-desk", project_id=project_id, segments=acme)print(live.version, live.via, live.live_scope.kind if live.live_scope else None)
# A run that names no version gets it.run = kindgi.runs.start( agent="acme.order-desk", project_id=project_id, segments=acme, input={"userMessage": "Where is order A-1002?"},)if run.agent is not None: # set on an agent's runs print(run.agent.version, run.agent.via)It prints the version a run there would get, then the version the run got:
1.1.0 live segment1.1.0 livekindgi.agents.live.list, rollback and unpin, and
kindgi.agents.promotions.list and get, do what the commands on this page
do.
Roll back, or unpin
Section titled “Roll back, or unpin”A rollback puts a scope back on the version that was live there before, at once:
pnpm exec kindgi agents rollback acme.order-desk --tenant --reason="1.2.0 answers worse"uv run kindgi agents rollback acme.order-desk --tenant --reason="1.2.0 answers worse"./kindgiw agents rollback acme.order-desk --tenant --reason="1.2.0 answers worse"./kindgiw agents rollback acme.order-desk --tenant --reason="1.2.0 answers worse"--to=<version> rolls back to another version instead. It prints the
rollback:
{ "id": "7da674c5-c835-42c0-8cde-59571afd0915", "agentId": "acme.order-desk", "scope": { "kind": "tenant" }, "action": "rollback", "fromVersion": "1.2.0", "toVersion": "1.0.0", …}kindgi agents unpin removes a scope's own live version. Its runs then use
the scope above, or the latest version when nothing above is live:
pnpm exec kindgi agents unpin acme.order-desk --tenant --reason="Back to the latest"uv run kindgi agents unpin acme.order-desk --tenant --reason="Back to the latest"./kindgiw agents unpin acme.order-desk --tenant --reason="Back to the latest"./kindgiw agents unpin acme.order-desk --tenant --reason="Back to the latest"Every promotion, rollback and unpin is kept, with who asked and why.
kindgi agents promotions list acme.order-desk --table lists them, as
The history shows.
Rollbacks and unpins
lists what each refuses.
Gate a release
Section titled “Gate a release”A gate policy says what a promotion must show before a version goes live for a scope: a recent comparison on your test set, enough judged cases, figures at least this good, a clean replay and, if you want, a reviewer's approval. An agent has one policy per scope. A promotion is checked by the most specific one covering its scope, at its latest version.
Set a gate
Section titled “Set a gate”A gated scope needs a live version of its own, so give the tenant one first:
pnpm exec kindgi agents promote acme.order-desk 1.0.0 --tenant --reason="1.0.0 is the version we checked"uv run kindgi agents promote acme.order-desk 1.0.0 --tenant --reason="1.0.0 is the version we checked"./kindgiw agents promote acme.order-desk 1.0.0 --tenant --reason="1.0.0 is the version we checked"./kindgiw agents promote acme.order-desk 1.0.0 --tenant --reason="1.0.0 is the version we checked"Ask your coding agent to write the policy:
Before a new version of my agent goes live, I want it to have done well on my test set, and a senior reviewer to approve it.For reference, the policy it writes looks like this. This guide saves it as
gate.json:
{ "comparison": { "maxAgeHours": 168 }, "evidence": { "minCases": 3 }, "metrics": [{ "name": "weightedYesShare", "minCandidate": 0.6, "maxDrop": 0.02 }], "replay": { "maxRefusedWrites": 0 }, "approvals": { "role": "senior" }}Then publish it:
pnpm exec kindgi gate-policies publish acme.order-desk-release --policy-version=1.0.0 --agent=acme.order-desk --tenant --spec=@gate.json --description="What a release of the order desk must show"uv run kindgi gate-policies publish acme.order-desk-release --policy-version=1.0.0 --agent=acme.order-desk --tenant --spec=@gate.json --description="What a release of the order desk must show"./kindgiw gate-policies publish acme.order-desk-release --policy-version=1.0.0 --agent=acme.order-desk --tenant --spec=@gate.json --description="What a release of the order desk must show"./kindgiw gate-policies publish acme.order-desk-release --policy-version=1.0.0 --agent=acme.order-desk --tenant --spec=@gate.json --description="What a release of the order desk must show"acme.order-desk-releaseis the policy's id, and--policy-versionits version.--agentand--tenantare the agent and the scope it gates.--spec=@gate.jsonreads the policy from the file.
The tenant's promotions of acme.order-desk now go through the gate.
Gate policies has what each
part asks, and who may publish one.
Check before you promote
Section titled “Check before you promote”--check asks the gate without recording anything:
pnpm exec kindgi agents promote acme.order-desk 1.3.0 --tenant --eval-run=<comparison-id> --checkuv run kindgi agents promote acme.order-desk 1.3.0 --tenant --eval-run=<comparison-id> --check./kindgiw agents promote acme.order-desk 1.3.0 --tenant --eval-run=<comparison-id> --check./kindgiw agents promote acme.order-desk 1.3.0 --tenant --eval-run=<comparison-id> --check--eval-run=<comparison-id> names the comparison of 1.3.0, where
<comparison-id> is its eval run's id. It prints what the gate would say:
{ "outcome": "needs-approval", "policy": { "id": "acme.order-desk-release", "version": "1.0.0" }, "checks": [ { "name": "comparison.freshness", "passed": true, "message": "The comparison finished 0.013 hours ago; the policy accepts up to 168.", … }, { "name": "baselineIsLive", "passed": true, "message": "The baseline is acme.order-desk 1.0.0, the version live for the scope.", … }, { "name": "metric.weightedYesShare.minCandidate", "passed": true, "message": "The weighted yes share is 1; the policy needs at least 0.6.", … }, … ], "approval": { "role": "senior", "count": 1, "separateApprover": true }}gate-failed lists each check that failed, such as
This policy needs a comparison: name one with `evalRunId` (a finished comparison eval run of this version).
when there's no --eval-run.
Promote through the gate
Section titled “Promote through the gate”Promote it the same way, without --check:
pnpm exec kindgi agents promote acme.order-desk 1.3.0 --tenant --eval-run=<comparison-id> --reason="1.3.0 did as well on the order checks"uv run kindgi agents promote acme.order-desk 1.3.0 --tenant --eval-run=<comparison-id> --reason="1.3.0 did as well on the order checks"./kindgiw agents promote acme.order-desk 1.3.0 --tenant --eval-run=<comparison-id> --reason="1.3.0 did as well on the order checks"./kindgiw agents promote acme.order-desk 1.3.0 --tenant --eval-run=<comparison-id> --reason="1.3.0 did as well on the order checks"It prints the promotion or the gate's refusal:
- When the policy has no
approvals, a promotion that passes goes live at once,"status": "promoted". - When it has, it waits:
"status": "pending-approval", with anapprovalId. The scope keeps its version until a reviewer approves. - When it fails, nothing changes. It's refused with every failed check,
such as
The gate refused acme.order-desk 1.3.0: The weighted yes share rests on 3 judged cases for the candidate; the policy needs at least 20. …, and kept in the history asrefused.
Approve it
Section titled “Approve it”The request is an approval like any other, for a reviewer with the policy's role, as Decide an approval shows:
pnpm exec kindgi approvals complete <approval-id> --decision=approve --rationale="Checked the comparison"uv run kindgi approvals complete <approval-id> --decision=approve --rationale="Checked the comparison"./kindgiw approvals complete <approval-id> --decision=approve --rationale="Checked the comparison"./kindgiw approvals complete <approval-id> --decision=approve --rationale="Checked the comparison"<approval-id> is the promotion's approvalId. The version goes live, and
the promotion is promoted. Whoever asked can't approve their own request:
Approval <approval-id> was asked for by you: someone else must approve itThat holds unless the policy's approvals has "separateApprover": false.
A deployment that one person uses with the API token has nobody else to
approve, so its gates with approvals need "separateApprover": false.
With people of their own, register a
second reviewer instead.
Promote from your app
Section titled “Promote from your app”Your app checks the gate, then promotes. Ask your coding agent: "Check the release gate for my new agent version, and promote it unless the gate refuses." For reference, the script looks like this, with the comparison's id as its argument:
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi devconst evalRunId = process.argv[2]; // the comparison of 1.3.0const request = { version: '1.3.0', scope: { kind: 'tenant' as const }, evalRunId, reason: '1.3.0 did as well on the order checks',};
// What would the gate say? Nothing is recorded.const check = await kindgi.agents.promotions.check('acme.order-desk', request);console.log(check.outcome);for (const c of check.checks.filter((c) => !c.passed)) console.log(c.message);
if (check.outcome !== 'gate-failed') { const promotion = await kindgi.agents.promotions.create('acme.order-desk', request); console.log(promotion.status, promotion.approvalId ?? '');}import sys
from kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi devrequest = { "version": "1.3.0", "scope": {"kind": "tenant"}, "eval_run_id": sys.argv[1], # the comparison of 1.3.0 "reason": "1.3.0 did as well on the order checks",}
# What would the gate say? Nothing is recorded.check = kindgi.agents.promotions.check("acme.order-desk", **request)print(check.outcome)for c in check.checks: if not c.passed: print(c.message)
if check.outcome != "gate-failed": promotion = kindgi.agents.promotions.create("acme.order-desk", **request) print(promotion.status, promotion.approval_id or "")It prints the gate's outcome, then the promotion waiting for its approval:
needs-approvalpending-approval fa6fbbcf-7b0d-4c70-b462-d6a78bbcfd11kindgi.gatePolicies.publish, or gate_policies.publish in Python, and
kindgi.agents.gatePolicy.resolve, or agents.gate_policy.resolve, do what
kindgi gate-policies publish and kindgi agents gate-policy do.