Skip to content

Agent and tool versions

View as Markdown

This page explains which tool version a run of an agent gets, how a published agent version locks it, how a pin holds a tool at a version, which versions a self-hosted runtime can run, and what unregistering a version does.

Tools and agents are versioned, and several versions of each can be active at once. An agent names each tool by a range.

A range picks the highest active version it allows. ^1.0.0 picks 1.2.0 when 1.0.0, 1.1.0 and 1.2.0 are active, and never picks 2.0.0.

A pack can hold several versions of one tool side by side: two tool files with the same id and different versions. Both are served, and each agent runs the one its range picks. kindgi tools versions <tool-id> lists them.

When an agent version is published, by kindgi deploy or by POST /v1/agents, each tool range is resolved once, to an exact version: the version's pins. Every run of that version uses them.

An agent acme.greeter 1.0.0 asks for acme.greet ^1.0.0 while acme.greet 1.0.0 and 1.1.0 are published. It's pinned to 1.1.0, as reading the version shows:

Terminal window
curl "$KINDGI_API_URL/v1/agents/acme.greeter/versions/1.0.0" -H @<(printf 'Authorization: Bearer %s\n' "$KINDGI_API_TOKEN")

$KINDGI_API_URL and $KINDGI_API_TOKEN are your runtime's URL and API token. It prints the version, with its pins:

{
"version": "1.0.0",
"pins": { "tools": { "acme.greet": "1.1.0" }, "prompts": {}, "settings": {} },
"pinsDigest": "sha256:d63b0dc80adfcece6df1989ba7a423d78ec4c917f1f501fa46495457af9b75eb",
…
}
Excerpt

Then acme.greet 1.2.0 is published. acme.greeter 1.0.0 still runs 1.1.0. The next agent version, 1.0.1, is pinned to 1.2.0. A new tool version reaches a published agent only through a new agent version.

A range that no published version matches is refused when the agent is published, so a version can't be published with a tool it can't run:

{
"code": "validation-failed",
"message": "Agent \"acme.greeter\" uses tool versions that aren't published (1 issue)",
"details": {
"issues": [
{
"path": "/tools/0/version",
"message": "tool \"acme.greet\" has no published version in \"^3.0.0\" (published: 1.2.0, 1.1.0, 1.0.0); publish one in range first, or change the range"
}
]
}
}

Under kindgi dev, agents aren't published: each turn resolves its ranges when it starts, so a tool you save is used at the next turn. An agent version published without pins resolves its ranges per run too.

A pin holds a tool at a version for a project, an org or the whole tenant (runtime 0.3.0 or later), so you can publish a new tool version without it reaching the agents and flows made there. When an agent or flow version is made in a project a tool's pin covers (the project's own pin, then its org's, then the tenant's), the version locks the tool at the pinned version:

  • An agent's tool range locks the pinned version when the range allows it. A range that leaves it out, such as the exact 1.2.0 while 1.1.0 is pinned, picks as above.
  • A flow's tool step locks the pinned version.

This pin is set for a scope. It isn't a version's own pins, the tool versions that agent or flow version locked when it was published, which never change.

A pin counts only while its version is active. It changes no version already made: a pin reaches an agent or flow through its next version, as a new tool version does.

To try a new tool version without putting it in use:

  1. Pin the version in use, 1.1.0, for the project.
  2. Publish 1.2.0. Agent and flow versions made in the project still lock 1.1.0.
  3. Try it in an agent version whose range is the exact 1.2.0, and run that version by name (agentVersion). With the agent's pinned version set, runs that name no version keep the one they had.
  4. Move the pin to 1.2.0. The next agent and flow versions made there lock it.

Ask your coding agent: "I want to pin my tool's version in a project, then try a new version there without putting it in use." For reference, step 1 looks like this, with the project's id as its argument:

pin-a-tool.ts
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
const projectId = process.argv[2];
// Agent and flow versions made in the project lock acme.greet 1.1.0.
await kindgi.tools.pinned.pin('acme.greet', {
version: '1.1.0',
scope: { kind: 'project', projectId },
reason: '1.1.0 is the version we checked',
});
// Which version would the next agent or flow version made there lock, and why?
const locks = await kindgi.tools.pinned.resolve('acme.greet', { projectId });
console.log(locks.version, locks.via);

It prints the version the next agent or flow version made there locks, and how it was chosen:

1.1.0 pinned

kindgi.tools.pinned also has resolve (the version a flow version made in a project would lock, and why), list, unpin and history, as Pin a flow's version shows for flows. On the CLI, they're kindgi tools pin, pinned, pinned-versions, unpin and pin-history list. Pinning and unpinning need pin on the tool, which its admins have.

In an environment order, versions are made in the order's first environment, so a tool in an order is pinned there. A pin for a later environment would never act, and is refused with 409 environment-takes-promotions, naming the first environment in details.firstProjectId when you can read that project. A pin for an org or the tenant is refused with 409 pin-scope-spans-environments.

Some tools can't be pinned. A tool an MCP endpoint's catalog lists (its metadata has the catalog's kindgi.mcp key) follows its server: a call goes to the live server whatever the version, so a pin couldn't keep it still. The runtime's own tools follow the runtime. Pinning either is refused with 400 tool-not-pinnable; reference them with a range.

kindgi deploy publishes the agents in your pack. A published version never changes. So when an agent keeps its version number but its pins would change, because a tool it uses has a new version, the deploy registers the next free version and says so:

agent acme.matcher: registered new version 1.4.1 (1.4.0's pins changed: tool acme.score 1.0.0 → 1.1.0); set version: '1.4.1' in acme.matcher to match

Set that version in the agent's code, so the code and the runtime agree. Until you do, deploying again registers nothing new and says 1.4.0 runs as 1.4.1, registered by an earlier deploy.

The same happens when you change an agent's code and keep its version number: the deploy registers the next free version, because 1.4.0 is taken by a different definition.

A tool's version doesn't move for you. A tool version keeps the definition it was first published with, so code you change under the same version would run under the old one. When you change what a tool declares, such as its description, its input or output, its needs, mutating or its approvals, and keep its version, the deploy is refused with 409 tool-already-registered. The refusal names the fields that differ, and nothing is deployed. Give the tool a new version. A range that takes it (^1.0.0 takes 1.1.0) picks it up in the agent's next version, as above. A new image of the same tool keeps its version: the deploy points the version at the new image.

Flows are pinned the same way. A flow version pins its tool nodes to their latest at publish (a tool's pinned version, where a pin covers the flow's project), and its agent steps that don't name a version to what a run in the flow's project gets then: the pinned version that covers it, else the project's Latest. A tool change reaches a flow through a new agent version and a new flow version, in one deploy, with a line for each.

Where a version runs. An agent version runs in the project it was published in, and in each project a deployment that isn't retired shipped it into, such as the next environment kindgi promote carried its build to. So a change to the agent reaches each of them. client.agents.projects(agentId) (GET /v1/agents/{agentId}/projects) lists them, the agent's first project first, naming only the ones you can read. A project stays on the list after the versions shipped there are unregistered, because reinstating one would run there again. Each project says what a run there that names no version gets, and why: the pinned version that covers it, else its Latest.

An agent version published without pins, such as by an older runtime, gets a pinned next version at the first deploy:

agent acme.matcher: registered new version 1.4.1 (1.4.0 was published before pins; 1.4.1 pins its tools); set version: '1.4.1' in acme.matcher to match

An app that runs an exact version keeps running the old, unpinned one until you change it: agentVersion: '1.4.0', or --agent-version=1.4.0 from the CLI. An app that names no version gets the pinned one, since the deploy made it Latest.

A self-hosted runtime runs what your pack has

Section titled “A self-hosted runtime runs what your pack has”

A self-hosted runtime with one pack service, as in the Docker and Azure Container Apps setups, sends every tool call to that service, whatever version the call names. (On Cloud Run, each deployment has a pack service of its own, and a call goes to the newest deployment in the run's project that shipped the tool version it names.) A new build of your pack replaces the one the service runs. So a tool version runs only while the build the service runs has it, and so does each agent and flow version that locks it. What this page says about an older version still running holds there while its tools are in the pack.

When the pack no longer has a tool version that an agent or flow version locked, a run of that version still starts. A flow version fails at that tool's step, after the steps before it have run, and an agent version's call to the tool fails:

tool-version-mismatch: Tool "acme.score" is 1.1.0 in this pack; the caller asked for 1.0.0

After a deploy that gives a tool a new version:

  • Run the versions the deploy made. It registered a new version of each agent and flow whose pins changed, with a line for each. A run that names no version takes the newest, unless a pinned agent version or a pinned flow version holds it at an older one: move it.
  • Move the tool's pin too. While a pin holds the tool at its old version, the agent and flow versions made under it lock the old version, and fail the same way.
  • To keep an older version running, keep its tools in the pack. A pack can hold several versions of a tool side by side (A range picks a version), and the service runs each.

Until the service runs the new build, the versions the deploy made fail the same way, since the build before it doesn't have their new tool versions.

The many-tenants setup runs a pack service for each deployment, and sends each call to the newest one in the run's project that shipped the version it names. There, a version keeps running after later deploys while that service is up.

Unregistering a version stops it from being chosen. It doesn't stop the versions that pin it. Ask your coding agent: "I want a script that takes an old version of my agent out of use, and can bring it back." For reference, the code looks like this:

unregister-version.ts
import { createClient } from '@kindgi/sdk/client';
import type { AgentId, Semver } from '@kindgi/sdk/types';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
const agent = 'acme.greeter' as AgentId;
const version = '1.0.0' as Semver;
// No new run gets 1.0.0. What pins it keeps running it.
await kindgi.agents.versions.unregister(agent, version);
const unregistered = await kindgi.agents.versions.get(agent, version);
console.log(unregistered.unregisteredAt, unregistered.pins?.tools);
// 1.0.0 can be chosen again, unchanged.
await kindgi.agents.versions.reinstate(agent, version);

It prints when the version was unregistered, and its pins, which it keeps:

2026-10-06T05:42:12.899Z { 'acme.greet': '1.1.0' }

Tool and flow versions are unregistered and reinstated the same way. This is for a runtime you deploy to: under kindgi dev, the pack's files are the agents and tools, so you remove or change the file instead. There, an unregister is refused:

Under kindgi dev, the pack is the source of agents: change the agent in the pack's files and kindgi dev reloads it.
  • No new run gets it: not by a range, not as Latest, not by its exact number. A run that names it is refused with 404 agent-not-found:

    Agent "acme.greeter@1.0.0" is unregistered; reinstate it or run another version.

    A flow's version is refused the same way, as 404 flow-not-found:

    {"error":{"code":"flow-not-found","message":"Flow \"acme.refunds@1.0.0\" is unregistered; reinstate it or run another version.","requestId":"req-…"}}
    Excerpt

    A run that names no version gets the pinned version that covers it, or the project's Latest among the versions still registered. A run already going, one waiting for an approval included, finishes on the version it started.

  • A version pinned somewhere can't be unregistered. First unpin it or pin another version everywhere this one is pinned, as Release an agent version shows. Until then, it's refused with 409 agent-version-pinned, naming where it's pinned in details.scopes:

    acme.order-desk 1.0.0 is pinned in a scope: unpin, or pin another version there first, then unregister it.
  • A pinned flow or tool version can't be unregistered either (runtime 0.3.0 or later). Move or remove the pins first; until then, it's refused with 409 flow-version-pinned or 409 tool-version-pinned, naming the pins' scopes in details.scopes.

  • What pins it keeps running: an agent version pinned to an unregistered tool version, or a flow whose step names an unregistered agent version, runs as before.

  • You can still read it: it keeps its pins, and fetching it shows unregisteredAt. Reinstating it makes it choosable again.

  • Finding one to reinstate: the versions list of an agent, flow, tool or set of test cases includes unregistered versions when you ask, each with unregisteredAt. Ask with ?includeTombstoned=true, or kindgi agents versions <id> --include-unregistered, and the same for flows and tools.

  • A deploy brings a tool version back: deploying an image whose pack has an unregistered tool version reinstates it, as kindgi tools reinstate does, so the same image deploys again after a deploy that failed and rolled back. A tool version that names a model provider's key stays unregistered: the pack's tool names it too, and the deploy is refused with 400 deployment-validation-failed, as for any tool that names one (A model provider's key isn't a tool's).

  • One with every version unregistered is retired: fetching it answers 410, such as agent-gone. Its list leaves it out unless you ask with ?includeRetired=true, or kindgi agents list --include-retired, and the same for flows, tools and test-sets. Then it's listed as its highest version, with unregisteredAt. The lists of blocks, policies and release gates take ?includeRetired=true too, as client.blocks.list({ includeRetired: true }) does, and the same for policies and releaseGates. From the CLI, ask with kindgi blocks list --include-retired or kindgi release-gates list --include-retired.

  • A run waiting on an approval resumes after its version is unregistered.