Skip to content

Start a run

Terminal window
kindgi runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
kindgi runs start --flow=acme.review-order --input=@order.json

--input is the run's input as JSON, or @ and a file to read it from. The command waits for the run to finish, however long it takes, and prints it. Ctrl+C stops the waiting, not the run:

Stopped waiting. Run f91ff793-b029-48e8-9ce4-f9fa70bd0c40 goes on: kindgi runs get f91ff793-b029-48e8-9ce4-f9fa70bd0c40

With --no-wait, it prints the run as soon as it exists:

Terminal window
kindgi runs start --flow=acme.reserve-order --input='{"orderId":"A-300"}' --no-wait
{
"id": "1b1f6486-3faf-4ab7-8506-c09f50f989bc",
…
"flowId": "acme.reserve-order",
"flowVersion": "0.1.0",
"status": "pending",
"dryRun": false,
…
}

kindgi runs get <run-id> shows it again, with its output once it's done.

--agent-version=<version> (or --flow-version) runs that version instead of the latest, and --project=<project-id> runs in that project instead of the agent's or flow's own, which is Default for what kindgi deploy registers (kindgi projects list lists the projects with their ids). With authorization on, naming a project needs write on it:

Terminal window
kindgi runs start --agent=acme.order-desk --agent-version=0.1.0 --project=<project-id> --input='{"userMessage":"Where is order A-1002?"}'

A --project that isn't a project in your tenant stops before anything runs, with No project with id "<project-id>" in this tenant (the API answers 404 project-not-found). One that isn't an id at all, like --project=support, gets `projectId` must be a project id (a UUID).

Create a client. With no arguments it reads the API's URL and a token from KINDGI_API_URL and KINDGI_API_TOKEN, which you set in your env file (.env / .env.local). In development, when they aren't set, it uses the running kindgi dev, which writes both to .kindgirc.json, and warns once that you should set them. In production (NODE_ENV or KINDGI_ENV set to production) it never does, so set them there.

import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
const run = await kindgi.runs.start({
flow: 'acme.review-order',
input: { orderId: 'A-200' },
});
console.log(run.status, run.output);
completed { status: 'held', orderId: 'A-200' }

start returns when the run completes, fails or waits for something (a person's approval, for example: status is then suspended). Use it when the run is short and the caller needs the answer.

Each client waits for an answer up to its timeout: 30 seconds in TypeScript (timeoutMs, on createClient or on one call to start), 60 seconds in Python (timeout=, on Kindgi(...) or on one call). A waited start whose run takes longer fails on the client, and the run's id never arrives, though the run may still be going:

The run didn't end within 30 s, the client's timeout (timeoutMs). A waited start answers only when the run ends, so the run may still be going, and its id didn't arrive. Start a run that can take longer with `options: { wait: false }`: the answer carries its id at once. Then follow it with `runs.stream(runId)` or `runs.get(runId)`. Or raise `timeoutMs`.

So start a run that can take longer than the timeout in the background (below), and follow it, rather than waiting on it or sending the start again.

With wait: false, start returns as soon as the run exists, with status pending, and the run finishes on the server:

let run = await kindgi.runs.start({
flow: 'acme.reserve-order',
input: { orderId: 'A-300' },
options: { wait: false },
});
console.log(run.id, run.status);
while (run.status === 'pending' || run.status === 'running') {
await new Promise((resolve) => setTimeout(resolve, 1000));
run = await kindgi.runs.get(run.id);
}
console.log(run.status, run.output);
dbcf7457-0d25-49b9-9112-b03c9b44e9e8 pending
completed { attempt: 3, orderId: 'A-300', reserved: true }

Polling works, but there are better ways to learn that a run ended: follow its events, or have Kindgi send your app a webhook when it finishes.

Agent runs take options: { wait: false } too: the turn then runs in the background. Start an agent with agent instead of flow, and its input ({ "userMessage": "…" }, see Give an agent its input).

Field
id The run's id: for get, cancel, the journal and the event stream.
status pending, running, suspended, completed, failed or cancelled. kindgi runs resume <run-id> says what a suspended run waits for (details).
output What the run returned, once it completed.
failure Why it failed: its code and message (an agent's turn has its own code, such as budget-exceeded; any other failure is run-failed). See why runs failed.
flowId, flowVersion The flow and the version the run is pinned to. An agent's turn reports agent.turn, and its agent is in agent.
agent For an agent's turn: the agent's id, the version that ran, and the conversationId. A flow's run has none, nor does a turn from before Kindgi 0.1.3.
dryRun Whether it was a dry run.
publicAccessToken A read-only token for this run, for a browser, when the deployment issues them (details). Only start returns it.

The client references have every field and option: TypeScript, Python, and the CLI.

A run that fails is still a run: start returns it, with status: "failed" and its failure. A request the API refuses (a flow that doesn't exist, a malformed input) raises an error instead, and no run exists:

import { KindgiApiError } from '@kindgi/sdk/client';
const run = await kindgi.runs.start({ flow: 'acme.review-order', input: { orderId: 'Z-9' } });
if (run.status === 'failed') console.log(run.failure?.code, run.failure?.message);
try {
await kindgi.runs.start({ flow: 'acme.no-such-flow', input: {} });
} catch (err) {
if (err instanceof KindgiApiError) console.log(err.error.code, err.message);
}
run-failed handler-error: Tool "acme.get-order" handler threw: handler-throw: Handler for tool "acme.get-order" threw: Error: No order Z-9
not-found No flow "acme.no-such-flow" is registered in this tenant.

A request that times out on your side may have started the run anyway. Retry a start safely shows how to retry without starting it twice.

A failed run's page says what happened in a sentence, then the runtime's own words and the code:

The turn ran out of its budget.
Agent turn steps budget exceeded (limit 1, observed 1)
Code budget-exceeded

A failed agent run's page: the agent acme.tight at 1.0.0, Failed, then its message and tool call, and the failure: "The turn ran out of its budget.", "Agent turn steps budget exceeded (limit 1, observed 1)" and "Code budget-exceeded".

A run that ended at its approval didn't go wrong: someone, or the approval's deadline, decided. Its page says which, not in red: "A reviewer rejected it.", "Its approval was withdrawn.", "No one decided its approval in time." or "Its approval was closed before anyone decided.", then "It ended at its approval: a review outcome, not an error.", the reviewer's rationale and the code (Decide an approval).

A rejected run's page: "A reviewer rejected it.", "It ended at its approval: a review outcome, not an error.", "Reviewer rejected the session-HITL gate: not this week" and "Code hitl-rejected".

A finished run's page has Run again for whoever may run its agent or flow. A step inside a flow has none. The dialog, Run again, for real, says what the new run is and does, for that run:

The agent demo.echo-agent at 1.0.0, its version then, with the same input.
The turn's parameters aren't kept with the run: add them under `parameters` if it had any.
This is a new, live run: every tool it calls acts for real again (a refund would be issued again).
What the agent remembers about cust-9 is read, and may be added to.
It starts a new conversation; the original one is left as it was.
It starts as you, with your access, not as whoever started the original.

The input is the run's, and you can change it before Start the run.

The Run again, for real dialog over a finished run's page: the six lines above, the input as the run had it, and the buttons Don't run it and Start the run.