<!-- Source: https://docs.squirro.com/en/latest/technical/neo/admin-guide/agent-config/cli-reference.html -->
# CLI Reference

> **Warning**
>
> Project Neo is currently in [Technical Preview](../../../../a-z/squirro-glossary.md#term-Technical-Preview). Features described in this section may change before general availability.

The `genai` command line interface authors, validates, and deploys agent configuration. It is a thin client over the HTTP API, so it never writes to a cluster directly. For the HTTP surface it wraps, see the [API Reference](api-reference.md#neo-agent-config-api) page.

This page is the flag-by-flag reference for the commands. If you have not set the tool up yet, start elsewhere: for what to have in place first, such as a target Squirro project and a refresh token, see the [Prerequisites](prerequisites.md#neo-agent-config-prerequisites) page. To install the tool, see the [Installation](installation.md#neo-agent-config-installation) page. To author and deploy a first configuration end to end, see the [Quick Start](quick-start.md#neo-agent-config-quick-start) page.

> **Note**
>
> The `genai` CLI configures GenAI agents. It is a separate tool from the `neo-ui` CLI, which builds frontend dashboard extensions. For the `neo-ui` CLI, see the [Developer Guide](../../dev-guide/index.md#neo-dev-guide) page.

## Running It

```text
genai --help
```

Every command takes an optional directory as its first argument, written as `ROOT` in the synopses below. It is the directory holding your agent configuration project, the files you author, validate, and deploy, and it defaults to the current directory. Omit it when you run from inside the project, or pass a path to point a command somewhere else on disk:

```text
genai lint                                  # the current directory
genai lint ./my-agent-configuration-project # a project elsewhere
```

The `--help` output of the tool calls this argument the workspace root, which is the same thing.

Commands that talk to a server also resolve a target cluster URL and a Squirro project ID, described in the next section.

## Target Resolution

Each agent configuration project deploys to exactly one cluster and one Squirro project. Both fields resolve independently for each command.

| Field | Precedence | Meaning |
| --- | --- | --- |
| `cluster` | `--cluster` flag, then `GENAI_CLUSTER`, then local target configuration | The upstream server URL, for example `https://squirro-demo.example.com`. |
| `project` | `--project` flag, then `GENAI_PROJECT`, then local target configuration | The Squirro project ID, for example `Xy7QmN2pR9sT4vWz1AbC3D`. |

Not every command needs both fields. `genai lint` and `genai tree` run offline and accept neither. `genai login`, `genai logout`, and `genai whoami` act on a cluster and accept `--cluster` only. Every other command, including every `genai task` subcommand, accepts both.

To stay readable, the command synopses further down omit both flags. The exception is `genai init`, where supplying them is the point of the command. Add either flag to any command that accepts it.

The three ways to supply them, in precedence order:

```text
# Flags win over everything else
genai deploy    --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai status    --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai task list --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai whoami    --cluster https://squirro-demo.example.com

# Environment variables come next, which suits continuous integration
export GENAI_CLUSTER=https://squirro-demo.example.com
export GENAI_PROJECT=Xy7QmN2pR9sT4vWz1AbC3D
genai deploy

# The local target configuration comes last. genai init writes it once,
# so later commands need no flags and no variables.
genai init --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai deploy
```

On a terminal, a command that cannot resolve the cluster or the project prompts for the missing field and saves the answer. Run non-interactively, it exits with an error naming the missing field.

Commands that talk to a server also need credentials. When none are cached for the cluster, they authenticate on demand: interactively on a terminal, or using `GENAI_API_KEY` when it is set.

The environment variables that control this behavior:

| Variable | Purpose |
| --- | --- |
| `GENAI_CLUSTER` and `GENAI_PROJECT` | The target fields, following the precedence above. |
| `GENAI_API_KEY` | The refresh token, for non-interactive login. It takes the same refresh token as the `--api-key` flag on `genai login`. For how to generate one, see the [Authentication](../../../api/authentication.md#api-authentication) page. |
| `GENAI_NO_INPUT` | Turns off all prompts, so a missing input becomes an error. Useful for continuous integration. |
| `GENAI_CREDENTIALS_PATH` | Overrides the location of the credentials cache. |

## genai init

```text
genai init [ROOT] [--cluster URL] [--project ID] [--api-key TOKEN]
```

Takes a checkout from a fresh clone to ready to deploy. It resolves the cluster and the project, prepares the local target configuration, and authenticates you to the cluster.

The command is idempotent and runs without prompting when every input is supplied through a flag or an environment variable, which is the continuous integration path.

## genai lint

```text
genai lint [ROOT]
```

Runs every offline check against the agent configuration project, with no network and no credentials. On success it prints `OK (N resources, M agents)`. On failure it prints one line per failure and exits with an error.

`genai lint` is sufficient as a pre-merge check. The cluster-bound checks, such as credential handle resolvability and publish policy, run at deploy time. Among the offline checks is whether a `constrains` block admits any value at all, so a contradictory predicate is caught here. For the predicate syntax itself, see the [Predicate Syntax for Constraints](resource-reference.md#neo-agent-config-predicates) section.

### Advisory Warnings

Once the checks that can fail a deploy have passed, `genai lint` runs a second set of checks that only advise. These report a configuration that deploys and runs but is probably not what you meant. Each one prints a line naming its severity, the resource, and what it found, and then the command reports `OK` and exits successfully:

```text
genai lint
warning: Project.prefers: 2 KnowledgeGraph resources authored (company_kg, product_kg) but no source.knowledge_graph.default is set; the kg tool will reject any call that omits kg_id.
warning: Agent.deep_research: entrypoint_order 0 is shared by deep_research, assistant; the (entrypoint_order, id) tiebreaker decides their picker order. Set distinct values to make the order explicit.
OK (4 resources, 2 agents)
```

Read them as suggestions rather than errors. The severity is `warning` for something worth changing and `info` for something worth knowing. Because the command still succeeds, a continuous integration step that gates on the exit code passes with warnings present, which is intended: treat them as review material rather than a broken build.

The current advisories cover a model flagged as supporting reasoning or image input where the platform knows that model does not, a project publishing several knowledge graphs without naming a default, two entrypoint agents sharing an `entrypoint_order`, an agent carrying entrypoint-only fields that a delegate call ignores, a context budget whose truncation target sits above the real model window, and legacy document-source tuning fields set under the `split` engine.

Only `genai lint` runs these checks. `genai deploy` runs the deploy-blocking checks and stops there, so a deploy never surfaces an advisory. That is another reason to lint while you author rather than relying on the deploy to tell you everything.

Lint rather than deploy while you author. Almost every authoring mistake is structural, and lint catches those locally: a duplicate identifier, an `extends` cycle, a pin that contradicts a constraint, a delegate with no description, a missing `inference.model`. Because it needs no cluster and no credentials, it runs on a laptop offline and fits a pull-request check that has no deploy secrets. A plain `genai deploy` is not a test but a replacement, since it switches the live configuration of the Squirro project. `genai deploy --dry-run` is the safe way to test against a cluster, and it is the right gate before shipping, but it still needs a reachable cluster, a resolved project, and a valid token, so it is a slower loop to author against. Deploy also runs the offline checks first and stops before contacting the cluster if any fail, so it finds the same structural problems as lint, only later.

## genai deploy

```text
genai deploy [ROOT] [--dry-run] [--label KEY=VALUE]...
```

Publishes the agent configuration project as a full configuration snapshot. It first runs the offline checks, and if they fail it exits without contacting the cluster. On clean checks it posts the files to the cluster and prints the deploy receipt.

```text
genai deploy --label git.hash=abc1234
deployed: sha256:4a3f...
at:       2026-05-14T11:32:00+00:00
labels:   git.hash=abc1234
```

`--dry-run` runs every check the server would run, including the cluster-bound checks, without writing the snapshot. It prints `dry-run OK` instead of a receipt, and the previous snapshot is untouched.

`--label KEY=VALUE` attaches an arbitrary string label to the deploy receipt. It is repeatable. Empty keys are rejected, and a duplicate key takes the last value.

When the content you deploy is identical to what is already live, `genai deploy` still contacts the cluster and prints the usual receipt, followed by a hint that nothing changed. Running the deploy above a second time, with no edits in between:

```text
genai deploy --label git.hash=abc1234
deployed: sha256:4a3f...
at:       2026-05-14T11:41:07+00:00
labels:   git.hash=abc1234
Hint: no change — content is identical to the current deployment.
```

The receipt still records a fresh timestamp and your labels, because the deploy did happen. Only the content hash is unchanged, which is what the hint is comparing.

## Roll Back a Deployment

There is no rollback command, and the cluster does not let you redeploy a past snapshot: `genai log` returns each past deploy receipt, not its resources, and `genai export` only writes the current snapshot. Because every deploy is a full-snapshot replacement, you roll back by redeploying a previous version of your configuration files.

Keep your agent configuration project in version control so a previous configuration is always recoverable. To revert, check out the last known-good version and deploy it again:

```text
git checkout <prior-commit>
genai deploy --dry-run
genai deploy --label reason=rollback --label git.hash=$(git rev-parse HEAD)
```

The `--dry-run` deploy is optional: it runs the full set of cluster checks against the version you are about to restore without writing it, so you can confirm the rollback passes before it replaces the live snapshot.

Attaching a `git.hash` label to every deploy lets you match a deploy receipt from `genai log` back to the commit that produced it, so the previous good version is easy to find.

The two hashes in a receipt are unrelated. The `hash` field is computed by the server over the deployed content, and identical content always produces the same value. A `git.hash` label is a string you supply, and the key name is only a convention used in these examples: the platform stores labels without interpreting them, so `commit` or `build` would work just as well. The commit is recoverable from a receipt only because you chose to record it there.

## genai reseed

```text
genai reseed [ROOT] [--force]
```

Returns a Squirro project to the starter configuration the platform ships with it, rebuilding the snapshot from that starter and deploying it. Use it to discard experiments and start over from a known baseline, or to pick up an updated starter after a platform upgrade. It prints the same fields as `genai status`:

```text
genai reseed
project:  Xy7QmN2pR9sT4vWz1AbC3D
cluster:  https://squirro-demo.example.com
deployed: 2026-05-14T14:08:52+00:00
by:       alice@example.com
hash:     sha256:7b02...
```

That starter configuration is called the **bundled workspace**. The platform ships it with the project, and it is separate from the agent configuration project you author. Not every project has one, and `genai reseed` works only for a project that does.

Whether a reseed is allowed depends on how the project got its current configuration:

| Current state | What `genai reseed` does |
| --- | --- |
| Never deployed | Seeds the project from its bundled workspace. No override needed. The platform also does this on its own the first time the project is used, so a project works before anyone authors a configuration for it. |
| Deployed by an operator | Refuses, so a reseed cannot silently discard authored work. Pass `--force` to override. |

The first `genai deploy` moves a project from the first state to the second, and the platform never reverts it to the bundled workspace on its own after that: your deployed configuration is authoritative.

> **Warning**
>
> Passing `--force` replaces the current operator-deployed configuration with the bundled workspace. The replaced configuration is not recoverable from the server. Restore it from version control if needed, as described in the [Roll Back a Deployment](#neo-agent-config-rollback) section.

## genai tree

```text
genai tree [ROOT]
```

Prints the expanded resource tree: every envelope grouped by kind, after symlinks are followed and multi-document files are split. The same files always produce the same output, so you can commit a copy of it and have continuous integration compare later runs against that copy, which surfaces a resource added or removed by accident.

```text
genai tree
Project (1):
  project.yaml
Model (3):
  models/claude-sonnet.yaml
  models/claude-haiku.yaml
  models/gpt-5.yaml
Agent (2):
  agents/assistant.md
  agents/deep_research.md
```

## genai status

```text
genai status [ROOT]
```

Prints the deploy metadata for the current snapshot of the configured project: when it deployed, who deployed it, the content hash, and any labels attached.

```text
project:  Xy7QmN2pR9sT4vWz1AbC3D
cluster:  https://squirro-demo.example.com
deployed: 2026-05-14T11:32:00+00:00
by:       alice@example.com
hash:     sha256:4a3f...
labels:   git.hash=abc1234  ci.build=42
```

## genai log

```text
genai log [ROOT] [--since ISO] [--until ISO] [--actor IDENTITY]
```

Prints the deploy history for the configured project, most recent first. The flags filter on the server side, and all are optional and combinable.

```text
2026-05-14T11:32:00+00:00  alice@example.com  sha256:4a3f...  git.hash=abc1234
2026-05-13T09:15:44+00:00  bob@example.com    sha256:9c12...  git.hash=def5678
```

## genai explain

```text
genai explain PATH --agent AGENT_ID [--user-prefs FILE] [--request-prefs FILE]
```

Prints how a setting resolves for an agent: the winning value, the source, and one entry for each source the resolver examined, along with its state. The states are `selected`, `shadowed`, and `invalid`.

> **Warning**
>
> Like the other server-bound commands, `genai explain` sends a request to the cluster it resolves, in this case to a resolve route. That route is a development-time facility that a deployed Squirro instance does not serve, so during Technical Preview the command works only against a local run of the configuration library’s development application. Pointed at a Squirro instance it fails with a `404`. To reason about resolution in the meantime, read the `prefers`, `constrains`, and `pins` blocks of your `Project` resource and of the agent. For the route, see the [API Reference](api-reference.md#neo-agent-config-api) page.

```text
genai explain inference.model --agent deep_research --user-prefs prefs.yaml
path:     inference.model
value:    claude-sonnet
source:   agent.prefers
preferences:
  - slot: user             value: claude-haiku   state: invalid
  - slot: agent.prefers    value: claude-sonnet  state: selected
  - slot: project.prefers  value: claude-opus    state: shadowed
```

Read the entries as the resolver walked them, from the request down to the project: `request`, `user`, `agent.prefers`, `project.prefers`. Here the user asked for a model the agent does not allow, so that entry is `invalid` and the walk continued. The agent value was allowed, so it is `selected`. The project value never came into play, so it is `shadowed`.

`--user-prefs` and `--request-prefs` accept a file containing a flat map of preference paths to values. Both are optional, and they let you try out a value a user or a single request would contribute without deploying anything. A file named `.json` is read as JSON, and any other name is read as YAML.

A `prefs.yaml` holding three user preferences:

```text
inference.model: claude-haiku
trait.style: concise
source.documents.limit: 50
```

The same three as `prefs.json`:

```text
{
  "inference.model": "claude-haiku",
  "trait.style": "concise",
  "source.documents.limit": 50
}
```

Passing both files adds a `request` and a `user` entry to the receipt. Here the request asks for a model the agent allows, so it wins outright, and every lower slot is either shadowed or, in the case of the user value the agent does not allow, invalid:

```text
genai explain inference.model --agent deep_research \
    --user-prefs prefs.yaml --request-prefs request-prefs.json
path:     inference.model
value:    claude-sonnet
source:   request
preferences:
  - slot: request          value: claude-sonnet  state: selected
  - slot: user             value: claude-haiku   state: invalid
  - slot: agent.prefers    value: claude-sonnet  state: shadowed
  - slot: project.prefers  value: claude-opus    state: shadowed
```

Each key is a whole path written as one string, dots included, exactly as it appears on the [Preference Reference](preference-reference.md#neo-agent-config-preference-reference) page. Do not nest the file the way YAML would let you:

```text
# Wrong: nothing here is contributed
inference:
  model: claude-haiku
```

That file has a single key named `inference`, whose value happens to be a map. The resolver looks for a key named `inference.model` and finds none, so the file contributes nothing. Nothing is reported either, because the file is still a valid mapping. The symptom is a result that looks normal with your value missing from it: no `user` or `request` entry appears in the receipt.

## genai export

```text
genai export [ROOT] [-y / --yes]
```

Writes the currently deployed snapshot of the project to disk, one file per resource, in the layout the tool always uses: the three single-instance kinds as `project.yaml`, `document-source-settings.yaml`, and `code-interpreter.yaml` at the root, and the rest grouped into `models/`, `agents/`, `mcp/`, `knowledge-graphs/`, and `trait-dimensions/`.

The result is what the platform runs, not the files you wrote. Exporting a project you authored yourself gives you back your resources, but your original directory names, your ordering inside each file, and your comments are gone, because the deployed configuration does not carry them. Treat `genai export` as a way to read a deployed configuration, not as a round trip with `genai deploy`.

If the root already contains authored files, `genai export` prompts before overwriting. `-y` or `--yes` skips the prompt.

> **Note**
>
> `genai export` always exports the current snapshot. There is no command to export a past snapshot. Use `genai log` to inspect a past deploy’s receipt, which carries its content hash, actor, timestamp, and labels, but not its resources.

## genai task

Unlike the commands above, `genai task` is a group of subcommands, one per subsection below. A task is an instruction owned by the user who created it, plus a schedule that runs it against the configured project. Run `genai task --help` to see the whole group at once.

For the concepts behind tasks and the `--hitl` approval model, with worked examples, see the [Scheduled Tasks and Approvals](tasks-and-approvals.md#neo-agent-config-tasks) page.

### genai task create

```text
genai task create --name NAME --instruction TEXT
                  --recurrence {daily|weekly|monthly|annually} --time HH:MM --timezone ZONE
                  [--days-of-week 0,1,...] [--day-of-month N] [--month-of-year N]
                  [--enabled/--disabled] [--auto-fire/--no-auto-fire]
                  [--email-notifications] [--hitl PATTERN=DISPOSITION]...
```

Defines a task. `--name` and `--instruction` say what it is and what it does, and the recurrence flags say when it runs. On success it prints the new task in the same shape as `genai task get`, so you get its identifier back straight away.

`--time` is a time of day, written as `HH:MM` or `HH:MM:SS`, and it is read in the `--timezone`, which takes an IANA name such as `Europe/Zurich`. Because the time is local, a task keeps firing at the same clock time across a daylight-saving change. On the day the clocks go back, when the time occurs twice, the task fires at the first of the two. On the day they go forward, when the time does not exist at all, it fires as soon as the clock passes it.

The recurrence decides which further flags you must supply:

| `--recurrence` | Extra flags it needs |
| --- | --- |
| `daily` | None. |
| `weekly` | `--days-of-week`, comma-separated, where `0` is Sunday and `6` is Saturday. |
| `monthly` | `--day-of-month`, from 1 to 28. |
| `annually` | `--day-of-month` and `--month-of-year`. |

The same four schedules as commands, with the instruction left out for brevity:

```text
# Every day at 02:00
genai task create --name "Nightly digest" --instruction "..." \
    --recurrence daily --time 02:00 --timezone Europe/Zurich

# Every Monday and Thursday at 08:30
genai task create --name "Twice-weekly report" --instruction "..." \
    --recurrence weekly --days-of-week 1,4 --time 08:30 --timezone Europe/Zurich

# The first of every month at 06:00
genai task create --name "Monthly rollup" --instruction "..." \
    --recurrence monthly --day-of-month 1 --time 06:00 --timezone Europe/Zurich

# Every 31 January at 09:00
genai task create --name "Year-end review" --instruction "..." \
    --recurrence annually --day-of-month 31 --month-of-year 1 --time 09:00 --timezone Europe/Zurich
```

A task has two switches, and it fires on its schedule only when both are on. `--enabled` and `--disabled` are the master switch, so `--disabled` creates a task paused. `--auto-fire` and `--no-auto-fire` control the schedule alone.

`--email-notifications` emails the owner when a run completes. It is off unless you ask for it.

`--hitl PATTERN=DISPOSITION` is a repeatable rule matching tool names against a pattern. The last match wins, and a tool that needs approval but matches no rule is refused. The dispositions are `approve`, `reject`, `interactive`, `defer`, and `hide`.

### genai task list

```text
genai task list [--limit N] [--offset N] [--sort FIELD] [--order asc|desc]
```

Lists the tasks you own in the configured project. Tasks belong to whoever created them, so this never shows another person’s tasks. Each row carries the task identifier, whether the task is enabled, its schedule, and its name:

```text
genai task list
9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab  on   daily at 02:00 Europe/Zurich                Nightly digest
4b81d05c-93af-4e11-8c7a-2f6be0d1a934  on   weekly at 08:30 Europe/Zurich days=[1, 4]   Twice-weekly report  (auto-fire off)
c07e2f18-6a54-4d90-ba33-91f7c4e5b206  off  monthly at 06:00 Europe/Zurich dom=1        Monthly rollup
(3 total)
```

The identifier in the first column is the `TASK_ID` every other subcommand takes. A row ends with `(auto-fire off)` when the task itself is enabled but its schedule is not, so it runs only when you fire it with `genai task run`. When you own no tasks in the project, the command prints `(no tasks)`.

`--limit` defaults to 50 and the server accepts at most 100. `--offset` skips that many rows, so `--limit 50 --offset 50` gives you the second page.

`--order` defaults to `desc`, and `--sort` defaults to `created_at`. `--sort` accepts `created_at`, `updated_at`, `name`, `next_execution_at`, or `last_execution_at`, and any other field is rejected.

### genai task get

```text
genai task get TASK_ID
```

Shows one task in full:

```text
genai task get 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
id:            9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
name:          Nightly digest
instruction:   Summarize the documents added today and email the summary to the team.
trigger:       daily at 02:00 Europe/Zurich
enabled:       True
auto-fire:     True
hitl:
  - delete_* → reject
  - send_email → defer
next run:      2026-05-15T02:00:00Z
last run:      2026-05-14T02:00:04Z succeeded
conversations: 12
created:       2026-05-01T09:14:22Z by alice@example.com
```

`enabled` and `auto-fire` are the two switches, so a task fires on its schedule only when both read `True`. The `hitl` block lists the approval rules in the order they are matched, and it is omitted entirely for a task that has none. `next run` reads `(none)` when nothing is scheduled, which is the case for a task with either switch off, and `last run` reads `(none)` until the task has run once.

### genai task update

```text
genai task update TASK_ID [--name NAME] [--instruction TEXT]
                  [--recurrence {daily|weekly|monthly|annually}] [--time HH:MM] [--timezone ZONE]
                  [--days-of-week 0,1,...] [--day-of-month N] [--month-of-year N]
                  [--enabled/--disabled] [--auto-fire/--no-auto-fire]
                  [--email-notifications/--no-email-notifications] [--hitl PATTERN=DISPOSITION]...
```

Changes a task, as a partial patch: send only the flags you want to change. Two things are replaced as a whole rather than patched, the schedule and the approval rules, so passing any `--hitl` rule discards the rules the task had.

That distinction decides how you make two common changes:

- **Pause or resume**

  Send `--enabled` or `--disabled` on its own. The schedule is untouched.
- **Toggle auto-firing**

  Send `--auto-fire` or `--no-auto-fire` together with `--recurrence`, `--time`, and `--timezone`, because those flags rebuild the schedule the switch belongs to.

It prints the updated task, in the same shape as `genai task get`. Pausing the nightly digest, for example:

```text
genai task update 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab --disabled
id:            9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
name:          Nightly digest
instruction:   Summarize the documents added today and email the summary to the team.
trigger:       daily at 02:00 Europe/Zurich
enabled:       False
auto-fire:     True
hitl:
  - delete_* → reject
  - send_email → defer
next run:      (none)
last run:      2026-05-14T02:00:04Z succeeded
conversations: 12
created:       2026-05-01T09:14:22Z by alice@example.com
```

`enabled` has flipped to `False` and `next run` has become `(none)`, because a paused task has nothing scheduled. Everything else is untouched, which is what a partial patch means. Passing no field at all is an error rather than a silent no-op.

### genai task delete

```text
genai task delete TASK_ID
```

Removes the task definition, so it never fires again, and confirms with a single line:

```text
genai task delete 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
deleted task 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
```

The task itself is gone, but its run history and the conversations its runs produced survive, so the work a task did outlives the task.

### genai task run

```text
genai task run TASK_ID
```

Fires the task immediately, as a manual run. This works whatever the two switches are set to, which is how you test a task before letting its schedule start it.

It prints the run it started:

```text
genai task run 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
run:           35
task:          9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
status:        running
trigger:       manual
conversation:  conv_44b9d1
started:       2026-05-14T16:22:09Z
```

The `completed` and `error` lines appear once the run has finished, and `conversation` reads `(none)` if the run failed before one existed.

### genai task runs

```text
genai task runs TASK_ID [--limit N] [--offset N]
```

Lists a task’s runs, most recent first. Each row carries the run number, its status, what triggered it, when it started, and the conversation it produced:

```text
genai task runs 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
34  succeeded         scheduled   2026-05-14T02:00:04Z  conv_8f21ac
33  failed            scheduled   2026-05-13T02:00:03Z  conv_5d09b7
32  running           manual      2026-05-12T16:41:55Z  conv_1a77e2
31  succeeded         scheduled   2026-05-12T02:00:06Z  conv_c3be40
```

The trigger is `scheduled` when the schedule fired the run and `manual` when you did, with `genai task run` or `genai task retry`. A run still in progress shows as `running`, and a run whose conversation ended without finishing shows as `incomplete`. The last column is empty for a run that failed before its conversation started.

The listing ends with a `(N total)` line, and a task that has never run prints `(no runs)`. The number in the first column is the `RUN_ID` that `genai task retry` takes.

`--limit` defaults to 50 and the server accepts at most 100. `--offset` skips that many runs, so `--limit 50 --offset 50` gives you the second page of the history.

### genai task retry

```text
genai task retry TASK_ID RUN_ID [--conversation-id CID]
```

Re-runs a past run as a new run. `RUN_ID` is the number in the first column of `genai task runs`, not a task identifier. Only a run that failed or ended without finishing can be retried, and any other run is rejected.

Retrying run 33:

```text
genai task retry 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab 33
run:           36
task:          9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
status:        running
trigger:       manual
conversation:  conv_9e02fa
started:       2026-05-14T16:35:41Z
```

The retry is a new run with its own number, and run 33 stays in the history as it was, so `genai task runs` shows both. Its trigger is `manual` even though the run it replaces was `scheduled`, because you started it. By default it works in a fresh conversation, as here; pass `--conversation-id` to continue an existing one instead.

## genai login

```text
genai login [ROOT] [--cluster URL] [--api-key TOKEN]
```

Authenticates you to a cluster and caches the credential, so later commands do not ask again. `genai init` does this as part of setup, which leaves `genai login` for re-authenticating after a token expires, or for adding a second cluster.

With `--api-key`, or with `GENAI_API_KEY` set, it uses the refresh token you supply. Without either, it points you at the settings page of the cluster, tries to open it in your browser, and waits for you to paste a token:

```text
genai login --cluster https://squirro-demo.example.com
Not logged in to https://squirro-demo.example.com — let's authenticate.
Mint a refresh token (API key) on the Squirro cluster:
  https://squirro-demo.example.com/app/#settings

Paste refresh token:
logged in to https://squirro-demo.example.com as alice@example.com
```

The token is exchanged once to confirm it works and to read your identity from it, and only then cached. For how to mint one, see the [Authentication](../../../api/authentication.md#api-authentication) page.

## genai logout

```text
genai logout [ROOT] [--cluster URL]
```

Forgets the cached credential for one cluster, and says so:

```text
genai logout --cluster https://squirro-demo.example.com
logged out of https://squirro-demo.example.com
```

Only the local cache is cleared. The refresh token itself stays valid on the cluster until you revoke it there, so logging out is not a way to withdraw access.

## genai whoami

```text
genai whoami [ROOT] [--cluster URL]
```

Prints the identity cached for the cluster:

```text
genai whoami
alice@example.com
```

Without `--cluster`, the cluster resolves as it does everywhere else: `GENAI_CLUSTER` if it is set, otherwise the local target configuration that `genai init` wrote. So running the command inside an agent configuration project tells you who you are on that project’s cluster, with no flag needed. If none of the three yields a cluster, the command asks for one on a terminal, and otherwise exits with an error naming what to supply.

With no cached credential for that cluster it prints `not logged in` and exits with an error, which makes it usable as a check in a script before a command that needs credentials.
