# Pass data between steps

> Build each step's input from the run's input, earlier steps' outputs and fixed values, and declare what the run returns.

This guide builds each flow step's input from the run's input, earlier steps' outputs and fixed values, and declares what the run returns.

Each step's `inputMapping` says where every key of its input comes from. The flow's `output` says what the run returns.

## Before you start

- **A pack with the tools** from [Write a flow](https://docs.kindgi.com/v0.1.9/guides/flows/write-a-flow.md), such as `acme.get-order`, on a runtime such as `kindgi dev`, with Kindgi 0.1's CLI.

This guide adds one more tool. Ask your coding agent: "I want a tool that puts an order on hold." Kindgi's skills in your pack tell it how ([How it knows Kindgi](https://docs.kindgi.com/v0.1.9/start/coding-agents.md)). For reference, the tool looks like this. This guide calls it `acme.hold-order`:

- **TypeScript**

  `tools/hold-order/index.ts`

  ```ts
  import { defineTool } from '@kindgi/sdk/define';
  import type { ToolId } from '@kindgi/sdk/types';
  import { z } from 'zod';

  const defined = defineTool({
    id: 'acme.hold-order' as ToolId,
    description: 'Puts an order on hold for a person to review.',
    version: '0.1.0',
    input: z.object({ orderId: z.string(), reason: z.string() }),
    output: z.object({ orderId: z.string(), status: z.literal('held') }),
    effects: [],
    // no `mutating: false`: it writes, so a dry run stops before it
    handler: async ({ orderId }) => ({ orderId, status: 'held' as const }),
  });

  if (defined.kind === 'err') throw new Error(defined.error.message);

  export default defined.value;
  ```

- **Python**

  `tools/hold_order.py`

  ```python
  from typing import Literal

  from pydantic import BaseModel, Field

  from kindgi import tool


  class HoldInput(BaseModel):
      order_id: str = Field(alias="orderId")
      reason: str


  class Held(BaseModel):
      order_id: str = Field(alias="orderId")
      status: Literal["held"] = "held"


  @tool(id="acme.hold-order")  # no mutating=False: it writes, so a dry run stops before it
  def hold_order(input: HoldInput) -> Held:
      """Puts an order on hold for a person to review."""
      return Held(orderId=input.order_id)
  ```

- **Java**

  `src/main/java/acme/tools/HoldOrder.java`

  ```java
  package acme.tools;

  import com.fasterxml.jackson.annotation.JsonProperty;
  import com.kindgi.pack.Tool;

  public final class HoldOrder {
    public record Input(String orderId, String reason) {}

    public enum Status {
      @JsonProperty("held") HELD
    }

    public record Output(String orderId, Status status) {}

    public static final Tool<Input, Output> TOOL = Tool.define("acme.hold-order")
        .description("Puts an order on hold for a person to review.")
        .version("0.1.0")
        .input(Input.class)
        .output(Output.class)
        // no mutating(false): it writes, so a dry run stops before it
        .handler((input, ctx) -> new Output(input.orderId(), Status.HELD));

    private HoldOrder() {}
  }
  ```

- **Scala**

  `src/main/scala/acme/tools/HoldOrder.scala`

  ```scala
  package acme.tools

  import com.kindgi.pack.scaladsl._

  object HoldOrder {
    final case class Input(orderId: String, reason: String)

    enum Status {
      case held
    }

    final case class Output(orderId: String, status: Status)

    val tool: Tool[Input, Output] = Tool[Input, Output]("acme.hold-order")
      .description("Puts an order on hold for a person to review.")
      .version("0.1.0")
      // no readOnly: it writes, so a dry run stops before it
      .handler((in, _) => Output(in.orderId, Status.held))
  }
  ```

## Map a step's input

A mapping gives each step exactly the input it needs. Ask your coding agent: "I want a flow that looks up an order, then puts it on hold while it waits for stock." For reference, the flow looks like this. `acme.hold-for-stock` looks an order up, then holds it with a fixed reason:

- **TypeScript**

  `flows/hold-for-stock/index.ts`

  ```ts
  import { defineFlow } from '@kindgi/sdk/define';

  const defined = defineFlow({
    id: 'acme.hold-for-stock',
    version: '0.1.0',
    name: 'Hold an order for stock',
    description: 'Looks up an order and puts it on hold.',
    nodes: [
      {
        id: 'order',
        kind: 'tool',
        ref: 'acme.get-order',
        inputMapping: { orderId: { path: 'runInput.orderId' } },
      },
      {
        id: 'hold',
        kind: 'tool',
        ref: 'acme.hold-order',
        inputMapping: {
          orderId: { path: 'nodeOutputs.order.orderId' },
          reason: { literal: 'Waiting for stock' },
        },
      },
    ],
    edges: [
      { id: 'e1', from: '$start', to: 'order' },
      { id: 'e2', from: 'order', to: 'hold' },
      { id: 'e3', from: 'hold', to: '$end' },
    ],
    output: {
      mapping: {
        orderId: { path: 'runInput.orderId' },
        customer: { path: 'nodeOutputs.order.customer' },
        status: { path: 'nodeOutputs.hold.status' },
      },
      schema: {
        type: 'object',
        properties: {
          orderId: { type: 'string' },
          customer: { type: 'string' },
          status: { type: 'string' },
        },
        required: ['orderId', 'customer', 'status'],
      },
    },
  });

  if (defined.kind === 'err') throw new Error(defined.error.message);

  export default defined.value;
  ```

- **Python**

  `flows/hold_for_stock.py`

  ```python
  from kindgi import Flow

  hold_for_stock = Flow(
      id="acme.hold-for-stock",
      version="0.1.0",
      name="Hold an order for stock",
      description="Looks up an order and puts it on hold.",
      nodes=[
          {
              "id": "order",
              "kind": "tool",
              "ref": "acme.get-order",
              "inputMapping": {"orderId": {"path": "runInput.orderId"}},
          },
          {
              "id": "hold",
              "kind": "tool",
              "ref": "acme.hold-order",
              "inputMapping": {
                  "orderId": {"path": "nodeOutputs.order.orderId"},
                  "reason": {"literal": "Waiting for stock"},
              },
          },
      ],
      edges=[
          {"id": "e1", "from": "$start", "to": "order"},
          {"id": "e2", "from": "order", "to": "hold"},
          {"id": "e3", "from": "hold", "to": "$end"},
      ],
      output={
          "mapping": {
              "orderId": {"path": "runInput.orderId"},
              "customer": {"path": "nodeOutputs.order.customer"},
              "status": {"path": "nodeOutputs.hold.status"},
          },
          "schema": {
              "type": "object",
              "properties": {
                  "orderId": {"type": "string"},
                  "customer": {"type": "string"},
                  "status": {"type": "string"},
              },
              "required": ["orderId", "customer", "status"],
          },
      },
  )
  ```

- **Java**

  `src/main/java/acme/flows/HoldForStock.java`

  ```java
  package acme.flows;

  import com.kindgi.pack.Flow;
  import java.util.List;
  import java.util.Map;

  public final class HoldForStock {
    public static final Flow FLOW = Flow.define("acme.hold-for-stock")
        .version("0.1.0")
        .set("name", "Hold an order for stock")
        .set("description", "Looks up an order and puts it on hold.")
        .node(Map.of("id", "order", "kind", "tool", "ref", "acme.get-order",
            "inputMapping", Map.of("orderId", Map.of("path", "runInput.orderId"))))
        .node(Map.of("id", "hold", "kind", "tool", "ref", "acme.hold-order",
            "inputMapping", Map.of(
                "orderId", Map.of("path", "nodeOutputs.order.orderId"),
                "reason", Map.of("literal", "Waiting for stock"))))
        .edge("e1", "$start", "order")
        .edge("e2", "order", "hold")
        .edge("e3", "hold", "$end")
        .set("output", Map.of(
            "mapping", Map.of(
                "orderId", Map.of("path", "runInput.orderId"),
                "customer", Map.of("path", "nodeOutputs.order.customer"),
                "status", Map.of("path", "nodeOutputs.hold.status")),
            "schema", Map.of(
                "type", "object",
                "properties", Map.of(
                    "orderId", Map.of("type", "string"),
                    "customer", Map.of("type", "string"),
                    "status", Map.of("type", "string")),
                "required", List.of("orderId", "customer", "status"))))
        .build();

    private HoldForStock() {}
  }
  ```

- **Scala**

  `src/main/scala/acme/flows/HoldForStock.scala`

  ```scala
  package acme.flows

  import com.kindgi.pack.scaladsl._

  object HoldForStock {
    val flow: Flow = Flow("acme.hold-for-stock")
      .version("0.1.0")
      .set("name", "Hold an order for stock")
      .set("description", "Looks up an order and puts it on hold.")
      .node(Map("id" -> "order", "kind" -> "tool", "ref" -> "acme.get-order",
        "inputMapping" -> Map("orderId" -> Map("path" -> "runInput.orderId"))))
      .node(Map("id" -> "hold", "kind" -> "tool", "ref" -> "acme.hold-order",
        "inputMapping" -> Map(
          "orderId" -> Map("path" -> "nodeOutputs.order.orderId"),
          "reason" -> Map("literal" -> "Waiting for stock"))))
      .edge("e1", "$start", "order")
      .edge("e2", "order", "hold")
      .edge("e3", "hold", "$end")
      .set("output", Map(
        "mapping" -> Map(
          "orderId" -> Map("path" -> "runInput.orderId"),
          "customer" -> Map("path" -> "nodeOutputs.order.customer"),
          "status" -> Map("path" -> "nodeOutputs.hold.status")),
        "schema" -> Map(
          "type" -> "object",
          "properties" -> Map(
            "orderId" -> Map("type" -> "string"),
            "customer" -> Map("type" -> "string"),
            "status" -> Map("type" -> "string")),
          "required" -> List("orderId", "customer", "status"))))
      .build()
  }
  ```

Each key of `inputMapping` is a key of the tool's input, and its value is one of:

- **`{ path: 'runInput.…' }`**: from the input the run was started with.
- **`{ path: 'nodeOutputs.<step>.…' }`**: from the output of an earlier step, by the step's `id`.
- **`{ literal: … }`**: a fixed value, any JSON.

Paths are dot-separated field names: `nodeOutputs.order.customer` is the `customer` field of the `order` step's output.

In Python, the keys are the tool input's names as they travel: a field's alias when it has one, else its name. `order_id: str = Field(alias="orderId")` travels as `orderId`.

Run it:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs start --flow=acme.hold-for-stock --input='{"orderId":"A-200"}'
  ```

- **Python**

  ```sh
  uv run kindgi runs start --flow=acme.hold-for-stock --input='{"orderId":"A-200"}'
  ```

- **Java**

  ```sh
  ./kindgiw runs start --flow=acme.hold-for-stock --input='{"orderId":"A-200"}'
  ```

- **Scala**

  ```sh
  ./kindgiw runs start --flow=acme.hold-for-stock --input='{"orderId":"A-200"}'
  ```

`--flow` is the flow, and `--input` its input. It prints the run, with the output the flow declares:

```json excerpt
{
  …
  "flowId": "acme.hold-for-stock",
  "status": "completed",
  …
  "output": {
    "status": "held",
    "orderId": "A-200",
    "customer": "grace@example.com"
  },
  …
}
```

The journal shows the input each step got, after mapping:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs journal <run-id>
  ```

- **Python**

  ```sh
  uv run kindgi runs journal <run-id>
  ```

- **Java**

  ```sh
  ./kindgiw runs journal <run-id>
  ```

- **Scala**

  ```sh
  ./kindgiw runs journal <run-id>
  ```

`<run-id>` is the run's id. It prints:

```json excerpt
    {
      "sequence": 5,
      "kind": "step.started",
      "nodeId": "hold",
      "payload": {
        "input": {
          "reason": "Waiting for stock",
          "orderId": "A-200"
        }
      },
      …
    },
```

## Without a mapping

A step without `inputMapping` gets the output of the step before it, or the run's input after `$start`. The tool's input schema takes the fields it declares and drops the rest. So a step that needs exactly what the previous step returns doesn't need a mapping. A step with several incoming edges needs one.

## When a path doesn't resolve

A path that doesn't resolve leaves its key out of the input, rather than setting it to `null`. If the tool requires that key, the step fails. Mapping `reason` from `nodeOutputs.order.reason`, which `acme.get-order` doesn't return, fails the run with:

```text
input-validation-failed: Input for tool "acme.hold-order" failed validation
```

A key that may be missing, because it comes from a branch that may not run, belongs in the tool's input as optional: `.optional()` in zod, a default in pydantic. [Join branches](https://docs.kindgi.com/v0.1.9/guides/flows/join-branches.md) has an example.

## An agent step's answer

An agent step's output has the answer as text, and as fields when the agent declares a [typed answer](https://docs.kindgi.com/v0.1.9/guides/agents/typed-answer.md):

- `nodeOutputs.<step>.text`: the answer as text.
- `nodeOutputs.<step>.output.<field>`: a field of the typed answer. Note the `.output`: `nodeOutputs.<step>.<field>` doesn't resolve.

## Declare the run's output

`output.mapping` is resolved when the run finishes, from the same roots as `inputMapping`. With a `schema`, the result is checked against it, and a run whose output doesn't match fails. A `schema` that doesn't compile is refused when the flow is loaded or deployed, not at each run. If `customer` were mapped from a path that doesn't resolve, `acme.hold-for-stock`'s run would fail with:

```text
output-schema-violation: the run's output does not match the flow's output schema: [{"instancePath":"","schemaPath":"#/required","keyword":"required","params":{"missingProperty":"customer"},"message":"must have required property 'customer'"}]
```

Without `output`, the run returns the output of the step that reached `$end`. Declare `output` when callers rely on the shape: it stays the same when you add or reorder steps.

## Next

- [Branch a flow](https://docs.kindgi.com/v0.1.9/guides/flows/branch-a-flow.md)
- [Join branches](https://docs.kindgi.com/v0.1.9/guides/flows/join-branches.md)
- [Give an agent a typed answer](https://docs.kindgi.com/v0.1.9/guides/agents/typed-answer.md)
