# Start a run

> Start a flow or an agent from the CLI or your app (TypeScript, Python, Java or Scala), and wait for its result or let it finish in the background.

## From the CLI

- **TypeScript**

  ```sh
  pnpm exec kindgi runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
  pnpm exec kindgi runs start --flow=acme.review-order --input=@order.json
  ```

- **Python**

  ```sh
  uv run kindgi runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
  uv run kindgi runs start --flow=acme.review-order --input=@order.json
  ```

- **Java**

  ```sh
  ./kindgiw runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
  ./kindgiw runs start --flow=acme.review-order --input=@order.json
  ```

- **Scala**

  ```sh
  ./kindgiw runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
  ./kindgiw 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:

```text
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:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs start --flow=acme.reserve-order --input='{"orderId":"A-300"}' --no-wait
  ```

- **Python**

  ```sh
  uv run kindgi runs start --flow=acme.reserve-order --input='{"orderId":"A-300"}' --no-wait
  ```

- **Java**

  ```sh
  ./kindgiw runs start --flow=acme.reserve-order --input='{"orderId":"A-300"}' --no-wait
  ```

- **Scala**

  ```sh
  ./kindgiw runs start --flow=acme.reserve-order --input='{"orderId":"A-300"}' --no-wait
  ```

```json excerpt
{
  "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:

- **TypeScript**

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

- **Python**

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

- **Java**

  ```sh
  ./kindgiw runs start --agent=acme.order-desk --agent-version=0.1.0 --project=<project-id> --input='{"userMessage":"Where is order A-1002?"}'
  ```

- **Scala**

  ```sh
  ./kindgiw 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) ``.

## From your app

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.

Ask your coding agent: "I want my app to start runs on Kindgi." For reference, the client it creates looks like this:

- **TypeScript**

  ```ts excerpt
  import { createClient } from '@kindgi/sdk/client';

  const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
  ```

- **Python**

  ```python excerpt
  from kindgi.client import Kindgi

  kindgi = Kindgi()  # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
  ```

- **Java**

  ```java excerpt
  import com.kindgi.client.Kindgi;

  Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
  ```

  The Java client is `com.kindgi:kindgi-client` on Maven Central ([Call Kindgi from a Java app](https://docs.kindgi.com/v0.1.9/start/java-app.md#1-get-the-client)).

- **Scala**

  ```scala excerpt
  import com.kindgi.client.Kindgi

  val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
  ```

  A Scala app calls Kindgi with the Java client, `com.kindgi:kindgi-client` (`libraryDependencies += "com.kindgi" % "kindgi-client" % "<version>"`). Its methods take Java collections, so convert a Scala `Map` with `.asJava`.

Each example below is a whole file, with its imports.

### Wait for the result

Ask your coding agent: "I want my app to start a run and wait for its result." For reference, the code looks like this:

- **TypeScript**

  `scripts/start-and-wait.ts`

  ```ts
  import { createClient } from '@kindgi/sdk/client';

  const kindgi = createClient();

  const run = await kindgi.runs.start({
    flow: 'acme.review-order',
    input: { orderId: 'A-200' },
  });
  console.log(run.status, run.output);
  ```

  ```text
  completed { status: 'held', orderId: 'A-200' }
  ```

- **Python**

  `scripts/start_and_wait.py`

  ```python
  from kindgi.client import Kindgi

  kindgi = Kindgi()

  run = kindgi.runs.start(flow="acme.review-order", input={"orderId": "A-200"})
  print(run.status, run.output)
  ```

  ```text
  completed {'status': 'held', 'orderId': 'A-200'}
  ```

- **Java**

  `src/main/java/com/acme/app/StartAndWait.java`

  ```java
  package com.acme.app;

  import com.kindgi.client.Kindgi;
  import com.kindgi.client.models.Run;
  import com.kindgi.client.models.StartRunBody;
  import java.util.Map;

  public final class StartAndWait {
    public static void main(String[] args) {
      Kindgi kindgi = Kindgi.create();

      Run run = kindgi.runs().start(StartRunBody.WithFlow.builder()
          .flow("acme.review-order")
          .input(Map.of("orderId", "A-200"))
          .build());
      System.out.println(run.status() + " " + run.output());
    }
  }
  ```

  ```text
  completed {status=held, orderId=A-200}
  ```

- **Scala**

  `src/main/scala/acme/app/StartAndWait.scala`

  ```scala
  package acme.app

  import com.kindgi.client.Kindgi
  import com.kindgi.client.models.StartRunBody
  import scala.jdk.CollectionConverters._

  object StartAndWait {
    def main(args: Array[String]): Unit = {
      val kindgi = Kindgi.create()

      val run = kindgi.runs().start(StartRunBody.WithFlow.builder()
        .flow("acme.review-order")
        .input(Map("orderId" -> "A-200").asJava)
        .build())
      println(s"${run.status} ${run.output}")
    }
  }
  ```

  ```text
  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), 60 seconds in Java and Scala (`Kindgi.builder().timeout(…)`). 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:

- **TypeScript**

  ```text
  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`.
  ```

- **Python**

  A `NetworkError`, after 60 seconds:

  ```text
  The run didn't end within 60 s, the client's timeout (timeout). 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(run_id)` or `runs.get(run_id)`. Or raise `timeout`.
  ```

- **Java**

  A `ConflictException`, just after 60 seconds. The client sends a start again after a timeout, with the same idempotency key, and the server answers that the first one is still running:

  ```text
  com.kindgi.client.ConflictException: A request with this Idempotency-Key is still running. Retry after it answers (Retry-After), and you'll get its answer.
  ```

- **Scala**

  The Java client's `ConflictException`, just after 60 seconds. The client sends a start again after a timeout, with the same idempotency key, and the server answers that the first one is still running:

  ```text
  com.kindgi.client.ConflictException: A request with this Idempotency-Key is still running. Retry after it answers (Retry-After), and you'll get its answer.
  ```

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.

### Start it in the background

With `wait: false`, `start` returns as soon as the run exists, with `status` `pending`, and the run finishes on the server. Ask your coding agent: "I want my app to start a run without waiting, and check on it until it ends." For reference, the code looks like this:

- **TypeScript**

  `scripts/start-in-background.ts`

  ```ts
  import { createClient } from '@kindgi/sdk/client';

  const kindgi = createClient();

  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);
  ```

  ```text
  dbcf7457-0d25-49b9-9112-b03c9b44e9e8 pending
  completed { attempt: 3, orderId: 'A-300', reserved: true }
  ```

- **Python**

  `scripts/start_in_background.py`

  ```python
  import time

  from kindgi.client import Kindgi

  kindgi = Kindgi()

  run = kindgi.runs.start(
      flow="acme.reserve-order", input={"orderId": "A-300"}, options={"wait": False}
  )
  print(run.id, run.status)

  while run.status in ("pending", "running"):
      time.sleep(1)
      run = kindgi.runs.get(run.id)
  print(run.status, run.output)
  ```

  ```text
  0ed299be-e04d-47bb-84b8-2ff9a4286822 pending
  completed {'attempt': 3, 'orderId': 'A-300', 'reserved': True}
  ```

- **Java**

  `src/main/java/com/acme/app/StartInBackground.java`

  ```java
  package com.acme.app;

  import com.kindgi.client.Kindgi;
  import com.kindgi.client.models.Run;
  import com.kindgi.client.models.StartRunBody;
  import java.util.Map;

  public final class StartInBackground {
    public static void main(String[] args) throws InterruptedException {
      Kindgi kindgi = Kindgi.create();

      Run run = kindgi.runs().start(StartRunBody.WithFlow.builder()
          .flow("acme.reserve-order")
          .input(Map.of("orderId", "A-300"))
          .options(StartRunBody.WithFlow.Options.builder().wait_(false).build())
          .build());
      System.out.println(run.id() + " " + run.status());

      while (run.status().value() == Run.Status.Value.PENDING
          || run.status().value() == Run.Status.Value.RUNNING) {
        Thread.sleep(1000);
        run = kindgi.runs().get(run.id());
      }
      System.out.println(run.status() + " " + run.output());
    }
  }
  ```

  ```text
  4bfea550-9f61-42aa-a0b8-48333d209773 pending
  completed {attempt=3, orderId=A-300, reserved=true}
  ```

  `wait` is a Java keyword, so its builder method is `wait_`.

- **Scala**

  `src/main/scala/acme/app/StartInBackground.scala`

  ```scala
  package acme.app

  import com.kindgi.client.Kindgi
  import com.kindgi.client.models.{Run, StartRunBody}
  import scala.jdk.CollectionConverters._

  object StartInBackground {
    def main(args: Array[String]): Unit = {
      val kindgi = Kindgi.create()

      var run = kindgi.runs().start(StartRunBody.WithFlow.builder()
        .flow("acme.reserve-order")
        .input(Map("orderId" -> "A-300").asJava)
        .options(StartRunBody.WithFlow.Options.builder().wait_(false).build())
        .build())
      println(s"${run.id} ${run.status}")

      while (Set(Run.Status.Value.PENDING, Run.Status.Value.RUNNING).contains(run.status.value)) {
        Thread.sleep(1000)
        run = kindgi.runs().get(run.id)
      }
      println(s"${run.status} ${run.output}")
    }
  }
  ```

  ```text
  cc0443f5-537f-4701-a9d3-d1bd911e8e74 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](https://docs.kindgi.com/v0.1.9/guides/runs/follow-a-run.md), or have Kindgi [send your app a webhook](https://docs.kindgi.com/v0.1.9/guides/webhooks.md) when it finishes.

Agent runs take `options: { wait: false }` (`StartRunBody.WithAgent` in Java and Scala) 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](https://docs.kindgi.com/v0.1.9/guides/agents/give-an-agent-input.md)).

## What you get back

| 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](https://docs.kindgi.com/v0.1.9/guides/approvals/decide-an-approval.md#what-a-run-waits-for)). |
| `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](https://docs.kindgi.com/v0.1.9/guides/runs/see-why-runs-failed.md).                   |
| `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.                                                                                                                                       |
| `dryRun`                | Whether it was a [dry run](https://docs.kindgi.com/v0.1.9/guides/flows/dry-run-a-flow.md).                                                                                                                                                            |
| `publicAccessToken`     | A read-only token for this run, for a browser, when the deployment issues them ([details](https://docs.kindgi.com/v0.1.9/guides/runs/follow-from-the-browser.md)). Only `start` returns it.                                                           |

The client references have every field and option: [TypeScript](https://docs.kindgi.com/v0.1.9/reference/typescript/sdk/kindgi/sdk/client.md), [Python](https://docs.kindgi.com/v0.1.9/reference/python/resources/runs.md), [Java](https://github.com/kindgi/kindgi-sdk/tree/@kindgi/sdk@0.1.9/sdks/java#readme) (the README of `kindgi-client`, which Scala apps use too), and the [CLI](https://docs.kindgi.com/v0.1.9/reference/cli/runs.md). In Java, a run's fields are the record's methods (`run.status()`, `run.output()`), and `status()` prints as its wire value (`completed`).

## When it fails

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. Ask your coding agent: "I want my app to handle a run that fails, and a start that's refused." For reference, the code looks like this:

- **TypeScript**

  `scripts/start-failures.ts`

  ```ts
  import { KindgiApiError, createClient } from '@kindgi/sdk/client';

  const kindgi = createClient();

  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);
  }
  ```

  ```text
  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.
  ```

- **Python**

  `scripts/start_failures.py`

  ```python
  from kindgi.client import Kindgi, NotFoundError

  kindgi = Kindgi()

  run = kindgi.runs.start(flow="acme.review-order", input={"orderId": "Z-9"})
  if run.status == "failed" and run.failure is not None:
      print(run.failure.code, run.failure.message)

  try:
      kindgi.runs.start(flow="acme.no-such-flow", input={})
  except NotFoundError as err:
      print(err)
  ```

  ```text
  run-failed handler-error: Tool "acme.get-order" handler threw: handler-throw: Handler for tool "acme.get-order" threw: ValueError: No order Z-9
  No flow "acme.no-such-flow" is registered in this tenant.
  ```

- **Java**

  `src/main/java/com/acme/app/StartFailures.java`

  ```java
  package com.acme.app;

  import com.kindgi.client.Kindgi;
  import com.kindgi.client.NotFoundException;
  import com.kindgi.client.models.Run;
  import com.kindgi.client.models.StartRunBody;
  import java.util.Map;

  public final class StartFailures {
    public static void main(String[] args) {
      Kindgi kindgi = Kindgi.create();

      Run run = kindgi.runs().start(StartRunBody.WithFlow.builder()
          .flow("acme.review-order")
          .input(Map.of("orderId", "Z-9"))
          .build());
      if (run.status().value() == Run.Status.Value.FAILED) {
        System.out.println(run.failure().code() + " " + run.failure().message());
      }

      try {
        kindgi.runs().start(StartRunBody.WithFlow.builder()
            .flow("acme.no-such-flow")
            .input(Map.of())
            .build());
      } catch (NotFoundException e) {
        System.out.println(e.serverCode() + " " + e.getMessage());
      }
    }
  }
  ```

  ```text
  run-failed handler-error: Tool "acme.get-order" handler threw: handler-throw: Handler for tool "acme.get-order" threw: java.lang.IllegalArgumentException: No order Z-9
  flow-not-found No flow "acme.no-such-flow" is registered in this tenant.
  ```

- **Scala**

  `src/main/scala/acme/app/StartFailures.scala`

  ```scala
  package acme.app

  import com.kindgi.client.{Kindgi, NotFoundException}
  import com.kindgi.client.models.{Run, StartRunBody}
  import scala.jdk.CollectionConverters._

  object StartFailures {
    def main(args: Array[String]): Unit = {
      val kindgi = Kindgi.create()

      val run = kindgi.runs().start(StartRunBody.WithFlow.builder()
        .flow("acme.review-order")
        .input(Map("orderId" -> "Z-9").asJava)
        .build())
      if (run.status.value == Run.Status.Value.FAILED) {
        println(s"${run.failure.code} ${run.failure.message}")
      }

      try {
        kindgi.runs().start(StartRunBody.WithFlow.builder()
          .flow("acme.no-such-flow")
          .input(Map.empty[String, Any].asJava)
          .build())
      } catch {
        case e: NotFoundException => println(s"${e.serverCode} ${e.getMessage}")
      }
    }
  }
  ```

  ```text
  run-failed handler-error: Tool "acme.get-order" handler threw: handler-throw: Handler for tool "acme.get-order" threw: java.util.NoSuchElementException: No order Z-9
  flow-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](https://docs.kindgi.com/v0.1.9/guides/runs/retry-a-start-safely.md) shows how to retry without starting it twice.

### In the console

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

```text
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".](https://docs.kindgi.com/v0.1.9/_astro/run-failure.DupGTYQL_1jlrdt.webp)

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](https://docs.kindgi.com/v0.1.9/guides/approvals/decide-an-approval.md)).

![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".](https://docs.kindgi.com/v0.1.9/_astro/run-review-outcome.vmJJAt-5_1a5KeV.webp)

## Run it again

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:

```text
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.](https://docs.kindgi.com/v0.1.9/_astro/run-again-dialog.1drvqg0e_to4rJ.webp)
