Kindgi 0.5.0
Released November 7, 2026. For the docs of this version, see docs.kindgi.com/v0.5.0.
-
Connections have names. A connection takes its name when it's made: the one given at the connect, else its account's, such as an email address. The console's Connect dialog has Name (optional), and after the connect it asks for a name or keeps the account's. Rename one with Rename on the Connections page or on the connection's own page, with
kindgi connections update <connection> --label=<name>, or withPATCH /v1/connections/{connectionId}and itslabel(client.connections.updatein the clients). Its owner or a tenant admin may rename it.kindgi connections connect <app> --label=<name>names a new one. A name is 1 to 100 characters, with no control, format or invisible characters, and one owner's live connections of an app in an environment each have their own (409 connection-label-taken).kindgi connections update,kindgi connection-choices set,--reconnectand--connectiontake a connection's name or its id. A connection made before 0.5.0 has no name until someone gives it one: the console shows its account, marked not named. A tool reads the name too, in every language: each connection inctx.connectionshas itsname, the connection's name, else its account's (pack protocol 2.11.0). -
The company keeps a connection per account, so it can connect two mailboxes of one app, such as support@acme.example and sales@acme.example. Connecting an account again reconnects its connection, which keeps its name, when the grant keeps every permission the connection holds. A connect can name the connection it reconnects:
reconnectsonPOST /v1/connections, or--reconnect=<connection>onkindgi connections connect. -
Choose which company connection an agent or a flow uses. When a project has several company connections of an app, a project admin chooses one for each agent and flow: on the agent's and the flow's Connections page (Choose and Clear); with
kindgi connection-choices set <connection> --agent=<id>(or--flow=<id>),listandunset; or over HTTP, withGETandPUT /v1/connection-choices,DELETE /v1/connection-choices/{choiceId}andGET /v1/connection-choices/candidates(client.connectionChoicesin TypeScript,client.connection_choicesin Python,client.connectionChoices()in Java). An unknown or removed choice is404 connection-choice-not-found. In the console, a connection's Used by, on its page and in Disconnect's confirm, lists the choices that name it and the mail triggers that store it. A flow's choice is for the tool steps the flow runs itself: an agent the flow runs uses its own. Within a run and the runs it starts, an agent or a flow keeps the connection its first call got through its choice, so replacing or removing that choice applies to every run that hasn't used it yet. The project's members see the connections' names; project admins also see each one's account and state. A rename, a choice and a removed choice are audit events (connection-renamed,connection-choice-set,connection-choice-removed), kept on legal hold, andkindgi exports createwrites each project's choices (connection-choices.jsonl) (Choose a connection). -
A call uses the company connection it's meant to, or none. A call that needs the company's connection of an app uses the mailbox the run's mail came to, else the agent's or the flow's choice, else the project's only company connection of the app. It never falls back to another: a mailbox's or a choice's connection that can't serve the call fails it with
connection-missing, naming the connection and why. With several and no choice, the run fails withconnection-not-chosen, and its failure names the ones to choose from: "This project has 2 company Google connections (sales, support), and agent acme.mail-helper has no choice of one." -
A mail trigger for each of the company's mailboxes. A trigger on the company's mailbox names the connection it reads with:
config.mailbox.connectionId,--connection=<connection>onkindgi mail-triggers createandupdate(with--as=company), the Connection picker when you make a trigger in the console, and Point at another on its Mailbox card. Make one trigger for each mailbox: a run's reply leaves from the mailbox its mail came to. With several company connections of the app, a trigger made without one is refused (409 connection-not-chosen, listing them), and nothing is made. Pointing a trigger at another connection is a change of mailbox: it takes a project admin, it reads the inbox unless a folder is named, and its tools allowed unattended, if it has any, are named again. -
An approval names the connection its call runs on, the company's included: an approval of a call on the company's connection carries it as its
connection, with itsname, and the console shows the name with the account under it. A call's connection is decided before it waits for its yes, and asked for again right before the tool runs. When the two differ, the call doesn't run, and the model reads why as its result (connection-changed). So an approved call runs only on the connection it was approved against, a person's connection too (An approval's fields). -
A run's journal says how each call's connection was picked. Each call an agent makes on a connection already has a record in the run's journal of the connection it used, and each attempt of a flow's tool step now has one too. The record now also says how that connection was picked (
pick.by: by the run's mailbox, the account the call named from its tool's set, a choice, the company's only one, or the person's main or only one), whose it was (as), how many a set held (setSize), and through which registration. These new fields name registrations only, never a person's connection names or addresses. The console words the record below each tool call that ran, in an agent's turn or a flow's tool step, on the run's page (What a run records). -
A person can connect several accounts of an app, and one is their main. A person keeps a connection per account too, such as a work account and a home account at Google, each with its own name. Connecting an account they already have reconnects that connection when the grant keeps every permission it holds, as the company's does. Their first connection is the one their agents use. When they connect a second account (for a second Google account, the console's connect dialog reads Connect another Google account), the first becomes their main, and their agents go on using it.
kindgi connections update <connection> --main, Make main in the console, orPATCH /v1/connections/{connectionId}with{ "main": true }makes another one main, and only the person whose connection it is can.mainis on the connection and on the person's candidates,kindgi connections listmarks it(main), a connect's finish says what became of it (main:set,keptormissing), and the CLI's connect says which one their agents use. Making one main is an audit event,connection-main-set. With several and none main, such as after they disconnect their main, a call that needs their connection fails the run withconnection-not-chosen, which counts them and names none, and the run's page links them to Make one your main (A person's accounts). -
Choose a connection for one tool, or for your own runs. A choice can be one tool's own, ahead of its agent's or flow's:
--tool=<tool-id>onkindgi connection-choices set, with--need=<name>for a tool that uses the app twice (toolIdandneedover HTTP). A person makes their own choices, of their own connections, for their own runs:--mine(person).GET /v1/connection-choices/needslists what a choice can be for, andGET /v1/connections/{connectionId}/choicesthe live choices that name a connection (connectionChoices.needsandconnections.choicesin the clients).GET /v1/connection-choicestakestoolIdandowner=me, and its candidates takeowner=me.kindgi connection-choices listtakes--tooland--mine, with TOOL, NEED and CONNECTIONS columns. A choice its agent or flow would never read is refused (400 bad-input): the company's where the version it resolves to now uses the app, or the tool's need, only as each person's own connection, and a person's own where it's only the company's. One that doesn't use the app yet still takes a choice, and a refused one can be made again once the version moves to one that reads it (Choose one for a tool, Your own choices). -
Read several accounts with one tool. A tool that only reads can be given a set of 2 to 10 named connections, one of them its default:
kindgi connection-choices set <connection>... --tool=<tool-id> --default=<connection>,connectionIdsover HTTP, or Choose several on the agent's or the flow's Connections page. The model then sees anaccountsinput on the tool, with the set's names, and each call names the account it reads, or reads the default. The tool itself never seesaccounts. A tool that changes things, declaressensitive-data-egress, or has anaccountsinput of its own keeps one connection. A name outside the set, or any name once the tool's version no longer takes a set, fails the call withconnection-missing, without repeating the name (Read several accounts with one tool). -
A trigger on a person's mailbox stores the connection it reads with.
--connectionwith--as=personnames one of the owner's own connections of the app (config.mailbox.connectionId). Without it, the trigger stores their main, else their only one, and with several and none main it's refused (409 connection-not-chosen), counting them. Making another connection main never moves it, and a take-over drops it (Which mailbox a trigger reads). -
List an account's calendars, and read several in one call. The new tools
googleCalendarCalendarsListandoutlookCalendarCalendarsList(@kindgi/connections/google/toolsand@kindgi/connections/microsoft/tools), each for a person or the company, list the calendars of the connected account. Each has itsid,name,main,access(owner,write,readorfree-busy) and, when someone else owns it, itsowner, with the main calendar first, then by name. From version 1.1.0, the events list and the free/busy tools take an optionalcalendarIds, 1 to 10 ids from that list, and read those calendars in one call: the events come back in start order, the earliesttopof them across the calendars, each with itscalendarId, and the busy times are joined. WithoutcalendarIdsthey read the main calendar, and answer exactly as before. Creating and changing events stays on the main calendar. On Google, the list tool needs a new permission,calendar.calendarlist.readonly("See the list of your calendars"), and the events list still reads only calendars the account owns. Outlook needs no new consent, and its free/busy reads at most 1000 events across the calendars, failing past that (Several calendars).
-
Java SDK: an app's own Jackson (2.15 and later, or 3) now keeps a newer runtime's variant of a union of objects, and an open record's other properties. Before, Jackson 2.15 and 2.17 dropped them without a sign and 2.18 failed the read, and on every Jackson, the client's too, a property named
propertiesoradditionalPropertieswas lost or broke the read. -
A HEAD request is checked as its GET. Some routes let a HEAD through without the check its GET makes, so a caller without the right read the GET's status and headers; others asked a HEAD for more than its GET. Now every route asks a HEAD for the same right as its GET: without it both are
403, and with it the HEAD answers as the GET. -
Every program the CLI starts is the one on
PATH, never a copy in the folder it runs in. 0.4.0 did so for git andkindgi dev's sandbox helpers. Now docker, the package manager, a cloud's CLI for a token, the pack's Python, JDK, Maven and sbt, and the other programs it starts are found inPATH's absolute entries and run by that path. A command set as a path (dev.python,JAVA_HOME,EDITOR) runs as given. -
A deploy of an image that's deployed already says so, and says its pack service's state. Its banner reads
200 OK (this image was deployed before), no longer an idempotent replay. When the deployment's pack service was asked for, as when you deploy a pack again after the upgrade from the one pack service, the deploy says whether it's asked for, ready or taken down, askindgi deployments getdoes. -
At start, the line that lists the deployments with no routes says how to deploy them again: the same image, each with a new idempotency key (
kindgi deploy --idempotency-key <new value>), so it isn't answered as its earlier deploy was. -
When the app allows less than a connect asked for and the connection is stored anyway, the console says it was connected. The page you come back to from the app said Not connected and that nothing was connected. It now says Connected, with less than asked (Reconnected, with less than asked when the connect names the connection it reconnects), what the app allowed and what it didn't, and that a run that needs what's missing asks again.
-
A connection app that refuses its client id or secret now says so. When an app refused its client id or secret, Kindgi said the app's token endpoint answered with
invalid_client. On a sign-in at Google, LinkedIn or any app whoseaccount.issuerslists more than one issuer, it said instead that the token endpoint couldn't be reached or answered badly, so a wrong client secret looked like a network failure, and so did a sign-in code that was used or had expired. Now a sign-in or a renewal says the app didn't accept the client id or secret registered for it, and at those apps a used or expired code says so too. The page you come back to from the app, The app didn't give a token, says nothing was connected, then what the app refused and what to do next: for a refused client id or secret, check both in the registration, then start again. Before, it said the sign-in's code was refused, whatever the app refused. A sign-in's token request or a renewal that the app fails is logged with the app's error code and what failed, by class name and code only, never a message, a body or a token. -
The links at the foot of each console page are underlined. Your own links there (
KINDGI_CONSOLE_LINKS) showed their underline only on hover, and so did a project's name on the Environments page. Both are underlined now, like the console's other links, and each link at the foot ends with the arrow of a link that opens a new tab. The sign-in page's links were underlined already. -
Erasing a person's run clears why it failed from its webhook deliveries. A
run.finisheddelivery says why the run failed infailureMessage, and again infailure, with any cause. Erasing the run removedfailureMessage, which the event requires, and leftfailure: the delivery kept the person's words, and one not yet sent had a body a receiver could fail to read. NowfailureMessageisnull, as the erased run reads back, andfailureis gone. A run erased before you upgrade can still have the person's words in its webhook deliveries, in why the run failed. The server deletes each delivery it has finished sending 30 days after it was created.
Changed
Section titled “Changed”-
Connecting another account of an app makes a second connection, the company's or a person's. Each kept one connection per registration and environment, so connecting another account was refused with
connection-account-mismatch, and changing accounts meant disconnecting first. A person's second connection makes their first their main. -
To add permissions to a connection, reconnect that one. A connect that names no connection asks only for the permissions it lists, and no longer reconnects the one you have by itself. Connecting an account you already have reconnects its connection only when the grant keeps every permission the connection holds. Otherwise the connection stays as it was: the connect answers connected if the connection is working and already holds what it asked for, and is declined with what's
missingif not. Name the connection to reconnect it, and the connect asks for what it holds as well as what it lists:--reconnect=<connection>onkindgi connections connect, orreconnectsonPOST /v1/connections. The console's Reconnect shows on a connection that needs reconnecting, and asks again for what it holds. -
With a second company connection of an app, agents, flows and mail triggers need to know which one. While the company has one, nothing changes. Once it has two, a call of an agent or a flow with no choice fails its run with
connection-not-chosen, and a company mail trigger that stores no connection doesn't read, and showsconnection-not-chosenwith the names, until a project admin points it at one. A trigger that stores no connection, such as one made before 0.5.0, is pointed at the project's only company connection of the app when it next reads. A person's trigger that stores no connection is pointed at their main connection of the app, else their only one; with several and none their main, it doesn't read, and showsconnection-not-chosenwith how many there are, until they make one their main. -
Names refuse more hidden characters. A connection's name, a site's display name and a console link's label (
KINDGI_CONSOLE_LINKS) follow one rule, which now also refuses a lone surrogate, the characters that render as nothing (such as the Hangul fillers and the combining grapheme joiner), and a variation selector anywhere but in an emoji. An emoji with its variation selector still passes. -
An approval's
connectionnames no account when it's a person's own, andassays whose,companyorperson. It was always the person's, with theiraccount, so code that readsaccountnow finds it optional in the clients' types. The approval shows "this person's Google account" for a Google connection. An approval made before the upgrade keeps itsaccount, and has noas(An approval's fields). -
kindgi connections listshows each connection's name first, in a LABEL column. Its JSON is the server's, which now has each connection'slabelwhen it has one. -
A trigger on a person's mailbox shows the address it reads to its owner and tenant admins only. Anyone else reads the trigger without its
account, andkindgi mail-triggers list --tableshows "this person's Google account" in its place. -
A project or a team moved into or out of an org, or one whose org was deleted, no longer stays open to the old org's admins. A move or an org's delete changed only the row, so the old org's admins kept their way in, and the new org's admins had none. Its authorization parents now change with it. What earlier moves and deletes left behind, the upgrade sets right (Upgrade steps).
-
Moving a project or a team into or out of an org takes a tenant admin. A
PATCHof a project or a team whose body namesorgId, even its current org ornull, needs a tenant admin, as making one in an org does. Anyone else gets403 permission-denied, and nothing in the body changes. Before, the project's or the team's own admins, a project's owner among them, could move it. A body withoutorgIdworks as before. -
The console, the CLI, the server's messages and the docs call the place a workspace. What you type or match on keeps
tenant:--tenant,kindgi tenant,tenantId,KINDGI_TENANT_*, the scope valuetenant, the error codes and the paths, and so do the tenant admin role and the system routes' messages, for the operator of a runtime that serves several workspaces. Match on a message's code, not its text. A new runtime names its first workspace Default workspace; one made before keeps its name. -
kindgi buildrefuses a block file or template named in another case than the disk has. On macOS and Windows such a pack ran underkindgi dev, then a build on Linux, as in CI, didn't find the file. The build names each one as the pack names it and as the disk spells it.kindgi devsays the same once, and still loads the block. -
--clear-labeltakes a schedule's, a webhook trigger's or a mail trigger's label off. Onupdate, an empty or blank--label=is now a usage error that names--clear-label, and a label is saved trimmed.
Known issues
Section titled “Known issues”-
The run's page can be wrong about which calls ran on a connection. In an agent's turn that's still running, or that failed, a call that the check right before its tool stopped, such as with
connection-changed, still shows the connection it would have run on. And in a turn that completed, a call that ran, was refused by the app and then couldn't get its connection again shows none. -
A command that names a renamed project's old slug can act on another project. The CLI looks a slug up each time it runs, wherever you name a project by its slug:
kindgi deploy --project, say, or an environment'sprojectin your pack config (environments.<name>.project), whichkindgi deploy,kindgi promote,kindgi environments set, andkindgi secretsandkindgi envwith--envread. After a rename, the old slug names no project, so such a command is refused, unless another project has that slug: one in another org may have it already, and any project can take it later. Then the command acts on that project with no error: a deploy goes into it, and a secret or value is set on it, andkindgi environments setputs it in the order. After you rename a project, change the old slug to the new one in every script, pack config and--projectflag before you run one of those commands again, or name the project by its id, which a rename doesn't change. Organize work by org and project has the details. -
Your app's own Jackson can't read some of the Java SDK's answers yet. With your app's own
ObjectMapper(Jackson 2 or 3), an agent doesn't read, and neither does a list of agents, a test run that has a comparison (alone or in a list), or a secret rotation's answer. The client's own calls are unaffected. Read them withKindgiJson.read(json, Type.class)fromkindgi-client, such asKindgiJson.read(json, Agent.class).
Upgrade steps
Section titled “Upgrade steps”-
From 0.3.x, follow 0.4.0's Upgrade steps first (Kindgi 0.4.0).
-
Before you upgrade, check your console links' labels. A runtime whose
KINDGI_CONSOLE_LINKSholds a label with one of the newly refused characters doesn't start, and says which link. Type the label again without it (Your own links in the console). -
The upgrade's two migrations let the company and each person keep a connection per account. They add three tables (
connection_choices,connection_choice_usesandconnection_choice_members) and new columns that are empty, and fill nothing in: your connections keep working, with no name, and no agent or flow has a choice. Name them when you like. The first start runs the migrations, as it runs any other migration. They lockconnectionsandconnection_requests, and each keepsconnectionslocked while it reads every row, to build its new indexes or check a new rule: meanwhile anything that reads or saves a connection waits, such as a call that runs on one, on a server of the earlier version still running too. With many connections (SELECT count(*) FROM connections), 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 or Azure Container Apps, the old revision serves meanwhile; on a single server, such as the Docker setup, the runtime is down until the new start succeeds. -
Before you connect a second company account of an app, choose for the agents and flows that use it. For each agent and flow whose tools use the company's connection of that app, choose the connection it uses now, and check that each company mail trigger of the app shows its Connection, which it stores when it first reads after the upgrade. Otherwise their runs stop with
connection-not-chosenonce the second account is connected. A call that isn't an agent's or a flow's can't have a choice, so it needs a project with one company connection of the app. -
A person's account label is no longer recorded. When a call runs on a person's own connection, the run's journal keeps the account's id without its label, and an approval shows "this person's Google account" for a Google connection. A pick from several of their accounts says how many there were, without their names. A company connection's record keeps its label. Runs and approvals recorded before 0.5.0 keep the label they were recorded with, such as an email address. What a call took and returned, and what the model wrote, stay as the model and the tool made them, so an account the call named can still show there.
-
To list Google calendars, add the
https://www.googleapis.com/auth/calendar.calendarlist.readonlyscope to your Google Cloud project's consent screen. A connection made before grants it once, when it's reconnected (--reconnect=<connection>, or Reconnect in the console once it needs reconnecting); the permissions it had stay. -
In Java and Scala, code that builds a
ToolConnectionadds itsname. The record's constructor is nownew ToolConnection(connectionId, app, appName, accessToken, account, name, expiresAt), and none withoutnameis kept, so a unit test that builds one adds the argument;nullis fine. In Python,nameis the last field and defaults toNone, so nothing changes there. -
On Azure Container Apps, the way back's plan shows the server's secrets removed and added again. The plan in the way back that
upgrade.shprints changes the images and the server'sdatabase-urlreference only, but Terraform plans the server's secrets as one set, so it shows them all removed and added again, their contents hidden. The printed steps and the module's README now say so. -
On a runtime serving many tenants, run
kindgi-runtime tenants repair --allafter the upgrade. It sets right the authorization parents that earlier moves of projects and teams, and deletes of orgs, left behind, and each tenant's line counts the changes it queued, such as1 parent change queued for the server to apply(After each upgrade: tenants repair). A runtime serving one tenant does it when it starts.