> ## Documentation Index
> Fetch the complete documentation index at: https://agents.nanonets.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Interactive tasks

> When an agent needs more from you mid-run, reply to it and let it continue.

Not every task runs straight through. If the agent calls `ask_user`, or if you've built
a conversational agent that expects a back-and-forth, the task pauses with
`status: "waiting_for_input"` until you reply.

## The reply loop

```
run agent  →  poll  →  status == "waiting_for_input"
                            │
                            ▼
                  POST /v1/tasks/{id}/message
                            │
                            ▼
                       poll again  →  status == "completed"
```

The reply is processed asynchronously. After posting it the task usually flips back to
`running` within a second; resume polling `GET /v1/tasks/{task_id}` exactly like a fresh
run. See [Async tasks and polling](/docs/async-and-polling) for the polling loop itself.

## Detecting that input is needed

Just check `status`. The same `GET /v1/tasks/{task_id}` you're already polling tells you:

```json theme={null}
{
  "task_id": "f1c2a3b4-...",
  "agent_id": "...",
  "status": "waiting_for_input",
  "title": "Reconcile the March invoices",
  "created_at": "2026-05-12T10:15:00Z",
  "updated_at": "2026-05-12T10:15:42Z"
}
```

To see *what* the agent asked, fetch the message log:

```bash theme={null}
curl https://agents.nanonets.com/api/v1/tasks/$TASK_ID/messages \
  -H "Authorization: Bearer $NANONETS_API_KEY"
```

The response is the chronological conversation log: user replies you've sent plus the
agent's messages back, oldest first. The most recent `assistant` message is the question
the agent is waiting on.

## Sending the reply

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://agents.nanonets.com/api/v1/tasks/$TASK_ID/message \
    -H "Authorization: Bearer $NANONETS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"message": "Use the second invoice line, not the first."}'
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      f"https://agents.nanonets.com/api/v1/tasks/{task_id}/message",
      headers={"Authorization": f"Bearer {os.environ['NANONETS_API_KEY']}"},
      json={"message": "Use the second invoice line, not the first."},
      timeout=10,
  )
  ```

  ```javascript Node.js theme={null}
  await fetch(`https://agents.nanonets.com/api/v1/tasks/${taskId}/message`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.NANONETS_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ message: "Use the second invoice line, not the first." }),
  });
  ```
</CodeGroup>

You can also send a message to a task that's still `running`; it gets appended to the
conversation and the agent picks it up on its next turn. This is how multi-turn
conversational agents work.

## End-to-end: handle the pause inside your polling loop

Adapting the polling loop from the previous guide to handle `waiting_for_input`:

```python Python theme={null}
import os, time, requests

BASE = "https://agents.nanonets.com"
HEADERS = {"Authorization": f"Bearer {os.environ['NANONETS_API_KEY']}"}
TERMINAL = {"completed", "failed", "stopped"}

def get_last_assistant_message(task_id):
    msgs = requests.get(f"{BASE}/api/v1/tasks/{task_id}/messages", headers=HEADERS).json()
    for m in reversed(msgs["messages"]):
        if m["role"] == "assistant":
            return m["content"]
    return None

def reply(task_id, message):
    requests.post(
        f"{BASE}/api/v1/tasks/{task_id}/message",
        headers=HEADERS, json={"message": message}, timeout=10,
    ).raise_for_status()

def run_until_done(task_id, answer_fn, timeout_s=600):
    deadline = time.time() + timeout_s
    delay = 1.0
    while time.time() < deadline:
        body = requests.get(f"{BASE}/api/v1/tasks/{task_id}", headers=HEADERS).json()
        if body["status"] in TERMINAL:
            return body
        if body["status"] == "waiting_for_input":
            question = get_last_assistant_message(task_id)
            reply(task_id, answer_fn(question))
        time.sleep(delay)
        delay = min(delay * 1.5, 10.0)
    raise TimeoutError(task_id)
```

`answer_fn` is your callback, typically a function that prompts a human, looks up an
answer from a database, or asks another LLM. The shape of the call is up to you; the API
just takes whatever string you send.

## Cancelling instead of replying

If you can't answer (the user walked away, the timeout has elapsed), call
`POST /v1/tasks/{task_id}/cancel` to mark the task `stopped`. The agent won't progress
further, but the conversation history stays available via the messages endpoint.
