# Kindgi 0.1.10

> What's new, fixed and changed in Kindgi 0.1.10, and the steps to upgrade to it from 0.1.9.

Released October 23, 2026. For the docs of this version, see [docs.kindgi.com/v0.1.10](https://docs.kindgi.com/v0.1.10/).

## Security fixes

- **The database's address in the runtime's log:** when a password with a raw `/`, `?` or `#` makes `KINDGI_DATABASE_URL` misread, the `Can't connect to the database` line names `KINDGI_DATABASE_URL` where it named the host, so no part of the credentials shows. Percent-encode those characters in the password: `%2F`, `%3F` and `%23`. If your password has a raw `/`, `?` or `#` and the runtime's logs leave the machine, change the password.

- **Secret names and your own secret manager:** a secret's name is now checked before it reaches your secret manager (`KINDGI_SECRETS_BACKEND=secret-manager`).

## New

- **Cloud Run module:** the database instance ends any session left idle in a transaction for 10 minutes (`database_idle_in_transaction_timeout_ms`, `0` for off). An instance made with an earlier copy gets the flag at its next apply, with no restart.

- **Deploy: an image the runtime can't read, or one past its bounds:** a deploy refused with `image-unverifiable` can also say `image-unreadable` (a layer that isn't an archive the runtime can read) or `image-limit-exceeded` in `details.cause`. For a limit, `details.limit` names which of the image's own it passed (`manifest`, `file`, `layer`, `read`, `unpack`), so the same image is refused again. `kindgi deploy` gives a hint for each ([`image-unverifiable`](https://docs.kindgi.com/reference/errors.md#image-unverifiable)). A deploy past the tenant's hour of image reads answers `429 rate-limit-exceeded` instead, with `details.limit: "tenant-unpack-hour"` and a `Retry-After` of the time until the hour has room for the image again, and `kindgi deploy` waits it out as it does the server's other limits, up to 10 minutes in all.

- **A TypeScript pack's image with only what its tools import:** `image: { dependencies: 'loaded' }` in `kindgi.config` downloads only the packages the pack's tools import, with their own dependencies, and what `image.keep` names, instead of every production dependency of the app. `kindgi build` cuts the app's `package.json` and lockfile down to them, checks the result against the app's lockfile, and the image installs it frozen. `'all'` stays the default. The build says how much of the lockfile the image installs, and which of the pack's own dependencies it leaves out. Most misses fail the build, naming the package. A package loaded by a name no import shows, such as a plugin named in a config, fails that call at run time, saying what to add to `keep`. A `node_modules` out of date with the lockfile fails the build, saying what to run. In a few cases, such as Yarn 2 or later or a local package (`link:`, `file:`, `workspace:`) among what the tools import, the image downloads everything and trims it after, and the build says why. A Python pack's `[tool.kindgi.image] dependencies = "loaded"` is refused for now ([Add Kindgi to a TypeScript or Node app](https://docs.kindgi.com/start/existing-app-typescript.md#what-the-packs-image-needs)).

- **Let a site's person back in:** a tenant admin lets someone removed in Kindgi back in through their site, with `kindgi sites restore-person <site-id> <user-id>` (`POST /v1/projects/{projectId}/sites/{siteId}/people/{userId}/restore`). They're the same person, with their history, and their next sign-in through the site gives them what their roles give then. Their API keys and sessions stay gone. It's refused with `409 site-person-restore-refused` when they weren't removed, when their site said they left it, or when they also sign in another way ([WordPress](https://docs.kindgi.com/sites/wordpress.md#pause-re-key-or-remove)).

- **Console: a closed conversation's page** ends with a **Conversation closed** step, at the time it closed, once its last page of messages is loaded. It no longer offers **Continue conversation**: it says `Closed: it takes no new turns`. A closed conversation named in the **Start run** form is flagged under **Conversation**, and **Start run** doesn't start it.

- **Console: the Conversations list** filters by status (**All**, **Open** or **Closed**, kept in the page's address), and **Load more** brings the next page, as on **Runs**.

- **Read your usage:** `GET /v1/usage` answers your tenant's usage, aggregated: runs started and ended (each with its class), approvals asked for and decided, model calls, and projects put in production or taken out. Each group has its count, the sum of its quantities and its first and last times, by project, agent, kind, class, state or day, over at most 92 days. A tenant admin reads every project; anyone else, the projects they may read. `kindgi usage` reads a month or a window of it, and the TypeScript and Python clients add `client.usage.read`. The operator reads usage across tenants with `GET /v1/system/usage` (`system:usage:read`). Both answer only on a runtime that counts usage ([`KINDGI_USAGE_METERING=on`](https://docs.kindgi.com/reference/env-vars.md#kindgi_usage_metering); [`GET /v1/usage`](https://docs.kindgi.com/reference/api/operations/usageread.md)).

- **Rename your workspace: its name, its slug, or both.** Its id stays, so whatever names it by its id keeps working, your keys and the CLI included. A tenant admin renames it with `kindgi tenant rename --name=<name> --slug=<slug>` (either or both), `PATCH /v1/tenant`, the TypeScript and Python clients' `tenant.update`, or **Rename** on the workspace's home page in the console; `kindgi tenant get` prints it. The console's addresses carry the slug, so a link with the old one opens a page that links to the workspace. An operator renames any tenant with `PATCH /v1/system/tenants/{tenantId}` under `system:tenants`. Two new refusals: `409 tenant-slug-in-config` when `KINDGI_AUTH_VERIFIED_DOMAINS` names the tenant by its slug, or names the slug asked for, and `409 tenant-setup-pending` while the tenant's creation hasn't finished ([Rename your workspace](https://docs.kindgi.com/guides/projects/organize-by-org-and-project.md#rename-your-workspace); [Serve many tenants](https://docs.kindgi.com/deploy/self-hosted/serve-many-tenants.md)).

- **Purge a tenant through the system API:** `POST /v1/system/tenants/{tenantId}/purge`, with `confirm` set to the tenant's slug, purges a closing tenant for good once its export window has passed, as `kindgi-runtime tenants purge` does, and answers when it's done. It takes a system key holding `system:tenants:purge`, which no other scope includes, or `kindgi:system`. The TypeScript and Python clients add `systemTenants.purge` (`system_tenants.purge`). The servers need the new `KINDGI_TENANT_PURGE_RECORD`, where purge records are kept: while it's unset, every purge is refused with `409 purge-record-unset`. One purge of a tenant runs at a time, through the route or the command: a second waits for the first ([Serve many tenants](https://docs.kindgi.com/deploy/self-hosted/serve-many-tenants.md#through-the-system-api)).

- **After a restore, purged tenants stay purged, even without their record:** each copy of the erasure ledger (`KINDGI_ERASURE_LEDGER_EXPORT`) also names every tenant marked purged, and `kindgi-runtime tenants repurge` reads the newest copy (`--ledger`, or there by default) as well as the purge records, so a tenant whose record is gone is purged again too. Each record and entry is checked against its tenant by when the tenant was created, which a rename doesn't change, and `--dry-run` says what named each tenant ([After a restore: tenants repurge](https://docs.kindgi.com/deploy/self-hosted/serve-many-tenants.md#after-a-restore-tenants-repurge)).

- **A key for the license renewal's counts:** a runtime serving one tenant now mints system keys too, with its own token, and only ones holding `system:license`. Such a key reads the license and its use and nothing else, so a renewal that runs on its own can send the deployment's use without holding the runtime's own token. There, a system key holding any other scope is refused on every route (`403 system-key-scope-not-served`). On Azure, the module's `license_usage_key_secret` names the vault secret that holds the key, and the module alerts when a run sends no counts ([A key for the counts](https://docs.kindgi.com/deploy/self-hosted/renew-the-license-key.md#a-key-for-the-counts)).

## Fixed

- **A database failure in the block registry, proposals or improvement passes:** a request that failed there got a bare `500` with no body, and nothing was logged. It's now answered as other routes answer it: a `GET` or `HEAD` the database was too busy for gets `503` ([`database-busy`](https://docs.kindgi.com/reference/errors.md#database-busy)) with `Retry-After: 5`, and any other failure the JSON `500` `internal-server-error` with its `requestId`, logged. The same goes for an inbound webhook delivery whose refusal couldn't be recorded. A schedule's fire that failed this way says `Could not …` in its `detail`, where it said `[object Object]`. Creating a proposal at the moment its change's open proposal is withdrawn is tried again once, where it failed.

- **A stop while the runtime starts:** a stop that comes before it serves, such as while it waits for its database, ends it within 3 seconds. Its log says `Stopped while starting (SIGTERM).`, and it exits with code 143, or 130 for Ctrl-C ([Restart the runtime](https://docs.kindgi.com/deploy/self-hosted/restart-and-check-health.md)).

- **A database that never answers:** the runtime asks again, 3 times in all, waiting its connect timeout each time and logging a warning before each new try. Then it exits with code 1, and its log says `no host answered within 30 s (3 tries)`. The connect timeout is the URL's `connect_timeout`, else `PGCONNECT_TIMEOUT`, else 30 seconds. A URL that lists several hosts is tried once. The `kindgi-runtime check` and `kindgi-runtime tenants` commands try once, say the same without `(3 tries)`, and exit with code 2 ([Where errors show](https://docs.kindgi.com/deploy/self-hosted/restart-and-check-health.md#where-errors-show)).

- **An `Idempotency-Key` retry of a change that answered `204`:** a retry with the same key and body was answered `500`, though the change wasn't made again. It's answered `204` again, with no body and `X-Idempotent-Replay: true`, as other replays are. This covers changing or deleting an org, a project or a team, changing or removing a project's or a team's member, and changing or removing a team's grant on a project.

- **More than one replica:** a run that another replica had just resumed or taken over could be failed by a sweep while it ran, with `Interrupted: the run was ready to continue, but nothing resumed it before its lease expired.` or `…but every attempt to resume it failed.` The sweep now checks again, under the run's lock, that the run is still one to sweep, and skips it if not.

- **Console: a conversation past 100 messages:** its page showed only the first 100, so the newest turns were missing. It now reads every message, showing each 100 as they come, with `Loading later messages…` until the last is in. Its **JSON** download is on only once every message is read, so part of a conversation can't pass for the whole.

- **`createWorkerTemplateRenderer`'s `close()`** (`@kindgi/agents/render-worker`): once the running renders had finished, `close()` could fail to settle, and a process whose last work was `await renderer.close()` exited with a non-zero code. It now settles within two seconds: a worker that doesn't stop on `SIGTERM` gets `SIGKILL` a second later. Running renders still finish before any worker is stopped.

- **An agent read back through the API** left out its `output` and `toolErrors`, though the agent applied both: `GET /v1/agents`, `/v1/agents/{agentId}`, its `/versions` and `/versions/{version}`, and the answer to `POST /v1/agents/{agentId}/versions`. Each is now in the answer when the agent sets it, as published.

- **A tool or an MCP endpoint read back through the API** left out its `secretScopes`, where each secret it names is read, though the server recorded it: `GET /v1/tools`, `/v1/tools/{toolId}`, its `/versions` and `/versions/{version}`, and `GET /v1/mcp/endpoints` and `/v1/mcp/endpoints/{endpointId}`. It's now in the answer as recorded, and so in the files `kindgi exports create` writes. A register body still can't carry it: one that does is refused with `400`. So a read of one that records secret scopes can't be sent back to register as it is: drop `secretScopes` first.

- **The API spec lists an artifact's `bucket` and `key`:** `BlobMeta`, in `GET /v1/artifacts` and `POST /v1/artifacts` answers, now has them, optional. A server whose storage gives a blob an S3-compatible address (`/s3/{bucket}/{key}`) answered them, and a client checking an answer against the spec refused it. The TypeScript and Python clients' `BlobMeta` have both. The answers don't change.

- **The console lists every test set:** the Test sets page, the Improve dialog and a proposal's Evaluate dialog showed only a project's first 100 judged test sets, and a test set's versions only its first 100.

- **`NODE_ENV` in `buildEnv` no longer leaves a pack's build steps without its dev dependencies:** `image.buildEnv` is set before the image's install, and with npm, Yarn 1 or pnpm 9 a `NODE_ENV: 'production'` there narrowed the install to production dependencies, so a build step's tool, such as the `prisma` CLI, was missing. The install now asks for dev dependencies itself when build steps need them, so a `NODE_ENV` in `buildEnv` doesn't change which dependencies it takes ([What the pack's image needs](https://docs.kindgi.com/start/existing-app-typescript.md#what-the-packs-image-needs)).

- **A promotion finds a model provider's key where the project's runs read it:** in the project of the environment you promote to, else its org, else the tenant. `GET /v1/deployments/{deploymentId}/values` reads it the same way. Before, a key stored only in the project or its org was listed under `Not ready in <environment>:` and the promotion was refused, unless the key was also stored for the whole tenant ([`kindgi promote`](https://docs.kindgi.com/reference/cli/promote.md)).

- **Sign-in starts without a secrets store** (`KINDGI_SECRETS_BACKEND=none`): Continue with Google, Microsoft or GitHub and the emailed link work, and adding or changing a workspace's own identity provider is refused (`409 identity-providers-need-secrets-store`). Before, the runtime didn't start with sign-in on ([Sign-in](https://docs.kindgi.com/deploy/self-hosted/sign-in.md)).

- **A SAML sign-in that fails lands on the sign-in page.** When the identity provider's page posted a SAML response that couldn't be accepted, the browser showed an error answer instead of the sign-in page. It now goes to the sign-in page with `sign-in-unavailable`, and the runtime's log says why.

- **Cloud Run module: the first apply writes `sql_connection_name`.** Step 3 reads it with `terraform output -raw sql_connection_name`, and after step 1's targeted apply the output was missing, so the command failed. It's there now, with or without `database_host_instance`. With a host instance, its checks (a public IP, Postgres 16 or later) now run in step 1 too, not only in the full apply after it ([Deploy on Google Cloud Run](https://docs.kindgi.com/deploy/self-hosted/cloud-run.md#3-the-secrets)).

- **A promotion copies the env your pack declares, and checks it first** (with `KINDGI_PACK_ROUTING=per-deployment`). A promoted deployment didn't record the names its pack declares, so `GET …/deployments/{deploymentId}/pack-env` listed none of them, and a host that gives a pack service its values from that list gave it none: under the default check, the service answered `503` (`missing env`). It records them now, and a deployment promoted before 0.1.10 gets them when its build is promoted into that environment again. A required name that's set nowhere in the target environment, or set there both as a secret and as a plain value, is now listed under `Not ready in <environment>:`, and the promotion is refused (`409 promotion-values-not-ready`), as `--check` shows ([Declare the environment your code reads](https://docs.kindgi.com/guides/secrets/pack-env.md#in-a-deployment)).

## Changed

- **An agent can't set `conversationPolicy.autoCloseAfterInactiveSeconds` (breaking):** it was accepted and never acted on. A conversation closes only when someone closes it: **Close** in the console, `kindgi conversations close`, or `POST /v1/conversations/{id}/close`. `defineAgent`, the Java, Scala and Python indexers, and `POST /v1/agents` (`400 validation-failed`) now refuse any value, so a pack that sets it doesn't build. A pack built with it before still deploys, and an agent already deployed with it keeps working; its answers leave it out of `conversationPolicy`.

- **A pack image without build steps installs production dependencies only:** `kindgi build` used to install your app's dev dependencies too and drop them after. With no build steps, such as `prisma()`, it now installs production dependencies only, so the image's install downloads less. With build steps, it still takes dev dependencies for them and drops them after, and with Yarn 2 or later it always does. The build says which. While building, your modules now load with `NODE_ENV=production`, as in the deployed service, unless `buildEnv` sets `NODE_ENV` ([What the pack's image needs](https://docs.kindgi.com/start/existing-app-typescript.md#what-the-packs-image-needs)).

- **The tools guide and skill cover a package that ships only CommonJS:** a pack's code loads with Node's own `import()`, so Node gives a default import of such a package its whole `module.exports`, with the class on its `default`, where Vitest, Next.js and tsx in a CommonJS app give the class itself. A tool could pass `kindgi test` and then fail under `kindgi dev` with "is not a constructor". The `kindgi-authoring-tools` skill and the guide take the class either way with a small typed helper, for a dynamic `import()` too ([A CommonJS package in TypeScript](https://docs.kindgi.com/guides/tools/write-a-tool.md#a-commonjs-package-in-typescript)).

- **The providers skill leads with the pack's config:** the `kindgi-authoring-providers` skill used to have a coding agent run `kindgi providers register --preset=…`, which registers the provider in one worktree's dev database only, so another worktree, a `--reset` or a teammate's `kindgi dev` had none. It now declares the provider in the pack's config and commits it: the declaration names a key, never holds it, and `kindgi dev` registers it at each boot. On a runtime `kindgi dev` doesn't run, register by hand once per runtime, with the key set in each environment first: `kindgi deploy` doesn't register providers. The getting-started skills, the Java and Scala agent-authoring skills and the [Models](https://docs.kindgi.com/guides/models.md) guide say to commit the declaration too.

## Upgrade steps

- **With your own secret manager** (`KINDGI_SECRETS_BACKEND=secret-manager`), **check your secrets' names before you upgrade.** A secret stored under a name outside the rule for new names (ASCII letters, digits, `.`, `-` and `_`; starting with a letter, digit or `_`; no `.` at the end and no two in a row; at most 255) can no longer be read or rotated: it's refused with `secret-store-error`. Store its value again under a name that fits, point what uses it at the new name, then revoke the old one. Revoking it leaves its value in your secret manager, so delete it there too. `--hard` can't erase it either: the old name is revoked as without it, so `kindgi secrets list --include-revoked` still shows it, and the answer is `secret-store-error` to say the value is left there.

- **If an agent sets `conversationPolicy.autoCloseAfterInactiveSeconds`,** take it out before you build its pack: the build now refuses it. It never closed anything, so taking it out changes nothing.

- **A small dev deployment on Cloud Run** (the module with `network_mode = "none"`, at 0.1.8 or 0.1.9) can be upgraded with the module's `upgrade.sh`. It runs the upgrade checks against the database and stops if one fails, backs up Kindgi's database as an export, shows the plan, and applies it only when you type `yes`. If the new revision doesn't come up, it prints the way back rather than going back on its own. It sets the new image in your var-file, keeping the previous file next to it, and it grants the Cloud SQL instance's service account `roles/storage.objectAdmin` on the backup bucket, a grant that stays. See `./upgrade.sh --help` ([Deploy on Google Cloud Run](https://docs.kindgi.com/deploy/self-hosted/cloud-run.md#operate-it)).
