Kindgi 0.1.10
Released October 23, 2026. For the docs of this version, see docs.kindgi.com/v0.1.10.
Security fixes
Section titled “Security fixes”-
The database's address in the runtime's log: when a password with a raw
/,?or#makesKINDGI_DATABASE_URLmisread, theCan't connect to the databaseline namesKINDGI_DATABASE_URLwhere it named the host, so no part of the credentials shows. Percent-encode those characters in the password:%2F,%3Fand%23. If your password has a raw/,?or#and the runtime's logs leave the machine, change the password. -
Secret names and your own secret manager: a secret's name is now checked before it reaches your secret manager (
KINDGI_SECRETS_BACKEND=secret-manager).
-
Cloud Run module: the database instance ends any session left idle in a transaction for 10 minutes (
database_idle_in_transaction_timeout_ms,0for off). An instance made with an earlier copy gets the flag at its next apply, with no restart. -
Deploy: an image the runtime can't read, or one past its bounds: a deploy refused with
image-unverifiablecan also sayimage-unreadable(a layer that isn't an archive the runtime can read) orimage-limit-exceededindetails.cause. For a limit,details.limitnames which of the image's own it passed (manifest,file,layer,read,unpack), so the same image is refused again.kindgi deploygives a hint for each (image-unverifiable). A deploy past the tenant's hour of image reads answers429 rate-limit-exceededinstead, withdetails.limit: "tenant-unpack-hour"and aRetry-Afterof the time until the hour has room for the image again, andkindgi deploywaits it out as it does the server's other limits, up to 10 minutes in all. -
A TypeScript pack's image with only what its tools import:
image: { dependencies: 'loaded' }inkindgi.configdownloads only the packages the pack's tools import, with their own dependencies, and whatimage.keepnames, instead of every production dependency of the app.kindgi buildcuts the app'spackage.jsonand lockfile down to them, checks the result against the app's lockfile, and the image installs it frozen.'all'stays the default. The build says how much of the lockfile the image installs, and which of the pack's own dependencies it leaves out. Most misses fail the build, naming the package. A package loaded by a name no import shows, such as a plugin named in a config, fails that call at run time, saying what to add tokeep. Anode_modulesout of date with the lockfile fails the build, saying what to run. In a few cases, such as Yarn 2 or later or a local package (link:,file:,workspace:) among what the tools import, the image downloads everything and trims it after, and the build says why. A Python pack's[tool.kindgi.image] dependencies = "loaded"is refused for now (Add Kindgi to a TypeScript or Node app). -
Let a site's person back in: a tenant admin lets someone removed in Kindgi back in through their site, with
kindgi sites restore-person <site-id> <user-id>(POST /v1/projects/{projectId}/sites/{siteId}/people/{userId}/restore). They're the same person, with their history, and their next sign-in through the site gives them what their roles give then. Their API keys and sessions stay gone. It's refused with409 site-person-restore-refusedwhen they weren't removed, when their site said they left it, or when they also sign in another way (WordPress). -
Console: a closed conversation's page ends with a Conversation closed step, at the time it closed, once its last page of messages is loaded. It no longer offers Continue conversation: it says
Closed: it takes no new turns. A closed conversation named in the Start run form is flagged under Conversation, and Start run doesn't start it. -
Console: the Conversations list filters by status (All, Open or Closed, kept in the page's address), and Load more brings the next page, as on Runs.
-
Read your usage:
GET /v1/usageanswers your tenant's usage, aggregated: runs started and ended (each with its class), approvals asked for and decided, model calls, and projects put in production or taken out. Each group has its count, the sum of its quantities and its first and last times, by project, agent, kind, class, state or day, over at most 92 days. A tenant admin reads every project; anyone else, the projects they may read.kindgi usagereads a month or a window of it, and the TypeScript and Python clients addclient.usage.read. The operator reads usage across tenants withGET /v1/system/usage(system:usage:read). Both answer only on a runtime that counts usage (KINDGI_USAGE_METERING=on;GET /v1/usage). -
Rename your workspace: its name, its slug, or both. Its id stays, so whatever names it by its id keeps working, your keys and the CLI included. A tenant admin renames it with
kindgi tenant rename --name=<name> --slug=<slug>(either or both),PATCH /v1/tenant, the TypeScript and Python clients'tenant.update, or Rename on the workspace's home page in the console;kindgi tenant getprints it. The console's addresses carry the slug, so a link with the old one opens a page that links to the workspace. An operator renames any tenant withPATCH /v1/system/tenants/{tenantId}undersystem:tenants. Two new refusals:409 tenant-slug-in-configwhenKINDGI_AUTH_VERIFIED_DOMAINSnames the tenant by its slug, or names the slug asked for, and409 tenant-setup-pendingwhile the tenant's creation hasn't finished (Rename your workspace; Serve many tenants). -
Purge a tenant through the system API:
POST /v1/system/tenants/{tenantId}/purge, withconfirmset to the tenant's slug, purges a closing tenant for good once its export window has passed, askindgi-runtime tenants purgedoes, and answers when it's done. It takes a system key holdingsystem:tenants:purge, which no other scope includes, orkindgi:system. The TypeScript and Python clients addsystemTenants.purge(system_tenants.purge). The servers need the newKINDGI_TENANT_PURGE_RECORD, where purge records are kept: while it's unset, every purge is refused with409 purge-record-unset. One purge of a tenant runs at a time, through the route or the command: a second waits for the first (Serve many tenants). -
After a restore, purged tenants stay purged, even without their record: each copy of the erasure ledger (
KINDGI_ERASURE_LEDGER_EXPORT) also names every tenant marked purged, andkindgi-runtime tenants repurgereads the newest copy (--ledger, or there by default) as well as the purge records, so a tenant whose record is gone is purged again too. Each record and entry is checked against its tenant by when the tenant was created, which a rename doesn't change, and--dry-runsays what named each tenant (After a restore: tenants repurge). -
A key for the license renewal's counts: a runtime serving one tenant now mints system keys too, with its own token, and only ones holding
system:license. Such a key reads the license and its use and nothing else, so a renewal that runs on its own can send the deployment's use without holding the runtime's own token. There, a system key holding any other scope is refused on every route (403 system-key-scope-not-served). On Azure, the module'slicense_usage_key_secretnames the vault secret that holds the key, and the module alerts when a run sends no counts (A key for the counts).
-
A database failure in the block registry, proposals or improvement passes: a request that failed there got a bare
500with no body, and nothing was logged. It's now answered as other routes answer it: aGETorHEADthe database was too busy for gets503(database-busy) withRetry-After: 5, and any other failure the JSON500internal-server-errorwith itsrequestId, logged. The same goes for an inbound webhook delivery whose refusal couldn't be recorded. A schedule's fire that failed this way saysCould not …in itsdetail, where it said[object Object]. Creating a proposal at the moment its change's open proposal is withdrawn is tried again once, where it failed. -
A stop while the runtime starts: a stop that comes before it serves, such as while it waits for its database, ends it within 3 seconds. Its log says
Stopped while starting (SIGTERM)., and it exits with code 143, or 130 for Ctrl-C (Restart the runtime). -
A database that never answers: the runtime asks again, 3 times in all, waiting its connect timeout each time and logging a warning before each new try. Then it exits with code 1, and its log says
no host answered within 30 s (3 tries). The connect timeout is the URL'sconnect_timeout, elsePGCONNECT_TIMEOUT, else 30 seconds. A URL that lists several hosts is tried once. Thekindgi-runtime checkandkindgi-runtime tenantscommands try once, say the same without(3 tries), and exit with code 2 (Where errors show). -
An
Idempotency-Keyretry of a change that answered204: a retry with the same key and body was answered500, though the change wasn't made again. It's answered204again, with no body andX-Idempotent-Replay: true, as other replays are. This covers changing or deleting an org, a project or a team, changing or removing a project's or a team's member, and changing or removing a team's grant on a project. -
More than one replica: a run that another replica had just resumed or taken over could be failed by a sweep while it ran, with
Interrupted: the run was ready to continue, but nothing resumed it before its lease expired.or…but every attempt to resume it failed.The sweep now checks again, under the run's lock, that the run is still one to sweep, and skips it if not. -
Console: a conversation past 100 messages: its page showed only the first 100, so the newest turns were missing. It now reads every message, showing each 100 as they come, with
Loading later messages…until the last is in. Its JSON download is on only once every message is read, so part of a conversation can't pass for the whole. -
createWorkerTemplateRenderer'sclose()(@kindgi/agents/render-worker): once the running renders had finished,close()could fail to settle, and a process whose last work wasawait renderer.close()exited with a non-zero code. It now settles within two seconds: a worker that doesn't stop onSIGTERMgetsSIGKILLa second later. Running renders still finish before any worker is stopped. -
An agent read back through the API left out its
outputandtoolErrors, though the agent applied both:GET /v1/agents,/v1/agents/{agentId}, its/versionsand/versions/{version}, and the answer toPOST /v1/agents/{agentId}/versions. Each is now in the answer when the agent sets it, as published. -
A tool or an MCP endpoint read back through the API left out its
secretScopes, where each secret it names is read, though the server recorded it:GET /v1/tools,/v1/tools/{toolId}, its/versionsand/versions/{version}, andGET /v1/mcp/endpointsand/v1/mcp/endpoints/{endpointId}. It's now in the answer as recorded, and so in the fileskindgi exports createwrites. A register body still can't carry it: one that does is refused with400. So a read of one that records secret scopes can't be sent back to register as it is: dropsecretScopesfirst. -
The API spec lists an artifact's
bucketandkey:BlobMeta, inGET /v1/artifactsandPOST /v1/artifactsanswers, now has them, optional. A server whose storage gives a blob an S3-compatible address (/s3/{bucket}/{key}) answered them, and a client checking an answer against the spec refused it. The TypeScript and Python clients'BlobMetahave both. The answers don't change. -
The console lists every test set: the Test sets page, the Improve dialog and a proposal's Evaluate dialog showed only a project's first 100 judged test sets, and a test set's versions only its first 100.
-
NODE_ENVinbuildEnvno longer leaves a pack's build steps without its dev dependencies:image.buildEnvis set before the image's install, and with npm, Yarn 1 or pnpm 9 aNODE_ENV: 'production'there narrowed the install to production dependencies, so a build step's tool, such as theprismaCLI, was missing. The install now asks for dev dependencies itself when build steps need them, so aNODE_ENVinbuildEnvdoesn't change which dependencies it takes (What the pack's image needs). -
A promotion finds a model provider's key where the project's runs read it: in the project of the environment you promote to, else its org, else the tenant.
GET /v1/deployments/{deploymentId}/valuesreads it the same way. Before, a key stored only in the project or its org was listed underNot ready in <environment>:and the promotion was refused, unless the key was also stored for the whole tenant (kindgi promote). -
Sign-in starts without a secrets store (
KINDGI_SECRETS_BACKEND=none): Continue with Google, Microsoft or GitHub and the emailed link work, and adding or changing a workspace's own identity provider is refused (409 identity-providers-need-secrets-store). Before, the runtime didn't start with sign-in on (Sign-in). -
A SAML sign-in that fails lands on the sign-in page. When the identity provider's page posted a SAML response that couldn't be accepted, the browser showed an error answer instead of the sign-in page. It now goes to the sign-in page with
sign-in-unavailable, and the runtime's log says why. -
Cloud Run module: the first apply writes
sql_connection_name. Step 3 reads it withterraform output -raw sql_connection_name, and after step 1's targeted apply the output was missing, so the command failed. It's there now, with or withoutdatabase_host_instance. With a host instance, its checks (a public IP, Postgres 16 or later) now run in step 1 too, not only in the full apply after it (Deploy on Google Cloud Run). -
A promotion copies the env your pack declares, and checks it first (with
KINDGI_PACK_ROUTING=per-deployment). A promoted deployment didn't record the names its pack declares, soGET …/deployments/{deploymentId}/pack-envlisted none of them, and a host that gives a pack service its values from that list gave it none: under the default check, the service answered503(missing env). It records them now, and a deployment promoted before 0.1.10 gets them when its build is promoted into that environment again. A required name that's set nowhere in the target environment, or set there both as a secret and as a plain value, is now listed underNot ready in <environment>:, and the promotion is refused (409 promotion-values-not-ready), as--checkshows (Declare the environment your code reads).
Changed
Section titled “Changed”-
An agent can't set
conversationPolicy.autoCloseAfterInactiveSeconds(breaking): it was accepted and never acted on. A conversation closes only when someone closes it: Close in the console,kindgi conversations close, orPOST /v1/conversations/{id}/close.defineAgent, the Java, Scala and Python indexers, andPOST /v1/agents(400 validation-failed) now refuse any value, so a pack that sets it doesn't build. A pack built with it before still deploys, and an agent already deployed with it keeps working; its answers leave it out ofconversationPolicy. -
A pack image without build steps installs production dependencies only:
kindgi buildused to install your app's dev dependencies too and drop them after. With no build steps, such asprisma(), it now installs production dependencies only, so the image's install downloads less. With build steps, it still takes dev dependencies for them and drops them after, and with Yarn 2 or later it always does. The build says which. While building, your modules now load withNODE_ENV=production, as in the deployed service, unlessbuildEnvsetsNODE_ENV(What the pack's image needs). -
The tools guide and skill cover a package that ships only CommonJS: a pack's code loads with Node's own
import(), so Node gives a default import of such a package its wholemodule.exports, with the class on itsdefault, where Vitest, Next.js and tsx in a CommonJS app give the class itself. A tool could passkindgi testand then fail underkindgi devwith "is not a constructor". Thekindgi-authoring-toolsskill and the guide take the class either way with a small typed helper, for a dynamicimport()too (A CommonJS package in TypeScript). -
The providers skill leads with the pack's config: the
kindgi-authoring-providersskill used to have a coding agent runkindgi providers register --preset=…, which registers the provider in one worktree's dev database only, so another worktree, a--resetor a teammate'skindgi devhad none. It now declares the provider in the pack's config and commits it: the declaration names a key, never holds it, andkindgi devregisters it at each boot. On a runtimekindgi devdoesn't run, register by hand once per runtime, with the key set in each environment first:kindgi deploydoesn't register providers. The getting-started skills, the Java and Scala agent-authoring skills and the Models guide say to commit the declaration too.
Upgrade steps
Section titled “Upgrade steps”-
With your own secret manager (
KINDGI_SECRETS_BACKEND=secret-manager), check your secrets' names before you upgrade. A secret stored under a name outside the rule for new names (ASCII letters, digits,.,-and_; starting with a letter, digit or_; no.at the end and no two in a row; at most 255) can no longer be read or rotated: it's refused withsecret-store-error. Store its value again under a name that fits, point what uses it at the new name, then revoke the old one. Revoking it leaves its value in your secret manager, so delete it there too.--hardcan't erase it either: the old name is revoked as without it, sokindgi secrets list --include-revokedstill shows it, and the answer issecret-store-errorto say the value is left there. -
If an agent sets
conversationPolicy.autoCloseAfterInactiveSeconds, take it out before you build its pack: the build now refuses it. It never closed anything, so taking it out changes nothing. -
A small dev deployment on Cloud Run (the module with
network_mode = "none", at 0.1.8 or 0.1.9) can be upgraded with the module'supgrade.sh. It runs the upgrade checks against the database and stops if one fails, backs up Kindgi's database as an export, shows the plan, and applies it only when you typeyes. If the new revision doesn't come up, it prints the way back rather than going back on its own. It sets the new image in your var-file, keeping the previous file next to it, and it grants the Cloud SQL instance's service accountroles/storage.objectAdminon the backup bucket, a grant that stays. See./upgrade.sh --help(Deploy on Google Cloud Run).