# Agent and tool versions

> How a range picks a tool version, how a published agent version locks its tools, what a deploy does when they change, and what unregistering a version does.

This page explains which tool version a run of an agent gets, how a published agent version locks it, and what unregistering a version does.

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

## A range picks a version

A range picks the highest live version it allows. `^1.0.0` picks `1.2.0` when `1.0.0`, `1.1.0` and `1.2.0` are live, 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.

## A published agent version locks its tools

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:

```sh
curl "$KINDGI_API_URL/v1/agents/acme.greeter/versions/1.0.0" -H "Authorization: Bearer $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:

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

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:

```json
{
  "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.

## Deploys make the new version for you

`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:

```text
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`.

**Flows** are pinned the same way. A flow version pins its tool nodes, and its agent steps that don't name a version, to their latest at publish. A tool change reaches a flow through a new agent version and a new flow version, in one deploy, with a line for each.

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

```text
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 runs the latest gets the pinned one.

## What unregistering a version does

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:

- **TypeScript**

  `unregister-version.ts`

  ```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);
  ```

- **Python**

  `unregister_version.py`

  ```python
  from kindgi.client import Kindgi

  kindgi = Kindgi()  # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev

  # No new run gets 1.0.0. What pins it keeps running it.
  kindgi.agents.unregister("acme.greeter", "1.0.0")
  unregistered = kindgi.agents.get_version("acme.greeter", "1.0.0")
  print(unregistered.unregistered_at, unregistered.pins.tools if unregistered.pins else None)

  # 1.0.0 can be chosen again, unchanged.
  kindgi.agents.reinstate_version("acme.greeter", "1.0.0")
  ```

- **Java**

  `src/main/java/com/acme/app/UnregisterVersion.java`

  ```java
  package com.acme.app;

  import com.kindgi.client.Kindgi;
  import com.kindgi.client.models.Agent;

  public final class UnregisterVersion {
    public static void main(String[] args) {
      Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
      String agent = "acme.greeter";
      String version = "1.0.0";

      // No new run gets 1.0.0. What pins it keeps running it.
      kindgi.agents().unregister(agent, version);
      Agent unregistered = kindgi.agents().getVersion(agent, version);
      System.out.println(unregistered.unregisteredAt() + " "
          + (unregistered.pins() == null ? null : unregistered.pins().tools()));

      // 1.0.0 can be chosen again, unchanged.
      kindgi.agents().reinstateVersion(agent, version);
    }
  }
  ```

  The operation ids name the methods: `agents().unregister`, `agents().getVersion`, `agents().reinstateVersion`. The pins print as a Java map: `{acme.greet=1.1.0}`.

- **Scala**

  `src/main/scala/acme/app/UnregisterVersion.scala`

  ```scala
  package acme.app

  import com.kindgi.client.Kindgi

  object UnregisterVersion {
    def main(args: Array[String]): Unit = {
      val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
      val agent = "acme.greeter"
      val version = "1.0.0"

      // No new run gets 1.0.0. What pins it keeps running it.
      kindgi.agents().unregister(agent, version)
      val unregistered = kindgi.agents().getVersion(agent, version)
      println(s"${unregistered.unregisteredAt} ${Option(unregistered.pins).map(_.tools).orNull}")

      // 1.0.0 can be chosen again, unchanged.
      kindgi.agents().reinstateVersion(agent, version)
    }
  }
  ```

  The operation ids name the methods: `agents().unregister`, `agents().getVersion`, `agents().reinstateVersion`. The pins print as a Java map: `{acme.greet=1.1.0}`.

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

```text
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:

```text
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 the latest, not by its exact number. A run that names it is refused with `404 agent-not-found`:

  ```text
  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`:

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

  A run that names no version gets the latest live one. A run already going, one waiting for an approval included, finishes on the version it started.

- **A version live somewhere can't be unregistered.** First roll back, unpin or promote another version in each scope where it's live, as [Release an agent version](https://docs.kindgi.com/v0.1.8/guides/evals/release-an-agent-version.md#roll-back-or-unpin) shows. Until then, it's refused with `409 agent-version-live`, naming the scopes in `details.scopes`:

  ```text
  acme.order-desk 1.0.0 is live in a scope: roll back, unpin, or promote another version there first, then unregister it.
  ```

- **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:** an agent's, flow's, tool's or test set's versions list 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`.

- **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 `eval-suites`. Then it's listed as its highest version, with `unregisteredAt`.

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

## Next

- [Release an agent version](https://docs.kindgi.com/v0.1.8/guides/evals/release-an-agent-version.md)
- [Write an agent](https://docs.kindgi.com/v0.1.8/guides/agents/write-an-agent.md)
- [Choose the model an agent uses](https://docs.kindgi.com/v0.1.8/guides/agents/choose-a-model.md)
