# Ask before a long conversation continues

> Make every turn past a set number in a conversation wait for a person's approval.

This guide makes each turn of a long conversation wait for a reviewer, once the conversation has run a number of turns.

Some conversations should get a person's attention once they run long: a support chat that hasn't been resolved in a few turns, say. `hitl.afterTurns` lets the first turns of a conversation run, then makes each turn after them wait for a reviewer before it starts.

## Before you start

- **An agent,** in a pack on a runtime such as `kindgi dev`, with Kindgi 0.1's CLI. This guide uses `acme-ops.echo-agent`, from a sample pack named `acme-ops` (`kindgi init acme-ops --template=sample`).
- **A reviewer's role.** Under `kindgi dev`, you're an `admin` reviewer. [Decide an approval](https://docs.kindgi.com/v0.1.9/guides/approvals/decide-an-approval.md) shows how to become one elsewhere.

## Set the limit

Give the agent's conversation policy a limit. Ask your coding agent: "Once a conversation runs long, make my agent wait for a person before each turn." For reference, it adds a limit like this:

- **TypeScript**

  ```ts excerpt
  // in agents/echo-agent/index.ts
    conversationPolicy: { historyLimit: 10, hitl: { afterTurns: 2 } },
  ```

- **Python**

  ```python excerpt
  # in agents/echo_agent.py
      conversation_policy={"historyLimit": 10, "hitl": {"afterTurns": 2}},
  ```

- **Java**

  ```java excerpt
  // in src/main/java/acmeops/agents/EchoAgent.java
        .set("conversationPolicy", Map.of("historyLimit", 10, "hitl", Map.of("afterTurns", 2)))
  ```

- **Scala**

  ```scala excerpt
  // in src/main/scala/acmeops/agents/EchoAgent.scala
      .set("conversationPolicy", Map("historyLimit" -> 10, "hitl" -> Map("afterTurns" -> 2)))
  ```

`afterTurns: 2` lets two turns run as usual. From the third on, each turn of the same conversation waits for an approval before it runs.

## Try it

Start a conversation, then continue it twice. `--agent` is the agent, and `--input` its input. `<conversation-id>` is the `conversationId` the first turn returns, in its `output`:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs start --agent=acme-ops.echo-agent --input='{"userMessage":"My order is late."}'
  pnpm exec kindgi runs start --agent=acme-ops.echo-agent --input='{"userMessage":"It was due Monday.","conversationId":"<conversation-id>"}'
  pnpm exec kindgi runs start --agent=acme-ops.echo-agent --input='{"userMessage":"Can you refund it?","conversationId":"<conversation-id>"}'
  ```

- **Python**

  ```sh
  uv run kindgi runs start --agent=acme-ops.echo-agent --input='{"userMessage":"My order is late."}'
  uv run kindgi runs start --agent=acme-ops.echo-agent --input='{"userMessage":"It was due Monday.","conversationId":"<conversation-id>"}'
  uv run kindgi runs start --agent=acme-ops.echo-agent --input='{"userMessage":"Can you refund it?","conversationId":"<conversation-id>"}'
  ```

- **Java**

  ```sh
  ./kindgiw runs start --agent=acme-ops.echo-agent --input='{"userMessage":"My order is late."}'
  ./kindgiw runs start --agent=acme-ops.echo-agent --input='{"userMessage":"It was due Monday.","conversationId":"<conversation-id>"}'
  ./kindgiw runs start --agent=acme-ops.echo-agent --input='{"userMessage":"Can you refund it?","conversationId":"<conversation-id>"}'
  ```

- **Scala**

  ```sh
  ./kindgiw runs start --agent=acme-ops.echo-agent --input='{"userMessage":"My order is late."}'
  ./kindgiw runs start --agent=acme-ops.echo-agent --input='{"userMessage":"It was due Monday.","conversationId":"<conversation-id>"}'
  ./kindgiw runs start --agent=acme-ops.echo-agent --input='{"userMessage":"Can you refund it?","conversationId":"<conversation-id>"}'
  ```

The first two runs complete. The third is `suspended`:

```json excerpt
{
  "id": "0c424165-fc78-4563-965f-5ca9ba0ec4a9",
  …
  "status": "suspended",
  …
}
```

Its approval says why. List the pending approvals. `--status=pending` lists only the ones still `pending`:

- **TypeScript**

  ```sh
  pnpm exec kindgi approvals list --status=pending
  ```

- **Python**

  ```sh
  uv run kindgi approvals list --status=pending
  ```

- **Java**

  ```sh
  ./kindgiw approvals list --status=pending
  ```

- **Scala**

  ```sh
  ./kindgiw approvals list --status=pending
  ```

```json excerpt
{
  "data": [
    {
      "id": "ff98fdd3-5d1d-4245-bdab-2ab9971f3962",
      …
      "subjectKind": "agent-turn:session-hitl-gate",
      "subjectRef": {
        "agentId": "acme-ops.echo-agent",
        "threshold": 2,
        "turnCount": 2,
        "agentVersion": "0.1.0",
        "conversationId": "5d72d0da-188f-4ea0-b923-766be818891e"
      },
      "requiredRole": "standard",
      "status": "pending",
      "title": "HITL review required for Echo Agent",
      "description": "Conversation reached HITL threshold: 2 turns >= 2",
      "provenanceRef": {
        "runId": "0c424165-fc78-4563-965f-5ca9ba0ec4a9"
      },
      …
    }
  ],
  "hasMore": false
}
```

The turn hasn't run yet: the model hasn't been called. `kindgi runs resume <run-id>` names the approval the run waits for, with the command that decides it, as [What a run waits for](https://docs.kindgi.com/v0.1.9/guides/approvals/decide-an-approval.md#what-a-run-waits-for) shows.

## Approve or reject

Decide the approval to let the turn run or stop it. `<approval-id>` is the approval's `id` from the list:

- **TypeScript**

  ```sh
  pnpm exec kindgi approvals complete <approval-id> --decision=approve
  ```

- **Python**

  ```sh
  uv run kindgi approvals complete <approval-id> --decision=approve
  ```

- **Java**

  ```sh
  ./kindgiw approvals complete <approval-id> --decision=approve
  ```

- **Scala**

  ```sh
  ./kindgiw approvals complete <approval-id> --decision=approve
  ```

It prints the recorded decision as JSON: the approval, the decision, and what it did to the run. The turn runs, and it's the conversation's third: the run's output has `"turnNumber": 3`. The next turn asks again: every turn past the limit needs its own approval.

Reject, and the run fails without calling the model. `--rationale` is kept with the decision. Then get the run by its id:

- **TypeScript**

  ```sh
  pnpm exec kindgi approvals complete ff98fdd3-5d1d-4245-bdab-2ab9971f3962 --decision=reject --rationale="Hand this one to a person"
  pnpm exec kindgi runs get 0c424165-fc78-4563-965f-5ca9ba0ec4a9
  ```

- **Python**

  ```sh
  uv run kindgi approvals complete ff98fdd3-5d1d-4245-bdab-2ab9971f3962 --decision=reject --rationale="Hand this one to a person"
  uv run kindgi runs get 0c424165-fc78-4563-965f-5ca9ba0ec4a9
  ```

- **Java**

  ```sh
  ./kindgiw approvals complete ff98fdd3-5d1d-4245-bdab-2ab9971f3962 --decision=reject --rationale="Hand this one to a person"
  ./kindgiw runs get 0c424165-fc78-4563-965f-5ca9ba0ec4a9
  ```

- **Scala**

  ```sh
  ./kindgiw approvals complete ff98fdd3-5d1d-4245-bdab-2ab9971f3962 --decision=reject --rationale="Hand this one to a person"
  ./kindgiw runs get 0c424165-fc78-4563-965f-5ca9ba0ec4a9
  ```

```json excerpt
{
  "id": "0c424165-fc78-4563-965f-5ca9ba0ec4a9",
  …
  "status": "failed",
  …
  "failureMessage": "{\"__agent_turn_failure__\":true,\"error\":{\"code\":\"hitl-rejected\",\"message\":\"Reviewer rejected the session-HITL gate: Hand this one to a person\",\"rationale\":\"Hand this one to a person\"}}"
}
```

A rejected turn doesn't count: the conversation still has two turns, and the next message asks again.

You need to be a reviewer to see and decide these, as [Decide an approval](https://docs.kindgi.com/v0.1.9/guides/approvals/decide-an-approval.md) shows.

## Next

- [Decide an approval](https://docs.kindgi.com/v0.1.9/guides/approvals/decide-an-approval.md)
- [Ask before a tool runs](https://docs.kindgi.com/v0.1.9/guides/approvals/ask-before-a-tool-runs.md)
- [Hold a conversation](https://docs.kindgi.com/v0.1.9/guides/agents/conversations.md)
