# Kindgi 0.4.0

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

Released November 3, 2026. For the docs of this version, see [docs.kindgi.com/v0.4.0](https://docs.kindgi.com/v0.4.0/index.md).

## Security fixes

- **An erasure now reaches a test run's stored results, and a test run still going.** Before, erasing a person's words cleared a test run's case results as it ran, but not the copy in the finished run's result (`perCase`), which reads serve once it ends. Now both copies are cleared. A test run still going with a case of that person's is waited for, and the erasure shows `waiting-on-run`, with `waitingOn` naming it, then cancels it at the deadline: `KINDGI_ERASURE_SHARED_WAIT_MS`, 7 days by default, or sooner when a tenant admin forces it (`kindgi memory erasures resume <erasure-id> --force`). A failed model call in Improve's drafter now records "The drafter's model call failed." in place of the provider's message, which could quote corrections and which an erasure couldn't reach.

## New

- **Files in an S3 bucket.** `KINDGI_ARTIFACTS`, `KINDGI_ERASURE_LEDGER_EXPORT`, `KINDGI_TENANT_PURGE_RECORD`, and the `tenants` and `erasures` commands' `--record`, `--from`, `--ledger` and `--out`, take `s3:<bucket>[/<prefix>]`, on AWS S3 or a store that speaks S3, such as MinIO or R2. `KINDGI_S3_REGION` names the buckets' region. On AWS the runtime signs in as its AWS identity (`KINDGI_AWS_IDENTITY`); a compatible store takes `KINDGI_S3_ENDPOINT`, `KINDGI_S3_PATH_STYLE` and its own key in `KINDGI_S3_CREDENTIALS_PATH`. A delete removes every version of a file and its unfinished uploads. The runtime checks a `KINDGI_ARTIFACTS` bucket on `s3:` at start, and refuses one with MFA Delete on, or an Object Lock default retention, since a tenant purge couldn't delete from it, and one it can't list unfinished uploads in (`s3:ListBucketMultipartUploads`), or versions in, when it keeps them (`s3:ListBucketVersions`). The erasure ledger and purge records need a store that honors `If-None-Match` ([Choose where the runtime keeps files](https://docs.kindgi.com/v0.4.0/deploy/self-hosted/store-files.md)).

- **The Azure Container Apps module has an `upgrade.sh`.** A deployment of the module on runtime 0.1.8 or later, in either network shape, can run it to upgrade to 0.2.0 or later, as the Cloud Run module's does. It copies the new image into the module's registry, runs the upgrade checks against the database as Container Apps jobs in the module's environment, checks that the database server keeps at least 7 days of backups, and changes the running deployment only when you type `yes` at a plan it has checked. If the new revision doesn't come up, it prints the way back, which restores the database server as of just before the apply, into a new server, rather than rolling back on its own. `--dry-run` lists what a real run would change. Use it as of the `@kindgi/sdk@0.4.0` tag. The module's new `database_url_version` pins the version of the `database-url` secret the server reads: the way back sets it, and `upgrade.sh` refuses to start while it's set ([Azure Container Apps](https://docs.kindgi.com/v0.4.0/deploy/self-hosted/azure-container-apps.md#upgrade)).

- **Retired blocks, policies and release gates can be found.** `GET /v1/blocks`, `/v1/policies` and `/v1/release-gates` take `?includeRetired=true`, as the lists of agents, flows, tools and test sets do: one whose every version is unregistered is listed as its highest version, with `unregisteredAt`, so you can reinstate a version. In the TypeScript client, `blocks.list`, `policies.list` and `releaseGates.list` take `includeRetired`, and in the Python client `include_retired`. In the CLI, `kindgi blocks list` and `kindgi release-gates list` take `--include-retired`. `kindgi exports create` now includes them too ([Agent and tool versions](https://docs.kindgi.com/v0.4.0/guides/agents/agent-and-tool-versions.md)).

- **On Cloud Run, each deployment gets a pack service of its own.** The module runs the pack orchestrator, a Cloud Run Job that Cloud Scheduler starts once a minute: each pass makes a service for each deployment that needs one, gives running ones their changed env, and retires what should go. A service scales to zero when it's idle, and its first call after that waits for an instance to start. The pack's env comes from Kindgi, with `kindgi env set`, and a secret of yours in Secret Manager can be one of its values, as a cloud secret pinned to a version ([Run a pack service for each deployment on Cloud Run](https://docs.kindgi.com/v0.4.0/deploy/self-hosted/pack-orchestrator.md)).

- **Correcting an agent, its test set, and Improve work as one loop.**

  - A correction says what was wrong and who fixes it: Improve, a developer, or the agent's owner, which each agent now has. Its status is Open, Being fixed, With the owner, Fixed, Won't fix or Removed, and it says where it went: an Improve run, a suggested change or a pull request.
  - Each agent has a test set of its own: its corrections that have something to check, and cases added by hand. A case can be retired, with a reason; a retired one isn't tested again. The project-wide test sets are gone.
  - A test run grades each case's answer with a judging model, through the tenant's providers, and its cost counts under the test run. An answer with no model to judge it is `unchecked`, never a pass.
  - Checking a suggested change runs it on a frozen copy of the project's test set for that agent. Its card offers Check it, Pin it, Add as Latest and Withdraw, and says how many corrections it fixes, past 100 too.
  - A pin, or a move of Latest, marks the corrections its test run checked Fixed in that version, and going back reopens them.
  - An Improve run works in one project: it starts from the version the project runs, takes the agent's open corrections handed to Improve, and learns from cases with an answer or decisions marked wrong. It tries changes on part of the test cases and checks the best on the rest, cases it didn't learn from, and keeps a change only when it passes more cases than the version it started from and fails none that passed. The cheaper path that kept a settings change for costing less is gone.
  - Each block version and suggested change records who made it, as Made by in the console.

- **Agents can read files: photos, PDFs and text.** A run's input, a tool's result and a conversation message can carry stored files (`files`, each a `FileValue`), and the model call sends them inline: JPEG, PNG and WebP images (5 MB each) and PDFs (10 MB) to a model that declares `vision`, and text, Markdown and CSV files (1 MB) to any model, each under a line that says it comes from outside: data, not instructions. A file that can't be shown becomes a line that names it and why, and the call goes on. A call the provider refuses for its files is sent once more without them, with a `file-refused` warning. `GET /v1/conversations/{conversationId}/messages` answers each message's `files`. Files need `KINDGI_ARTIFACTS`, and the runtime checks each file a run is started with (its type and its size) before the run starts: a bad one is refused as `bad-input`, naming it. `KINDGI_FILE_MEMORY_MIB` sets the memory, in MiB, a server keeps for the bytes of the files its agents read (512, or a quarter of the server's memory when that's less), and `KINDGI_FILE_CACHE_MIB` how much of it keeps them for later calls: half by default, or 0 for none.

- **A tool can store the files it fetches, and read the files it's given.** A tool that declares `storesFiles: true` gets `ctx.files.store({ name, contentType, bytes })`, and one that declares `readsFiles: true` gets `ctx.files.read(artifactId)`. Only a tool with a handler can declare them. In a pack (protocol 2.10.0), they go through `POST /v1/tool-files` and `GET /v1/tool-files/{artifactId}`, with the call's own token. The mail tools `gmailAttachmentGet` and `outlookAttachmentGet` store a mail's attachment for the agent to read.

- **An agent's corrections are its test set, in each project.** `/v1/agents/{agentId}/test-set` (`client.agents.testSet`) reads it: every correction of the agent's runs is a case, except won't-fix and removed ones. Add a case by hand with `POST .../test-set/cases`, and retire one with `.../test-set/cases/{caseId}/retire`. A test run of `agent.<agentId>.<projectId>` freezes the project's active cases as a version first. It grades what each case expects: its decisions exactly, and its answer with a judging model. `passRate` counts those parts, so a test set of cases that expect only an answer has a `passRate` where it had `null`. A comparison says in `unchecked` why a case's answer wasn't checked ([An agent's test set](https://docs.kindgi.com/v0.4.0/guides/improve/build-a-test-set.md)).

- **A pin's test run fixes or reopens its project's corrections.** When a pin, an unpin or Latest moving changes the version a project runs, and the project's test set has cases, Kindgi starts a test run of that version. When it ends, each correction it checked is `fixed` in that version, or reopened, with `reopenedIn` (`{ version, how, by?, at }`). A pin that names a finished test run of the same version, test set version and project uses that one. A release gate takes a `cases` rule: `minPassing`, and `noNewFailures`, which refuses a version that fails a case that passed before ([Release an agent version](https://docs.kindgi.com/v0.4.0/guides/improve/release-an-agent-version.md)).

- **An agent has an owner.** `PUT /v1/agents/{agentId}/owner` with `{ "userId": "..." }` (`client.agents.setOwner`), with admin on the agent, names the person who owns it, and `GET /v1/agents/{agentId}` answers `owner: { userId, name }`. A correction's wrong decisions go to the owner. `kindgi agents owner <agent-id>` shows it, and `--set=<user-id>` sets it.

- **Corrections export, and come from your users' thumbs.** `GET /v1/corrections/export?agentId=<agent-id>` streams an agent's corrections as CSV, or as JSON lines with `format=jsonl`, and the audit log records each export. `client.corrections.export` and `kindgi corrections export --agent=<agent-id>` give it, and `--out=<file>` writes it to a file. `client.corrections.thumbs` records an end user's thumbs up or down from your app's server, as a correction of the run's answer; a thumbs down needs `why`. A suggested change says how many corrections it fixes in `correctionCount` ([Record your users' thumbs](https://docs.kindgi.com/v0.4.0/guides/improve/record-your-users-thumbs.md)).

- **A reviewer can approve a fact with their own text.** `POST /v1/approvals/{approvalId}/complete` takes `factText`, with `decision: approve` on a `fact` approval only (any other answers `400 bad-input`), and the fact holds that text in place of the agent's. It's checked as the agent's own text is: not empty or only spaces, and at most 2,000 characters. The decision carries it as `decision.factText`, and `approvals.decide` takes it. The `remember` tool no longer stores a text of only spaces.

- **A block version says who published it, where it came from, and where it runs.** A block version has `publishedBy`, and `source`: `api`, `deploy` with the deployment's id, or `suggested-change`. `GET /v1/blocks/{blockId}/runs-in` (`client.blocks.runsIn`) lists each project, and each segment pin, whose running agent version uses a version of the block, and whether the pack's files have it (`inYourFiles`). `kindgi blocks pull` writes the newest running version of each block into the pack's block files, and never over a file that's newer. Publishing, unregistering and reinstating a block version, and registering and unregistering a provider, are in the audit log.

- **A deployment records where its pack lives in git, and a project can open pull requests.** `kindgi deploy`, in a git work tree, sends `source`: the `origin` remote as `host/owner/name`, the pack's folder, the commit, the branch, and whether the folder had changes the commit doesn't hold. `PUT /v1/projects/{projectId}/repo-connection` (`client.projects.setRepoConnection`) sets the company-wide GitHub connection a project opens its block changes' pull requests through. That connection's GitHub App needs Contents and Pull requests, read and write.

- **A block change opens a draft pull request in the project's repo.** When a block version is pinned in a project, or added as its Latest, Kindgi opens a draft pull request with the new block files, through the project's repo connection: a company GitHub connection, for github.com, that a project admin picks on the project's Connections page, under Pull requests for block changes. Its first paragraph says where the change came from ("From an Improve run.", or "From a scheduled Improve run."), how many corrections it fixes, and its test set score, such as "Test set: 25 of 27, up from 23.". One that can't be opened is logged, not retried; `kindgi blocks pull` brings the version into your files instead. None is opened under `kindgi dev`. A block's versions show where each runs, and whether your files hold it.

- **A suggested change is pinned, or added as Latest, for its project.** `POST /v1/suggested-changes/{suggestedChangeId}/pin` pins its version through the project's release gate. `.../add-as-latest` (`client.suggestedChanges.addAsLatest`, `kindgi suggested-changes add-as-latest`) makes it the Latest of a project on Latest, with no gate. One built on a version its project no longer runs is built again on the version it runs now, with a new check: pinning it, or adding it as Latest, first answers `409 suggested-change-out-of-date` and starts that (`details.rebuilding`). One the project's version already has, or whose block that version no longer uses, isn't built again (`rebuilding: false`). A deploy makes a suggested change in each project that pins another version of the agent (`madeBy: deploy`). It's never built again: once its project moves, pinning it answers the same 409 with `rebuilding: false`. Making, pinning, adding as Latest and withdrawing one are audit events.

- **`kindgi suggested-changes pull <suggested-change-id>` writes a suggested change into the pack's files.** With `--pr`, it commits them on a new branch and opens a pull request (with `gh`), and marks the corrections it fixes that are still `open` as `being-fixed`. Both `pull` commands refuse a block file that holds edits the runtime doesn't have, naming it, and write nothing ([Suggest a change, and test it](https://docs.kindgi.com/v0.4.0/guides/improve/suggest-a-change.md)).

- **`kindgi flows` and `kindgi tools` pin versions.** Each has `pinned`, `pinned-versions`, `pin`, `unpin` and `pin-history list`, as `kindgi agents` has, per project, org or tenant ([Pin a flow's version](https://docs.kindgi.com/v0.4.0/guides/flows/pin-a-flow-version.md)).

- **Under `kindgi dev`, Improve runs, and a pin keeps what it writes over in your pack's files.** A pin for the whole workspace writes the version into your pack's files, as making a version live did before. It now keeps what it writes over, so pinning the version before undoes it, where before the way back was git, and an edit made since the check is no longer written over: the pin answers `409 pin-superseded`, which was `promotion-superseded`. An unpin is still refused. Before a pin writes, `kindgi agents pin` and `kindgi suggested-changes pin` say which files git hasn't committed (`uncommittedFiles` and `notInGit` on the pin check), as the console's pin dialog does, and the pin still goes. `POST /v1/improve-runs`, Suggest a change and Check again work there, and an Improve run's suggested change is for the whole workspace. Improve schedules are off there: start Improve by hand. The `returns-helper` template's README walks the loop end to end.

- **Calendar tools.** `@kindgi/connections/google/tools` has `googleCalendarEventsList`, `googleCalendarFreeBusy`, `googleCalendarEventCreate` and `googleCalendarEventUpdate`, and `@kindgi/connections/microsoft/tools` has the four `outlookCalendar*` tools. Create and update invite people, so they wait for a yes in a run no person started. A connection made for mail only needs the calendar permissions added ([Calendars](https://docs.kindgi.com/v0.4.0/guides/connections/calendar.md)).

- **A WooCommerce store connects with its owner's approval.** In a pack made with `kindgi init --template=woocommerce`, `pnpm connect-store https://shop.example.com` opens the store's WordPress Authorize Application page (WordPress 7.0 or later). Once one of its administrators approves, it makes the `kindgi-agent` user a Shop Manager and saves `WOO_STORE_URL`, `WOO_USER` and `WOO_APP_PASSWORD` in the pack's env, with no password copied by hand. `--hosted --env=<env> --scope=project:<id>` saves them for a deployed pack.

- **`kindgi init --template=returns-helper`, and skills that teach the correcting loop.** The template is one agent with a find-order and a refund tool on sample orders, and three blocks. The `kindgi-fix-corrections` skill reads the open corrections sorted to a developer and fixes each where it belongs. The authoring skills say how an agent takes its instructions and settings from blocks, and what a tool call's deadline does: the pack service aborts the handler's signal at `KINDGI_PACK_CALL_TIMEOUT_MS`.

- **The pack orchestrator's routes and settings.** `kindgi-runtime packs orchestrate` runs one pass of the pack orchestrator and exits, for a Cloud Run Job once a minute, with a system key that has `system:deployments` and `system:pack-env`. It reads `KINDGI_ORCHESTRATOR_RUNTIME_URL`, `KINDGI_ORCHESTRATOR_SYSTEM_KEY`, `KINDGI_ORCHESTRATOR_PROJECT`, `KINDGI_ORCHESTRATOR_REGION`, `KINDGI_ORCHESTRATOR_PACK_SERVICE_ACCOUNT` and `KINDGI_ORCHESTRATOR_PACK_SERVICE`, and gives each pack service `KINDGI_PACK_SERVICE_MAX_CONCURRENCY`, its concurrency. For a runtime behind Cloud Run IAM, `KINDGI_ORCHESTRATOR_RUNTIME_AUTH=google-id-token` adds a Google ID token to its calls. Each pass claims the round (`POST /v1/system/deployments/round`), and a deployment still waiting for its pack service carries its last `problem`. A new code, `pack-env-name-too-long`, there or on a ready one's `packEnvProblem`, means a name set as a secret is too long for the secret it's kept in, which is named after the pack service. The logs name the subsystems `pack-orchestrator` and `block-version-prs` ([Run a pack service for each deployment on Cloud Run](https://docs.kindgi.com/v0.4.0/deploy/self-hosted/pack-orchestrator.md)).

- **Per-deployment pack routing works on a runtime that serves one tenant, and can seal under a key of yours.** `KINDGI_PACK_ROUTING=per-deployment` needed `KINDGI_TENANCY=multi` and `KINDGI_SECRETS_BACKEND=postgres`. A one-tenant runtime now takes it too, with `KINDGI_PACK_ENDPOINT_HOSTS` naming the hosts a deployment's endpoint may be on, and each endpoint's token can be sealed under `KINDGI_PACK_ENDPOINT_KMS_KEY`, a Cloud KMS key or a Key Vault key, in place of the secrets' own.

- **Only the operator sets a pack's env name to a cloud secret.** With `KINDGI_ENV_CLOUD_SECRETS=on` (it needs `KINDGI_PACK_ROUTING=per-deployment`; off, the default, its routes answer `501 cloud-secrets-unsupported`), a system key with `system:env-refs` sets a name to `projects/<project>/secrets/<secret>/versions/<number>`, as `kindgi env set NAME --cloud-secret <path> --tenant=<id>` does. A tenant's own key is refused (`403 cloud-secret-system-only`). The tenant sees the names, never the path: `GET /v1/env/cloud-secrets`, and `kindgi env list` prints `NAME (cloud secret)`.

- **`kindgi deploy --hosted` says how many builds are kept, and `kindgi deployments get` what keeps each.** A build's pack service stays while something a call can still ask for needs it, and no newer deployment into its project serves that. `kindgi deploy` prints `Builds kept in this project: N` (`workspace`, when the deploy names no project), and `kindgi deployments get` lists what keeps the build under `Kept for:` (`endpoint.keptFor`, and `keptBuilds` on `GET /v1/deployments`). `kindgi deployments` says why a `superseded` one was taken down ([Deploy your agent to Kindgi Cloud](https://docs.kindgi.com/v0.4.0/hosted/deploy-your-agent.md)).

- **Every union in `openapi.json` maps its tags.** Nine unions, among them `AgentPinScope`, `PinScope`, `ImproveRunOutcome` and `MCPEndpointAuth`, now have a `discriminator.mapping`, so a client generated from it types each tag as the value the server sends, such as `suggested`. Nothing on the wire changes.

- **In the console:**

  - On a runtime that serves many tenants, the runtime card says Kindgi Core is run for you, with nothing to set up or upgrade.
  - An empty list says what goes in it, and how to add one.
  - The project's Approvals card also counts approvals that belong to no project.
  - An agent's corrections can be exported, as CSV or JSON lines, and each export is recorded in the audit log.
  - An agent's tools list the version each tool's runs call.
  - A reviewer can approve a fact an agent wants to remember in their own words.
  - A run's corrections form says what to judge, and who fixes it.
  - Corrections, and each Cost report, have a page for each of what were their tabs: To review, To fix, Who corrects, Rules and Trends, and By agent and the like. The trail's last crumb lists them, and their addresses are the same.
  - Correcting links each next step. A saved correction links To fix while it isn't fixed, Run Improve on its agent when it's Improve that fixes it (for whoever may run Improve), and its case in the test set. Next run to review opens the next run's Corrections page, and a run's header has Correct this run.
  - An agent's and a flow's Corrections page is the project's To fix list for that agent or flow, with its filters in the page's link. A test set's case opens its run's Corrections page, an Improve run links the test set it ran on, and a pinned version links the suggested change it came from. Correction lists name each run, which opens its Corrections page, and a rule's open count opens To review for that rule. Saving over a correction on its way to a fix says which one it replaced.
  - A pin that would take a project off Latest warns first: pin and stop following Latest, or cancel. On a suggested change, you can add it as Latest instead.
  - An agent's Projects section shows the version each project runs, whether it's pinned or follows Latest, and when a pin took it off Latest or an unpin put it back. Its highest version links to the suggested change waiting in each pinned project.
  - An Improve start that isn't ready says how to fix each thing it's missing, and one refused while another run is going links to that run. An Improve run's page shows the check of its change on every case of the test set, against the version as it is.
  - Approvals come in two groups, Tool calls and turns, then Versions to pin, and the Inbox names each approval's group and filters by it. An approval with no project says why: it's for the whole workspace or an org, or it's from before Kindgi 0.1.3.
  - The new suggested change form keeps Suggest it off, and says so, until the content differs from what the version pins.
  - On the tool, flow, block and agent pages, a version's link keeps the versions table's Show retired, sort and page.

- **The WordPress plugin 0.2.0 has a Kindgi menu.** Inbox, Activity, Ask and Settings are pages of their own, in the person's admin color scheme. The Inbox lists what waits for you, what waits for someone else and why, and the project's decided approvals. A card reads in words, and its buttons say what each decision does, such as **Approve and send** on a reply. A count of what waits for you shows on the menu and in the admin bar. Activity is a feed of the project's runs by day. Ask reads as a chat, and a call that didn't go through says why: it wasn't allowed, a guardrail or a connection stopped it, a reviewer rejected it, or it failed. Settings, once the site is connected, opens on a status card. The WordPress dashboard has a Kindgi box, and on a WooCommerce store an order's screen has one too, with a Kindgi column on the Orders list. Upload `kindgi-0.2.0.zip` from the 0.4.0 release over the plugin you have ([Let a store's staff ask and approve in WordPress](https://docs.kindgi.com/v0.4.0/sites/wordpress.md)).

- **A site person's Ask can use the site project's tools, behind an approval floor.** A person the site signs in, whose role may ask, now has the project's tools in their own agent turns. There, every call of a tool that isn't declared read-only (`mutating: false`) in every one of its versions waits for a yes, and so does every `kindgi_remember`. An MCP tool always counts as one that changes things, so each of a store's actions waits, product lookups included. The person who asked may approve their own call when their role reviews approvals, unless the agent sets `separateApprover`. They still can't run a flow or call a tool directly ([What an Ask may do](https://docs.kindgi.com/v0.4.0/sites/wordpress.md#what-an-ask-may-do)).

## Fixed

- **The runtime image has a directory for uploaded files.** `/var/lib/kindgi/artifacts` belongs to the server's user (uid `10001`), so a Docker named volume mounted there takes that owner, for `KINDGI_ARTIFACTS=local:/var/lib/kindgi/artifacts`. Each `local:` target is checked at start, and a directory the server can't write in stops it, naming the server's user id.
- **A `gcs:` bucket is checked at start.** The runtime refuses to start when the server's identity lacks a permission it needs, naming each one, or when the artifacts bucket has a retention policy or a default event-based hold, since no tenant purge could delete a file there. It warns when the bucket keeps deleted files restorable (soft delete) or has a lifecycle rule that can delete files it still names. `STORAGE_EMULATOR_HOST` works in development only (`KINDGI_DEV`).
- **The WordPress plugin's Ask page opens a conversation.** Since the plugin came out in Kindgi 0.1.9, a question to one of the site's own agents was refused for everyone the site signs in. The plugin named the site's project when it opened the conversation, and naming a project needs `write` on it, which a site's people never get. Now it names no project for the site's own agents, so Kindgi opens the conversation in the agent's own project, the site's. For an agent another project shares with the site's, it names the site's project, where calling the agent is enough. To get the fix, upload `kindgi-0.2.0.zip` from the 0.4.0 release over the plugin you have.
- **Two fixes in the WordPress plugin's Ask and Inbox pages.** A saved conversation no longer opens with "nullnull" above it. An approval opened from its email link after it was decided no longer shows twice, under **From your link** and again under **Decided by you lately**: it opens in the Inbox under the list that holds it, **Waits for you**, **Waits for others** or **Decided**, and **From your link** shows only one that none of them holds. Both fixes are in the same `kindgi-0.2.0.zip`.
- **A test run's replays no longer send `run.finished`.** Before, an endpoint that takes `run.finished` got one for each replay of a test run. A run inside another run still sends none ([Webhook events and deliveries](https://docs.kindgi.com/v0.4.0/guides/webhooks/webhook-events.md)).
- **A pin's release gate reads only a completed test run.** A comparison cancelled or failed part way kept the summary of the cases it got through, and the gate checked it as if the run had completed. Now `POST /v1/agents/{agentId}/pinned/pin` and `.../pinned/check` refuse a `testRunId` whose run didn't complete (`400 bad-input`).
- **OpenAI-compatible providers send a model's registered `maxOutputTokens` when the call sets none.** Before, a cap registered on a model didn't apply on OpenAI, OpenRouter, Groq or a self-hosted OpenAI-compatible server. The `openai`, `groq` and `openrouter` presets register their models with a cap, so calls through them now send it.
- **The CLI runs git, and `kindgi dev`'s Linux sandbox its helpers, by their full path on `PATH`.** A `git`, or an `sh`, `bwrap`, `mktemp`, `sed` or `rm`, in the pack's folder no longer runs in their place. When one isn't on `PATH`, `kindgi dev` stops with `spawn <name> ENOENT`.
- **`kindgi deploy` says when nothing new was deployed.** When the server answers from its record of an earlier request with the same `Idempotency-Key`, it says nothing new was deployed, where it printed "Deploy complete." under its replay note. Its JSON adds `idempotentReplay: true`.
- **The deploy banner and `KINDGI_PACK_ROUTING`'s description say a call goes to the newest deployment in the run's project**, not the tenant's newest.
- **The Azure Container Apps guide keeps the registry login token off the command line.** It pipes the token to `docker login`'s stdin in place of a plain `az acr login`, as the module's `upgrade.sh` does ([Azure Container Apps](https://docs.kindgi.com/v0.4.0/deploy/self-hosted/azure-container-apps.md)).
- **The woocommerce template's README starts with a question about your orders**, and its refund example uses a test order, since a refund of up to 100 runs with no approval when you ask for it.
- **A rotated secret's old version resolves until its grace period ends.** With `kindgi secrets rotate --revoke-old-after`, the old version stopped resolving at once, as if revoked then. It now resolves until the time the rotation set, and revoking it in that window ends it at once.
- **Under `kindgi dev`, a pack tool marked `resultIsOutsideData` has its results treated as outside data again.** `kindgi dev` dropped the mark, so the model got the tool's results as plain text. A deployed pack had it right.
- **The Retire dialogs say where a retired one is listed.** They, and the notice on a flow, agent or tool whose every version is retired, say it's listed under Show retired. They said it stayed listed as retired, while the tables and lists hid it.

## Changed

- **0.4.0 renames its ideas, and keeps no old name.** A judgment is a correction, a judge class a corrector group, the judging queue the expert queue, an eval suite a test set, an eval run a test run, choosing what a project runs a pin, an improvement pass an Improve run, a proposal a suggested change, and a gate policy a release gate. Routes, fields, values, error codes, schemas, the clients' names, the CLI's commands and flags, and the docs' addresses all change, as the lists below say. Environment promotion keeps its name: `kindgi promote`, `POST /v1/deployments/{deploymentId}/promotions` and `client.deployments.promote`.

- **A judgment is now a correction.**

  - `/v1/judgments` is `/v1/corrections`, and `/v1/judgments/{judgmentId}/unregister` is `/v1/corrections/{correctionId}/remove`. The retention domain `judgment` is `correction` (`POST /v1/retention/sweep/correction`).
  - `judgmentId` and `judgmentIds` are `correctionId` and `correctionIds`. `judgmentCount`, `runsWithJudgments` and `anyJudgment` are `correctionCount`, `runsWithCorrections` and `anyCorrection`. An answer's `judgments` is `corrections`, a comparison summary's `corrections.judgments` is `corrections.results`, a comparison case's `judgedItems` is `correctedItems`, and a `changes.new` entry's `judged` is `corrected`.
  - The error code `judgment-not-found` is `correction-not-found`.
  - `Judgment` and `JudgmentWithCopies` are `Correction` and `CorrectionWithCopies`. The `{ calls, notCalled }` value that was called `Correction` is `ExpectedDecisions`, graded as `ExpectedDecisionsResult`. `Judged*` names are `Corrected*`, and `UnregisterJudgmentResult` is `RemoveCorrectionResult`.
  - `client.judgments` is `client.corrections`, and its `unregister` is `remove`. `kindgi judgments add|list|show|unregister` is `kindgi corrections add|list|show|remove`.

- **Judge classes are now corrector groups, the judging queue is the expert queue, and judging rules are queue rules.**

  - `/v1/judge-classes` is `/v1/corrector-groups`, with `{correctorGroupId}` in its paths. `/v1/projects/{projectId}/judging-queue` is `/v1/projects/{projectId}/expert-queue`, and `/v1/projects/{projectId}/judging-rules` is `/v1/projects/{projectId}/queue-rules`, with the same paths below them. The retention domain `judge_class` is `corrector_group`.
  - `judgeClassId` is `correctorGroupId`. A correction, a corrected item and a test case's item say `expert` where they said `restricted`. `classWeights` is `groupWeights`, and its value `restricted-only` is `experts-only`. A release gate's `onlyRestrictedClasses` is `onlyExpertGroups`, and its check is named `groupWeights.expertsOnly`. The expert queue's state `judged` is `corrected`, as is a rule result's `judged` count; progress `byClass` is `byGroup`, and `wantedClassIds` is `wantedGroupIds`.
  - Error codes starting `judge-class-` start `corrector-group-`, codes starting `judging-rule-` start `queue-rule-`, and the other `judging-` codes start `expert-queue-`.
  - `JudgeClass*` names are `CorrectorGroup*`, `JudgingRule*` names are `QueueRule*`, and the judging queue's `Judging*` names are `ExpertQueue*`. In the Python client, `Restricted` is `Expert`.
  - `client.judgeClasses` is `client.correctorGroups`, `client.projects.judgingQueue` is `client.projects.expertQueue`, and `client.projects.judgingRules` is `client.projects.queueRules`.
  - `kindgi judge-classes` is `kindgi corrector-groups`, `kindgi judging-queue` is `kindgi expert-queue`, and `kindgi judging-rules` is `kindgi queue-rules`. `--class` is `--group`, `--clear-class` is `--clear-group`, and `--class-weights` is `--group-weights`.

- **Eval suites are now test sets.**

  - `/v1/eval-suites` is `/v1/test-sets`, with `{testSetId}` in its paths and the same paths below it, `runs` too.
  - `suiteId` is `testSetId` wherever it names a test set, and `suiteVersion` is `testSetVersion`. A test set's and a test run's kind `judged` is `corrections`. A release gate's `comparison.suite` is `comparison.testSet`, and so is its check's name; a comparison's summary says `testSet` where it said `suite`. The retention domain and the authorization resource type `eval_suite` are `test_set`. A `reference-out-of-scope` issue names a test set with the kind `test-set`, where it said `suite`.
  - Error codes starting `eval-suite-` start `test-set-`.
  - `EvalSuite*` names are `TestSet*`, `EvalKind` is `TestSetKind`, `EVAL_KINDS` is `TEST_SET_KINDS`, and `JudgedEvalCase`, `JudgedEvalCaseCollectionPage` and `JudgedEvalCaseCorrection` are `CorrectedTestCase`, `CorrectedTestCaseCollectionPage` and `CorrectedTestCaseCorrection`.
  - The test set file's schema is `@kindgi/specs/test-set.schema.json` (was `eval-suite.schema.json`). A file whose `$schema` names `https://kindgi.com/schemas/v1/eval-suite.schema.json` needs `https://kindgi.com/schemas/v1/test-set.schema.json`. Its `kind` takes `corrections`, as the API does, and its reference page is [Test set](https://docs.kindgi.com/v0.4.0/reference/schemas/test-set.md).
  - `client.evalSuites` is `client.testSets`. `kindgi eval-suites` is `kindgi test-sets`, `--suite` is `--test-set`, and `--suite-version` is `--test-set-version`. `kindgi exports create` writes them as `test-sets` and `test-set-versions`.

- **Eval runs are now test runs.**

  - `/v1/eval-runs` is `/v1/test-runs`, with `{testRunId}` in its paths; `cancel`, `events` and `rescore` keep their paths. `POST /v1/test-sets/{testSetId}/runs` still starts one.
  - A test run's own id is `testRunId` where it was `runId`: in the start and rescore answers, the run view and the list rows. Ids of agent runs, such as a case's `runIds`, keep their names. `evalRunId` is `testRunId` wherever it names a test run: in an agent pin's body, in a run's replay marker (`{ of, testRunId }`), in a replay's provenance, and in the `/v1/runs` filter.
  - The stream's events `eval-run.completed`, `eval-run.failed`, `eval-run.cancelled`, `eval-run.case-completed` and `eval-run.case-failed` are `test-run.*`, with the same endings. The compliance evidence kind `eval-run` is `test-run`, and `GET /v1/compliance/evidence?kind=` answers `400` for a kind that is neither built in nor named by the classifier, such as `eval-run`, where it answered an empty page.
  - Error codes starting `eval-run-` start `test-run-`.
  - `EvalRun*` names are `TestRun*`. `EvalComparison`, `EvalBaseline`, `EvalOverrides`, `EvalSample`, `EvalReads` and `EvalClassWeights` are `TestRunComparison`, `TestRunBaseline`, `TestRunOverrides`, `TestRunSample`, `TestRunReads` and `TestRunGroupWeights`.
  - `client.evalRuns` is `client.testRuns`. `kindgi eval-runs` is `kindgi test-runs`, and `--eval-run` is `--test-run`, on `kindgi agents pin` and `kindgi runs list`. `kindgi exports create` writes them as `test-runs`.

- **Choosing what a project runs is a pin, for agents as for flows and tools.**

  - An agent's `/promotions` is `/pinned/pin`, `/promotions/check` is `/pinned/check`, its list of promotions and `/promotions/{promotionId}` are `/pin-history` and `/pin-history/{changeId}`, `/live` is `/pinned`, `/live-versions` is `/pinned-versions`, and `/live/unpin` is `/pinned/unpin`. `/live/rollback` is removed: going back is a pin of an earlier version, through the same release gate and approval.
  - `promotionId` is `changeId`: on a pinned version, in a deployment's pin gate and in a refused pin's `details`. A pin's status `promoted` is `pinned`, and the check's outcome `would-promote` is `would-pin`; a pin's action is `pin` or `unpin`.
  - What an agent resolves to: `via: 'live'` is `'pinned'`, and `liveScope` is `pinnedScope`. So are a run's `via` and a queue rule's `when.versions` value; a `when.versions` entry that is neither an exact version, such as `1.2.0`, nor `pinned` is refused (`400`). A test run comparison's baseline `{ live: ... }` is `{ pinned: ... }`, a compared version's `via` value `live` is `pinned`, and the CLI's `--baseline live` is `--baseline pinned`.
  - Unregistering a pinned agent version answers `agent-version-pinned` (was `agent-version-live`), as for flows and tools, and lists the scopes it's pinned in under `pinned` (was `live`). The permission `promote` on an agent is `pin`. The audit kinds `agent-promotion`, `agent-promotion-refused`, `agent-live-unpinned` and `agent-live-pin-inactive` are `agent-pinned`, `agent-pin-refused`, `agent-unpinned` and `agent-pin-inactive`, and `agent-rollback` is gone.
  - `promotion-not-found`, `promotion-superseded` and `promotion-gate-unsupported` are `pin-not-found`, `pin-superseded` and `pin-gate-unsupported`, and `live-scope-spans-environments` is `pin-scope-spans-environments`, the code a flow or tool pin answers. `nothing-to-roll-back` is gone with the rollback route. Environment promotion's codes keep their names.
  - `Promotion`, `PromotionPage` and `PromotionCheck` are `Pin`, `PinPage` and `PinCheck`. `PromoteBody` and `UnpinBody` are `AgentPinBody` and `AgentUnpinBody`. `LivePin`, `LivePinList` and `LiveVersionResolution` are `AgentPinnedVersion`, `AgentPinnedVersionList` and `AgentPinnedVersionResolution`. An agent's pin scope is `AgentPinScope`, made of the `PinScope*` schemas flows and tools use, with `PinScopeSegment` for a segment. In `@kindgi/types`, `LiveScope` is `AgentPinScope`. In the Python client, `LiveScopeOrg`, `LiveScopeProject`, `LiveScopeSegment` and `LiveScopeTenant` are `PinScopeOrg`, `PinScopeProject`, `PinScopeSegment` and `PinScopeTenant`, and `LiveScopeUnrecognized` is `AgentPinScopeUnrecognized`.
  - `client.agents.pinned` has `resolve`, `list`, `pin`, `check`, `unpin`, `history` and `getChange`; `client.agents.promotions` and `client.agents.live` are removed. Its types are `AgentPinnedClient`, `AgentPinnedWhere`, `AgentUnpinInput` and `ListPinHistoryFilter`.
  - `kindgi agents promote` is `kindgi agents pin`, `promotions list|get` is `pin-history list|get`, `live` is `pinned`, and `live-versions` is `pinned-versions`. `kindgi agents rollback` is removed. `<promotion-id>` is `<change-id>`.

- **A deployment promotion's make-live is now a pin.** A promotion's `makeLive` is `pin`: it pins each agent version the promotion carries in the target project, each through its scope's release gate, and each flow version it carries. The answer lists the carried agents under `pinned` (was `live`), beside `pinnedFlows`. A write error after some were pinned lists them in `error.details.pinned` (was `error.details.live`) and `error.details.pinnedFlows`. `DeploymentLivePin` and `DeploymentLivePinGate` are `DeploymentPinnedAgent` and `DeploymentPinGate`. `kindgi promote --make-live` is `kindgi promote --pin`, and `--wait` goes with `--pin`; its output lists the carried agents' pins under `Agent pins in <env>:` (was `Live in <env>:`). `kindgi dev`'s line for a block version pinned in the console says `pinned:` (was `made live:`).

- **Improvement passes are now Improve runs.**

  - `/v1/improvement-passes` is `/v1/improve-runs`, with `{improveRunId}` in its paths, and `cancel` keeps its path. `POST /v1/proposals/improve` is `POST /v1/improve-runs`, and `POST /v1/proposals/improve/estimate` is `POST /v1/improve-runs/check`.
  - `passId` is `improveRunId` wherever it names an Improve run: on a schedule's fire and on a suggested change's `madeBy`. A suggested change's `drafter` is `madeBy` and its `hypothesis` is `why`, in the body that creates one and in an Improve run's comparisons too. An Improve run's outcome `proposed` is `suggested`. A test run's `passCount` keeps its name: it counts the cases that passed.
  - `improvement-pass-not-found` and `improvement-pass-finished` are `improve-run-not-found` and `improve-run-finished`.
  - `ImprovementPass*` names are `ImproveRun*`. The start body is `StartImproveRunBody` (was `ImproveBody`), for a start and a check, and the check's answer is `ImproveRunCheck` (was `ImprovementPassEstimate`). `SuggestedChangeDrafter` is `SuggestedChangeMadeBy`.
  - `client.improveRuns` has `start`, `check`, `list`, `get` and `cancel`; `client.improvementPasses` and `client.proposals` are removed. Their inputs are `StartImproveRunInput` and `CheckImproveRunInput`, and `@kindgi/sdk` exports `ImproveRunsClient` where it exported `ProposalsClient`.
  - `kindgi improve runs start` starts one (was `kindgi proposals improve`), and `kindgi improve runs list`, `get` and `cancel` replace `kindgi proposals passes`; `kindgi proposals` is removed. `<pass-id>` is `<improve-run-id>`. `kindgi exports create` writes them as `improve-runs`.

- **Proposals are now suggested changes.**

  - `/v1/proposals` is `/v1/suggested-changes`, with `{suggestedChangeId}` in its paths. `.../request` is `.../pin`. `.../rollback` is removed: going back is pinning the agent's earlier version.
  - `proposalId` is `suggestedChangeId` wherever it names a suggested change: on a version's `derivedFrom`, on an Improve outcome, and in a refused gate's `details`. A suggested change's status `promoted` is `pinned`. The header `X-Proposal-Deduped` is `X-Suggested-Change-Deduped`.
  - Error codes starting `proposal-` start `suggested-change-`.
  - `Proposal*` and `FixProposal*` names that stay are `SuggestedChange*`, and `FixProposalId` is `SuggestedChangeId`. A suggested change rolled back by the removed route reads as `pinned`.
  - `client.suggestedChanges` holds suggested changes, and its `request` is `pin`. Its `rollback` is removed: pin the agent's earlier version with `client.agents.pinned.pin`. `kindgi proposals` commands are `kindgi suggested-changes`, whose verbs change too (below), and `rollback` is removed: `kindgi agents pin` with the earlier version puts it back. `kindgi exports create` writes them as `suggested-changes`.

- **A gate policy is now a release gate.**

  - `/v1/gate-policies` is `/v1/release-gates`, with the same paths below it, and an agent's `/gate-policy` is `/release-gate`. The operation ids `gatePolicies.*` and `agents.gatePolicy.resolve` are `releaseGates.*` and `agents.releaseGate.resolve`, under the tag `release-gates`.
  - A release gate's `policyId` is `releaseGateId`. The release gate a pin names is `releaseGate` (was `policy`): on a pin and in the pin history, in a pin check's answer, in a refused pin's `details`, in a `pin-gate-unsupported` answer, in a deployment promotion's pin gate, and in the answer of `GET /v1/agents/{agentId}/release-gate`.
  - A test run comparison's `weightedYesShare` is `passRate`, and its `judgedCoverage` is `caseCoverage`. So are the metric names in a release gate's spec, and the `objective` of an Improve run, a schedule's Improve run and the Improve run webhook event (`passRate`, the default, or `weightedPrecisionAtK`); the CLI's `--objective` takes the same values. The gate's check `baselineIsLive` is `baselineIsPinned`.
  - `gate-policy-not-found`, `gate-policy-already-registered`, `gate-policy-scope-taken`, `gate-policy-scope-changed`, `gate-policy-scope-unpinned`, `gate-policy-needs-pin` and `gate-policy-descendant-unpinned` are `release-gate-not-found`, `release-gate-already-registered`, `release-gate-scope-taken`, `release-gate-scope-changed`, `release-gate-scope-unpinned`, `release-gate-needs-pin` and `release-gate-descendant-unpinned`, with the same statuses and bodies.
  - A spec's `approvals.forRollback` is gone, since going back is a pin, which the gate checks: the key is refused as any other unknown key is.
  - `GatePolicy`, `GatePolicyPage`, `GatePolicyRef`, `GatePolicyResolution`, `GatePolicySpec` and `PublishGatePolicyBody` are `ReleaseGate`, `ReleaseGatePage`, `ReleaseGateRef`, `ReleaseGateResolution`, `ReleaseGateSpec` and `PublishReleaseGateBody`.
  - `client.gatePolicies` is `client.releaseGates`, and `client.agents.gatePolicy` is `client.agents.releaseGate`, with the types `ReleaseGatesClient`, `ReleaseGateVersionsClient`, `AgentReleaseGateClient`, `ReleaseGateFilter` and `PublishReleaseGateInput`.
  - `kindgi gate-policies` is `kindgi release-gates`, `kindgi agents gate-policy` is `kindgi agents release-gate`, `<policy-id>` is `<release-gate-id>`, and `--policy-version` is `--release-gate-version`. In an export, the kinds `gate-policies` and `gate-policy-versions` are `release-gates` and `release-gate-versions`. Don't resume an export an earlier CLI started: run it again into a new folder, since resuming writes the new kinds beside the old ones.

- **The improve guides moved, with no redirects.** The pages under `guides/evals/` are under `guides/improve/`, so old links to them don't open. Four have new names: `judge-a-runs-output` is [`correct-a-runs-output`](https://docs.kindgi.com/v0.4.0/guides/improve/correct-a-runs-output.md), `propose-a-change` is [`suggest-a-change`](https://docs.kindgi.com/v0.4.0/guides/improve/suggest-a-change.md), `judging-reference` is [`expert-queue-reference`](https://docs.kindgi.com/v0.4.0/guides/improve/expert-queue-reference.md), and `live-versions-reference` is [`pinned-versions-reference`](https://docs.kindgi.com/v0.4.0/guides/improve/pinned-versions-reference.md). The 0.3 pages stay under `/v0.3/`.

- **For a runtime of your own, built on `@kindgi/api`: the bindings take the new names too.**

  - `createApp`'s inputs: `agentReleases` is `agentPins`, `judgmentRegistry` is `correctionRegistry`, `evalSuiteRegistry` is `testSetRegistry`, `evalRunBinding` is `testRunBinding`, `judgingQueue` is `expertQueue`, `improvementPasses` is `improveRuns`, and `evalCaseStore` is `frozenCaseStore`. Each is optional, so in JavaScript an old name is ignored without an error, and the runtime runs as if that binding were absent. New optional inputs serve what's new in 0.4.0: `toolFiles`, `pinFiles`, `testSetFreeze`, `blockUse`, `blockVersionPullRequests` and `envCloudSecrets`. Each one's type says what happens without it. For example, without `testSetFreeze`, making or checking a suggested change answers `501 suggested-change-check-unsupported`, and without `envCloudSecrets` the cloud secrets routes answer `501 cloud-secrets-unsupported`.
  - Pins: implement `AgentPinnedVersionBinding` (was `LiveVersionBinding`) and `PinBinding` (was `PromotionBinding`), with `AgentPinBindings` and `AgentPinGateDeps` (were `AgentReleaseBindings` and `AgentReleaseGateDeps`). `AgentPinBindings` holds them as `pinned` and `changes` (were `live` and `promotions`). Their types are `Pin`, `PinStatus`, `PinAction`, `PinError`, `PinErrorCode`, `PinRequestInput`, `ListPinHistoryInput`, `AgentPinInput`, `AgentUnpinInput`, `AgentPinnedVersion`, `AgentPinResolution` and `AgentPinResolveInput`; `PromotionActor` and `PinActor` are one `PinActor`, and `liveScopeCoordinates` is `agentPinScopeCoordinates`. `rollback` and `RollbackInput` are gone, and an unregister a pin refuses answers `pinned` (was `live`). A run's `liveScope` is `pinnedScope` in `@kindgi/runtime` and `@kindgi/agents`.
  - Release gates: implement `ReleaseGateBinding` (was `GatePolicyBinding`) as `AgentPinBindings`' `releaseGates` (was `gatePolicies`), with `ReleaseGateError`, `ReleaseGateErrorCode`, `ReleaseGateListInput`, `ReleaseGatePublishInput` and `ReleaseGateVersionInput`.
  - Latest: `AgentPinBindings.latest`, a `LatestBinding`, is required, and so are `AgentPublishInput.madeFor` and `AgentRegistryBinding.markLatest`. `whatRuns` and `whatRunsByProject` answer a scope's pin, else its Latest.
  - Test runs and test sets: `EvalSubjectInvoker` is `TestRunSubjectInvoker`, and `EvalCaseStoreBinding`, `EvalCasePutInput`, `EvalCaseListInput` and `EvalCasePage` are `FrozenCaseStoreBinding`, `FrozenCasePutInput`, `FrozenCaseListInput` and `FrozenCasePage`.
  - Corrections: `CorrectionRegistryBinding` gains `update`, `counts`, `correctionsByIds` and `applyRunOutcome`, and the test set's `testSet`, `listCases`, `getCase`, `runCopies`, `addCase` and `retireCase`, all required. Its class methods are group methods: `createGroup`, `listGroups`, `getGroup`, `updateGroup` and `unregisterGroup`. It loses `listJudgedRuns`, and its `unregister` sets the status to `removed`. `sortAtSave` and `planChange` are exported, so every store sorts and changes a correction by the same rules.
  - Suggested changes: `SupervisorBinding`'s `listProposals`, `getProposal`, `createProposal` and `recordProposal` are `listSuggestedChanges`, `getSuggestedChange`, `createSuggestedChange` and `recordSuggestedChange`.
  - Pack services: `SystemDeploymentsBinding.listReady` is new and required. It answers `GET /v1/system/deployments?endpoint=ready` (`client.systemDeployments.list('ready')`): the deployments whose endpoint is `ready` and whose pack service stays, across tenants, oldest first, with the same paging as `endpoint=missing` and `endpoint=retire`.
  - `@kindgi/tools`: `latestVersion` is `highestVersion`, since it answers the highest version, which isn't always what a project runs.

- **A correction says whether a run's decisions and its answer were right, and no old field is kept.** `POST /v1/corrections` takes `decisions` and `answer`, each `right` or `wrong`, and `items`, at least one of the three. Wrong decisions need `shouldCall` (`[{ tool, args? }]`, with `args` only when the inputs matter) or `mustNotCall` (tool ids). A wrong answer needs `shouldSay` or `why`. `items` (`[{ key, pointer?, rank?, verdict }]`) is for an answer that is a ranked list. `item`, `verdict`, `reason` and `correction` are gone, and a body with one is refused (`400`), naming its new field. A second correction of a run by the same caller, for the same `participantId`, replaces the first ([Correct a run's output](https://docs.kindgi.com/v0.4.0/guides/improve/correct-a-runs-output.md)).

- **A correction is sorted when it's saved, and moves until it's fixed.** Wrong decisions go to the agent's owner (`with-owner`), a wrong answer or item goes to Improve (`open`), and a correction with nothing wrong is `fixed`, as `recorded-right`. An app end user's correction (one with a `participantId`, and not an expert's) stays `open` and unsorted, unless a project admin sets `sortEndUserCorrections: true` on `PATCH /v1/projects/{projectId}`. A correction answers `status`, `sortedTo`, `sorted`, `link`, `fixedIn`, `statusNote` and `updatedAt`. `PATCH /v1/corrections/{correctionId}` (`client.corrections.change`) sorts it again, changes its status, or both: it needs `publish` on the agent, or, for an agent more than one project serves, `write` on the correction's project. A change its status doesn't allow answers `409 correction-status-change-not-allowed`, with `details: { from, to }`. Removing a correction, by its author or an admin of its project, makes it `removed`. `GET /v1/corrections` filters by `status` (`not-fixed` for `open`, `being-fixed` and `with-owner`), `sortedTo` (`none` for the unsorted), `from` (`experts`, `end-users` or `all`), `madeBy=me`, `since` and `until`, and `verdict` is gone. `GET /v1/corrections/counts` (`client.corrections.counts`) counts a project's corrections, or one agent's, by status.

- **`kindgi corrections` takes a correction's parts as the API names them.** `kindgi corrections add` takes `--decisions=right|wrong`, with `--should-call=<tool>[:<json-args>]` and `--must-not-call=<tool>` (each repeatable) for wrong ones, `--answer=right|wrong`, with `--should-say=<text>` for a wrong one, `--why=<text>`, `--group=<corrector-group-id>`, and `--item=<key>` with `--yes` or `--no` as before. `--correction`, `--reason` and `--item=answer` are gone. Each call still adds to your correction of the run: a part it gives replaces what you said about that part. The new `kindgi corrections set <correction-id>` takes `--sorted-to=improve|developer|owner`, `--status=open|being-fixed|wont-fix`, `--link` for what fixes it, and `--reason`, which `wont-fix` needs. `kindgi corrections list` loses `--verdict`, and takes `--status`, `--sorted-to`, `--from=experts|end-users|all` and `--since=<time>`. It lists removed corrections only when `--status` names `removed`.

- **`client.supervisor` is gone from the TypeScript client.** Every method on it threw `not-yet-wired`: use `client.suggestedChanges` and `client.observations`. With it go `SupervisorClient`, `SupervisorProposalsClient` and their input types, `SupervisorSpec`, `Supervisor`, `DryRunCriterion`, and the proposal result types no method returned. The error code `supervisor-header-missing`, which the API never sent, leaves the API's codes and the TypeScript, Python and Java clients.

- **The TypeScript client's `client.observations` keeps only `query`.** `recordRun` and `patterns`, which threw `not-yet-wired`, are gone, with `PatternsInput`.

- **The TypeScript client's list pages drop `items`.** A list call answers the API's page alone: `data`, `hasMore` and `nextCursor`. `items` was deprecated before and is gone now: read `page.data`.

- **A run that names no version gets the agent's Latest in its project, no longer the highest version its project reaches.** That holds where no pin covers the run's scope. Latest moves only when a deployment into the project ships a version, when a person publishes a full version (`POST /v1/agents`), or when a person adds a suggested change's version as Latest. A pin never moves it. A project where no version has a time yet has no Latest, and refuses such a run: "none is pinned there, and none is its Latest". The highest version still numbers the next one, and `GET /v1/agents/{agentId}` still answers it. `GET /v1/agents/{agentId}/projects` says, for each project, what a run that names no version gets there (`runs`: the version, since when, and the pin or deployment behind it) and why (`follows`: `pin` or `latest`), the pins on segments inside it (`segments`), and the agent's `highest` version. What serves a scope, a pin's check, a flow publish's default agent pin and a trigger's `toolsAllowedUnattended` check all read Latest the same way: a covering pin, else Latest.

- **A pin into a scope where nothing runs yet is refused.** Where no version of the agent runs (no pin, and no Latest), a pin answers `409 nothing-runs-in-scope`, or `409 project-suspended` when the project is suspended, since there's nothing to measure it against. It was measured against the highest version. The TypeScript client throws `ConflictError` for it.

- **A trigger's `toolsAllowedUnattended` is refused while nothing it checks runs in the trigger's project.** A schedule, webhook or mail trigger whose list names a tool its target doesn't call answers `400 bad-input` when no version of the agent runs there (no pin, and no Latest), since its tools can't be checked. A flow is refused the same way when an agent step names no version and no flow version pins one. Name `agentVersion`, or deploy or publish a version into the project first. The check read the agent's highest version before.

- **A pin answers `{ pin, testRuns, fixed, reopened }`, and an unpin answers `201`.** `POST /v1/agents/{agentId}/pinned/pin` answers the pin change under `pin`, the test runs that check it, and the corrections a reused test run changed (`PinAnswer`), still with `201`, or `202` while it waits for an approval. `POST /v1/agents/{agentId}/pinned/unpin` answers the same shape with `201`, where it answered `200` with the change alone. `POST /v1/suggested-changes/{suggestedChangeId}/pin` answers the same shape, with the suggested change under `suggestedChange` (`SuggestedChangePinAnswer`). `agents.pinned.pin`, `agents.pinned.unpin`, `suggestedChanges.pin`, `kindgi agents pin`, `kindgi agents unpin` and `kindgi suggested-changes pin` give the same shapes.

- **A pin that would take a project off Latest is refused unless you say so.** A project or segment pin of a project that follows Latest, or a tenant or org pin over one, answers `409 pin-stops-latest`, even when the version doesn't change, with each project and the version it runs now in `details.projects`. Nothing is recorded. Send `stopFollowingLatest: true` to pin anyway, or `--stop-following-latest` on `kindgi agents pin` and `kindgi suggested-changes pin`; the change then records `stoppedFollowingLatest: true`. Such a pin went through before. A deployment promotion's `pin` is never refused this way.

- **Kindgi names its own test runs, so a start can't.** `POST /v1/test-sets/{testSetId}/runs` refuses a `correlationId` that starts with `pin:` or `latest:` (`400 bad-input`): those name the test runs a pin or Latest moving starts.

- **A suggested change's `fixes` are checked when it's made.** Each id must be a correction in the suggested change's own project: `POST /v1/suggested-changes` refuses an unknown id, or another project's, with `400 validation-failed`, listing them in `details.fixes`, and makes nothing. `fixes` names at most 100 ids (`400 bad-input`).

- **An Improve run works in one project, and keeps a change only when it passes more of the cases and fails none that passed.** `POST /v1/improve-runs` takes `{ agentId, projectId, tiers?, model?, candidates?, budget?, objective?, groupWeights? }`. `projectId` is required, and a body that sends `scope`, `testSetId` or `fromVersion` is refused (`400 bad-input`), naming the field. It starts from the version the project runs, takes the project's open corrections sorted to Improve, and searches and proves on the project's test set. A change is kept only when its proof, on every case at the reference's repetitions, passes more cases than the version it started from and fails none that passed. The cheaper path for model-settings changes is gone. `GET /v1/improve-runs` takes `projectId` and `status` ([Improve an agent](https://docs.kindgi.com/v0.4.0/guides/improve/improve-an-agent.md)).

- **An Improve run checks it's ready before it starts.** `POST /v1/improve-runs/check` answers `{ ready, startsFrom, items, estimate, month }`: `items` lists what's missing, from `wording`, `settings`, `test-cases`, `permission`, `one-at-a-time` and `project`, each with its `fix`. The check's `estimatedCostUsd`, `maxCostUsd` and `basis` move under `estimate` (`{ costUsd, maxCostUsd, basis }`), and `month` is this month's spend against the Improve schedule's cap. A start that isn't ready answers `409 improve-not-ready`, with the missing items in `details.items`, and a second start for the same agent and project answers `409 improve-run-running`, with `details.improveRunId`. A start no longer answers `validation-failed` for `no-tunable-keys` or `no-pinned-prompt-block`, and `proposal-needs-pin` is gone.

- **An Improve run's record changes.** `candidatesEvaluated` is `tried`. It gains `projectId`, `startsFrom` (`{ version, follows }`), `testSetVersion`, `setAside` (`{ cases, of }`) and `takes`, the correction ids it took, and loses `fromVersion`, `scope` and `testSetId`; `groupWeights` is always there. Its `outcome` is `suggested`, `nothing-better` (was `nothing-found`, with `reason` and an optional `holdOut`), `cost-cap` (new, with `spentUsd`) or `failed`. A comparison's `part` can be `all`, for the proof. The `improve-run.finished` webhook event carries the same shape.

- **An Improve schedule is for one agent in one project.** Its target is `improve: { agentId, projectId }`, one per agent and project (`409 schedule-exists`, with `details.scheduleId` and `details.triggerId`), and a segment or tenant scope is gone. Its input is `{ corrections, from, runCapUsd, monthCapUsd, tiers?, model? }`, with the defaults 5, `experts`, 5 and 20 (`monthCapUsd` was `monthlyCapUsd`). `threshold`, `candidates`, `budget`, `objective` and `groupWeights` are gone, and an option it doesn't have is refused. A schedule at its month's cap stays `active` and says until when (`pausedUntil`, with `pausedReason: 'month-cap'`). One whose owner loses `publish` on the agent is paused at its next fire (`statusReason: 'owner-lost-publish'`), and saving it with a new owner resumes it. Registering, changing, pausing, resuming, firing or unregistering one takes `write` on the project and `publish` on the agent; an admin of its project may also pause or unregister one without `publish`.

- **The TypeScript client and the CLI follow.** `StartImproveRunInput` and `CheckImproveRunInput` take `projectId` (required), `tiers`, `objective`, `groupWeights`, `model`, `candidates` and `budget`, and `ImproveRunsListInput` takes `projectId` and `status`. A `409 improve-not-ready` is a `conflict` whose `fields.items` lists what's missing. `kindgi improve runs start` takes `--project` (required) and `--check`, which prints the readiness items, the estimate and the month, starts nothing, and exits `3` when it isn't ready; `--tier` repeats. `--tenant`, `--org`, `--segment`, `--pass-project`, `--test-set` and `--from-version` are gone. `kindgi improve runs list` takes `--project` and `--status`. `kindgi schedules create --improve` needs `--project`, and its `--segment` is gone.

- **The Anthropic and Gemini adapters throw `ModelProviderError`.** For a response that refuses a call, they throw the shared error from `@kindgi/adapter-model-shared`, as the Bedrock, Azure OpenAI and OpenAI-compatible adapters do. Its `kind` comes from the status and the provider's words (`invalid-request`, `auth`, `rate-limited`, `unavailable`, `context-too-long` or `content-filter`, and `network` for Anthropic's failed connection), with its `status`, and its message is the same as before. The provider SDK's own error (Anthropic's `APIError`, Gemini's `ApiError`) is now its `cause`: code that checked the thrown error's type checks `cause` instead. An abort, and Gemini's failed connection, are thrown as before.

- **A stdio MCP server's secrets go by reference, and a credential in `config.env` is refused.** Give a stdio endpoint its secret variables in `secretEnv`, each name with the secret its value is read from when the runtime starts the server, as in `"secretEnv": { "GITHUB_TOKEN": { "envName": "production", "name": "FILES_GITHUB_TOKEN" } }` (1 to 16 of them). The value is never stored with the endpoint or answered. Registering an endpoint with a credential-named variable in `config.env`, such as `GITHUB_TOKEN`, `OPENAI_API_KEY` or `DB_PASSWORD`, is refused (`invalid-mcp-endpoint`, reason `credential-in-env`), naming the variable and the `secretEnv` that replaces it; a lone `KEY`, as in `CACHE_KEY`, doesn't count. `secretRef` on a stdio endpoint is refused too (`secret-ref-on-stdio`), since a stdio server gets no headers, and `secretEnv` on an HTTP endpoint (`secret-env-on-http`). Every `config.env` value reads back as `"[redacted]"`, its name kept, on endpoints registered before too, so an export holds the names and not the values. A `config.env` value of exactly `"[redacted]"` is refused (`invalid-stdio-env`), so registering an endpoint again from what its `GET` answers fails until its values are put back, or its secrets move to `secretEnv`. A value with a NUL, and a name that's empty or holds `=` or a NUL, are refused too. Endpoints registered before keep working, and `kindgi doctor`'s new check `mcp-env` lists each stdio endpoint that keeps a credential in `config.env`, or a `secretRef`, with the fix. If you go back to an earlier runtime, an endpoint registered with `secretEnv` starts without those variables ([MCP servers](https://docs.kindgi.com/v0.4.0/guides/tools/mcp-servers.md)).

- **A provider registration with a field it doesn't take is refused.** `POST /v1/providers` answers `400 invalid-provider`, with reason `unknown-model-field` or `unknown-metadata-field`, naming the field and the fields a model takes. Before, such a field was dropped without a word, so a misspelled `thinking` registered a model that doesn't think. Registrations stored before aren't checked again.

- **A suggested change is made for one project, and checked on that project's test set.** `POST /v1/suggested-changes` takes `agentId`, `projectId`, `block`, and `values` for a settings block or `template` for a prompt block, with `why` and `fixes` (at most 100 correction ids from the project). It's built on the version the project runs, and its check starts at once: its version and the one it's built on run the same cases of the project's test set. `POST .../check` checks it again, in place of `evaluate` and `rescore`, which are gone. `fromVersion`, `scope`, `tier`, `change` and `evidence` are gone from the body and the answer, and `candidate`, `evaluation`, `promotion` and `rolledBack` from the answer. The list takes `agentId`, `projectId`, `status` and `madeBy`, not `tier` or a scope. Its status is `checking`, `check-failed`, `ready`, `waiting-for-second-person`, `pinned`, `added-as-latest` or `withdrawn`. One made at 0.3.x for a tenant, an org or a segment can only be withdrawn (`409 suggested-change-not-for-a-project`). In the TypeScript client, `create` takes the new body, `check` replaces `evaluate` and `rescore`, and `ProposalTier` and `EvaluateProposalInput` are gone from it. `kindgi suggested-changes create` replaces `draft`: it needs `--project` and takes `--why` and `--fixes`, once per correction, and `--from-version`, `--tenant`, `--org`, `--segment` and `--tier` are gone. `check` replaces `evaluate` ([Suggest a change, and test it](https://docs.kindgi.com/v0.4.0/guides/improve/suggest-a-change.md)).

- **Deriving an agent version through the API is gone.** `POST /v1/agents/{agentId}/versions` is removed, with `client.agents.versions.derive`, `DeriveAgentVersionInput` and `kindgi agents derive`. A suggested change makes such a version now, and checks it. A version derived before keeps its `derivedFrom`.

- **Test sets aren't built from corrections any more: an agent's corrections are its test set.** `POST /v1/eval-suites/{suiteId}/versions/from-judgments`, `client.evalSuites.buildFromJudgments` (`build_from_judgments` in the Python client) and `kindgi eval-suites from-judgments` are removed, and so is the error code `test-sets-not-supported`. A `corrections` test set built before still reads by its id, but `GET /v1/test-sets` and `kindgi test-sets list` list one only with `kind=corrections`. A test set id that starts with `agent.` is kept for an agent's own test set: `POST /v1/test-sets` refuses one (`400 bad-input`), and the TypeScript client refuses it before sending. A test set published under such an id before keeps reading and running, but takes no new version: publish it under another id. `CorrectedTestCase`'s `runId` is required.

- **A block an agent version uses for its model settings takes model settings only.** `POST /v1/blocks` refuses a new version of such a block when its values aren't model settings, or name a model the agent version names too (`400 validation-failed`, naming the agent version).

- **A deploy that would put back an older block stops.** Before anything is published, `POST /v1/deployments` compares each of the pack's data blocks with the newest version that runs now in any project of the pack's agents. When the pack's file is older, the deploy answers `409 deploy-would-put-back-older-blocks`, lists each block in `details.blocks`, and writes nothing. To put one back on purpose, name it in the body's `replace` (`[{ blockId, reason }]`), or run `kindgi deploy --replace=<block> --reason=<text>`. `POST /v1/deployments/check` (`client.deployments.check`) answers the same with nothing written, and `kindgi deploy` asks it before it builds an image. A deployment promotion with `pin` runs the same check against the target project, and `kindgi promote --pin` takes `--replace` too.

- **The Cloud Run module's one-pack-service variables are gone.** `pack_image`, `pack_min_instances`, `pack_env` and `pack_secret_env`, and the output `pack_service_url`, are removed: each deployment has a pack service of its own, and the pack's env comes from Kindgi (`kindgi env set`). `name_prefix` can't be `kp` or start with `kp-`. `pack_concurrency` and `pack_max_instances` take whole numbers from 1 to 1000, `pack_timeout` whole seconds from `"1s"` to `"3600s"`, and `pack_cloud_sql_instances` at most 10. New: `pack_secret_readers`, the Secret Manager secrets a pack's env names as cloud secrets, and `orchestrator_key_version`, which is `null` by default and leaves the pack orchestrator out until it's set, with the output `orchestrator_key_steps`. `service_accounts` names `server`, `pack` and `orchestrator` ([Cloud Run module reference](https://docs.kindgi.com/v0.4.0/deploy/self-hosted/cloud-run-reference.md)).

- **A new tenant's default project is named "First Project".** One made before keeps "Default". Messages, help and docs say "the default project" for the one with `isDefault: true`. To find it, use `isDefault`, `GET /v1/projects/default` or `client.projects.getDefault()`, never the name or the slug `default`.

- **A write whose database fails answers `500`, not `404` or `409`.** Unregistering or reinstating an agent, flow, tool or test set version, deleting an env value, and unregistering an MCP endpoint, a provider or a reviewer answered `404` when the database failed, as if what they named weren't there. Publishing a test set answered `409`, as if it were already there. They now answer `500` and change nothing. A session whose activity can't be recorded is logged at `WARN`, and its next request records it again.

- **With per-deployment routing, an older build keeps its pack service while something can still call it.** A deployment's service stays while a version it ships is registered, or held: a pin still reaches it, or a run in flight uses it. Then it's superseded and retired. Before, every version a deployment shipped counted, so a service went only once a later deploy shipped the same versions. `GET /v1/deployments` says how many builds are kept (`keptBuilds`), and each endpoint what keeps its build (`keptFor`, `keptForTotal`).

- **In the console:**

  - The console says correction, corrector group, test set, test run, suggested change, Improve run, pin and release gate, as the API now does, and the project's Quality page is Corrections.
  - Agents, flows and runs have pages where they had rows of tabs. An agent's are Overview, Runs, Corrections, Test set, Suggested changes and Pinned versions, and its Overview says where each stands under How it gets better. A flow's are Overview, Runs and Corrections. A run's pages are under its agent or its flow, and an agent's Runs page has its turns in flows too; Menu > Runs still lists every run. The trail at the top ends on the page you're on, and its last crumb lists the other pages. Every page has its own link, and Copy copies the whole link. A run's id alone, as the CLI, the API and the logs give it, still opens its page.
  - The release gate form sets how many cases must pass, that none that passed before fails, and a second person who approves. The comparison, evidence, figures, replay and only-experts rules are set with the API or the CLI: a gate saved from the form keeps them as they were, and lists them under Kept as they are.
  - Schedule Improve opens from an agent's Suggested changes page, for that agent, and no longer from the Schedules page, which still lists every schedule.
  - Links are underlined, and one that opens a new tab ends with an arrow. A section's buttons sit on the section they change, and only a page's main action is a filled button.

## Known issues

- **A release gate's `cases.noNewFailures` doesn't count a case whose answer couldn't be checked, even if its decisions failed.** Its message says how many cases it checked.

## What the upgrade changes

- **On Cloud Run with the module, the upgrade turns on Cloud Scheduler's API,** which starts the pack orchestrator. With `secrets_backend = "none"`, it also turns on Cloud KMS's and makes the module's key ring and key: each pack endpoint's token is now sealed under that key (`KINDGI_PACK_ENDPOINT_KMS_KEY`). With a private-IP database (`database_private_network`), turning on these APIs makes the plan show the server's `KINDGI_DATABASE_SERVER_CA` as `(known after apply)`: it's still the instance's CAs ([Upgrade by hand](https://docs.kindgi.com/v0.4.0/deploy/self-hosted/cloud-run.md#upgrade-by-hand)).

## Upgrade steps

- **Eighteen new migrations:** four new tables (`block_version_prs`, `env_cloud_secrets`, `run_files` and `tool_file_tokens`), new columns that are empty or have a fixed default, with nothing rewritten, and the `supervisor_fix_proposals` table dropped (below). The first start runs them, as it runs any other migration. They lock the tables they change or point to: `agent_registry`, `agent_registry_versions`, `agent_promotions`, `block_registry_versions`, `deployments`, `deployment_endpoints`, `eval_runs`, `eval_suite_cases`, `improvement_proposals`, `judgments`, `orgs` and `projects`. Seven of the migrations hold the lock while they read every row, to check a new rule or build an index, and two of those tables can be large. The one that sorts corrections keeps `judgments` locked while it indexes every judgment: meanwhile reading or saving judgments and corrections waits, on a server of the earlier version still running too. The one for test runs indexes `eval_runs`: meanwhile starting or updating a test run waits. With many of either (`SELECT count(*) FROM judgments`, `SELECT count(*) FROM eval_runs`), upgrade at a quiet time. A long transaction holding one of those tables makes the start stop after about a minute, naming the session that holds it. End that session or let it finish, then start again. On Cloud Run, the old revision serves meanwhile; on a single server, such as the Docker setup, the runtime is down until the new start succeeds.

- **A `gcs:` artifacts bucket: give the server's identity `storage.buckets.get` on it before you upgrade.** The upgraded runtime reads the bucket's settings at start, and `roles/storage.objectAdmin` doesn't grant that permission, so without it the runtime refuses to start. Grant `roles/storage.legacyBucketReader` on the bucket, or add the permission to a custom role. A ledger or purge-record bucket needs only `storage.objects.create`.

- **With `KINDGI_ARTIFACTS` set, give the server a core file limit of 0.** Otherwise, on Linux, each start warns "Core files are on": a crash of the server's process would leave the files it holds in memory on disk, outside retention and the tenant purge. `docker-compose.prod.yml` sets `ulimits: core: 0`; elsewhere, use `--ulimit core=0` with `docker run`, or `LimitCORE=0` in a systemd unit.

- **A site person's Ask gets the project's tools at their next sign-in.** There's no migration. The first start brings each tenant's authorization store up to the new model, as at every upgrade, and logs `store <id> now has the current model`. A store it couldn't update logs `could not bring store <id> up to the current model` (or `could not read its store's model to bring it up to date`), and the next start tries again. A site person whose role may ask gets the new `turn_invoker` relation at their next sign-in through the site. Until then, or while their tenant's store has the old model, their Ask's calls of the project's tools are refused, as before. The approval floor holds from the first start, though, so a `kindgi_remember` in their Ask waits for a yes from then on. A sign-in whose store refuses the write logs a `WARN`, once per tenant: "a site person's turn_invoker wasn't written". In the audit, a site person's tool call in their own turn logs two `authz-decision` events on the tool: the `invoke` deny, as before, then the allow, whose evidence names `turn_invoker`.

- Upgrading drops the old `supervisor_fix_proposals` table; at 0.3.x no route or job wrote to it.

- **Projects gain a `sort_end_user_corrections` column, a yes or no.** It's empty on existing projects, and empty reads as no: their end users' corrections aren't sorted, and an Improve schedule that takes everyone's corrections doesn't count them, until a project admin turns it on with `sortEndUserCorrections: true` on `PATCH /v1/projects/{projectId}` ([Where a correction goes](https://docs.kindgi.com/v0.4.0/guides/improve/correct-a-runs-output.md#where-a-correction-goes)).

- **Right after the upgrade, a project no pin covers runs its Latest.** That's whichever came last there: the version the newest deployment into the project shipped, or the newest version made in the project before 0.4.0, a derived one too, by when it was published. It's not always the highest version: the two differ after a deploy that rolled back, or a lower version published after a higher one. To keep a version running in a project, pin it there before you upgrade.

- **An Improve schedule saved at 0.3.x is read in 0.4.0's shape.** Nothing rewrites it. Its `threshold.judgments` is its `corrections`, and the threshold's `runs` and `judges` are dropped, so it fires on the count of corrections alone. `budget.maxCostUsd` is its `runCapUsd`, `monthlyCapUsd` its `monthCapUsd`, and `classWeights` its `from` (`as-recorded` reads as `everyone`, and `restricted-only` as `experts`). Its `objective`, `candidates` and the rest of its `budget` are dropped. One saved for a segment improves the segment's whole project. One saved with no `classWeights` reads as `experts`. At 0.3.x, where no expert group applied (none for the tenant, the project or the agent), it took everyone's corrections; now it takes only experts', so there it starts no Improve run. To keep everyone's, copy its `input` from `kindgi schedules get <schedule-id>`, set `from` to `everyone` in it, and send all of it back with `kindgi schedules update <schedule-id> --input=@<file>`. The update replaces the whole input: a field left out goes back to its default. The console can't change an existing schedule's input.

- **Corrections made at 0.3.x go to nobody, so Improve doesn't learn from them until they're handed to it.** A correction now says who fixes it: Improve, a developer or the agent's owner. An Improve run, or an Improve schedule, takes only an agent's open corrections handed to Improve. Upgrading keeps each older correction open, with nobody to fix it, and the console shows it as "Nobody yet" under Who fixes it. To have Improve learn from them, filter an agent's corrections by Who fixes it: Nobody yet, and hand each one to Improve with Hand it to someone else, or send `PATCH /v1/corrections/{id}` with `"sortedTo": "improve"`. Only an agent's corrections go to Improve: hand a flow's to a developer or the owner.

- **An Improve run still going when you upgrade ends, and doesn't carry on.** 0.4.0 checks a change case by case, and a run 0.3.x started didn't, so the run ends `failed`: "An older version of the runtime started this Improve run, and this version can't carry it on. Start it again." One someone asked to stop ends `cancelled`. The suggested change it made, and that change's check, stay for a person to look at. To lose no run, upgrade when none is going.

- **Upgrade your clients, the CLI and your own callers with the runtime.** 0.4.0 renames routes, fields, error codes and metric names, and keeps no old name: `/v1/gate-policies` is `/v1/release-gates`, and `kindgi gate-policies` is `kindgi release-gates`, for one. A release gate saved at 0.3.x needs no step: the runtime reads its old names. Every renamed name is listed under Changed, or in the steps below.

- **Console links saved before 0.4.0 can answer Page not found.** Pages moved under the agent or flow they belong to, and 0.4.0 keeps no old address. Under `/tenants/<tenant>/projects/<project>/`:

  - A run's tabs are under its agent or its flow: `agents/<agent>/runs/<run>/journal`, `/provenance`, `/cost` and `/corrections`, or the same under `flows/<flow>/runs/<run>`. A link to `runs/<run>` still opens the run, but `runs/<run>/journal`, `/provenance`, `/cost` and `/judgments` don't.
  - Quality is now Corrections: `corrections`, and `corrections` on each agent and flow. A link to `quality` answers Page not found, whatever its tab.
  - Suggested changes are at `agents/<agent>/suggested-changes`, which was `agents/<agent>/proposals`, and an Improve run at `agents/<agent>/improve-runs/<run>`, which was `agents/<agent>/proposals/passes/<pass>`.
  - A test run is at `test-runs/<run>`, which was `eval-runs/<run>`. The project's Test sets pages are gone: each agent has its test set, at `agents/<agent>/test-set`.
  - A new release gate is made at `agents/<agent>/release-gates/new`, which was `agents/<agent>/gate-policies/new`.

- **A pack's copy of the `kindgi-using` skill gives 0.3.x console links until it's refreshed.** A pack keeps the skill copies it was made with. Once its `@kindgi/cli` is upgraded, `kindgi dev` refreshes each copy you haven't edited as it starts, and `kindgi skills sync` refreshes them any time (`./kindgiw dev` and `./kindgiw skills sync` in a Java or Scala pack). A copy you edited is kept, and `kindgi skills sync --force` replaces it. Until then, an older copy gives a run's tabs as `runs/<run>/cost` and the like, which 0.4.0 doesn't open, and names Quality where the console says Corrections.

- **A webhook receiver, or code that reads approvals, sees 0.4.0's names.** The Improve run's event is `improve-run.finished`, which was `improvement-pass.finished`, and its data is in `data.improveRun`, which was `data.pass`. An endpoint subscribed to the old name gets the new one with no step; a new subscription must name the new one. An approval's `subjectKind` is `agent-pin`, `fact` or `supervisor:suggested-change`, which were `agent-promotion`, `memory-fact` and `supervisor:fix-proposal`. The API takes only the new names; approvals stored before read with them.

- **A stdio MCP server that keeps a credential in `config.env` still works, and the runtime says so at each start: move it into `secretEnv`.** For each stdio endpoint registered with a credential in plaintext `config.env`, or with a `secretRef`, which a stdio server is never sent, the boot logs a `WARN` under `mcp.env` that names the endpoint and the `secretEnv` that replaces it; `kindgi-runtime check mcp-env-credentials`, and `kindgi doctor`'s `mcp-env` check, list the same. Its `config.env` values now read back as `"[redacted]"`, and registering one again with a credential in `config.env` is refused. To move one, store each `config.env` value as a secret (a `secretRef` is stored already), then unregister the endpoint and register it again with `secretEnv` ([MCP servers](https://docs.kindgi.com/v0.4.0/guides/tools/mcp-servers.md)). An HTTP MCP endpoint with `secretEnv` in the config file stops the boot: only a stdio server is given one.

- **The expert queue's records are logged under `expert-queue`, which was `judging`.** In `KINDGI_LOG_LEVELS`, change `judging=<level>` to `expert-queue=<level>`. The old name only warns at boot, as a name no subsystem logs under, and has no effect.

- **On Cloud Run with the module, the upgrade moves the packs to the module's pack orchestrator.** With the one pack service, from the apply until each pack is deployed again, calls to a deployed pack's tools are refused (`pack-route-missing`, then, for about a minute, `pack-endpoint-pending`), so upgrade at a quiet hour. With `tenancy = "multi"`, stop your own orchestrator first: the services it made keep serving, while their hosts match `*-<project number>.<region>.run.app`, until each tenant deploys its packs again. The module's `upgrade.sh` makes the switch in one apply, and then prints the steps that finish it: mint the orchestrator's key, apply again with `pack_image`, `pack_min_instances`, `pack_env` and `pack_secret_env` out of the var files, set the pack's env in Kindgi, and deploy each pack again with a new idempotency key ([From the one pack service](https://docs.kindgi.com/v0.4.0/deploy/self-hosted/cloud-run.md#from-the-one-pack-service)).

- **On Cloud Run, a pack's first call after a quiet spell waits for an instance to start.** The one pack service of 0.3.x kept `pack_min_instances` instances running, 1 by default, so a first tool call didn't wait for one. Each deployment's own service keeps none: it scales to zero when it's idle. The Cloud Run module no longer takes `pack_min_instances`: `upgrade.sh`'s steps take it out of the var files, and in a `module` block that calls the module it's an error now, as `pack_image`, `pack_env` and `pack_secret_env` are.
