CLI reference
simian is a single binary with cobra subcommands. This page is generated from simian <cmd> --help output. There is a second binary, simian-eval, covered at the end: it drives a scenario pack against a subject and is deliberately not part of the operator binary.
To get the most up-to-date reference for any single command, run it with --help:
simian --help
simian serve --help
simian chaos --help
simian sut deploy --help
Subcommand index
| Subcommand | Purpose |
|---|---|
simian arena | Manage chaos arena namespaces (create/destroy/describe). The arena is the namespace+RBAC unit of isolation for chaos. |
simian sut | Manage Systems Under Test (deploy/destroy/list). Built-in SUT: Online Boutique. |
simian serve | Run the controller: Fault Executor + MCP server + autonomous loop. |
simian web | Serve the web UI alone, with no controller behind it, for watching several Simians from one page. No Kubernetes, no LLM. |
simian chaos | Submit a fault either as plain-text intent (LLM-translated) or as a hand-built FaultManifest (deterministic-control). Also list/clear active faults. |
simian plan | Generate an AttackPlan against a real arena and emit it as JSON. Default --dry-run=true does not apply. |
simian audit export | Fold the audit trail into one row per fault: what was asked for, what ran on what, and how it ended — or, with --cycles, one row per autonomous cycle: what it decided and why. Reads --audit-file output or kubectl logs. |
simian watch | Live terminal view of an arena: active faults, what autonomous mode decided (latest hypothesis and plan, each recent cycle’s outcome or skip reason), and recent faults. |
simian evaluate | Score a finished run offline from its audit log and the subject’s report. Contacts no cluster. |
Common flag patterns
Eligibility
--eligible-namespace <ns> (repeatable, simian serve) overrides the default annotation-based lookup. Without it, the controller treats any namespace with simian.chaos/eligible="true" as eligible.
LLM provider
--llm-provider gemini|stub (default gemini); --llm-model <id> overrides the default gemini-2.5-pro. Vertex/ADC and API-key auth are both supported (Vertex preferred for production via Workload Identity).
Executor safety policy
Set on simian serve:
| Flag | Default | Notes |
|---|---|---|
--duration-ceiling | 15m | Hard cap per fault. The autonomous planner is told it as max_fault_duration, and a plan that exceeds it goes back to the model for correction before anything is applied. |
--max-concurrent-faults | 0 (no cap) | Total leased faults across namespaces. Rejected applies surface as executor.rejected with reason safety:budget-exceeded. |
--min-cooldown | 0 | Per-namespace cooldown between consecutive faults. An apply already in flight against a namespace counts as consecutive. |
--permitted-tiers | namespace,node | Blast-radius tiers this installation permits (namespace|node|external). Repeatable or comma-separated. Unset keeps the default; pass just namespace to keep node-level chaos off the cluster entirely. An unrecognised name stops the controller starting rather than falling back — see Helm values. |
--default-efficacy-probes | true | Attach Simian’s own efficacy gate to every fault kind that has one. Set false to accept faults that report success without proving they landed. |
Autonomous mode
Set on simian serve together:
| Flag | Default | Notes |
|---|---|---|
--autonomous | false | Enable the planning loop. |
--autonomous-namespace | (required when --autonomous) | Repeatable. Arena namespace(s) the loop targets. |
--cycle-interval | 5m | Time between cycles. |
--max-faults-per-cycle | 3 | Cap on faults applied per cycle. |
--max-severity-per-cycle | namespace | Highest blast tier the loop will apply (namespace|node|external). Validated at startup: an unparseable cap would make the loop skip every step, which is indistinguishable from a planner producing nothing. |
--hypothesis-hint | empty | Soft preference passed to the LLM each cycle. Useful for biasing toward specific engines. |
Web UI
Set on simian serve:
| Flag | Default | Notes |
|---|---|---|
--ui | true | Serve the web UI at /ui/, its JSON API and event stream under /api/, and /healthz, on --ui-addr. |
--ui-addr | :8082 | The UI’s listen address. |
--ui-auth | none | none: read-only, for a port-forward. iap: behind Identity-Aware Proxy, checking IAP’s assertion on every request. |
--ui-iap-audience | empty | With --ui-auth=iap: the backend service’s audience, /projects/NUMBER/global/backendServices/ID. |
--ui-iap-project-number | empty | With --ui-auth=iap and no audience: accept any backend service in this project. |
--ui-writers | empty | With --ui-auth=iap: who may inject and clear faults — emails or domain:example.com. Repeatable. |
--ui-admins | empty | With --ui-auth=iap: who may also configure, pause and resume autonomous mode, clear all faults and halt. Repeatable. |
--ui-name | empty | How this controller is labelled in a page that watches several Simians; empty shows the host. |
--ui-allowed-origins | empty | Other pages that may call this controller from the browser with the user’s credentials — another Simian’s UI or the standalone UI — e.g. https://simian.example.com. Repeatable. Empty: same origin only. |
--ui-controllers | empty | Other Simians this controller’s page offers, as name=https://origin of each controller’s UI (or just the URL; the name is then its host). Repeatable. Each must list this page’s origin in its --ui-allowed-origins. A malformed entry stops the controller starting. |
Standalone web UI: simian web
The same page with no controller behind it, for one page that fronts several
Simians. It serves /ui/, /healthz and GET /api/controllers; every
other /api/ path is 404, so the page asks which Simian to watch.
simian web --addr :8080 \
--controllers simian-1=https://simian.example.com \
--controllers simian-2=https://simian-2.example.com
| Flag | Default | Notes |
|---|---|---|
--addr | :8080 | Listen address. |
--controllers | empty | Simians the page offers, as name=https://origin of each controller’s UI. Repeatable. Each must list this page’s origin in its --ui-allowed-origins (chart ui.allowedOrigins). |
--scorecards | empty | A directory of scorecard documents (simian-eval report output) to serve read-only at /scorecards/: GET /scorecards/ lists the *.json files directly in it, GET /scorecards/<name> returns one. Nothing else in or outside the directory is served. The page offers them in its Scorecard mode. |
Envoy SUT injection
| Flag | Where | Default | Notes |
|---|---|---|---|
--no-envoy-faults | simian sut deploy | true (skip) | Inverted flag. Set --no-envoy-faults=false to opt INTO injection. Default off because of the gRPC-probe limitation. |
--sut-inject-envoy-faults | simian serve | false | Controller-side policy. Set to true to inject Envoy when SUTs are applied via the establish_baseline MCP tool. |
See Known limitations for why these default off.
Submitting a fault
simian chaos accepts three input shapes:
# 1. LLM-translated path
simian chaos --intent "kill one paymentservice pod for 30 seconds" --namespace boutique-1
# 2. Deterministic-control path with engine + kind + spec
simian chaos --engine chaos-mesh --kind PodChaos \
--api-version chaos-mesh.org/v1alpha1 \
--namespace boutique-1 --workload paymentservice \
--duration 30s \
--spec '{"action":"pod-kill","mode":"one","selector":{"labelSelectors":{"app":"paymentservice"}}}'
# 3. Submit a fully-formed manifest
simian chaos --manifest examples/network-latency-manifest.json
Plus the inspection / management subcommands:
simian chaos --list-active # all leased faults
simian chaos --list-catalog # catalog the LLM sees (all engines)
simian chaos --clear f-<UID> # clear before lease expiry
--spec, --spec-file, and --stdin-spec are mutually exclusive — set at most one. The CLI rejects overlapping inputs upfront rather than silently picking one.
Scoring a run
--pack takes a built-in pack name (parity, lookout) or a directory of
scenario files.
simian evaluate --pack parity --audit run.log --report agent.json
simian evaluate --pack ./packs/custom --audit - --report agent.json --format json
Two artifacts, joined on the scenario ID the audit sink stamps onto every event: the audit log says which faults landed and when, the report says what the subject found and when. Nothing is executed and no kubeconfig is read, so the same artifacts produce the same scorecard on any machine, any time later.
A scenario whose fault has no passing efficacy record prints as NOT SCORED
rather than as a miss — the cluster was never broken, so a zero would mean
“nothing to find” while reading as “the agent missed it”. Below
--min-efficacy (default 0.8) the scorecard is still printed, then the
command exits non-zero: the numbers measure the harness, not the subject. Pass
--min-efficacy 0 to report anyway; the warning stays either way.
Exporting the audit trail
simian audit export --since 24h audit.jsonl.1 audit.jsonl
kubectl -n simian-system logs deploy/simian-controller | simian audit export --format json
kubectl -n simian-system exec deploy/simian-controller -- simian audit export /var/lib/simian/audit.jsonl
The controller image is distroless (no shell, no cat), so read the audit file
in the pod by running simian audit export there, as in the last line.
One row per fault UID, folded from its events: source, kind, targets, the spec
as it reached the cluster (after the safety stage narrowed it), whether the
engine confirmed injection and the efficacy verdict, how it ended —
refused, driver-failed, cleared, expired, or open if nothing ended it —
and, for a Chaos Mesh fault that ran to its deadline, whether its target pods
were Ready again within five minutes (RECOVERED; the JSON adds the pods still
down and why). A fault can end on time and leave its workload broken; no
there is the place to look first.
--since takes a duration back from now or an RFC 3339 time and keeps faults
first seen after it. --format json writes one object per line with the full
spec; the table truncates it. Lines that are not audit events are skipped, so a
controller’s whole log can be piped in.
--cycles folds the same records by autonomous-mode cycle instead: one row per
cycle and namespace with its outcome (completed, skipped, unfinished when
the controller restarted mid-cycle), the skip reason and what the gate, the LLM
or the validator said (health-gate: workload Deployment/adservice: 0/1 pods ready), the hypothesis, the plan’s steps, and how many steps were applied and
refused. --format json adds each step’s rationale and duration rationale.
This is the answer to “what is autonomous mode doing, and why” without reading
the controller log.
simian watch --namespace <arena> shows the same live: the latest plan in full
and a line per recent cycle above the active and recent faults. It reads the
controller’s get_recent_cycles MCP tool, which any MCP client can call too;
the controller rebuilds recent cycles from its audit file at start-up.
simian serve --audit-file PATH writes the file (--audit-file-max-bytes,
default 100 MiB, sets the rotation point). At start-up it adopts the faults a
previous process left running and closes the ones it left open; the chart turns it on by default — see
Helm values.
Tearing down
simian sut destroy --namespace boutique-1 # SUT only
simian sut destroy --namespace boutique-1 --with-arena # both layers
destroy --with-arena refuses if simian-managed faults are still leased; pass --force to override (after clearing them with simian chaos --clear).
simian-eval — running a pack against a subject
A second binary. Cluster lifecycle, subject processes and scoring have no business linking into the operator binary that runs in-cluster with chaos RBAC.
simian-eval --pack parity --subject exec:./bin/lookout --out runs/
simian-eval --pack parity,lookout --subject exec:./bin/agent --concurrency 4
simian-eval --pack ./packs/custom --subject noop: --cluster kind --out runs/floor
simian-eval --pack parity --subject exec:./bin/agent --only parity-crash-loop
Per scenario: provision an arena namespace, inject the faults through the
normal executor path — same validation, same safety stages, same leases, same
efficacy gates — hand the subject the prompt, collect its report, then clear the
chaos and put the namespace back. Two files land in --out: audit.log is
Simian’s side, run.json is the subject’s, and they join on the scenario ID.
A subject that keeps its own evidence leaves it there too.
The scorecard printed at the end comes from reading those two files back, so
simian evaluate reproduces it exactly, with or without the cluster.
| Flag | Default | Notes |
|---|---|---|
--pack | (required) | A built-in pack name — parity or lookout, both embedded in the binary — or a directory. Repeatable or comma-separated; several packs run as one suite. A bare built-in name always means the embedded pack, so spell a local directory of the same name ./parity. Two packs sharing a scenario ID is a load error, not a merge. |
--subject | (required) | exec:<command line>, lookout:<path to the lookout binary>, sre-agent:<path to the sre-agent binary>, or noop: for the zero-score floor. The lookout: adapter runs a namespace-scoped health scan and translates the finding stream; sre-agent: runs one assessment and reads the report out of the transcript it names. Both take the namespace from the prompt, as an agent does. |
--only | (all) | Scenario IDs to run. An ID that is not in the pack is an error, not an empty run. |
--out | runs/<timestamp> | Where audit.log and run.json go, and where a subject that keeps its own evidence leaves it — an sre-agent: subject writes one transcript-<namespace>.json per scenario. |
--cluster | current | current uses the kubeconfig’s cluster and leaves it standing; kind provisions a throwaway cluster for the run and deletes it afterwards, including on Ctrl-C. |
--concurrency | 1 | Ceiling, not a target: scenarios sharing a namespace are serialised regardless, and a control takes the cluster to itself. |
--subject-timeout | 10m | How long one investigation may take before the subject is killed and scored as a failure. |
--subject-dir, --subject-env | — | Working directory and extra KEY=VALUE environment for a subprocess subject. An sre-agent: subject takes its provider credentials this way. |
--remediation-poll | 5s | How often to ask whether the fault is gone while the subject works, for time-to-remediate. 0 disables the watch. |
--eligible-namespace | (annotation) | Fence the run to a fixed namespace list instead of reading simian.chaos/eligible. Reach for this when the cluster has other tenants. |
--terminating-wait | 2m | How long to wait for a namespace left over from an earlier run to finish deleting. Namespace deletion is asynchronous, so without this, running the same pack twice in a row fails on the first run’s teardown. Expiry is an InjectError, not a hang — a namespace held by a finalizer is not coming back. |
--teardown-timeout | 2m | Bound on cleanup, which runs even after Ctrl-C. Most of it goes on waiting for the chaos controller’s finalizers: the arena refuses to be destroyed while chaos is still leased in it, so a teardown attempted before the finalizer finishes is refused and the namespace is left standing. Raise it for faults that are slow to undo — a NetworkChaos partition and a DNSChaos are the two that have needed it. |
--keep-arenas | false | Leave arena namespaces standing afterwards, for poking at a scenario that went wrong. Faults are still cleared. |
--allow-short-faults | false | Permit faults shorter than --subject-timeout. Off by default: the lease expires mid-investigation, the reaper clears it, and that disappearance is recorded as the subject having remediated it. |
--score | true | Off writes the artifacts and stops. |
--format, --min-efficacy | text, 0.8 | As simian evaluate. |
Namespaces the run creates are annotated with simian.chaos/eval-run=<run id>
and destroyed at the end. A namespace that already existed is annotated and
left standing — a rig that deletes namespaces it merely found is one bad
scenario file away from deleting something that mattered.
Each scenario’s eval.scenario_started audit line records the pack, the
subject and the cluster (the kubeconfig context, or the kind cluster’s name),
so a run directory says what it measured and where.
simian-eval report — combining runs into a scorecard
simian-eval report --run runs/dataplane-lookout --run runs/dataplane-agent --out scorecard.json
Reads run directories, scores each exactly as its own scorecard did, and writes one self-describing JSON document — scenarios by subjects, with each scenario’s fault, ground truth and every subject’s report — that the web UI’s Scorecard mode renders with no server. See Reading an evaluation: the scorecard.
| Flag | Default | Notes |
|---|---|---|
--run | (required) | A run directory holding audit.log and run.json. Repeatable; each is a column. |
--out | stdout | File to write the scorecard to. |
--pack | (as recorded) | Packs to score against, by built-in name or directory, matched to each run by the pack name its audit log records. |
--floor | lookout | Subjects that are the floor (the deterministic detector). Default: subjects named lookout or k8s-lookout, with or without a version suffix. --floor "" for none. |
--min-efficacy | 0.8 | Efficacy rate below which a run is flagged as measuring the harness, not the subject. |
An arena that could not be destroyed is a suite-level failure: simian-eval
prints the whole scorecard, then exits non-zero naming the scenarios and the
namespaces. The scores themselves are untouched, because a namespace that would
not go away says nothing about what the subject answered — but what is left
behind carries simian.chaos/eligible, which is a standing permission to
inject there, so it is not something to find out about from the next run. The
fact is written to the audit log as eval.arena_leaked, so simian evaluate
re-scoring the artifacts later reports it too.