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

# Hugs

> How a run asks people for something mid-run — reviews, approvals, answers and files — and how the answer comes back.

Asking people

A **Hug** is a request a run makes of people while it is running: approve this plan, send it back, upload the guest list, tell me which customer this is about. Every way a team asks a person goes through the same Hug framework, so a review gate, an agent's question and an LLM's `ask_human` call all look the same to the people answering and behave the same while the run waits.

***

## How it works

```text theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
 phase (gate or agent)            cliqd                       CliqHub                    people
 ─────────────────────            ─────                       ───────                    ──────
 ctx.ask(spec) ──── open ───────▶ stores the Hug ── create ──▶ resolves channels ──────▶ notified on their
                                                              stores the Hug             channels (email, Slack,
                                                                                         in-app…) → Hug page
          ◀──── next (events) ─── persists events ◀─ deliver ── validates & stores ◀───── Submit response
 policy decides: wait / done / failed                          the response, files
```

* **CliqHub** stores the Hug, sends it to its channels, checks who may answer, validates answers against the requested inputs, stores uploaded files, sends reminders and expires the Hug. It never interprets an answer.
* **cliqd** carries Hugs between the phase and CliqHub. It keeps every event, so a phase that stops or a cliqd that restarts picks up where it left off.
* **The phase** (through cliq-sdk) decides what the answers mean — the *policy*: the first answer is enough, every reviewer group must approve, and so on.

With a local cliqd that isn't signed in to CliqHub, the Hug stays on your machine and `cliq run` asks the question in the terminal.

### Three ways to open a Hug

| Who asks | How | Typical use |
| - | - | - |
| A **review gate** | A `type: gate` phase with `agent: hug` | Approve, send back or reject the work so far; collect files or fields with the decision |
| An **agent** (code) | `ctx.ask(spec)` in a cliq-sdk agent | Ask for a value the agent needs to continue |
| An **LLM / CLI agent** | The `ask_human` tool, a `cliq_hug` JSON block, or a closing `QUESTION: …` line | Let a model ask a person when it is stuck |

A run never asks for its own missing inputs: inputs are checked when the run starts, and a phase that still lacks one fails with `Phase 'x' is missing inputs: a, b — supply them when starting the run`.

***

## Example 1 — an approval gate

The most common Hug: a person reviews the work and approves it, sends it back to an earlier phase, or rejects it.

```yaml theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
inputs:
  - name: reviewers
    type: channel
    label: Reviewers

phases:
  - name: draft
    agent: claude-code
    prompt: Write the release notes for this sprint into notes.md

  - name: approve
    type: gate
    agent: hug
    depends_on: [draft]
    route_targets: [draft]          # adds "Send back to draft"
    max_iterations: 3               # at most 3 rounds of review (default 3)
    review:
      reviewers:
        - { policy: any, channels: ["{{inputs.reviewers}}"] }
      message: Check the release notes in notes.md before they go out.
      artifacts: [notes.md]         # attached to the review (first round)
      timeout: 2h                   # the Hug expires after 2 hours (default 30m)
      remind_every: 30m             # remind people who haven't answered
      inputs:
        - { name: note, type: textarea, label: What should change?, required: false }

  - name: publish
    agent: exec
    depends_on: [approve]
    commands:
      - run: echo "Publishing notes.md"
```

The reviewer sees **Approve**, **Send back to draft** and **Reject**, then the `note` field and an optional comment. What each answer does:

| Answer | The gate… |
| - | - |
| Every reviewer group approves | **passes** — `publish` runs |
| `Send back to draft` | **routes** to `draft`, which runs again with the comment; then a new round of review |
| `Reject` | routes to the first route target if there is one, otherwise **escalates** (the phase fails) |
| Nobody answers before `timeout` | **escalates** (or, with `on_timeout: wait`, stays open up to 7 days with one nudge at the timeout) |
| The Hug is cancelled (e.g. the run was cancelled) | the phase fails with `HUG_CANCELLED` |

The first answer that isn't an approval decides. A **round** is one review: sending the work back and reviewing it again is round 2.

### Gate `review` fields

| Field | Meaning |
| - | - |
| `reviewers` | **Required.** Groups: `{ policy: any \| all, channels: [...] }`. `any` = one approval through any of the group's channels; `all` = one approval through **each** channel of the group. Every group must be satisfied. |
| `message` | What the reviewer is asked (default: "Review the work so far, then approve it, send it back, or reject it.") |
| `artifacts` | File globs from the workspace attached to the review (first round only) |
| `timeout` | Minutes, or `30m` / `2h`. Default `30m` |
| `on_timeout` | `escalate` (default) or `wait` (keep the review open, up to 7 days, nudge once at the timeout) |
| `remind_every` | Remind people who haven't answered (at least 5 minutes) |
| `route_targets` | Phases "Send back to …" offers (default: the gate's own `route_targets`) |
| `inputs` | Extra fields the reviewer fills in — see [Requested inputs](#requested-inputs) |

***

## Example 2 — collect a file in the review

A review can ask for files. The files stay in storage (CliqHub, or cliqd's own store for a terminal answer); a later phase saves the ones it needs with `cliq hug files`.

```yaml theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
- name: human-review
  type: gate
  agent: hug
  depends_on: [planner]
  review:
    reviewers:
      - { policy: any, channels: ["{{inputs.reviewers}}"] }
    artifacts: [plan.md]
    inputs:
      - name: guest_list
        type: file
        label: Guest list
        help: A CSV with a header row; the first column is each guest's name
        accept: [.csv, text/csv]
        max_mb: 1
        required: true

- name: invitations
  agent: exec
  depends_on: [human-review]
  commands:
    - name: get-guest-list
      run: cliq hug files human-review guest_list guests/
```

`cliq hug files human-review guest_list guests/` saves each file as `guests/<responder>-<file name>` (a second file of the same name gets `-2`) and writes `guests/files.json` saying who sent what:

```json theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
{
  "files": [
    {
      "file": "ann-guests.csv",
      "artifact_id": "3f6c2a8e-…",
      "name": "guests.csv",
      "size": 128,
      "content_type": "text/csv",
      "sha256": "9b1d…",
      "phase": "human-review",
      "round": 1,
      "hug_id": "c41e…",
      "input": "guest_list",
      "responder": { "user_id": "…", "name": "ann" },
      "decision": "approve",
      "response_seq": 1,
      "received_at": "2026-10-27T10:00:00Z",
      "current": true
    }
  ]
}
```

By default it takes the **latest round** of that phase's review, the **current** file from **everyone** who sent one. Narrow or widen that with options:

```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
cliq hug files human-review guest_list guests/                    # latest round, everyone
cliq hug files human-review guest_list guests/ --responder ann    # only Ann's (name or user id)
cliq hug files human-review guest_list all/ --round all           # every round
cliq hug files human-review guest_list r1/ --round 1              # round 1 only
cliq hug files human-review guest_list v/ --include-replaced      # also files from answers people later changed
cliq hug files human-review guest_list out/ --run quiet-amber-heron   # after a run, from the workspace
```

* Inside a phase, `cliq hug files` uses the run it is running in, and the folder must be inside the workspace. Outside a phase, name the run with `--run` (and `-w` for another workspace).
* No matching file is not an error: the folder gets an empty `files.json`. A phase or input that has **no** files at all is (`HUG_FILES_NOT_FOUND`), and the error lists what the run does have — so a typo is caught.
* Every file is checked against its size and SHA-256 before it is written, and written atomically.

The list of files comes from the run's **Hug files catalog**, `$CLIQ_RUN_DIR/hug-files.json` (`<workspace>/.cliq/runs/<run_id>/hug-files.json`), which cliqd keeps up to date as answers arrive.

***

## Example 3 — run `@cliq/hug-example`

`@cliq/hug-example` is a built-in team that shows a review collecting a file. A planner writes a party plan, a reviewer approves it and uploads the guest list, and the next phase writes one invitation per guest plus a shopping list.

**1. Save this as `guests.csv`** — you will upload it in the review:

```csv theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
name,email
Ada Lovelace,ada@example.com
Grace Hopper,grace@example.com
Alan Turing,alan@example.com
Katherine Johnson,katherine@example.com
```

> [!NOTE]
>
> The team keeps its parsing simple: the **first line is a header** (always skipped), **names are in the first column**, and names must **not contain commas** (quoted fields aren't handled — `"Smith, Jo"` would be cut at the comma). Blank lines and duplicates are dropped. The file must be a `.csv` of at most 1 MB.

**2. Run it**, asking yourself to review:

```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
mkdir party && cd party
cliq init
cliq assemble @cliq/hug-example
cliq run -i child=Maya -i theme=pirates -i reviewers=me
```

**3. Review it.**

* **Signed in to CliqHub:** you get a notification; open the Hug from it or from **HUGs** in the sidebar. Read `plan.md` (Preview), pick **Approve**, upload `guests.csv`, and **Submit response**.
* **Local cliqd (not signed in):** `cliq run` asks in the terminal. Choose `approve` and give the path to `guests.csv`.

**4. Check the result.** The `invitations` phase saves the file with `cliq hug files human-review guest_list guests/` and writes:

```text theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
guests/<you>-guests.csv          # the uploaded file, named after who sent it
guests/files.json                # who sent it, in which round, with which decision
guests.txt                       # Ada Lovelace, Alan Turing, Grace Hopper, Katherine Johnson
invitations/Ada_Lovelace.txt     # "Dear Ada Lovelace, You are invited to a pirates party for Maya!"
invitations/Alan_Turing.txt
invitations/Grace_Hopper.txt
invitations/Katherine_Johnson.txt
shopping-list.md                 # Guests: 4 — cupcakes 6, party bags 4, balloons 8
```

If you **reject** instead, the gate has no route target, so the run escalates and stops before `invitations`. Because the guest list is `required`, the form asks for a file whatever you decide.

`@cliq/hello-world` is the smallest Hug: an `exec` phase writes `review.txt`, a gate (`agent: hug`, 60-minute timeout, `policy: any`) asks you to approve it, and a final phase runs on approval. It is the team [Get started](https://docs.getcliq.io/get-started.md) runs.

***

## Example 4 — an agent asks a person

An agent written with cliq-sdk asks with `ctx.ask`. The phase's `hug:` block in `team.yml` says where its questions go, how long they stay open and how often people are reminded (from `examples/hello-ask` in the cliq repo):

```yaml theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
inputs:
  - { name: asked, type: channel, label: Who to ask, required: true }

phases:
  - name: ask
    agent: hello-asker
    hug:
      channels: ["{{inputs.asked}}"]
      timeout: 2h
      remind_every: 30m
```

```ts theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import { StandardAgent } from '@getcliqio/cliq-sdk';

class HelloAsker extends StandardAgent {
    constructor() { super('hello-asker'); }

    async on_invoke(ctx) {
        const answer = await ctx.ask({
            title: 'Hello from hello-ask',
            prompt: 'Who should we greet? Attach a file if you like.',
            requested_inputs: [
                { name: 'name', type: 'text', label: 'Name', max_length: 80 },
                { name: 'attachment', type: 'file', label: 'A file (optional)', required: false },
            ],
        });
        const { name, attachment } = answer.values;
        if (!attachment) return { text: `Hello, ${name}!` };

        // A file is a reference; read it through cliqd wherever it is stored.
        let bytes = 0;
        for await (const chunk of await ctx.download(attachment)) bytes += chunk.length;
        return { text: `Hello, ${name}! Thanks for ${attachment.name} (${bytes} bytes).` };
    }
}

new HelloAsker().start();
```

`ctx.ask` returns `{ values, responses, … }`. With the default policy, `values` are the first answer's values keyed by input name. It throws when the Hug ends without a result:

| Error | When |
| - | - |
| `HugExpiredError` | Nobody answered before it expired |
| `HugCancelledError` | The Hug was cancelled — the run ended, or CliqHub refused it (no channel anyone can answer through) |
| `HugRejectedError` | A `groups` policy saw a rejecting answer |
| `HugRefusedError` | The request was invalid (e.g. a bad requested input) or cliqd refused it |

All extend `HugError`. Uncaught, they fail the phase.

### `ctx.ask` options

| Field | Meaning |
| - | - |
| `title` | Up to 200 characters |
| `prompt` | What you are asking (markdown, up to 20,000 characters) |
| `requested_inputs` | The fields to fill in — see [Requested inputs](#requested-inputs) |
| `context` | Blocks shown with the question (markdown, files, checks); up to 64 KB — attach large things as files |
| `channels` | Who to ask; default: the phase's `hug.channels` |
| `expires_in_min` | 1 minute to 7 days; default: the phase's `hug.timeout`, else 24 hours |
| `reminders` | `{ every_min (≥ 5), at_min: [...] (≤ 10) }`; default from `hug.remind_every` |
| `policy` | When the answer is complete — `any()` (default), `groups(...)`, `custom(...)` |
| `wait` | `block` (default) keeps the agent running while people answer; `yield` ends the process and resumes the phase when an answer arrives |

***

## Example 5 — several reviewers, and letting the agent stop while waiting

```ts theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
import { groups } from '@getcliqio/cliq-sdk';

// Inside on_invoke(ctx):
const answer = await ctx.ask({
    title: 'Release 4.2 go / no-go',
    prompt: 'Engineering and product both need to approve.',
    channels: ['eng-leads', 'product'],
    expires_in_min: 24 * 60,
    reminders: { every_min: 120 },
    requested_inputs: [
        { name: 'decision', type: 'enum', choices: [{ value: 'go' }, { value: 'no-go' }] },
        { name: 'notes', type: 'text', multiline: true, required: false },
    ],
    // Done when each group has one "go"; any "no-go" fails at once (HugRejectedError).
    policy: groups(
        [{ policy: 'any', channels: ['eng-leads'] }, { policy: 'any', channels: ['product'] }],
        { accept: (v) => v.decision === 'go', reject: (v) => v.decision === 'no-go' },
    ),
    // Don't keep the agent process alive for a day: stop now, resume when answers arrive.
    wait: 'yield',
});
```

With `wait: 'yield'` the agent's process ends, the phase and run show **`awaiting_input`**, and cliqd starts the phase again when the Hug has news (an answer, its expiry or its cancellation). The SDK finds the same Hug again — it is never asked twice — and `ctx.ask` returns or throws as if it had waited. With `wait: 'block'` (the default, and what the review gate does) the run stays `running`.

Either way **the phase never times out while it waits for people**: its `timeout` stops counting while any of its Hugs is open and resumes, with the time that was left, when they end. The Hug's own expiry bounds the wait instead.

For full control, open Hugs yourself with `ctx.hug.open(spec, { state })` and wait on several with `ctx.wait({ sessions, state })`.

***

## Example 6 — read uploaded files in agent code

The same catalog `cliq hug files` reads is available to agents:

```ts theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
// Every current guest list from the latest round of the human-review phase, everyone's.
const lists = ctx.hug_files({ phase: 'human-review', input: 'guest_list' });

for (const f of lists) {
    // Save it in the workspace (checked against size and sha256, written atomically)…
    const saved = await ctx.download(f, `guests/${f.responder.name}-${f.name}`);
    console.log(`${f.responder.name} sent ${f.name} in round ${f.round}: ${saved.path}`);
}

// …or stream it without writing a file.
const stream = await ctx.download(lists[0]);
```

`ctx.hug_files({ phase, input, round?, responder?, include_replaced? })` takes the same choices as the CLI (`round`: `'latest'` (default), `'all'` or a number). `ctx.download(ref)` returns a readable stream; `ctx.download(ref, dest)` saves to a path inside the workspace and returns `{ path, size, sha256 }`. Both read through cliqd, wherever the file is stored, and retry for up to two minutes if cliqd is restarting.

***

## Example 7 — an LLM agent asks

Agents that drive a model (LLM and CLI agents) can ask people without any code:

* the **`ask_human`** tool, when the model supports tools;
* a fenced **`cliq_hug`** block with the same JSON as `ctx.ask` (`title`, `prompt`, `requested_inputs`);
* a final line **`QUESTION: <text>`**, which becomes a one-field text question.

These always stop the agent while people answer (`wait: 'yield'`), and use the phase's `hug:` block for channels, expiry and reminders. The answer comes back to the model as the next turn.

***

## Requested inputs

Each requested input is one field in the form. Every input has `name` (lowercase letters, digits and `_`, starting with a letter), and optionally `label`, `help`, `default` and `required` — **required defaults to `true`**. At most 50 per Hug.

| `type` | Extra fields | Value received |
| - | - | - |
| `text` | `multiline`, `min_length`, `max_length` (≤ 100,000) | string |
| `number` | `min`, `max`, `integer` | number |
| `boolean` | — | `true` / `false` |
| `enum` | `choices: [{ value, label? }]` (1–100), `multiple` | the chosen value (list if `multiple`) |
| `file` | `accept` (MIME types like `image/*` or extensions like `.csv`), `max_bytes`, `multiple` (≤ 10 files) | `{ artifact_id, name, size, content_type, sha256 }` (list if `multiple`) |
| `channel` | `multiple` | a list of channel names |

The review gate's `review.inputs` use friendlier names that map onto these:

| Gate `type` | Becomes |
| - | - |
| `text` (default), `textarea` | `text` (`textarea` = multiline) |
| `number`, `boolean`, `channel` | the same |
| `select` with `choices: [a, b]` | `enum` |
| `file` with `accept`, `max_mb`, `multiple` | `file` (`max_bytes` = `max_mb` × 1 MiB) |

The gate adds its own `decision` and `comment` fields; those two names are reserved.

Files are checked against `accept` by declared type, extension and their first bytes, and against `max_bytes` and the server limit (`CLIQ_ARTIFACT_MAX_MB`, default 50 MB) — an input can lower the limit, never raise it.

***

## Who is asked, and who may answer

* **Channels** say who is asked: a person (by username, or `me`), or a shared channel your org set up (email, Slack, in-app…). `{{inputs.x}}` uses a run input — usually a `type: channel` input. Unknown names raise a `notification.unknown_channel` event; a Hug with no channel at all is refused.
* **Seeing** a Hug needs `runs.view` in its realm. **Answering** also needs it to be addressed to you — through your own channel or a shared channel it was sent to. Being asked grants nothing: a person reached by a channel who can't view the realm's runs is skipped, shown as "Not sent to …" on the Hug page, and raises `notification.no_access`. If nobody left can answer, the Hug is refused (`no_one_can_answer`).
* **Changing an answer:** a person can submit again while the Hug is open; their latest answer counts. Files from the earlier answer stay in the catalog with `current: false` (`--include-replaced` / `include_replaced` to see them).
* **Several people:** each answer goes to the phase as it arrives; the policy decides when it's complete — `any()` on the first answer, `groups` when every group is satisfied, or your own `custom(fn)`.

***

## Answering

### On CliqHub

Each Hug has a page at `/hugs/<hug id>`, linked from its notifications and from **HUGs** in the sidebar (its badge counts open Hugs addressed to you that you haven't answered yet; the HUGs page has **Waiting** and **Done** tabs). The page shows:

* who it was sent to, when it opened and when it expires;
* the title and prompt;
* the run's workflow — click a phase to see its output;
* the context: attached files as a list (**Preview** opens a dialog, with ‹ › to move between files; open in a new tab or download), notes and check results;
* **Your response**: required fields first, then optional ones, and **Submit response**. Files upload as you pick them.

After you answer you can change it while the Hug is open. Once it ends the page shows the outcome and everyone's responses.

### In the terminal

With a local cliqd that isn't signed in to CliqHub, `cliq run` asks each requested input in the terminal and checks it as you type. For a `file` input, give a path: the file is checked against `accept` and `max_bytes`, and cliqd keeps it in its own file store (`~/.cliqrc/artifacts/<run_id>/`, or `CLIQ_ARTIFACTS_DIR`) — the answer holds a reference, nothing is left in the workspace. Not at a terminal, `cliq run` prints the question instead.

***

## Lifecycle

| Status | Meaning |
| - | - |
| `open` | Waiting for answers |
| `closed` | The phase got what it needed and closed it |
| `expired` | Its expiry passed |
| `cancelled` | The run ended, or CliqHub refused it |

* **Reminders** go to people who haven't answered, every `remind_every` / `reminders.every_min`.
* **When a run ends**, its open Hugs are cancelled.
* **If cliqd restarts** while a run waits on a Hug, the run is picked up again and its phase finds the same Hug — nobody is asked twice (root runs; a sub-team's run can't be re-attached and is marked crashed). cliqd catches up on anything that happened while it was down.

### Defaults and limits

| | |
| - | - |
| Hug expiry | default 24 hours (gate: 30 minutes); 1 minute to 7 days |
| Reminders | every ≥ 5 minutes; at most 10 fixed times |
| Context | ≤ 64 KB (attach large things as files) |
| Agent state | ≤ 64 KB |
| Requested inputs | ≤ 50; text ≤ 100,000 characters; ≤ 10 files per `multiple` file input |
| File size | `CLIQ_ARTIFACT_MAX_MB` (default 50), or the input's `max_bytes` if smaller |

***

## Notifications and events

A new Hug is sent straight to its channels ("Input requested"). The rest are events you can route with [notification rules](https://docs.getcliq.io/hub-api/overview.md) — `hug.*` matches them all:

| Event | When |
| - | - |
| `hug.reminded` | A reminder went out |
| `hug.responded` | Someone answered |
| `hug.closed` | The phase closed it with an outcome |
| `hug.expired` | Nobody answered in time (severity error) |

For example, tell the release channel whenever a review expires:

```json theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
POST /v1/realms/set_notification_rules
{ "realm_id": "5b0c3a7e-…", "event": "hug.expired", "channel_id": "9a8b7c6d-…" }
```

The run's log also records `hug.opened`, `hug.responded`, `hug.closed`, `hug.expired`, `hug.cancelled`, and `phase.waiting` / `phase.resumed` when a phase stops and starts again around a Hug.

***

## APIs

* **CliqHub** ([Hub API › Hugs](https://docs.getcliq.io/api-reference/hub)): `hugs/get` (your Hugs, by realm or org), `hugs/get_by_id`, `hugs/respond` (answer one). `hugs/create`, `hugs/close` and `hugs/get_events` are for daemons. Errors: `HUG_EXPIRED` (410), `HUG_CLOSED` (409), `invalid_values` (422 — an answer that doesn't fit the requested inputs).
* **cliqd** ([Daemon API › Hugs](https://docs.getcliq.io/daemon-api/overview.md)): `hugs/open`, `hugs/next`, `hugs/save_state`, `hugs/close` for agents (cliq-sdk calls these for you), `hugs/deliver` for CliqHub, and `hugs/respond` for terminal answers. Errors: `HUG_NOT_FOUND` (404), `HUG_CLOSED` (409), `HUG_EXPIRED` (410), `HUG_REFUSED` (422).

## Related

* Phase types and gates → [Workflows & phases](https://docs.getcliq.io/workflows.md)
* Writing agents that ask → [Building agents](https://docs.getcliq.io/building-agents.md)
* `cliq hug files` and `cliq run` → [CLI](https://docs.getcliq.io/cli.md)
* Who can see runs → [Auth](https://docs.getcliq.io/auth.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.