Skip to content

Pinned versions reference

View as Markdown

This page lists everything about pinned agent versions: how a run gets its version, what conversations and flows keep, what requests to pin a version and unpins refuse, the pin history, every pin that covers a project, release gates, and what the console shows. On the CLI, pinning a version is kindgi agents pin. To release a version, follow Release an agent version.

A run's agent.via says how it got its version:

via The version is
pinned the pinned version of the scope in pinnedScope
latest the project's Latest, because nothing is pinned for this run
explicit the one the run named, with --agent-version or agentVersion
flow-pin the one its flow version holds, as Flows keep their versions describes
conversation the one its conversation started with, as Conversations keep their version describes

The most specific pinned version wins: a segment path in a project, then the project, the project's org, the workspace, and then the project's Latest. Without --project, a run is in its agent's own project, which is the default project for what kindgi deploy registers.

A version is only pinned when you pin it: publishing a newer version doesn't change what a scope with a pinned version runs.

Where nothing is pinned, a run gets its project's Latest. Latest moves only when:

  • a deployment into the project ships a version: kindgi deploy, or kindgi promote carrying a build to the next environment;
  • a person publishes a full version of the agent (POST /v1/agents); or
  • a person adds a suggested change's version as the project's Latest, as Add it as Latest shows.

It doesn't move by itself. A suggested change's version runs only where it's pinned or added as Latest, a version pinned somewhere doesn't become Latest, and a version shipped into another environment isn't Latest here until a deployment ships it here. When two versions became Latest at the same time, the higher one is. A version published before the runtime recorded Latest also counts from when it was published, in the project it was made in. A version that's unregistered drops out, and the one with the newest time of those left is Latest. For a workspace or org scope, which has no project, every version that can be Latest counts, wherever it was made or shipped: its Latest is the one with the newest time.

A project can have no pinned version and no Latest yet: a later environment before its first promotion, or one whose only version of the agent was derived. There, a run that names no version is refused with 404 agent-not-found, and so is GET /v1/agents/{agentId}/pinned. Pinning a version there is refused with 409 nothing-runs-in-scope, with or without a release gate (409 project-suspended when the project is suspended). Deploy or publish a version into the project first; into a later environment, promote a build that ships one.

A pin that would take a project that follows Latest off it asks first. A project or segment pin of such a project, or a workspace or org pin over one, is refused with 409 pin-stops-latest, whether or not the version changes, and nothing is recorded. details.projects lists each project with the version it runs now. Send stopFollowingLatest: true to pin anyway (kindgi agents pin --stop-following-latest): the pin goes on, and the change records stoppedFollowingLatest: true. A deployment promotion's pin is the ask itself, so its pins are never refused this way.

client.agents.projects(agentId) (GET /v1/agents/{agentId}/projects) says what a run in each project gets and why: follows is pin with the pinned version that covers the project, or latest with its Latest, since when and the deployment or person that made it Latest. It also gives the agent's highest version, which numbers the next one and is what client.agents.get(agentId) answers. The highest version isn't always what a project runs.

A follow-up turn in a conversation keeps the version the conversation started with, even after another version is pinned, so the conversation stays consistent. Here a conversation started on 1.2.0. Then 1.0.0 was pinned for the workspace, and the conversation's next turn still ran 1.2.0:

"agent": {
"id": "acme.order-desk",
"version": "1.2.0",
"conversationId": "9fce3360-5426-4578-b387-361afa9f2156",
"via": "conversation"
},
Excerpt

A new conversation gets the pinned version.

A published flow version holds the agent versions it was published with. Each agent step runs the version its node names, config.version. Otherwise it runs the one a run in the flow's project would get when the flow version is published: the pinned version, from the project, then the org, then the workspace, or the project's Latest when none is pinned. Its turns say "via": "flow-pin", and pinning another version doesn't change them.

Here acme.order-flow 1.0.0 was published while 1.3.0 was pinned. After the workspace pinned 1.0.0, its step still ran 1.3.0:

"agent": {
"id": "acme.order-desk",
"version": "1.3.0",
"conversationId": "5547f2a1-88b2-4b34-8d63-294f21fa5642",
"via": "flow-pin"
},
Excerpt

To run a new agent version in a flow, publish a new version of the flow.

Its tool steps are held the same way: each runs the tool version the flow version locked when it was published, which is the tool's pinned version for the flow's project when a pin covers it, else the tool's newest.

An unpin removes a scope's own pinned version, at once. No release gate checks it. Its runs then use a wider scope's pinned version, or the project's Latest when none is pinned. To go back to an earlier version, pin it again. On a self-hosted runtime with one pack service, that version runs only while your pack still has the tool versions it locked (A self-hosted runtime runs what your pack has).

An unpin answers as a pin does, 201 with { pin, testRuns, fixed, reopened }, the unpin under pin. In each project whose version it changes and that has corrections on its test set, it starts a test run of the version the project runs now, listed in testRuns. The corrections change when that run ends. An unpin reuses no run, so fixed and reopened are empty.

What a request to pin or unpin refuses:

  • a version that isn't registered: Version 9.9.9 is not registered (or was unregistered);
  • a pin for the workspace or an org, for an agent with a project in an environment order, since it would act in every environment of the order at once: 409 pin-scope-spans-environments. Pin it in one project at a time, or in a segment of one;
  • an unpin where the scope has no pinned version of its own: The scope has no pinned version of its own;
  • an unpin of a scope with a release gate, since such a scope keeps its own pinned version: segment company:acme in project <project-id> has a release gate for acme.order-desk, and a gated scope keeps its own pinned version: unregister the release gate first, or pin an earlier version instead.

To retire a version that's pinned somewhere, unpin it, or pin another version there first, in each of those scopes. Until then, kindgi agents unregister refuses it with 409 agent-version-pinned.

Every request to pin a version and every unpin is kept, with who asked and why, newest first:

Terminal window
pnpm exec kindgi agents pin-history list acme.order-desk --table

--table prints a table. STATUS says how each ended, done for an unpin:

ID ACTION STATUS SCOPE FROM TO BY AT
──────────────────────────────────── ──────── ──────── ───────────────────────────────────────────────────────── ───── ───── ───────────────────────────────────────── ────────────────────────
d6db591b-8afc-4413-81c8-a794d9f848a5 unpin done tenant 1.2.0 - user 5c0a7e11-0000-4000-8000-0000000000aa 2026-10-06T18:48:59.665Z
70ea0024-ec7e-4122-abc4-04df1eb7b9eb pin pinned tenant 1.0.0 1.2.0 user 5c0a7e11-0000-4000-8000-0000000000aa 2026-10-06T18:48:43.578Z
912e10e7-18fb-4228-92b9-33b3f2a4d45b pin pinned project 367e1657-8067-4a1d-8ec4-bfa3c5fc5865 company=acme - 1.1.0 user 5c0a7e11-0000-4000-8000-0000000000aa 2026-10-06T18:48:23.207Z
ea4e7c15-7990-41b6-b153-0847a83f2e9d pin pinned tenant - 1.0.0 user 5c0a7e11-0000-4000-8000-0000000000aa 2026-10-06T18:46:15.125Z
  • --tenant, --org, --project and --segment narrow it to one scope's history.
  • A request's status is pinned, pending-approval, refused, superseded, rejected or expired. A waiting request is superseded when something changed before it was approved: what serves the scope isn't what the release gate measured against (another request was pinned first, for example), the scope's release gate isn't the one it passed (a new version, another gate, or none now), the version is no longer registered, or pinning it would move a narrower scope that has a release gate of its own and follows this one.
  • --test-run=<test-run-id> on pin keeps the run on the test cases behind the decision on its record.

GET /v1/projects/<project-id>/pinned-versions lists every agent's pins that cover a project: the workspace's, the project's org's, the project's own and its segments'. They're ordered by agent, then by scope, and each one is as an agent's own list of pinned versions shows it:

{
"data": [
{
"agentId": "acme.order-desk",
"scope": { "kind": "tenant" },
"version": "1.0.0",
"changeId": "5b1d0c4e-2f7a-4c8e-9d36-0a8e4f1b7c21",
"setAt": "2026-10-06T18:46:15.125Z"
},
{
"agentId": "acme.order-desk",
"scope": {
"kind": "segment",
"projectId": "367e1657-8067-4a1d-8ec4-bfa3c5fc5865",
"path": [{ "key": "company", "value": "acme" }]
},
"version": "1.1.0",
"changeId": "a3f6e2d9-7c14-4b5a-8e0f-6d2c9b1a4e73",
"setAt": "2026-10-06T18:48:23.207Z"
}
],
"hasMore": false
}

A run in the project takes the most specific of them that covers it, as How a run gets its version says.

Reading it takes read on the project. It lists only the pins of agents and versions you can read, as an agent's own list does, so a page can hold fewer pins than limit, or none, and still have more: page on nextCursor, not on the count. limit is 25 by default and at most 100, and cursor=<nextCursor> gets the next page. In the TypeScript client it's projects.pinnedVersions.list, and in the Python client projects.pinned_versions.list. A runtime that can't list them answers 501 pinned-versions-not-supported.

A release gate's parts, and what each asks of a request to pin a version:

Part A request must show
comparison.maxAgeHours a run of the version on the test cases, finished at most this many hours ago
evidence.minCases each figure resting on at least this many corrected cases, for the version and for the pinned one: its n. For passRate, a case counts when it has corrected items or a graded expect
metrics each named figure at least minCandidate, and lower than the pinned version's by at most maxDrop. The figures are passRate, caseCoverage, or weightedPrecisionAtK with its k
replay at most this many diverged cases, errors, refused tool calls and stopped cases: maxDiverged, maxErrors, maxRefusedWrites, maxStopped. maxRefusedWrites counts refused reads too, despite its name. Those left out are 0. A version with other settings runs read-only tools live, which counts as diverged, so maxDiverged: 0 refuses it. A replay refuses a write whose arguments differ from the past run's, so for an agent whose job is a write, leave replay out and gate on cases (Set a release gate)
cases.minPassing at least this many of the run's cases passing. Each correction the run checked is a case, and it passes when every repetition did
cases.noNewFailures: true no case that passed before failing: none of the run's corrections that are Fixed now fails it. One the run didn't grade isn't counted, and the check says how many of the cases Fixed now it graded. That includes one the run had and couldn't grade, such as one whose answer couldn't be checked because the call to the judging model failed, even if its decisions failed. So read its message even when it passes: "The test run checked none of the 40 cases fixed now." is a pass
approvals then a reviewer with this role approves it, senior if left out. It's someone other than whoever asked, unless separateApprover: false lets the one who asked approve it
onlyExpertGroups: true a run on the test cases that counted only experts' corrections
  • A request must name its run on the test cases when the release gate has comparison, evidence, metrics, replay, cases or onlyExpertGroups. The run must be of the version you pin, with the same pins, and its recorded runs must be from the version pinned for the scope now.
  • With onlyExpertGroups, a run on the test cases that counted everyone's corrections is refused with The gate counts only experts' corrections; the test run counted everyone's. Re-run it with `groupWeights: 'experts-only'`.. So only expert groups can release a version.
  • With cases, a refusal's details add passing and needs for minPassing, and newlyFailing, the ids of the cases that passed before and fail now, for noNewFailures. The check's message names the first five.
  • To change a release gate, publish a new version of it. kindgi release-gates unregister acme.order-desk-release <version> goes back to the one before.
  • kindgi agents release-gate acme.order-desk --tenant shows the release gate that applies to a scope.
  • With authorization on, publishing a release gate takes admin on the workspace. Reading one takes read on its scope: its project, a segment's too, its org, or the workspace for a workspace-wide one. A release gate you can't read is left out of lists, and answers 404 release-gate-not-found, as if it weren't there.

A scope with a release gate needs a pinned version of its own

Section titled “A scope with a release gate needs a pinned version of its own”

Otherwise, the next published version, or pinning a version for a wider scope, would reach its runs without its release gate. A release gate for a scope without one is refused:

The workspace has no pinned version of acme.order-desk of its own, so publishing a version would make it serve here ungated: pin a version for this scope first (1.2.0 serves it now)

A scope whose runs get their version from a wider scope's pin, such as a segment that runs its project's version, is refused the same way, naming that pin:

Segment company:globex has no pinned version of acme.order-desk of its own, so a pin for project <project-id> would change it without this gate: pin a version for this scope first (1.0.0 serves it now)

Pin it in place: pin the version it serves now there. That changes nothing for its runs, so it needs no run on the test cases and no approval, even when a release gate already applies:

Terminal window
pnpm exec kindgi agents pin acme.order-desk 1.0.0 --project=<project-id> --segment=company:globex --reason="Pin globex before its gate"
  • <project-id> is the segment's project.
  • --segment=company:globex is the segment, key:value. A deeper segment repeats --segment, coarse to fine.
  • --reason says why, and is kept with the request.

Under a release gate, the request's one check says why it went through, and its reason starts with pin-in-place: acme.order-desk 1.0.0 already serves this scope (from a pin above it), so pinning it here changes nothing for runs: the gate's checks and approval don't apply. --check answers would-pin with that same check.

A scope with a release gate that still follows a wider scope's pin, such as one set up by an older runtime, refuses pinning a version or an unpin in that wider scope with 409 release-gate-descendant-unpinned, naming the scope and its version:

Pinning acme.order-desk for project <project-id> would also change segment company:acme, which a release gate applies to and which has no pinned version of its own, without its gate. Pin it at its current version first: segment company:acme at 1.0.0

Pin that scope in place, and pin the version again.

  • An agent's page says which version a run in this project gets, such as "Runs here use 1.0.0", and why: "It's pinned for every project (the workspace-wide default)." It links to its Pinned versions page.
  • Pinned versions has the same headline, and Check a segment path, a key and a value, to see what a run on a path would get. It lists every pin that reaches this project with its part, each with Unpin: "Runs here take it", or "Runs on this path take it" for a segment. Then it has the Pin history, with who and why.
  • Pin a version picks the version and where it is pinned: this project, every project in its org when it's in one, every project, or a segment path in this project. It says what will happen before you pin it, such as "Runs in this project (Support) will use 1.3.0."
  • A run's page says why it ran its version, such as "Pinned for company = acme in this project (First Project)".
  • Release gates, on the Pinned versions page, has each release gate in sentences, with its versions, New version and Add a release gate. The sentences read like "Share marked right: at least 60%, and no more than 2 points below the pinned version" and "Then a senior reviewer approves it, someone other than whoever asked".
  • Add a release gate has Count only experts: "The run must count only experts' corrections (start it with "Count only experts"), so a correction anyone may give can't change what the release gate decides."
  • Pin a version has Test run (optional), a completed run of the version on the test cases. One that counted only expert groups says "expert groups only". It runs the release gate's check before anything is recorded: "The release gate passes (release gate acme.order-desk-release 1.0.0) Then a senior reviewer approves it, someone other than you, and it's pinned.", with each check. Its button follows: Ask to pin 1.3.0, or "The release gate refuses it".
  • A request waiting for approval shows on Pinned versions, with Open the approval. Its approval page shows the request to pin it, who asked and why, the run on the test cases and the release gate, and each check. Once approved, it says "Pinned 1.3.0, replacing 1.0.0".