Operate a self-hosted runtime
This page leads to everything you do to keep a self-hosted runtime running, and says what to check at each upgrade.
These pages continue Self-host Kindgi: the same containers
(kindgi-server, kindgi-db, kindgi-pack), the same kindgi.env and
pack.env, and the same pack. Commands that take your API token read it from
$KINDGI_API_TOKEN.
- Restart the runtime and check its health: a restart that loses no runs, the health routes, the startup log, and where errors show.
- Back up and restore the runtime's database:
pg_dump, the keys a restore needs, a restore into a fresh database, and replaying erasures after it. - Rotate the runtime's tokens and keys: the API token, the pack service token, the license key, a signing key and the public run token key.
- Renew the license key: by hand or on a schedule.
- Logs: the log formats, the levels, and how to follow one request.
Upgrade the runtime
Section titled “Upgrade the runtime”Upgrade at least every 90 days while Kindgi is in preview. Until Kindgi 1.0, a release stops starting 90 days after it was released, and the runtime warns from 30 days before (Licensing). A restart after that day doesn't come back up.
Before you start, read the upgrade steps of each release you're upgrading across: 0.1.9's and 0.1.8's are in their release notes, and earlier releases' are below.
-
Pull the new version, and restart with it, with the same
kindgi.env:Terminal window docker pull quay.io/kindgi/runtime:<version>docker stop -t 30 kindgi-serverdocker rm kindgi-serverdocker run -d --name kindgi-server --network kindgi --restart unless-stopped \--add-host registry.localhost:host-gateway \-p 127.0.0.1:4000:4000 --env-file kindgi.env \quay.io/kindgi/runtime:<version>
When it starts, the runtime brings the database up to date: it applies the migrations the database doesn't have yet, then serves. On a database that has them all, it applies nothing, so a restart on the same version changes nothing. The log doesn't list them: once the Kindgi API server listening lines appear, they're done. If one fails, the runtime exits with code 1, and its log says kindgi-runtime: fatal: Error: migration failed for … and why.
Migrations only go forward, and an older runtime isn't guaranteed to work on a database a newer one migrated. To go back, restore the backup you took before the upgrade, and run the older version on it.
Checks you can run before upgrading
Section titled “Checks you can run before upgrading”From 0.1.6, the runtime image runs read-only checks against a database. They read only what the previous release has, so run the new version's image against the database you're about to upgrade, with the same kindgi.env:
docker run --rm --network kindgi --env-file kindgi.env \ quay.io/kindgi/runtime:<version> check provider-keysNothing starts and nothing is written, and they print names and ids, never a value. Each exits 0 when it finds nothing, 1 when it lists what it found, and 2 when it can't run. --tenant <uuid> checks one tenant; by default they check every tenant in the database.
| Check | What it lists |
|---|---|
provider-keys |
Each tool version, MCP endpoint and webhook endpoint that names a model provider's key. A model provider's key is used by its provider only, so from 0.1.6 they're refused: a tool's calls fail with provider-key-refused, and so do an MCP endpoint's connections and a webhook endpoint's deliveries. For each, store the key it needs under its own name (the same value is fine), name that instead, and deploy or register it again. See Give a tool a secret. |
secret-scopes |
Each tool version, MCP endpoint and webhook trigger in a project or an org that reads a secret at the tenant, registered by an earlier runtime that didn't record where its secrets are read. They keep reading at the tenant. One a tenant admin registered can stay. Register each other one again so where its secrets are read is recorded: a tool, publish a new version; an MCP endpoint, unregister it and register it again; a webhook trigger, kindgi webhooks update <trigger-id> --secret=<name>. The check ends with what to do about each, and also lists an unregistered tool version a flow or agent version still pins. |
mcp-header-credentials |
Each MCP endpoint that keeps a credential in plaintext config.headers (X-Api-Key, Authorization, …), registered by an earlier runtime that allowed it. They keep working, and their requests never follow a redirect. For each, store each header's value as a secret, and register the endpoint again with auth in place of the header. See Not in config.headers. |
stored-secrets |
Each live version of a stored secret that doesn't decrypt with the secrets' keys: the KMS key, the runtime's access to it, and the AAD key. Run it after a restore, before the runtime serves, since a restore brings back the database, not the keys. It needs KINDGI_SECRETS_BACKEND=postgres. See Back up and restore. |
purged-tenants |
Each tenant marked purged that still holds rows, with each table and its count: rows written after its mark, by work in flight then. Rows only, not the bytes under its prefix. Run it a day after a purge. For each tenant it lists, kindgi-runtime tenants purge <tenant id> --confirm <its slug>, with the server's KINDGI_ARTIFACTS and KINDGI_OPENFGA_API_URL, deletes them and leaves its mark and its record as they were. See Close and purge a tenant. |
Here check provider-keys runs on a 0.1.5 database where a tool and an MCP endpoint name Anthropic's key (exit 1):
provider-keys: 2 name a model provider's key (1 tenant checked): tenant=… mcp-endpoint=acme.docs secret=ANTHROPIC_API_KEY provider=anthropic tenant=… tool=acme.summarize@1.0.0 secret=ANTHROPIC_API_KEY provider=anthropicA model provider's key is used by its provider only: these are refused. For each, store the key it needs under its own name (the same value is fine), name that instead, and deploy or register it again.After each got a key of its own, the same run finds nothing (exit 0):
provider-keys: nothing names a model provider's key (1 tenant checked).The runtime logs the same at every start, one WARN per tool version or endpoint (docker logs kindgi-server). Here it's for another tool registered on 0.1.5 with Anthropic's key, acme.translate:
{"time":"2026-10-10T10:15:40.130Z","level":"warn","severity":"WARNING","subsystem":"providers.keys","message":"provider-key-declared tenant=… tool=acme.translate@1.0.0 secret=ANTHROPIC_API_KEY provider=anthropic: a model provider's key is used by its provider only, so this tool's calls are refused. Store the key it needs under its own name and use that name","tenantId":"…","kind":"tool","id":"acme.translate","version":"1.0.0","secretName":"ANTHROPIC_API_KEY","providerId":"anthropic"}Here check mcp-header-credentials runs on a database with two endpoints registered by an earlier runtime with a key and a secret in config.headers (exit 1). Each line ends with the auth that replaces the headers:
mcp-header-credentials: 2 MCP endpoints keep a credential in plaintext config.headers (1 tenant checked): tenant=… mcp-endpoint=inventory-legacy headers=X-Api-Key,X-Api-Secret → auth: { scheme: "header", headers: [{ name: "X-Api-Key", secretRef: { envName, name } }, { name: "X-Api-Secret", secretRef: { envName, name } }] } tenant=… mcp-endpoint=inventory-moved headers=X-Api-Key,X-Api-Secret → auth: { scheme: "header", headers: [{ name: "X-Api-Key", secretRef: { envName, name } }, { name: "X-Api-Secret", secretRef: { envName, name } }] }They still work, and their requests never follow a redirect, but each value is stored as given. For each, store the header's value as a secret (kindgi secrets set <NAME> --env=<env> --scope=tenant), then unregister the endpoint and register it again with that auth in place of the header.At every start, the runtime logs one WARN per such endpoint:
{"time":"2026-10-10T23:54:31.368Z","level":"warn","severity":"WARNING","subsystem":"mcp.headers","message":"mcp-header-credential tenant=… mcp-endpoint=inventory-legacy headers=X-Api-Key,X-Api-Secret: this endpoint keeps a credential in plaintext config.headers, from before that was refused. It still works, and its requests never follow a redirect. Store each value as a secret, then unregister the endpoint and register it again with auth: { scheme: \"header\", headers: [{ name: \"X-Api-Key\", secretRef: { envName, name } }, { name: \"X-Api-Secret\", secretRef: { envName, name } }] } in place of the header","tenantId":"…","endpointId":"inventory-legacy","headerNames":["X-Api-Key","X-Api-Secret"]}From 0.1.6 to 0.1.7
Section titled “From 0.1.6 to 0.1.7”The database migrates when 0.1.7 starts. One step adds a check to the table of trigger fires: Postgres reads the table under a lock while it adds it, and since fires are kept 30 days, that's short.
Security fixes in 0.1.7
Section titled “Security fixes in 0.1.7”-
The server's environment reached model providers' calls. The model SDKs fill options a call leaves out from environment variables. On a runtime whose environment set them:
ANTHROPIC_AUTH_TOKENwas sent as a bearer beside each Anthropic provider's own key;ANTHROPIC_BASE_URLsent every Anthropic provider's calls, with that provider's key, to that address instead of Anthropic's API;OPENAI_ORG_IDandOPENAI_PROJECT_IDwent as headers to each OpenAI-compatible provider's base URL;GOOGLE_GENAI_USE_VERTEXAI(orGOOGLE_GENAI_USE_ENTERPRISE) moved a Gemini Developer API provider to Vertex AI, in the project the environment named.
0.1.7's adapters set these options themselves, so nothing of the server's environment goes out with a provider's call. If your runtime's environment set
ANTHROPIC_BASE_URLto an address you don't fully trust, rotate the Anthropic keys of the providers it ran, andANTHROPIC_AUTH_TOKENtoo if it was set. -
A tenant's identity provider skipped the egress guard. The requests to a tenant's SSO identity provider (OIDC discovery when the provider is registered; the token, JWKS and userinfo requests at sign-in) didn't go through
KINDGI_TENANT_HOST_ACCESS's checks, as the other hosts tenant configuration names do. In 0.1.7 they do, checked at each connection; a discovery the guard refuses answers422 identity-provider-invalid. Origins listed inKINDGI_AUTH_PRIVATE_IDP_ORIGINSconnect as before (What a tenant can't make the server do). -
A key could mint a key that outlived it. A key that expires, even one limited to a project, could make a key that never expires, which kept working after the first one expired or was revoked. In 0.1.7, from a key that expires, a new key needs an
expiresAt(--expiresin the CLI) no later than it (403 token-expiry-exceeds-minter). A console signed in with a key mints as that key. The deployment's token, keys without an expiry and other sign-ins are unchanged (API keys).
What to check, and what's different
Section titled “What to check, and what's different”What to check before you upgrade, and what's different after:
- Rebuild your packs with the 0.1.7 CLI:
- A parameter named
inputis refused when an agent or a prompt block is defined, astoday,now,agent,conversationandsettingswere. Before, it was accepted, and a turn's structured input silently replaced its value. Rename it. - A pack's
providersdeclarations are checked against their preset: a preset's own settings are required, and another preset's are refused, by the types and bykindgi dev, which names whose setting it is. In TypeScript,presetis one of the preset names (KindgiPresetName, from@kindgi/handler-runtime), so a name computed at run time needsas KindgiPresetName(Declare them in your pack's config).
- A parameter named
- The runtime image carries the
kindgiCLI, which runs the license renewal jobs. On linux/amd64 it downloads 290 MB, up from 276 MB. - Embedding
@kindgi/agents:AGENTS_MIGRATIONS_DIRis only on the runtime-only subpath@kindgi/agents/migrationsnow, with no alias. - The API:
- A page cursor a list didn't issue answers
400 bad-inputon the agents, flows, policies and eval-suites/versionslists and on an agent's promotions, as it does on blocks' and tools'/versions. - When an admin ends someone's sessions, it shows on that person's
sign-in history (
GET /v1/audit/sign-ins?userId=), withbyUserIdsaying who, and no longer on the admin's. - Adding a team member who isn't a person of the tenant, was removed, or
is named by a malformed id is refused with
404 identity-user-not-found, as adding a project member is. Before, an unknown id was added. - A judging rule's results count the runs their judgments come from
(
runsWithJudgments).
- A page cursor a list didn't issue answers
- Models:
- Claude Haiku 4.5 asks for up to 16,000 output tokens by default in the
anthropicandbedrockpresets (it was 8,192). A model'smaxOutputTokensis the default cap a call gets when its agent sets none, not the model's own limit. A provider you registered from the preset by hand keeps 8,192 until you register the preset again (kindgi providers unregister <id>, thenregister --preset=…); one your pack declares updates at the nextkindgi devstart. An agent with its ownmaxOutputTokensisn't affected. - A typed agent is told its output's name and JSON Schema from the first model call, so most typed turns no longer pay for a repair call.
- Claude Haiku 4.5 asks for up to 16,000 output tokens by default in the
- The console:
- Its data pages are one kind of table: headers that sort, a search box on the lists, a Columns menu, and each view kept in the page's address. The judging queue's Show more reads Load more, and a flow run's row on the Runs page opens to its agent turns.
- On a run's Overview, a step that ended with its run reads Cancelled ("Stopped: the run failed" in a failed run), and the Journal marks each such start (After a cancel).
- Signing in with an emailed link, or with Google, Microsoft or GitHub,
returns you to the console page you were opening, and a console link
without its workspace (
/console/projects/…) opens in yours. - A tenant admin sees a line on every console page from 30 days before the license key expires, with how to renew.
kindgi dev:- There's one per pack. A second
kindgi devin the same pack exits with code 3 ("kindgi dev is already running for this pack") and says where the running one is: its process id, the console and the API (with--json, as{ "running": {…} }). Before, it took over the first one's runtime.--resetand--recreate-servicesare refused the same way while one runs (Your coding agent). - The console works at
localhost,127.0.0.1or[::1]on the runtime's port. A request from anywhere else is refused, and the refusal now names the address to use (Requests from other sites). - It says what it's doing while it starts: pulling the image (with its size and progress), starting the container, migrating the database, and waiting for it to serve, each with its time.
- There's one per pack. A second
- The CLI:
- In an app Kindgi was added to,
kindgi initsets up the testskindgi testruns (vitest and vite, and avitest.config.mtsthat maps your tsconfig paths); an app that runs vitest already keeps its own setup and is told what to add. It pins zod, vitest and vite exactly when your app pins exactly, else as a range. kindgi doctornames the config file it found.kindgi guardrails list --tableprints a table.- Printed npm commands read
npx --no -- kindgi …, so a flag such as--helpreaches Kindgi rather than npx. - An app that imports an agent file (for its output schema) builds with
Next.js or webpack, with no
serverExternalPackages.
- In an app Kindgi was added to,
- Fixed:
- A runtime starting while another serves the same database no longer
fails with
enable-rls … deadlock detected, and the serving one no longer answers500"Tenant-scoped query failed: deadlock detected". A start on an up-to-date database now takes no lock a serving instance's writes wait for, so a deploy, restart or scale-up no longer stalls writes. - An MCP tool whose name contains
__can be called. Two tools that would go to the model under one name (acme.a__bandacme.a.b) are refused before the call, naming both: rename one of them.
- A runtime starting while another serves the same database no longer
fails with
- New in 0.1.7:
- Webhook triggers: a signed request from WooCommerce, Drupal's
Webhooks module, GitHub, Shopify or any Standard Webhooks sender starts
a run of a flow, acting as whoever registered the trigger
(
kindgi webhooks, andwebhooksin the TypeScript, Python and Java clients; Start runs from WooCommerce, Drupal or any signed webhook). - A webhook's event, typed:
parseEventin@kindgi/sdk/webhooksreads the event in a verified request's body, as Python'swebhooks.parse_eventdoes (Verify a webhook). - An MCP endpoint can sign in with a user name and password, or with OAuth client credentials (Send a credential).
- Azure OpenAI and Amazon Bedrock as model providers, with the
azure-openaiandbedrockpresets (Presets). - AWS Secrets Manager for the secrets set through Kindgi's API (Keep secrets in your own secret manager).
- Azure Container Apps, from Kindgi's Terraform module (Deploy on Azure Container Apps).
- A deployment can renew its own license key:
kindgi license enrollonce, thenkindgi license renewby hand or on a schedule, which the Azure module can run for you. It works once access.kindgi.com has it on (Renew the license key). - Where the license stands:
GET /v1/licenseanswers itsstanding(valid,expiring,graceorexpired),daysLeftand its dates. The deployment's token reads all of it. Where the deployment is set up to serve one tenant (withoutKINDGI_TENANCY=multi) and its database holds only that one, that tenant's admins read the countdown only. Anyone else gets403 license-operator-only. - A tool's env values in a unit test:
invokeToolForTestandtoolContextForTestin@kindgi/sdk/definedecide them as a run does (Give a tool per-project values). - Two templates:
kindgi init --template=woocommerce, agents for a WooCommerce store, where a larger refund, a price change and publishing a product each wait for a person; and--template=drupal, agents for a Drupal site through its MCP Server, with a review flow a Drupal webhook starts. - Coding agents: a
kindgi-usingskill, with which your coding agent guides you in using what was built: the CLI's answer and the console page beside it (Your coding agent).
- Webhook triggers: a signed request from WooCommerce, Drupal's
Webhooks module, GitHub, Shopify or any Standard Webhooks sender starts
a run of a flow, acting as whoever registered the trigger
(
Known limitations in 0.1.7
Section titled “Known limitations in 0.1.7”-
In a tenant with several projects, flows and agents can use tools, agents and other items defined in another project of the same tenant. Checks inside a run (
ctx.authorize,can,check) aren't enforced yet; route-level permissions are. Until 0.1.8, give project admin roles only to people trusted across all of a tenant's projects, or use a separate tenant per customer. -
A project's tools and MCP endpoints use the tenant's secrets. A secret one of them names is looked up at the tenant, whoever registered it. Give project admin roles only to people you'd trust with every secret in the tenant. 0.1.8 fixes it.
-
A webhook trigger's signing secret is read at the tenant, in the env the runtime serves (
KINDGI_ENV). Give each trigger's secret a name of its own, since triggers that name the same secret share its value, and rotating it changes them all. Store it at the tenant in that env, as 0.1.7's--generate-secretdoes: a secret set only for a project or an org isn't read, and every delivery is refused. 0.1.8 fixes it. -
With authorization on, a runtime whose database user has no
BYPASSRLS(and isn't a superuser) doesn't start. That's the usual case on a managed Postgres. To check, run this as the runtime's database user:SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = current_user;If both are
f, use 0.1.8 or later for production with authorization on: 0.1.8 fixes it. Without authorization, the runtime starts as before. -
A tenant SSO sign-in whose identity provider's userinfo request fails (the provider is down, or
KINDGI_TENANT_HOST_ACCESSrefuses its address) ends at an empty 500 page instead of the sign-in page with an error. No session is created. Token and JWKS failures go back to the sign-in page as expected. -
A guardrail whose
scopelistsflowsnever checks a turn, in those flows' steps or anywhere else. A turn doesn't say which flow it runs in, so the guardrail skips it without a word: it never blocks, and its outcome counts stay at zero. Leaveflowsout of the scope, and run a flow's steps with an agent of their own that lists the guardrail. 0.1.8 fixes it (Which agents it covers). -
Run one runtime instance. 0.1.6 and 0.1.7 fix the known reasons several instances couldn't share a database: boot migrations wait their turn, the sign-in limit is shared, guardrail, tool and provider changes reach every instance, page cursors work across instances with one
KINDGI_PAGINATION_KEY, and from 0.1.7 a start takes no lock a serving instance's writes wait for. Several instances together aren't verified on a platform yet, so the Cloud Run module still runs one.
From 0.1.5 to 0.1.6
Section titled “From 0.1.5 to 0.1.6”The database migrates when 0.1.6 starts.
Security fixes in 0.1.6
Section titled “Security fixes in 0.1.6”- A guardrail could run another tenant's check. On a runtime serving
several tenants, a guardrail naming a pack check its own tenant had never
deployed could run another tenant's check of that id, if that tenant had
deployed one. 0.1.6 looks a pack check up in the guardrail's own tenant
only: such a guardrail fails with
unknown-checkevery time, and underhaltits turn fails. - A model provider's key could reach a tool. Anyone who could register a
tool, an MCP endpoint or a webhook endpoint could name a model provider's
key as its secret, and so hand the key to a pack's code or to another
server. 0.1.6 refuses that (run
check provider-keysfirst, below). - Who may judge as a class was shown to every reader. Anyone who could
read a judge class, a project viewer included, got the ids of the people
and service accounts allowed to judge as it. 0.1.6 sends those ids only to
an admin of the class's scope; everyone else gets their number
(
assertableBy.principalCount). - A malformed MCP credential could appear in the runtime's log. An MCP
endpoint's credential, or a value in its
config.headers, with a line break or a NUL inside it could show up in the sync log, in the error the HTTP library raised when it refused the header. 0.1.6 refuses such a value before sending, and its log names only the header. A log written before keeps what it wrote: rotate an MCP credential that was ever stored with a line break inside it. - A secret with a line break could appear in a run's failure message. An HTTP tool's secret, or a model provider's key, containing a line break could show up in the error the HTTP library raised when it refused the header: for a tool, in the run's failure message and journal, which anyone who can read the run sees; for a provider's key, in errors and the logs. 0.1.6 refuses such a value before sending, and names only the header or the provider. A failure message or log line recorded before keeps what it recorded: rotate a secret or key that was ever stored with a line break anywhere but at its very end (a bearer secret that started with one counts). One with only a trailing line break was never affected.
- The Python SDK held your app on old releases of cryptography. Its
verifyextra (pip install 'kindgi[verify]') cappedcryptographybelow 47. So an app that installed it couldn't take the fix for the OpenSSL inside cryptography's wheels (GHSA-537c-gmf6-5ccf, fixed in 48.0.1), nor those for CVE-2026-69247, CVE-2026-69248 and CVE-2026-69249 (fixed in 49.0.0 and 50.0.0). The extra in 0.1.6 requires cryptography 50.0.0 or later and sets no upper limit, so your app can upgrade it freely. The SDK's verifier only checks signatures with a public key. It doesn't call the PKCS#7 decryption or X.509 certificate verification those three CVEs are in, but the rest of your app shares the cryptography you install.
What to check, and what's different
Section titled “What to check, and what's different”What to check before you upgrade, and what's different after:
- Run
check provider-keysbefore you upgrade (see Checks you can run before upgrading). A model provider's key is now its provider's alone: a tool, an MCP endpoint or a webhook endpoint that names one is refused, and so are its calls, connections and deliveries (A model provider's key isn't a tool's). - Expect a slower first start on a large database. A new index on the runs table is built at the first start after the upgrade, without blocking writes; the more runs, the longer that start takes.
- Set
KINDGI_PAGINATION_KEY. List cursors are sealed, so they no longer show which row they point after. Without a key, a cursor handed out before a restart answers400after it, and a client starts the list again. More than one instance needs the key. When you rotate it, keep the old one asKINDGI_PAGINATION_PREVIOUS_KEYfor at least a day. - Rebuild your packs with the 0.1.6 CLI:
-
Variables your pack doesn't declare no longer reach it. Before your pack's code loads, its service drops every variable the pack doesn't declare (
envinkindgi.config,[tool.kindgi.env]inpyproject.toml,envinkindgi.config.json), except Kindgi's own (KINDGI_*) and the platform's. A key left inpack.envfor something else no longer reaches your tools. After the upgrade, look in the pack service's log for aWARNline with"event":"env-dropped". It names each variable the service dropped, never its value:{"time":"2026-10-10T07:01:47.789Z","level":"warn","severity":"WARNING","subsystem":"pack","message":"Dropped 2 variables the pack doesn't declare: ACME_UNDECLARED_KEY, AWS_SECRET_ACCESS_KEY (declare them in the pack's env, or set KINDGI_PACK_ENV_FILTER=off)","event":"env-dropped","kind":"env-dropped","names":["ACME_UNDECLARED_KEY","AWS_SECRET_ACCESS_KEY"]}If your code reads one of them, declare it (Declare the environment your code reads). To keep the old behaviour meanwhile, set
KINDGI_PACK_ENV_FILTER=offon the pack service. A Python pack's image always namesGPG_KEY: its base image sets it, and nothing reads it. -
A tool call has an idempotency key,
ctx.idempotencyKey, the same every time the same call runs. Pass it to the system a tool writes to, so a step that runs again doesn't refund or email twice (Make a side effect happen once). A pack built before 0.1.6 doesn't get it. -
A guardrail that names a built-in check is marked in the pack's index. One from an older CLI gets a warning in the runtime's log each time it loads; the built-in still runs. The CLI also warns when a pack's check id doesn't start with the pack's id.
-
A tool whose
needsSpecschema doesn't compile is refused when it's defined, registered or deployed, instead of being left out at load (Give a tool env values). A union of types (type: ['string', 'null']) now compiles everywhere.
-
- The API's own OAuth sign-in flow is removed. Sign-in runs in the
runtime's browser flow, which is unchanged.
POST /v1/auth/login/…andPOST /v1/auth/callback/…answer404; registering anoauth2identity provider answers422(one stored before still lists); and OIDCallowedRedirectUrisis refused (one stored before still works). The TypeScript and Python clients dropauth.loginandauth.callback. Sessions no longer hold identity-provider tokens: 0.1.6 clears the ones stored. - Times are strict. Every time the API reads, list cursors included, is
an RFC 3339 time with its zone (
2026-10-09T12:00:00Z), or Postgrestimestamptztext; anything else, a date alone included, answers400 bad-input. The CLI's time flags still take a date and send it as that day's start in UTC. memberisn't a project role you can give: useviewer, which it always meant. A role given asmemberbefore still reads back asmember.- Approvals and their runs end together
(Decide an approval):
-
A reviewer's withdraw ends the run waiting on it: its turn fails with
hitl-withdrawn, at once. -
A run's end withdraws the approvals it waits on (a tool call's or a turn's), with
withdrawnBecause: run-cancelledorrun-ended. An approval a run asked for without waiting on it, such as a memory fact's review, stays pending. -
Deciding one whose run has ended answers
409 run-already-terminal. -
After the upgrade, gate approvals still open (pending, assigned or in review) from runs that had already ended or been purged are withdrawn the same way, by a pass about once a minute per tenant, up to 500 runs each time (in a test, 40 seconds after the first start). They leave the reviewer queues with a system actor in the audit (
hitl-decision,via: run-end). Deciding one then answers409 approval-terminal, for a cancelled run and for one that ended otherwise:Approval … was withdrawn: its run was cancelledApproval … was withdrawn: its run endedExcerptA memory fact's review left open from before stays pending, and can still be decided.
-
- Who sees whom:
- listing a project's members or team grants takes
writeon the project, so a viewer no longer sees who else works there; - the reviewer roster is for tenant admins and reviewers;
- re-adding a member with another role answers
409 membership-existsand keeps the role they hold; 409 block-project-mismatchno longer names the other project.
- listing a project's members or team grants takes
- The access audit keeps every refusal of a caller the API knows, not only the authorization model's decisions, except four that don't refuse the caller (The access audit). Every secret and env write through the API is in the audit log too, never with a value.
- A turn stops when its run is stopped: a model or tool call in flight
is aborted at once on a cancel or a shutdown. A step that writes nothing
for a while, such as a long model call, stops within
KINDGI_RUN_ENDED_CHECK_MSof a cancel (5 s by default). - Cancelling a flow while it runs a loop stops the loop: no more of its steps start after the cancel.
- Two runtime instances starting at once no longer deadlock or race each other on migrations: one waits until the other's migrations finish. One starting beside an instance that's serving writes can still fail (see Known limitations in 0.1.6).
- Guardrail, tool and provider changes reach every runtime instance on one database at its next turn, with no restart, whichever instance (or a plain SQL change) made them.
- Paging no longer skips or repeats rows recorded in the same millisecond as a page's last row, on the lists that page by time: organizations, projects, teams and their members, memory erasures, an agent's promotions, a schedule's fires, cost records, observations, and the versions of agents, flows, policies, test sets, blocks and tools.
- The sign-in options lookup limit holds across instances: 30 lookups a
minute per client (
GET /v1/auth/sign-in-options), counted in Postgres rather than by each instance. - Safari signs in to
kindgi dev's console. Underkindgi devonhttp://localhostor127.0.0.1, the session cookie is a plain one, since Safari keeps aSecurecookie only overhttps. A deployment's console still needshttps. - The console's sign-in returns you only to a console page; a sign-in link that pointed elsewhere lands on the console's home.
- The console has a new look, light or dark: it follows your system, or the one you choose (also in ⌘K). Its links no longer point at an address without the tenant while a page loads.
- Every
/v1answer saysCache-Control: no-store, errors included, so no proxy or browser keeps one tenant's data or shows a kept answer again: a410for a flow since reinstated no longer outlives a reload (Behind a load balancer or ingress). - ⌘K and the breadcrumbs reach every project section; choosing Cost or Schedules there used to do nothing.
- A record opened under another project's address moves to its own project (a run, an approval, an agent and the pages under it, a tool, and the like), keeping its tab, instead of showing under the wrong project with actions that project then refused. One in a project you can't open says so: "This agent belongs to another project, which you can't open. Ask an admin of it for access."
kindgi devruns your pack's code sandboxed (macOS Seatbelt; Linux bubblewrap, which you may need to install): it can't read your keys or files outside the app, write outside it, or reach Docker, and keeps the network and the app's own files. On Linux, the UNIX sockets it closes are those under the home folder,/tmp,/var/tmpand/run. A tool that reads a path or a socket outside the app (~/.aws, a local Postgres socket) needs it opened in the pack'sdev.sandboxconfig. Where it can't run (Windows outside WSL, a container, inside another sandbox such as a coding agent's),kindgi devwarns and runs without it;KINDGI_DEV_SANDBOX=requiredrefuses instead (Keep your tools' code away from your keys).- Under
kindgi dev, Kindgi keeps the secrets you store in its own file,.kindgi/secrets.env, not your app's.env.local, and neither they nor a model provider's key reach the pack service's environment (kindgi secrets copycopies keys over; Keep local values in env files). - The CLI:
kindgi tools versions --include-tombstonedis now--include-unregistered, asblocksandflowssay. - The clients: the TypeScript client drops
eventTriggersandwebhooksand the Python client their resources, which the runtime never served (it fires schedules; start a run withPOST /v1/runs). Python'ssecrets.rotatereturns its sync or async answer, andKindgi()finds its settings when it's first used. - Embedding
@kindgi/api:createApp'ssigningKeyis gone (passexportSigning), and so arerefreshToken,exchangeCodeandoauthStateStore.SignInOptionsRateLimit.storetakes aRateLimitStore(in memory by default) to share the sign-in lookup limit across instances; a store that fails doesn't block sign-in. - New in 0.1.6:
- Judging: a project's judging rules queue the runs that need a person's verdict, results per agent version, and comparisons and proposals rescored after people judge the new answers (Judge a run's output, Improve an agent).
- What failed and why: a project's failed runs grouped by cause and
version (
GET /v1/runs/failures) and a failure'sreason(See why runs failed), and what a suspended run waits for (waitingFor). The runs list filters by status, time and version (List runs). - Guardrail outcomes: how often each guardrail passed, found
something, blocked a turn or couldn't run, by agent version
(
GET /v1/guardrails/{id}/outcomes). - Approvals: an
approval.requestedwebhook event, email to reviewers (KINDGI_REVIEWER_EMAIL), and an inbox in one read (--assigned-to=me --order=asc) (Receive webhooks). - The console: a reviewer's Inbox across projects, where you can decide several approvals in one go or by keyboard (Decide an approval). A project gets pages for its failed runs (What failed), schedules, flows, guardrails and members, each with a page of its own, plus Quality for judging, and costs for the project and each run. The tenant gets Teams, Webhooks, Sign-in, My access and its own costs. A page or action shows only to the roles that can use it (The console, by role).
- Access: who has access to a project and how
(
GET /v1/projects/{id}/access), a team's role on a project, what the caller may do (GET /v1/identity/me/permissions), the people list with their grants, and the tenant's sign-in history (GET /v1/audit/sign-ins) (Authorization). - Sign-in an operator manages alone: with
KINDGI_AUTH_TENANT_PROVIDERS=off, a tenant can't add, change or remove identity providers. - Your own secret manager: Google Secret Manager, Azure Key Vault or
HashiCorp Vault (
KINDGI_SECRETS_BACKEND=secret-manager; Keep secrets in your own secret manager), and on Azure a Key Vault key for the database's secrets, Azure Container Registry with the server's managed identity, and export signing with a Key Vault key (Export signed evidence). Retired export keys can stay listed, so older exports still verify. - A tool's secret can be optional (An optional secret).
- A retired agent, flow, tool or test set can be found and brought back (Agent and tool versions).
- Schedules list by project and name their owners.
- Coding agents:
kindgi initkeeps one out of the files that hold keys (Your coding agent).
Known limitations in 0.1.6
Section titled “Known limitations in 0.1.6”-
In a tenant with several projects, flows and agents can use tools, agents and other items defined in another project of the same tenant. Checks inside a run (
ctx.authorize,can,check) aren't enforced in 0.1.6; route-level permissions are. Until 0.1.8, give project admin roles only to people trusted across all of a tenant's projects, or use a separate tenant per customer. -
A project's tools and MCP endpoints use the tenant's secrets. A secret one of them names is looked up at the tenant, whoever registered it. Give project admin roles only to people you'd trust with every secret in the tenant. 0.1.8 fixes it.
-
With authorization on, a runtime whose database user has no
BYPASSRLS(and isn't a superuser) doesn't start. That's the usual case on a managed Postgres. To check, run this as the runtime's database user:SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = current_user;If both are
f, use 0.1.8 or later for production with authorization on: 0.1.8 fixes it. Without authorization, the runtime starts as before. -
Run one runtime instance. 0.1.6 fixes the known reasons several instances couldn't share a database: boot migrations wait their turn, the sign-in limit is shared, guardrail, tool and provider changes reach every instance, and page cursors work across instances with one
KINDGI_PAGINATION_KEY. Several instances together aren't verified yet, so the Cloud Run module still runs one. -
AWS Secrets Manager comes in 0.1.7. With
KINDGI_SECRETS_MANAGER=aws, a 0.1.6 runtime doesn't start (exit code 2):KINDGI_SECRETS_MANAGER=aws is reserved: this runtime doesn't support it yet. Currently supported: azure, gcp, vault. -
An MCP server's tool whose name contains
__can't be called. Kindgi names it<endpoint id>.<tool name>, and every model adapter turns each.into__for the model and back again on the way in. So a name with a__of its own comes back wrong: the model is told the agent has no such tool (unresolved-tool), and the turn fails if it calls it again, or the model answers without it. Drupal's MCP Server Tool Bridge names its tools this way (tool_api__…). There's no workaround for a server whose tool names you don't choose: 0.1.7 fixes it (From 0.1.6 to 0.1.7). -
A runtime starting while another serves the same database can fail to start. Each start applies row-level security to every tenant table again, under locks that a write in flight can deadlock with. So a new revision starting while the old one still serves, or a second instance, can stop at start (exit code 1):
kindgi-runtime: fatal: Error: migration failed for … (…/migrations/): Migration step "enable-rls" failed: deadlock detectedExcerptStarting it again serves. While it happens, the runtime that's serving can answer some writes with
500 internal-server-error, "Tenant-scoped query failed: deadlock detected", and keeps serving (in our test, some project creations). This isn't new in 0.1.6: 0.1.7 fixes it (From 0.1.6 to 0.1.7).
Runtime 0.1.5.1
Section titled “Runtime 0.1.5.1”Runtime 0.1.5.1 fixes one security bug in 0.1.5 and earlier, for deployments
with authorization on (KINDGI_OPENFGA_API_URL set): someone re-added to a
project or a team with a different role silently got that role as well, and
kept it after they were removed. The members list never showed it. Without
authorization, and under kindgi dev, nothing is affected. Only the runtime
changes: the 0.1.5 CLI and SDKs (npm, PyPI, Maven Central) stay as they are.
On 0.1.5.1:
- Removing someone from a project or a team removes the roles they held there.
- Re-adding someone who's already a member keeps their current role. To
give them another, change their role
(Project memberships). The answer
to the re-add still echoes the role you asked for (
201), as the API is unchanged in this patch; the members list shows the role they hold. - Removing a team's creator ends their team admin, which they hold without a membership row. This matches what the team's members list shows.
Run 0.1.5.1, pulled by its digest, with the same kindgi.env. It has no
migration:
docker pull quay.io/kindgi/runtime:0.1.5.1@sha256:7c1b111ff22091d137f45d9770f6ff9f2521575bf28130957a5db1ce80a2e56eOn Cloud Run, copy it into your repository the same way as 0.1.5 (see
The images into Artifact Registry)
and set server_image to its digest.
If you removed someone from a project or a team on an earlier version,
after re-adding them with another role, they may still hold that role.
Remove them again on 0.1.5.1: the removal now clears it. For a leftover
owner on a project, add the person as owner, then remove them. 0.1.6
adds a check that finds who needs it.
From 0.1.4 to 0.1.5
Section titled “From 0.1.4 to 0.1.5”The database migrates when 0.1.5 starts. What to check before you upgrade, and what's different after:
-
Signing in to the console with an API token is now off by default, except in
kindgi dev. If people sign in to your console by pasting an API token, setKINDGI_CONSOLE_TOKEN_SIGN_IN=onon the runtime when you upgrade (on Cloud Run, in the module'sserver_env: Turn on sign-in), or set up sign-in with your organization's identity provider (Turn on sign-in). Otherwise the console's sign-in page offers no way in, and the runtime's startup output says so too. API tokens keep working for the API, the CLI and the SDKs either way. -
Reach the console over
https. Every way of signing in now ends in a session cookie markedSecure, which browsers keep only overhttps(MDN: Set-Cookie,Secure). On the machine running Kindgi, Chrome also keeps it athttp://localhost(or127.0.0.1); Safari doesn't. At any other plain-httpaddress, no way of signing in works. WithoutKINDGI_PUBLIC_URL, the runtime also accepts the console's changes only fromhttps, orhttponlocalhost(Requests from other sites). -
Behind a load balancer or ingress, set
KINDGI_TRUSTED_PROXIES: how many proxies are in front of the runtime (1for one), or their addresses. Rate limits and audit records then see each client's own address. Unset, the runtime uses the connection's address and ignoresX-Forwarded-For(Behind a load balancer or ingress). -
The API token keeps its user across restarts. Without
KINDGI_SEED_USER_ID, 0.1.4 made a new user at every start; 0.1.5 keeps the token's user whileKINDGI_API_TOKENstays the same. A changed token acts as a new user, and the startup output warns. With authorization on, setKINDGI_SEED_USER_IDto keep one user across token changes (Point the runtime at it). ChangingKINDGI_API_TOKENalso signs out the console sessions the old token opened (Rotate the API token). -
Signing a person out everywhere works. In 0.1.4,
POST /v1/identity/users/<id>/revoke-sessionsanswered as if it had, and ended nothing. Revoking an API key now ends the console sessions it opened, and removing an identity provider ends the sessions opened through it (Signing out). The audit trail gainssigned-out,sessions-revokedandsign-in-refused. -
Only tenant admins list the tenant's people.
GET /v1/identity/usersanswers anyone else403; a person still reads their own record and sessions. A project admin adds a member by email (POST /v1/projects/<id>/membershipswithemail), and a project admin's member key can manage that project's members. -
A project editor can start an eval run (compare a version on a test set); it took a project admin. Unregistering or reinstating a test set version still does.
-
The cost aggregate counts only what you may read. Across projects (no scope, the tenant, or an org), it counts the projects the caller may read, and records with no project; a tenant admin's counts every project.
readon the tenant used to show every project's spend. -
With authorization on, every route checks what it touches. Reading tenant-wide settings (providers, policies, adapters, capabilities, signing keys, deployments, sign-in providers) needs
readon the tenant, and changing themadmin. Webhook endpoints and compliance evidence needadmin. Starting a run needsexecuteon what it runs, andwriteon a project it names. Lists hold only what the caller may read. A single admin sees no change (Authorization). Unregistering a conversation takeswriteon its project. -
With authorization on, a permission change that fails no longer holds up the others. In 0.1.4, one could leave a new project unreadable by its creator (
403) until an operator replayed the outbox. 0.1.5 also carries the fixes in runtime 0.1.4.1 and 0.1.4.2 (below). -
An agent, flow, tool or test set stays in the project it was first published into. Publishing a version of one under another project is refused with a
409(agent-project-mismatch,flow-…,tool-…,eval-suite-project-mismatch), even for an admin of both, and nothing is written; 0.1.4 accepted it. A deploy that includes one is refused whole, even when it's unchanged. The refusal carries the id, not the other project (Organize work by org and project). -
If your Postgres
DateStyleisSQL, DMYorGerman, check the times earlier releases saved. RunSHOW DateStyle;(DateStyle). With either style, earlier releases read a date on the 1st to the 12th of a month with day and month swapped, and saved some that way: session expiries, approval deadlines and decision times, wait timeouts, schedule fire times, memory facts' validity times and stored webhook bodies. A date after the 12th failed outright. With any other style, a time was read correctly or failed outright, so nothing was saved wrong. 0.1.5 reads and writes times correctly on anyDateStyle, but it can't repair a swapped time, which looks like a real one. -
Run OpenFGA v1.22.0. Published OpenFGA advisories affect v1.9.0 (An OpenFGA you already run).
-
Expired rows are deleted every hour: idempotency answers, sessions and sign-in state, which 0.1.4 kept for good (Retention).
-
Idempotency keys are per caller. The same key from someone else is their own request. A repeat sent while the first request still runs is refused (
409 idempotency-key-in-flight) instead of running again, and the answer of a request that made a secret (a new API key, say) isn't kept, so its repeat is refused withidempotency-key-replay-withheld. Keys 0.1.4 stored aren't found again; they'd have expired within 24 hours (Retry a start safely). -
A provider registration the runtime can't build is refused when it's registered (
422 provider-config-invalid, each problem indetails.issues).kindgi doctornames any registered before 0.1.5; to fix one, unregister it and register it again (Check a registration). -
A guardrail whose config its pack check would refuse is refused when it's registered (
422 guardrail-config-invalid), for a pack deployed by 0.1.5: redeploy your pack once. A guardrail already registered with such a config fails every turn it checks; unregister it, and register it again with a config that fits (Configure a guardrail). -
A halting guardrail whose check can't run now stops the turn; it used to let it through. That's a check that can't run, for any reason: no check by that name is registered, its configuration is invalid, an
llm-judgeguardrail's judge can't be routed to a model, or the check throws (check-failed: pack code that crashed, a pack service that couldn't be reached, a judge call that failed). Withhalt, the turn fails withguardrail-violation, andevaluationErrorssays which guardrail and why. With any other action, the turn goes on; in 0.1.4 a check that threw failed the turn whatever the action. Either way, the error is in the run's provenance and journal (When the check can't run). -
The built-in guardrail checks run. A guardrail that names one (
must-cite,never-call-tool,max-tool-calls,output-matches,tool-order,required-substring,forbidden-substring) runs it, from a pack file orPOST /v1/guardrails. In 0.1.4 it never ran (Use a built-in check). -
A pack can't ship its own guardrail check under a built-in check's id: the runtime runs the built-in for a guardrail naming one, so a pack's implementation under that id would be silently replaced. Building the pack refuses it with
reserved-check-id(Python:DefinitionError), saying to rename the check. Rebuild your packs with the 0.1.5 CLI: a pack built with an earlier one that ships such a check runs the built-in instead. -
Before you upgrade, check every guardrail that names a built-in check. In 0.1.4 such a guardrail never ran. In 0.1.5 the built-ins run and check their config, but a guardrail registered before the upgrade isn't checked again: if its config doesn't fit the built-in's settings, its check can't run at any turn, and with
haltit blocks every turn of the agents that list it. The runtime warns about each one when it loads it:20:40:17.669 WARN [guardrails] guardrail config invalid traceId=… tenantId=… guardrailId=acme.tool-budget check=max-tool-calls problems="config must NOT have additional properties" fix="unregister it (kindgi guardrails unregister <id>), then register it again or redeploy, with a config that fits; with several runtime instances, restart the others"ExcerptList your guardrails (
kindgi guardrails list), and check each one whosecheckis a built-in against its settings. To fix one, unregister it (kindgi guardrails unregister <id>), then deploy your pack, or register it again, with a config that fits: a deploy doesn't change a registered guardrail's config. If you run several runtime instances, restart them afterwards (below). -
The built-in guardrail checks check their config. A guardrail naming one with a config the check doesn't take is refused when it's registered (
422 guardrail-config-invalid, each problem indetails.issues) or deployed (deployment-validation-failed); one that still reaches a turn is a check that can't run, so ahaltguardrail stops the turn. -
A deploy keeps a registered guardrail only if it's the deploy's own: in the project the deploy registers into, with the same definition. A pack whose guardrail changed is refused (
409 guardrail-already-registered): unregister the guardrail, and deploy again. A guardrail with that id in another project is refused too (409 guardrail-project-mismatch, without naming the project). Either way nothing is deployed. Both used to pass silently, and the old definition stayed in force. An unchanged pack still redeploys, from a new image and across releases. -
The runtime signs exports (audit bundles, provenance, compliance evidence) with the deployment's export key: set
KINDGI_EXPORT_SIGNING_KEY_PATH,KINDGI_EXPORT_SIGNING_KEYorKINDGI_EXPORT_SIGNING_KMS_KEY. 0.1.4's runtime didn't sign them. Without a key, outside development, the exports answer404 signing-not-configured.kindgi exports verifyalso checks audit bundles made with@kindgi/api0.1.4 (Export signed evidence). -
The runtime's own address,
/, leads to the console, or lists what it serves; it answered404. Health checks stay on/ready(Check health and logs). -
The console: a run whose answer a guardrail blocked shows that answer as not sent, with the guardrail; it used to look sent (A halted turn). The dashboard's API keys, Service accounts and People cards open their own pages (they opened Deleted data), and ⌘K and the breadcrumbs reach them. Access audit shows to tenant admins only, the people its API answers.
-
Anthropic retires Claude Sonnet 4.5 (
claude-sonnet-4-5-20250929) on 2026-11-30. A provider registration that names it should move toclaude-sonnet-5-5, theanthropicpreset's default. No preset lists it, so only a registration made with a spec is affected (Anthropic: model deprecations). -
Claude agents use Anthropic's prompt cache: a turn's later calls read the prompt they repeat at a fraction of the input price, and the first write costs a little more. Register the
anthropicpreset again for the 5.5 models' cache-read rate (Prompt caching). -
Retrieved memory reaches the model as data, in a
<memory>block in a user message instead of a second system message, so what models see changes. A retrieval that searches by meaning (semantic) on a runtime without embeddings fails the turn withsemantic-unavailable; 0.1.4 skipped that search without a word. -
same-usermemory is the run's end user's, never the key's user. A run that names noparticipantIdreads nosame-usermemory, isn't offeredkindgi_remember, and its result warnsmemory-needs-participant. Such a run used to read and keep that memory as the user its key acts for, so an app's service account serving many customers mixed their memory; facts kept that way are no longer read as anyone's. Pass the person'sparticipantIdon each run (Give an agent memory). -
A comparison's replays run for the past turn's end user, with the same tools,
kindgi_rememberincluded (a replayed one stores nothing). A replay's conversation is never recalled as an earlier conversation. -
Your pack's service writes log records on stderr, as the runtime does: one per tool call, at the levels
KINDGI_LOG_LEVELandKINDGI_LOG_LEVELSset (Logs). Printing a tool's context (console.log(ctx),print(ctx)) no longer shows its secrets. -
A turn's warnings are logged, at
WARNunder[runs], once per tenant, agent and warning while the runtime runs, as well as in the run's result (Logs). -
The clients read every
409as a conflict (TypeScriptcode: 'conflict', PythonConflictError). Twenty codes used to come back as a server error, among themrun-lease-lost,agent-version-mismatchandsecret-write-conflict. Code that matched one of them by its class should match its code alone:err.serverCode(Pythone.server_code). -
TypeScript's
runs.followandruns.followProgressfollow a run to its end, as in Python, which gainsruns.followtoo.runs.streamandruns.streamProgressare deprecated (Follow a run). -
Paging conversations, approvals and runs no longer skips rows. A row created in the same millisecond as a page's last row (for approvals, at the same instant) could be left out of the next page. Cursors you hold keep working. A cursor whose time isn't a time is now
400 bad-inputon conversations and runs, as it already was on approvals. -
The CLI:
- a usage error (a missing argument, a bad flag value) exits
2;1is for a call that failed; kindgi buildversions each build by its time (YYYYMMDD.HHMMSS, UTC), so a build without--artifact-versionand--published-atis no longer reproducible;KEY=${KEY}in a pack's.envtakes the shell's value, as docker compose does; it used to come out empty;kindgi devgives the runtime your Google credentials only whenKINDGI_DEV_GOOGLE_CREDENTIALSnames them: a pack that uses Vertex AI addsKINDGI_DEV_GOOGLE_CREDENTIALS=adcto its.env(Gemini on Vertex AI);kindgi consoleopens the console, and so doeskindgi dev --open;- under
kindgi dev, Sign in as seeded user shows only when the console is at a loopback address (localhost,127.0.0.1or[::1]); from another address, sign in with the dev token. A deployed console no longer asks for it.
- a usage error (a missing argument, a bad flag value) exits
-
New in 0.1.5:
- Sign-in with your organization's identity provider (Set up SSO), and with Google, Microsoft or GitHub accounts or an emailed link through the deployment's own apps (Turn on sign-in).
- People, API keys and service accounts: each person and pipeline acts with its own key and grants (People, API keys and service accounts).
- Schedules: an agent or a flow at set times, as you, with a history of each time it ran (Run on a schedule).
- Memory: an agent remembers what its declaration allows
(
kindgi_remember), with the scope and how long chosen by you, not the model, and can recall earlier conversations. Retrieval can search by meaning through an embeddings endpoint (KINDGI_MEMORY_EMBEDDINGS). A tenant admin can erase an end user's words, their conversations and the runs that served them (kindgi memory erasures). - Improvement passes: propose new settings or a new prompt for an
agent version, compare the candidate on a test set, and promote it
through the scope's gate after review (
kindgi proposals). A pass can look for better settings on its own, and a schedule can start one. - Files kept with a run (artifacts) and the capability catalog (Keep files with a run).
- A tool's env values per project (
ctx.env) (Give a tool env values). - A failed run says why, as data:
failure, with itscodeandmessage, on the run. - Logs: a pack's service writes log records,
kindgi devshows them, and records from a run carry its ids; providers and MCP endpoints can opt in to the run's trace (Logs). - Java and Scala, as a preview (a Java or Scala pack needs Jackson 2.18 or later in your app, which is Spring Boot 3.4 or later): Quickstart: Java, Quickstart: Scala and Call Kindgi from a Java app.
- The Cloud Run module: an image repository you already have, no KMS key, calling your app's Cloud Run services from a tool, client addresses, and the runtime's own settings for sign-in (Deploy on Google Cloud Run).
Known limitations in 0.1.5
Section titled “Known limitations in 0.1.5”- Safari can't sign in to
kindgi dev's console. The console's sign-in is aSecurecookie, and Safari doesn't keep one over plainhttp, even athttp://localhost, where Chrome and Firefox do. Open the local console in Chrome or Firefox. A fix is planned. A deployment's console needshttpsin every browser (above). - Run one runtime instance. Several aren't supported yet (the Cloud Run module runs one). For one thing, a guardrail change reaches other instances only when they restart: registering, unregistering or deploying a guardrail takes effect at once on the instance that took the request, and other instances keep the guardrails they had. If you run several anyway, restart the others after changing a guardrail. A fix is planned.
- Cancelling a flow while it runs a loop can let a few more of the loop's
steps start before it stops: in our tests up to a few dozen, within
seconds. The run still ends
cancelled. A later release fixes it.
Runtime 0.1.4.2
Section titled “Runtime 0.1.4.2”Runtime 0.1.4.2 fixes one bug in 0.1.4 and 0.1.4.1, for every deployment: when the database drops its connections (a restart, a failover, a network blip), the runtime could exit instead of reconnecting. Its log then ends like this:
file:///app/node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/connection.js:255 const x = socket.write(chunk, fn) ^
TypeError: Cannot read properties of null (reading 'write') at Immediate.nextWrite (file:///app/node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/connection.js:255:22)On 0.1.4.2, requests that need the database fail while it's down, and the
runtime keeps running and answers again once it's back. Only the runtime
changes: the 0.1.4 CLI and SDKs (npm, PyPI) stay as they are. kindgi dev
keeps its pinned 0.1.4 runtime, so if your local database restarts under it,
restart kindgi dev.
Run 0.1.4.2, pulled by its digest, with the same kindgi.env. It has no
migration, and it carries 0.1.4.1's fix (Runtime 0.1.4.1).
Keep --restart unless-stopped on the runtime's container either way
(Restart the runtime):
docker pull quay.io/kindgi/runtime:0.1.4.2@sha256:420826ad9bac0c2fdb021c49517aabebeac1ff7ec236e02af47e90a31f5b825eOn Cloud Run, copy it into your repository the same way as 0.1.4 (see
The images into Artifact Registry)
and set server_image to its digest.
Runtime 0.1.4.1
Section titled “Runtime 0.1.4.1”Runtime 0.1.4.1 fixes one bug in 0.1.4, for deployments with authorization on
(KINDGI_OPENFGA_API_URL set): a redeploy that published a new version of an
existing agent could leave other permission changes made in the same few
seconds unapplied. A newly published agent or project could then answer 403
to the person who made it. Without authorization, and under kindgi dev, 0.1.4
is unaffected. Only the runtime changes: the 0.1.4 CLI and SDKs (npm, PyPI)
stay as they are.
With authorization on, run 0.1.4.1, pulled by its digest, with the same
kindgi.env. It has no migration, so going back to 0.1.4 works, but the bug
comes back with it:
docker pull quay.io/kindgi/runtime:0.1.4.1@sha256:3f14fcf7336c846b276c6119bc8dc96eaf44faee6dacfd45ba40b2e7c08d555fOn Cloud Run, copy it into your repository the same way as 0.1.4 (see
The images into Artifact Registry)
and set server_image to its digest.
0.1.4.1 and later don't retry a change 0.1.4 already lost. What comes back, and when:
- A project or agent that answered
403reads again from the upgrade on, since the upgrade restarts the runtime. At every start, the runtime writes again the permissions that place each project, agent, flow, tool, guardrail and test set of its tenant (KINDGI_TENANT_ID). - The creator's own rights on an agent come back when a new version of it is published on 0.1.4.1 or later.
- A project membership added while the bug hit stays missing, through
restarts and publishes, though the project's member list still shows the
person. Add them again on 0.1.4.1 or later with the same call
(
POST /v1/projects/<project-id>/memberships, see Project memberships), and their access applies within seconds.
On 0.1.4.1, re-publishing an agent can log a warning like this one:
WARN [authz.outbox] drain: FGA refused a batch; trying its tuples one by one tenantId=<tenant> rowCount=2 error="cannot write a tuple which already exists: user: 'project:<id>', relation: 'parent', object: 'agent:acme.alpha': tuple to be written already existed or the tuple to be deleted did not exist"It's expected: the runtime then applies the batch's changes one at a time,
and a change that's already there counts as applied. From 0.1.5, with
OpenFGA v1.22.0, the warning doesn't appear: the runtime asks OpenFGA to
ignore changes already in place, and v1.22.0 does, so it takes the batch
whole. With an older OpenFGA that doesn't, the runtime still applies them
one at a time, and logs it at info.
From 0.1.3 to 0.1.4
Section titled “From 0.1.3 to 0.1.4”kindgi.env needs no change: the database migrates when 0.1.4 starts. What's different after:
-
Agent versions are pinned. A version published from 0.1.4 on resolves its tool ranges once, when it's published, and keeps those versions (Agent and tool versions). Versions published before have no pins and keep resolving each run. The first
kindgi deployafter the upgrade registers a pinned next version of each agent, and says so:agent acme.matcher: registered new version 1.4.1 (1.4.0 was published before pins; 1.4.1 pins its tools); set version: '1.4.1' in acme.matcher to matchSet that version in the agent's code. An app that runs an exact version (
agentVersion: '1.4.0') keeps running the unpinned one until you change it; an app that runs the latest gets the pinned one. -
With authorization on (
KINDGI_OPENFGA_API_URL), the first start brings each tenant's authorization store up to 0.1.4's model, with a line per store before the banner:<time> INFO [authz] tenant 8c3b6779-c02b-47e1-a9e1-4d0cd7f0c5a7: store 01M48C11G6C00WQXYMAJEWH7DW now has the current model (01M48C11V76E0C6APGAKKPRXXK → 01M48C24EGSZQHYJMEE2ZD88S2)Later starts print nothing. A store it can't reach gets a warning instead (
could not bring store … up to the current model), the runtime starts anyway, and the next start tries again. -
Deleting an org keeps its record until a retention policy on
orgpurges it. It still answers404 org-not-found, its projects and teams stay without an org, and its slug is free at once (Delete an org). 0.1.3 removed the record at once. -
Unregistering a provider keeps its record until a retention policy on
providerpurges it; its id is free to register again at once (List, change and remove). 0.1.3 removed the record at once. -
KINDGI_PUBLIC_URLis new: the address clients use when it isn't the one the runtime binds, behind a proxy or a load balancer (Check it). -
The runtime's logs are structured. Each line is a record with a level and a subsystem: JSON when the output isn't a terminal (as on a server), readable lines on one.
KINDGI_LOG_LEVEL,KINDGI_LOG_LEVELSandKINDGI_LOG_FORMATchoose what's written, and a request'straceparentis joined (Logs). -
A conversation's turns run the agent version it was opened with. A turn that names another version is refused (
agent-version-mismatch); one whose version is no longer registered answers404(A conversation keeps its agent version). 0.1.3 ran the current version and refused the turn when it differed. -
kindgi runs startexits1for a failed run, withError [<code>]: <message>naming the error's own code, such asbudget-exceededormodel-invocation-failed. 0.1.3 printedError [server]and exited0for a run that failed. A script that checks the exit code now sees the failure; the run itself is still printed on stdout. -
dev-echosays it isn't a real model. Its answers start with⚠ dev-echo isn't a real model, and a turn it answers carries adev-echo-not-a-modelwarning. -
On Cloud Run, the Terraform module pins the version of the secrets' AAD key it reads: add
secrets_aad_key_version(normally"1") to your.tfvarsbefore you apply (Deploy on Google Cloud Run). -
Gemini 2.5 retires on Vertex AI. Vertex AI retires
gemini-2.5-proandgemini-2.5-flashon 2026-10-20; a turn routed to them fails after. The 0.1.4geminipreset listsgemini-3.8-flashwithgemini-3.5-flash-liteinstead (If you registered Gemini 2.5). Theanthropicpreset addsclaude-haiku-5-5besideclaude-haiku-4-5; Anthropic lists each model's status on its model deprecations page. -
Register presets again. A provider registered from a preset before 0.1.4 keeps what it had: no default model, so an agent that names none gets the first model by name (
claude-haiku-4-5foranthropic); none of the models' temperature and thinking marks; and only the two base prices. Unregister it and register the preset again with the 0.1.4 CLI, or restartkindgi devfor one the pack's config declares.kindgi doctorwarns (!) about one that's stale. -
Each provider can name a default model (
metadata.defaultModel): when nothing else decides, an agent gets it rather than the first model by name. Each preset names a mid-priced one, such asclaude-sonnet-5-5(How Kindgi picks). -
Models that take no temperature get none. A model registered with
"sampling": false(the Claude 5.5 and GPT-6 models) is called without one, and the turn carries asampling-unsupportedwarning instead of failing. A model'sthinkingsays how it thinks; thinking counts against its output limit and bills as output (Temperature and thinking). -
Costs follow what providers bill. A registration keeps its model's extra rates: cached prompts, cache writes, long prompts and data residency. A long prompt prices the whole call at the long rates (Claude Haiku 5.5 past 100,000 tokens, Gemini 3.1 Pro past 200,000, GPT-6 past 272,000). A rate that isn't a non-negative number is refused when you register. Kindgi's costs stay estimates from published prices; your provider's invoice is what you pay.
-
OpenAI calls use OpenAI's Responses API, which GPT-6 models need to call tools: agents with tools now work on them. Every call sends
store: false. An existing OpenAI registration moves over when you upgrade, with nothing to register again;"api": "chat-completions"in itsadapter_configkeeps the older API (OpenAI's own API). -
Gemini calls retry a temporary failure (a rate limit
429, an overloaded model503, and the like), three attempts in all, as the Anthropic and OpenAI-compatible adapters already did. -
A waited start that outlasts the client's timeout (30 seconds in TypeScript, now settable with
timeoutMs; 60 seconds in Python) fails on the client without the run's id, and says to start the run in the background and follow it (Wait for the result). -
In 0.1.2 and 0.1.3, the Python client could start a waited run up to three times when it outlasted the client's timeout. 0.1.4's client never sends a call again once it may be running: upgrade the client. A start you repeat yourself with the same idempotency key still returns the first run only once the first request has answered (Retry a start safely).