# Kindgi 0.1.8

> What's new, fixed and changed in Kindgi 0.1.8, its known issues, and the steps to upgrade to it from 0.1.7.

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

## Security fixes

- **A key limited to a project could file a conversation outside it.** A conversation started with a key limited to a project (`--project`) that named no project was filed in the tenant's default project, not the key's: the key couldn't read it afterwards, and the default project's readers could. A run continuing that conversation was filed there too. In 0.1.8 a conversation that names no project goes to the key's project, and a key can't continue a conversation outside its project (`403 key-project-mismatch`). 0.1.5 to 0.1.7 are affected ([API keys](https://docs.kindgi.com/v0.1.8/deploy/people-and-keys/#api-keys)).
- **Some clouds' metadata addresses weren't refused.** Kindgi refuses a cloud's metadata address wherever tenant configuration names a host (at `KINDGI_TENANT_HOST_ACCESS=deployed` and above) and in a webhook's URL outside development mode. Before 0.1.8 it refused only link-local addresses and AWS's IPv6 one, not the others that clouds document: Azure's WireServer (`168.63.129.16`), Alibaba Cloud's metadata service (`100.100.100.200`), the EKS Pod Identity Agent (`fd00:ec2::23`), Compute Engine's for IPv6-only VMs (`fd20:ce::254`), Akamai's (Linode's, `fd00:a9fe:a9fe::1`) and Scaleway's (`fd00:42::42`). Azure's counted as a public address, so a host or a webhook could name it at every level; the others counted as private, so a host could name them at `deployed`, and a webhook under `KINDGI_WEBHOOK_PRIVATE_NETWORKS=allow`. In 0.1.8 they're refused wherever link-local addresses are, and an IPv4 address written inside an IPv6 one as IPv4-translated (`::ffff:0:a.b.c.d`) is read as that IPv4 address, as the other forms already were. These ranges, which no service is reached at, are refused too: `192.88.99.0/24`, `2001:2::/48`, `2001:10::/28`, `3fff::/20`, `5f00::/16` and `100:0:0:1::/64`. Every release before 0.1.8 is affected.
  - A webhook URL refused for a link-local or metadata address now says "link-local and cloud metadata addresses are not allowed". Under `KINDGI_WEBHOOK_PRIVATE_NETWORKS=allow`, the other refusals say "loopback and reserved addresses are not allowed", where they said "loopback, link-local and reserved addresses are not allowed". A script that matches the old words needs the new ones.
- **The S3-compatible routes read a whole upload before checking its signature.** `@kindgi/api` serves S3-compatible routes (`/s3/*`) to an app that embeds it and sets its `s3Credentials` option. They read a request's whole body into memory before they checked its signature, so a caller with no credentials could make the server hold a body of any size. Kindgi's runtime doesn't serve these routes, self-hosted or hosted. In 0.1.8 the signature is checked first, from the headers, and the body is read only up to `artifactMaxBytes` (100 MiB by default): a larger one is refused with S3's `400 EntityTooLarge`. A larger object goes up as a multipart upload, each part under the limit. A header-signed body is checked against its `x-amz-content-sha256` when it's empty too. `@kindgi/api` 0.1.8 needs `hono` 4.13.8 or later.
- **A tenant's policies were skipped when they couldn't be read.** If reading a tenant's policies failed, such as on a database error, the tenant was treated as having none. A turn ran without its approval, model-routing and tool-errors policies, and a removed model provider that a retention policy keeps for good could be deleted with its adapter. In 0.1.8 such a read fails instead: the turn fails, nothing is deleted, and the policies API answers `500` rather than an empty list. Every release before 0.1.8 is affected; the provider deletion, 0.1.4 to 0.1.7.

## New

- **Tenants that don't trust one another:** a new level, `KINDGI_TENANT_HOST_ACCESS=isolated`, is for a runtime whose tenants don't trust one another. It refuses all that `deployed` does, and also private networks wherever tenant configuration names a host. A model provider can't sign in with the runtime's own cloud credentials there: Gemini on Vertex AI needs a service-account key in `secret_ref`, and Bedrock's `auth: aws-identity` (the `bedrock` preset's default) and Azure OpenAI's `auth: entra` are refused. The runtime won't start with it in development mode, or with `KINDGI_WEBHOOK_PRIVATE_NETWORKS=allow` or `KINDGI_AUTH_PRIVATE_IDP_ORIGINS` ([What a tenant can't make the server do](https://docs.kindgi.com/v0.1.8/concepts/security/#what-a-tenant-cant-make-the-server-do)).

- **One runtime for many tenants:** with `KINDGI_TENANCY=multi`, a runtime serves every tenant created through `POST /v1/system/tenants`, and takes each request's tenant from its key or session. It starts only with `KINDGI_TENANT_HOST_ACCESS=isolated`, per-deployment routing, OpenFGA with a key, the production gate and usage metering, and a start without any of them names each. The deployment's own token reaches only `/v1/system/*`. A closed tenant can still export its data for 30 days, and is then purged, and `kindgi-runtime tenants repair --all` sets up what an upgrade adds for each tenant ([Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants/)).

- **Memory limits for agent turns:** two new runtime settings limit the memory running agent turns hold, both off unless set: `KINDGI_TURN_MEMORY_PER_TENANT_MIB` for one tenant's turns, and `KINDGI_TURN_MEMORY_MIB` for every tenant's together. A turn is charged by size before each model call. Past either limit, the turn fails before the model is called, with `429 turn-memory-limit-reached` (`details.limit` is `tenant` or `server`). Every tool call it finished stays in the conversation, so a new turn there doesn't call it again. A resumed run is charged its journal first, and one whose journal alone is past a limit fails with the same code. One that only finds no room yet waits: a top-level run goes back to `pending`, first in line, until there is room; a flow's step is left to the lease sweeper, whose third try without room fails it (about 15 minutes at the default `KINDGI_RUN_LEASE_MS`). The startup log shows both settings when either is set ([Environment variables](https://docs.kindgi.com/v0.1.8/reference/env-vars/)).

- **A failed run's details:**

  - A failed run's `failure` can carry `details` when its code has them, wherever the run is read back: `GET /v1/runs/{runId}`, the runs list, and the answer to a `POST /v1/runs` that waited. For `rate-limit-exceeded`, `details` has `limit` (`schema-compile-time` or `template-render-time`) and `budgetMs`, the names the `429` uses. It never has the wait: the start's `429` gives it in `Retry-After`. Other codes, and runs from an older runtime, have no `details`.
  - A `run.finished` webhook's run can carry `failure` (`code`, `message`, `cause` when set, and `details`) beside `failureMessage`, which stays. It's there only when the run failed, and only from a runtime that sends it: read `failureMessage` when it's absent. `parseEvent` (`@kindgi/sdk/webhooks`) reads it.
  - The run stream's `run.failed` still carries only the message. For the code and `details`, read the run (`GET /v1/runs/{runId}`).

- **A license renewal can send the deployment's use:**

  - `kindgi license renew --runtime <url> --runtime-token <ref>` sends the deployment's use with a non-production key's renewal: counts only (runs started, people, projects in production, and the runtime's version), with no content, names or ids. access.kindgi.com keeps and reads them from the date on its privacy page. Without the two flags, a renewal carries none, as before ([Renew the license key](https://docs.kindgi.com/v0.1.8/deploy/renew-the-license-key/)).
  - A renewal whose counts are above what a non-production key covers is refused with `held-for-review`, and gets no new key until Kindgi reviews it.
  - `GET /v1/license/usage?since=<time>` answers those counts, for the deployment's own token or a system key holding `system:license`.

- **Coding agents:**

  - `kindgi init` and `kindgi skills sync` write Kindgi's skills to `.agents/skills/` as well as `.claude/skills/`. Codex and Gemini CLI read skills from `.agents/skills/`. Each folder holds the same copy of each skill, and `kindgi skills sync` checks each copy on its own: a copy you edited is kept unless you pass `--force`, and a folder you deleted is written again ([Coding agents](https://docs.kindgi.com/v0.1.8/start/coding-agents/)).
  - `kindgi skills sync` prints its summary on stderr, one block per folder, unless you pass `--json` or `--quiet`. In its JSON, each outcome names its `folder`.
  - A `.prettierignore` your project already has gets `.agents/skills/kindgi-*/` beside `.claude/skills/kindgi-*/`, from `kindgi init` in an existing TypeScript app and from `kindgi skills sync`, which never creates one.
  - The templates' `AGENTS.md` name both folders, and say which commands are yours to run in your own terminal.

- **Kindgi Cloud, from the CLI:** `kindgi build --hosted`, or the env block's `build: 'hosted'`, builds your pack on Kindgi Cloud's build service. `kindgi deploy --hosted` builds and deploys it, then waits up to 5 minutes for its pack service to be ready. It signs with a key of yours (`kindgi key create`, then `kindgi key trust` once), named with `--signing-key` or the environment's `signingKey`.

- **The CLI waits out a rate limit:** `kindgi deploy`, `kindgi flows publish`, `kindgi agents publish` and `kindgi blocks publish` wait out a `429 rate-limit-exceeded` that says when to try again (`Retry-After`), then send the same request again, printing one line on stderr for each wait. They wait 10 minutes at most in all. Past that, on a `429` without `Retry-After`, or when Ctrl+C stops a wait, they fail with the error, as before. A script that runs them can take up to 10 minutes longer to fail.

- **Read a run's journal a page at a time:** `GET /v1/runs/:runId/journal` takes `limit` (1 to 100) and `cursor`. Each page has `hasMore`, and `nextCursor` while more remain; in Python, `runs.journal` takes `limit` and `cursor`, and the page has `has_more` and `next_cursor`. Without `limit`, the whole journal comes in one answer, as before. A `limit` that isn't a whole number of 1 or more, or a malformed `cursor`, is `400 bad-input`. `kindgi runs journal --limit --cursor` and the TypeScript client took them already; a runtime before 0.1.8 answers the whole journal ([Read a run's journal](https://docs.kindgi.com/v0.1.8/guides/runs/read-the-journal/)).

- **Check a restore's secrets:** `kindgi-runtime check stored-secrets` decrypts each live version of every stored secret with the runtime's keys, and names by id each one that doesn't. Run it after a restore, before the runtime serves: a restore brings back the database, not the keys ([Back up and restore](https://docs.kindgi.com/v0.1.8/deploy/back-up-and-restore/#restore-into-a-fresh-database)).

- **Capabilities for a tenant's first admin:** the person a tenant is created with (`firstAdmin` on `POST /v1/system/tenants`, and whoever signs up for Kindgi Cloud) holds every capability but `secrets:reveal`: trusting signing keys, and writing secrets and environment values. Their sign-in sessions carry them, and the console's **Create a key** offers them under **What it can change**, so a key made there can trust signing keys and sync secrets ([Who can do what](https://docs.kindgi.com/v0.1.8/deploy/people-and-keys/#who-can-do-what)).

## Fixed

- **Authorization on a managed Postgres:**

  - With authorization on, a runtime whose database user has no `BYPASSRLS` (and isn't a superuser), the usual case on a managed Postgres, now starts. On 0.1.6 and 0.1.7 it didn't ([Known limitations in 0.1.7](https://docs.kindgi.com/v0.1.8/deploy/operate/#known-limitations-in-017)).
  - A runtime that shares its database with others brings only its own tenants' authorization stores up to the current model, and drains only their changes to OpenFGA.
  - When the runtime can't read or update one tenant's part of the queue of permission changes it sends to OpenFGA, it logs an error and goes on with the other tenants. That tenant's changes apply on a later pass, with no restart ([Run with authorization](https://docs.kindgi.com/v0.1.8/deploy/authorization/)).

- **Guardrails scoped to flows:** a guardrail whose `scope` lists `flows` now checks the turns those flows' steps start, and replays of those turns. In 0.1.7 it never checked a turn: a turn didn't say which flow it ran in, so the guardrail skipped it without a word. A `halt` one can now stop turns that 0.1.7 let through. A turn outside those flows is still skipped ([Which agents it covers](https://docs.kindgi.com/v0.1.8/guides/guardrails/halt-or-record/#which-agents-it-covers)).

- **Requests:**

  - An idempotency key compares request bodies byte for byte. A key limited to a project that sent one `Idempotency-Key` with another body now gets `409 idempotency-key-body-mismatch`, and a binary upload sent with a key is stored as it was sent.
  - A key limited to a project can upload a run's artifacts. An upload that names no project goes to the key's project, and one that names another project is refused with `403 key-project-mismatch`.

- **Secrets:** a secret the store can't take for its name or its value is refused with a `400` and a code that says why. It was a `500 secret-store-error`.

  - Under `kindgi dev`, a name that isn't capital letters, digits and underscores, starts with a digit, or is `KINDGI_*` answers `secret-name-invalid`, and a name starting with `kindgi.` answers `reserved-name-prefix`. That covers storing a secret, setting the tenant's configuration, and registering a webhook trigger that generates its secret, which is now refused before anything is stored.
  - Under `kindgi dev`, a value no env-file quoting can hold so that it reads back unchanged answers `secret-value-invalid`. The message doesn't repeat the value ([Under `kindgi dev`](https://docs.kindgi.com/v0.1.8/guides/secrets/store-a-secret/#under-kindgi-dev)).
  - With `KINDGI_SECRETS_BACKEND=postgres`, a name starting with `kindgi.` answers `reserved-name-prefix`, and a value over the store's size limit answers `secret-value-too-large`, as the in-memory store already did.
  - Rotating a secret in the Postgres store answers the same refusals with the same codes, and the in-memory store answers an oversized new value with `secret-value-too-large`.

- **`kindgi dev` says what stopped it:** when something around it refuses what it needs, `kindgi dev` says what was refused and what to do, and so do `kindgi doctor` and `kindgi auth registry` for Docker.

  - A `docker` that can't be started (`spawn docker EPERM`), a Docker socket that refuses the connection, and an engine that doesn't answer each get their own message. `kindgi dev` no longer tries to pull the runtime image when it can't reach Docker, and no longer suggests installing Docker, or another Postgres, when Docker is there.
  - When it isn't allowed to listen on any port of `127.0.0.1` (`listen EPERM`), it says so, where it read every port as in use. A pack service that can't listen gets the same.
  - A file or folder it can't write (`EROFS`, `EACCES`, `EPERM`) is named, with the code.
  - Each of these ends with what a coding agent's sandbox needs: "If a coding agent runs this, its sandbox is likely what stops it: run `kindgi dev` in your own terminal instead."
  - A `docker logs` that can't start while the runtime starts no longer stops `kindgi dev`.

- **Build logs:** a build's log stream that's cut before its end is opened again, and the lines it replays are printed once.

- **MCP servers:** an MCP server whose `outputSchema` doesn't compile no longer fails its tool listing: its tools are listed, and that tool's results fail with "The server's outputSchema doesn't compile: …" ([MCP servers](https://docs.kindgi.com/v0.1.8/guides/tools/mcp-servers/)).

- **Proposals:** evaluating a proposal whose model settings aren't valid model settings is refused with `400 validation-failed`, and nothing is stored.

- **Logs:** a `501` answer is logged at `info`, not `error`: the runtime doesn't offer what was asked, and nothing failed. So under `kindgi dev`, the console's permissions read no longer prints an `ERROR` line. Every other answer of 500 or more stays at `error` ([Logs](https://docs.kindgi.com/v0.1.8/deploy/logs/#requests)).

- **The license key:** the license renewer's key (`kgi_lrk_…`) is masked in logs, like every other Kindgi token.

- **Listing secrets:**

  - `kindgi secrets list --include-revoked`, and `includeRevoked` in the clients (`include_revoked` in Python, which gains it), now list revoked secrets too, with their `revokedAt` and `revokeReason`, never a value. Before, the API dropped the flag.
  - The list comes in pages, by name: 25 to a page, `--limit` up to 100, and while more remain a `nextCursor` to pass as `--cursor`. The `kindgi dev` store used to stop at the first page, so a 26th secret was out of reach. A `cursor` the list didn't issue is `400 bad-input`.
  - Names come in byte order, whatever the store or the database's collation: capital letters before lowercase ones, and `-` and `.` before letters (`ORDERS`, `orders-db`, `orders.db`, `ordersa`). A runtime on Postgres used to follow the database's own collation.
  - A secret's versions, `GET /v1/secrets/:name/versions`, come in pages the same way, oldest first.
  - A runtime before 0.1.8 returns only the first page of either list, with no `nextCursor`, and ignores `--include-revoked` ([Store a secret](https://docs.kindgi.com/v0.1.8/guides/secrets/store-a-secret/)).

- **Routing names a model provider the runtime can't set up:** a provider registered for the tenant whose settings the deployment no longer allows used to leave only `422 capability-routing-failed`, "No registered provider satisfies the capability declaration", with the reason in the runtime's log alone. Now the message names the first such provider and the setting its adapter refuses (or, when the adapter fails to build it, says the runtime's log has why), and counts the rest; the reasons list each under `provider set up` with the code `provider-setup-failed`, and `GET /v1/providers/{providerId}/check` gives the adapter's own words ([Choose a model](https://docs.kindgi.com/v0.1.8/guides/agents/choose-a-model/)).

- **A tenant SSO sign-in whose identity provider's userinfo request fails** (the provider can't be reached, or `KINDGI_TENANT_HOST_ACCESS` refuses its address) now ends at the sign-in page with [`sign-in-unavailable`](https://docs.kindgi.com/v0.1.8/reference/errors/#sign-in-unavailable), and the runtime logs why under `sso`. It ended at an empty 500 page. No session is created.

## Changed

- **A runtime release stops starting 90 days after it's released:** from 0.1.8 until Kindgi 1.0, in every mode, unless a production key exempts it. Releases before 0.1.8 don't expire. A running server keeps running, but a platform that restarts instances on its own (Cloud Run, Container Apps) doesn't bring one back after that day. The startup banner, the console and `kindgi dev` warn from 30 days before, and `GET /v1/license` reports it as `preview` ([Preview releases stop starting after 90 days](https://docs.kindgi.com/v0.1.8/concepts/licensing/#preview-releases-stop-starting-after-90-days)).

- **MCP endpoints with a credential in `config.headers`:** registering an MCP endpoint with a header whose name says it carries a credential (`Authorization`, `X-Api-Key`, …) in `config.headers` is refused: `invalid-mcp-endpoint`, reason `credential-in-headers`. Send it by reference instead, with `auth: { scheme: "header", headers: [...] }` ([MCP servers](https://docs.kindgi.com/v0.1.8/guides/tools/mcp-servers/#not-in-configheaders)). An endpoint registered that way before 0.1.8 keeps working. It reads back `[redacted]` for each such header, its requests never follow a redirect, and `kindgi doctor` warns about it.

- **Gemini through a proxy:** the Gemini adapter calls Google's own host for the Developer API and for each Vertex AI location. `GOOGLE_GEMINI_BASE_URL` and `GOOGLE_VERTEX_BASE_URL` in the runtime's environment no longer move its calls. If you set either to send Gemini calls through a proxy, they now go to Google directly.

- **What Kindgi reads from the servers it calls:**

  - An HTTP tool's `timeoutMs`, and a run's cancel, now cover reading the answer, and an answer over 10 MiB fails the call ([HTTP tools](https://docs.kindgi.com/v0.1.8/guides/tools/http-tools/)).
  - Kindgi asks the servers it calls for you (MCP servers, HTTP tools, a model's base URL, your identity provider, an image registry) for answers sent uncompressed (`accept-encoding: identity`). An answer sent compressed anyway is refused, never decoded.
  - An answer is read up to 10 MiB. An event stream (an MCP server's) is bounded per event, so a long stream keeps going.
  - Your identity provider and an image registry have 120 s to start answering.
  - Verifying an image reads its manifest up to 4 MiB and the file it needs up to 10 MiB, and cuts a layer that sends more than its manifest declares. The deploy answers `400 image-unverifiable`, its message naming the limit.
  - A layer that isn't a readable tar archive is refused, naming the archive's error: `Layer … isn't a readable tar archive: TAR_ENTRY_INVALID: checksum failure`, or `…: TAR_BAD_ARCHIVE: Truncated input …` for a file cut short, which was read cut short before.
  - A pack service's refusal is quoted up to its first 4 KiB, ending in `…` when there was more.

- **Request size and depth:**

  - A request body over 10 MiB is refused with `413 request-too-large`, whose `details.maxBytes` is the limit. JSON that nests objects and arrays more than 128 levels deep is refused with `400 bad-input`, whose `details.maxDepth` is the limit. Both were accepted before. An artifact upload keeps its own size limit (`413 artifact-too-large`), and its body isn't read as JSON; the webhook receiver keeps its own too (`413 webhook-body-too-large`).
  - A signed webhook delivery whose JSON event nests more than 128 levels deep is refused the same way, and starts no run. The trigger's fires record it with `detail: body-too-deep` ([Start runs from webhooks](https://docs.kindgi.com/v0.1.8/guides/webhooks/start-runs-from-webhooks/)).

- **Schemas:** a JSON schema over 1 MiB as JSON, or nested deeper than 64 levels, is refused when it's published, and nothing is stored. One stored before 0.1.8 is refused the first time it's used. That's every schema you write: a tool's input and output schemas (an MCP server's tools too, when it lists them), an agent's typed output, a flow node's output, a settings block's schema and a guardrail's config schema.

- **Guardrails:** a built-in check's regular expression (`must-cite`'s `sourcePattern`, `output-matches`' `pattern`) longer than 8,192 characters with its flags is refused like one that doesn't compile: registering the guardrail answers `422 guardrail-config-invalid`, and deploying a pack with one fails with `deployment-validation-failed`. A guardrail registered before 0.1.8 with one can't run its check ([Use a built-in check](https://docs.kindgi.com/v0.1.8/guides/guardrails/use-a-built-in-check/)).

- **Prompt templates:**

  - Template renders on a self-hosted server and under `kindgi dev` now have a memory limit: 16,777,216, in the template engine's memory units, roughly the characters of the strings the render builds and the items of its arrays. No setting changes it. A render past it fails the agent's turn with `model-invocation-failed`: `Prompt render failed: Template render failed: the render went over its memory limit of 16777216`.
  - An agent's instructions are checked as Liquid when the agent is defined, published (`POST /v1/agents`) or deployed. Instructions that aren't valid Liquid, or that use an unknown filter, are refused there; before, they were accepted and failed at every render. A deployment with such an agent is refused whole. To write `{{` or `{%` as text, put it in `{% raw %}…{% endraw %}`.
  - A prompt block whose template uses an unknown filter is refused when it's published. Rendering it always failed.
  - Partials (`{% include %}`, `{% render %}`, `{% layout %}`) are refused wherever a template comes in, with "templates can't use include, render or layout". A template stored with one before 0.1.8 fails when it's rendered (`render-failure`), with the same message; nothing stored is changed.

- **What a project reaches:**

  - A run that names a project runs only an agent or flow that project reaches. Naming one it doesn't is refused with `400 reference-out-of-scope`: `This project can't reference agent "<id>": it isn't registered in this project, its org or the tenant`.
  - Registering an agent, flow or guardrail is refused the same way when it names a tool, agent, flow, guardrail or data block its project doesn't reach, or one that doesn't exist yet: register a guardrail before the agent that names it.
  - A guardrail names a pack check only when a guardrail of the same project names it already: a deployment brings its checks with its guardrails. Built-in checks work in every project.
  - Under `kindgi dev`, the echo provider's `demo.echo` tool is every project's, and the seeded `demo.echo-agent` is the Default project's: another project needs agents of its own.
  - A conversation opened before 0.1.8 in a project its agent isn't registered for can't be continued: `404 agent-not-found`, `The conversation was opened with agent "<id>@<v>", which is no longer registered.` The runtime names such conversations when it starts.
  - An MCP endpoint's catalog tools are reached by the endpoint's scope.
  - A tool id is served as an MCP catalog tool only while every version of it that counts is one the catalog sync wrote. A version published under the same id (an HTTP tool, say) turns it off in every project until that version is unregistered; an unregistered version still counts while a flow or agent version pins it. The runtime logs `mcp-tool-unroutable` for such a tool: `MCP tool <id>@<v> isn't served: <id> also has a version the MCP catalog sync didn't write (<versions>), so no version of it is a catalog tool. Unregister it; one a flow or agent version still pins counts until those versions are replaced.` Retiring an endpoint unregisters only the versions the sync wrote.
  - A tool's metadata can't set `kindgi.` keys: `POST /v1/tools` and deploys refuse it.

- **Secrets:**

  - A tool version, MCP endpoint or webhook trigger registered on 0.1.8 records where each secret it names is read. A name its record lacks is read nowhere, never at the tenant. Only an item registered before 0.1.8, with no record at all, reads at the tenant as before.
  - A webhook trigger registered on 0.1.8 reads a signing secret set for its project or its org. On 0.1.7 such a secret wasn't read, and every delivery was refused ([Register the trigger](https://docs.kindgi.com/v0.1.8/guides/webhooks/start-runs-from-webhooks/#2-register-the-trigger)).
  - A tool version whose publisher administers no scope an optional secret could be read at fails every call with `secret-unavailable`, as it does for a required one: `tool "<id>" reads no secret "<NAME>": whoever published this version administers no scope it could be read at. A project admin publishes a new version.` The call is refused before any of its secrets is read. An optional secret that simply isn't set still lets the call go on without it.
  - Unregistering a version stops it being chosen, not the pins that hold it: a flow version runs the agent version each agent step holds and the tool versions it pins, and an agent version runs the tool versions it pins. The check and the startup warning also list an unregistered tool version a flow or agent version still pins, marked `(unregistered, still pinned)`. Its fix: publish a new tool version; for an agent that pins it, publish an agent version whose range takes the new one and make it live (`kindgi agents promote`); then publish a new version of each flow that pins the old tool or runs the old agent version, changing an agent step that names that version. The order matters: a flow pins each agent at its live version when the flow is published, so a flow published before the new agent version is live pins the old one again. The startup scan also warns about an unregistered agent version a flow version still runs.

- **Conversations and memory:**

  - Running a turn as an end user (`participantId`) takes being that person, or `write` on the project, as reading their memory does; anyone else gets `403 participant-not-allowed`. Nothing changes for existing callers: every role that can run an agent holds `write`.
  - With authorization on, an agent whose `memory.remember.scope` is `'tenant'` proposes a fact for the whole tenant only in a turn whose caller, a person or a key, is a tenant admin: facts every project reads are a tenant-level decision. With anyone else the call doesn't run, and the model is told it needs a tenant admin. The other scopes are unchanged ([Give an agent memory](https://docs.kindgi.com/v0.1.8/guides/agents/give-an-agent-memory/)).

- **Webhooks:** IPv6 site-local addresses (`fec0::/10`, deprecated since 2004) count as private. Outside development mode they're refused, as other private-network addresses are, unless `KINDGI_WEBHOOK_PRIVATE_NETWORKS=allow`, which opens them too.

- **Suspending a tenant or a project** (a runtime serving many tenants):

  - A suspended tenant's or project's webhook deliveries are answered `202` with `skipped: tenant-suspended` or `skipped: project-suspended`, as a paused trigger's are, so a sender such as WooCommerce doesn't count them as failures. The event isn't kept: a sender that can resend by hand can send it again once the tenant or project is restored.
  - A closing tenant refuses webhook deliveries with `409 tenant-closing`. When the tenant's state can't be read, a delivery gets `503 tenant-state-unavailable` with `Retry-After`.
  - A schedule's or a webhook trigger's fire that a suspension stops, or whose run starts as its tenant is suspended or starts closing, is recorded `skipped`, with the reason in its `detail`. It doesn't count toward a schedule's auto-pause. A tenant's suspension isn't caught up after the restore, whatever the schedule's `catchUp`.
  - Suspending a tenant or a project that's already suspended keeps the first suspension's time and reason.

- **The CLI:**

  - A deployment larger than the server takes in one request now says what to do: "The deployment is larger than the server takes in one request. Every tool, agent, flow and guardrail in the pack travels in it with its schemas and instructions: make the largest schemas smaller, or split the pack into two."
  - `kindgi dev`'s banner has a `Models` line in place of `Providers`: each provider with its models or, with none yet, what agents do meanwhile and a link to [Models](https://docs.kindgi.com/v0.1.8/guides/models/). `kindgi init`'s next steps say the same. `kindgi doctor` still names the `kindgi providers register` command to run.
  - A build service's refusals print plainly, with their code and the wait they ask for. A `429` with `Retry-After` is waited out, and the same build is sent again.
  - `kindgi deploy --project` checks the project before an inline build, so a project that isn't there fails before anything is built.
  - **Your API key goes only to the address it's for.** `kindgi deploy` with a stored key (the one `kindgi auth login` keeps, or `kindgi dev`'s `.kindgirc.json`) refuses to send it to another address, wherever that address is named: `--endpoint`, an environment's `endpoint`, `--url`, `KINDGI_API_URL` or a folder's `.kindgirc.json`. The refusal names both addresses and how to fix it. A stored key with no address stored beside it is refused too. A key given with `--token` or `KINDGI_API_TOKEN` goes where the command says, as before. Addresses are compared after normalizing: a host's case, a scheme's default port and a trailing `/` don't count, and `localhost`, `127.0.0.1` and `[::1]` are one address.
  - With `--hosted`, or `build: 'hosted'`, the build and the deploy go to the CLI's own API address (`--url`, `KINDGI_API_URL`, or the one you signed in to), and an environment's `endpoint` may only repeat it. Before, an environment's `endpoint` won. They don't read a `.kindgirc.json` that's for a `kindgi dev` runtime: one `kindgi dev` wrote (with `--runtime-url` too), or one at `localhost`, `127.0.0.1` or `[::1]`.
  - A build that signs checks its key before building anything. With no key, it stops and says how to make one, have it trusted and name it. A key file that isn't there, or isn't a file, stops it too, naming the path and where it was set.
  - `kindgi init`'s TypeScript templates leave `environments` empty, with a commented Kindgi Cloud block and a commented block for a runtime of your own. Before, a new pack carried placeholder environments that a deploy would use.
  - `kindgi deploy --project <slug>` with no such project points to `kindgi projects list`. It named a command that doesn't exist.
  - `kindgi adapters prepare --params` reads its JSON from a file with `@<file>`, as its usage line said and as every other such flag does. JSON that doesn't parse is a usage error (exit `2`), and nothing is sent.
  - Four commands' help now says what they do. `kindgi dev --tenant`: with the bundled Postgres, the default tenant is the project's own, the same for every pack and restart; otherwise it's the last run's, from `.kindgirc.json`, else a new one. `kindgi build --signer-key-id`: the default is the environment's `signerKeyId`, else the key file's name. `kindgi sso providers start`: `--kind` is optional, and comes from `--idp`, else from the registered provider. `kindgi schedules update`: a flag left out keeps the schedule's value.
  - `kindgi artifacts upload --help`, the API's description of `POST /v1/artifacts` and the clients' docs give the default upload cap as 100 MiB, what it always was (`100 * 1024 * 1024` bytes). They said 100 MB.

- **The license key's signing key:** the runtime and the CLI trust a new production signing key, so a production key issued after 0.1.8 can need runtime 0.1.8 or later. Kindgi tells you which runtime a key needs when it issues it, and a licensee on an older runtime keeps getting keys their runtime verifies.

- **Logs:**

  - `KINDGI_LOG_LEVELS` now warns at boot for `auth` and `db`: nothing logs under them, so a level set for either never had an effect. Levels for `judging`, `migrations`, `sso` and `tenants` always applied, and no longer warn that they don't.
  - Records from `aws-identity` and `openfga` now carry their own subsystem, where they said `root`, so a level for either applies too.
  - The scheduler's lines are now records under `schedules`: each fire, with its outcome and the run or improvement pass it started; a trigger paused; what a restore skipped; and failures. So are the `improvement-passes` warning that passes couldn't resume, and the `authz.outbox` warnings a request printed while it waited for its access changes to apply. They follow `KINDGI_LOG_LEVELS` and `KINDGI_LOG_FORMAT`: under JSON each is one object on its own line, where it used to break the stream ([The subsystems](https://docs.kindgi.com/v0.1.8/deploy/logs/#the-subsystems)).

## Known issues

- **A first admin who stops being a tenant admin loses their capabilities,** and being made one again doesn't return them. Keys that carry them make new keys with them, so replace the last one before it goes. With neither, contact support ([Make someone a tenant admin](https://docs.kindgi.com/v0.1.8/deploy/people-and-keys/#make-someone-a-tenant-admin)).
- **Run one runtime instance.** Several instances sharing a database aren't verified on a platform yet, so the Cloud Run module still runs one ([Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run/)).
- **A secret hard-revoked before 0.1.8 in your own secret manager** (`KINDGI_SECRETS_BACKEND=secret-manager`) lists under `kindgi secrets list --include-revoked` as a revoked secret: its name, `revokedAt` and `revokeReason`, never a value. The runtime kept nothing then that told a hard revoke from a soft one. A secret hard-revoked from 0.1.8 on is left out of every list.
- **With a key for OpenFGA** (`KINDGI_OPENFGA_PRESHARED_KEY`), **`kindgi-runtime tenants purge` and `tenants repurge` stop at a tenant with an OpenFGA store:** they don't send the key, or the ID token behind Cloud Run IAM, so the store's delete is refused, and they exit `2` with `OpenFGA answered <status> to deleting store <id>`. By then the tenant's artifacts' bytes are deleted, but none of its rows. A repurge stops there, before the records after it. Fixed in 0.1.8.1, the next release: once you've upgraded to it, run the purge again ([Close and purge a tenant](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants/#close-and-purge-a-tenant)).

## Upgrade steps

1. **Before you upgrade, list the MCP endpoints with a credential in `config.headers`,** with the 0.1.8 image's `check mcp-header-credentials` ([Operate](https://docs.kindgi.com/v0.1.8/deploy/operate/#checks-you-can-run-before-upgrading)). They keep working, but move each one: store each header's value as a secret, then unregister the endpoint and register it again with the header in `auth` ([MCP servers](https://docs.kindgi.com/v0.1.8/guides/tools/mcp-servers/#an-endpoint-registered-with-one-by-an-earlier-runtime)).
2. **Before you upgrade, list what reads a secret at the tenant from a project or an org,** with the 0.1.8 image's `check secret-scopes` ([Operate](https://docs.kindgi.com/v0.1.8/deploy/operate/#checks-you-can-run-before-upgrading)). They were registered before 0.1.8 and keep reading at the tenant. One a tenant admin registered can stay. The check ends with what to do about the others: register each again, a webhook trigger with `kindgi webhooks update <trigger-id> --secret=<name>`.
3. **If a server Kindgi calls compresses its answers whatever the request asks,** turn that off before you upgrade: 0.1.8 refuses a compressed answer.
4. **Check that your agents' instructions are valid Liquid.** A deployment with an agent whose instructions aren't is refused whole, so a CI deploy that passed on 0.1.7 can fail on 0.1.8.
5. **Expect a slower first start on a large database.** Two new indexes on the runs table are built at the first start after the upgrade, without blocking writes; the more runs, the longer that start takes.
6. **After you upgrade, read the startup log** for conversations that can't be continued (opened before 0.1.8 in a project their agent isn't registered for) and for unregistered versions a flow or agent version still pins: the runtime names each.
7. **If a deploy sends the key `kindgi auth login` or `kindgi dev` stored to another address,** give it that server's key with `--token` or `KINDGI_API_TOKEN`, point the address at the one stored with the key, or, for a key from `kindgi auth login`, sign in to that server with `kindgi auth login --url <url>`. The 0.1.8 CLI refuses to send a stored key anywhere else, and the refusal names the setting that names the other address.
