Pin a flow's version
This guide pins a flow at a version, tries a newer version without putting it in use, moves the pin, and reads the history.
A run that names no flow version takes the newest version its project reaches, so a version you publish is in use at once. A pin holds the flow at a version you choose, for a project, an org or the whole workspace. Runs there take the pinned version until you move the pin, and a version you publish meanwhile runs only when a run names it.
Before you start
Section titled “Before you start”- A flow with a published version (runtime 0.3.0 or later). This guide
uses
acme.process-orderfrom Write a flow, at 1.0.0, and publishes a 1.1.0. - A project's id:
kindgi projects listlists them. - For your app: Node 22.12 or later, Python 3.11 or later, or Java 17 or
later, with Kindgi 0.3's client, and
KINDGI_API_URLandKINDGI_API_TOKENset.
Which version a run takes
Section titled “Which version a run takes”A run that names its version (flowVersion) runs that version. A run that
names none takes the first of these:
- The project's own pin.
- The pin of the project's org.
- The workspace's pin.
- With none of those, the newest version the project reaches.
A pin counts only while its version is registered and active. A pin whose version doesn't reach the run's project is passed over, and the run takes the newest version the project reaches. A version reaches the project it was made in, and a project a deployment shipped it to.
A run keeps the version it started with, also when it resumes. A pin doesn't change what a version's steps run: each tool and agent step runs the version it was locked to when the flow version was published. The one exception is an agent step that names no version, in a run whose segment a release gate of that agent set on a segment covers: it runs the agent's pinned version for that segment, as a run started in that segment would. A release gate for the project, its org or the workspace doesn't change what the step runs.
Try a new version without putting it in use
Section titled “Try a new version without putting it in use”- Pin the version in use, 1.0.0, for the project.
- Publish the new version, 1.1.0, as you published 1.0.0. Runs in the project still take 1.0.0. On a self-hosted runtime with one pack service, 1.0.0 runs only while your pack still has the tool versions it locked (A self-hosted runtime runs what your pack has).
- Try it by naming it: a run with
flowVersion1.1.0 runs it; every other run there still takes 1.0.0. - Move the pin to 1.1.0 once it does what you want. Runs take it from the next run.
Ask your coding agent: "I want to pin my flow's version in a project, then try a new version there without putting it in use." For reference, the script looks like this, with the project's id as its argument, after step 2:
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 flow = 'acme.process-order';
// Hold the project's runs on 1.0.0.await kindgi.flows.pinned.pin(flow, { version: '1.0.0', scope: { kind: 'project', projectId }, reason: '1.0.0 is the version we checked',});
// Which version would a run there take, and why?const inUse = await kindgi.flows.pinned.resolve(flow, { projectId });console.log(inUse.version, inUse.via, inUse.pinnedScope?.kind);
// A run that names no version takes the pin; one that names 1.1.0 tries it.const held = await kindgi.runs.start({ flow, projectId, input: { orderId: 'A-100' } });const tried = await kindgi.runs.start({ flow, flowVersion: '1.1.0', projectId, input: { orderId: 'A-100' },});console.log(held.flowVersion, held.flowVersionVia);console.log(tried.flowVersion, tried.flowVersionVia);import sys
from kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi devproject_id = sys.argv[1]flow = "acme.process-order"
# Hold the project's runs on 1.0.0.kindgi.flows.pinned.pin( flow, version="1.0.0", scope={"kind": "project", "project_id": project_id}, reason="1.0.0 is the version we checked",)
# Which version would a run there take, and why?in_use = kindgi.flows.pinned.resolve(flow, project_id=project_id)print(in_use.version, in_use.via, in_use.pinned_scope.kind if in_use.pinned_scope else None)
# A run that names no version takes the pin; one that names 1.1.0 tries it.held = kindgi.runs.start(flow=flow, project_id=project_id, input={"orderId": "A-100"})tried = kindgi.runs.start( flow=flow, flow_version="1.1.0", project_id=project_id, input={"orderId": "A-100"})print(held.flow_version, held.flow_version_via)print(tried.flow_version, tried.flow_version_via)package com.acme.app;
import com.kindgi.client.Kindgi;import com.kindgi.client.models.PinScopeProject;import com.kindgi.client.models.PinVersionBody;import com.kindgi.client.models.PinnedVersionResolution;import com.kindgi.client.models.Run;import com.kindgi.client.models.StartRunBody;import com.kindgi.client.resources.FlowsPinnedResolveParams;import java.util.Map;import java.util.UUID;
public final class PinAFlow { public static void main(String[] args) { Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev UUID projectId = UUID.fromString(args[0]); String flow = "acme.process-order";
// Hold the project's runs on 1.0.0. kindgi.flows().pinned().pin(flow, PinVersionBody.builder() .version("1.0.0") .scope(PinScopeProject.builder().projectId(projectId).build()) .reason("1.0.0 is the version we checked") .build());
// Which version would a run there take, and why? PinnedVersionResolution inUse = kindgi.flows().pinned().resolve(flow, FlowsPinnedResolveParams.builder().projectId(projectId).build()); System.out.println(inUse.version() + " " + inUse.via() + " " + (inUse.pinnedScope() == null ? null : inUse.pinnedScope().kind()));
// A run that names no version takes the pin; one that names 1.1.0 tries it. Run held = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow(flow) .projectId(projectId) .input(Map.of("orderId", "A-100")) .build()); Run tried = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow(flow) .flowVersion("1.1.0") .projectId(projectId) .input(Map.of("orderId", "A-100")) .build()); System.out.println(held.flowVersion() + " " + held.flowVersionVia()); System.out.println(tried.flowVersion() + " " + tried.flowVersionVia()); }}package acme.app
import com.kindgi.client.Kindgiimport com.kindgi.client.models.{PinScopeProject, PinVersionBody, StartRunBody}import com.kindgi.client.resources.FlowsPinnedResolveParamsimport java.util.UUIDimport scala.jdk.CollectionConverters._
object PinAFlow { def main(args: Array[String]): Unit = { val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev val projectId = UUID.fromString(args(0)) val flow = "acme.process-order"
// Hold the project's runs on 1.0.0. kindgi.flows().pinned().pin(flow, PinVersionBody.builder() .version("1.0.0") .scope(PinScopeProject.builder().projectId(projectId).build()) .reason("1.0.0 is the version we checked") .build())
// Which version would a run there take, and why? val inUse = kindgi.flows().pinned().resolve(flow, FlowsPinnedResolveParams.builder().projectId(projectId).build()) println(s"${inUse.version} ${inUse.via} ${Option(inUse.pinnedScope).map(_.kind).orNull}")
// A run that names no version takes the pin; one that names 1.1.0 tries it. val held = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow(flow) .projectId(projectId) .input(Map("orderId" -> "A-100").asJava) .build()) val tried = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow(flow) .flowVersion("1.1.0") .projectId(projectId) .input(Map("orderId" -> "A-100").asJava) .build()) println(s"${held.flowVersion} ${held.flowVersionVia}") println(s"${tried.flowVersion} ${tried.flowVersionVia}") }}It prints the version a run there would take, then the version each run ran and how it was chosen:
1.0.0 pinned project1.0.0 pinned1.1.0 explicitTo move the pin, pin 1.1.0 for the same scope. Pinning the version the scope already pins changes nothing.
From the CLI, kindgi flows pin acme.process-order 1.0.0 --project=<project-id>
pins, and kindgi flows pinned acme.process-order --project=<project-id> says
which version a run there would take, and why.
Pin for an org or the whole workspace
Section titled “Pin for an org or the whole workspace”A pin's scope is { kind: 'project', projectId }, { kind: 'org', orgId }
or { kind: 'tenant' }. On the CLI, the scope is --project=<project-id>,
--org=<org-id> or --tenant. An org's pin covers its projects that have no
pin of their own, and the workspace's pin covers every project that has neither.
In a workspace with environment orders (the projects a version goes through on
its way to production, such as staging, then production), a flow in an order
can't be pinned for an org or the workspace: such a pin would act in every
environment of the order at once. The pin is refused with
409 pin-scope-spans-environments; pin it for each project instead. A pin
for a later environment of an order is fine: runs there take it.
A promotion into the next environment with pin pins each agent version
it carries in that project, and each flow version too: the answer lists
the flows in pinnedFlows. Once a flow is pinned in a project, a
promotion without pin keeps the pinned flow in use there: move the pin,
or promote with pin.
Remove a pin
Section titled “Remove a pin”kindgi.flows.pinned.unpin(flow, { scope }) (kindgi flows unpin on the
CLI) removes the scope's own pin. The scope then follows the next scope up,
and with no pin above it, the newest version its projects reach. A scope with
no pin of its own answers 409 not-pinned.
A pinned version can't be unregistered: unregistering it answers
409 flow-version-pinned, with the scopes that pin it in details.scopes.
Move or remove those pins first.
Read the pins and their history
Section titled “Read the pins and their history”kindgi.flows.pinned.list(flow) (kindgi flows pinned-versions) lists every
scope with a pin: its version, when it was set and by whom.
kindgi.flows.pinned.history(flow) (kindgi flows pin-history list) lists
every pin and unpin, newest first: the scope, the version before and after,
who asked, and their reason.
Deleting an org removes its pins and records an unpin for each, by the deletion. Deleting a project removes its pins.
Who can pin
Section titled “Who can pin”Pinning and unpinning need pin on the flow, which its admins have. For a
flow more than one project serves (an environment order carries it to later
environments), a project's pin needs admin on that project, and an org's or
the workspace's pin admin on each of the flow's projects. Reading the pins
needs read on the flow.
- Start a run: name a version with
flowVersion. - Write a flow: publish a new version.
- The API reference has each route, and the errors reference each refusal.