Skip to main content
levers9 --help prints the command list; levers9 COMMAND --help prints its options. Every command takes -c, --context NAME to target a context other than default; a context is one environment’s token, so -c staging deploys into staging.

Install and connect

Run and deploy code

Deploy options worth knowing: --replicas N (fixed replica count), --rollout auto|blue-green|replace for always-on services, --always-on / --scale-to-zero, --tcp for raw TCP through a box, --json for machine-readable output. Model endpoints take --llm plus --llm-model-id, --llm-engine vllm|sglang|tensorrt-llm, --llm-context-length and --llm-served-model-name so the controlplane can route and meter them as OpenAI-compatible services.

Watch what is running

Files: volumes

Volumes are persistent folders mounted into containers with Volume(name=..., mount_path=...). Paths are VOLUME/path/inside.
cp uses multipart transfers through the cluster’s object store when the controlplane supports them; remote paths are then written levers9://VOLUME/path. Older controlplanes only stream uploads through the controlplane (levers9 cp ./data myvol/data, no downloads). If a multipart transfer fails with a DNS error for an in-cluster host, pass --no-multipart to stream through the controlplane instead.

Secrets

Datasets

The Data Lab from a terminal. These talk to the dashboard API with the same workspace token, so the CLI and the Datasets page always show the same datasets.
inspect prints the --map flags it suggests, so the usual flow is inspect, check the sample, then paste them into create. create and publish follow the run and print its stages; --no-wait returns immediately, and Ctrl+C stops watching without stopping the run. --map takes two more roles for a dataset of any kind, known_phone and known_name: the row’s own phone number and the customer’s name. They’re handed to the personal-data scan so it can find them in the text, and they’re never stored in the rows and never exported (Known values). Give one twice when it sits in two columns:
pii scan runs the personal-data scan and prints its report: rows, rows with findings, findings by type and by how each was found, and what the scan learned from the dataset as a whole (names where a caller’s script puts them, names given as an answer, Latin names found written in Arabic). A small dataset is scanned at once. A large one is scanned on the cluster, and the command follows it until it’s done (--no-wait comes straight back, and pii report shows how it’s going). The scan runs on the platform, and with no option it asks no model. A model can be asked for the names the scan misses, the ones said with nothing beside them (Asking a model for names). A scan takes one of the two:
--names-model asks a language model you serve on levers9. It reads each text after the scan has masked what it found, and the text stays on your cluster, so this is the one for real customer data. It’s much slower than a plain scan, since the model reads every text. The report then says how many texts it read and gave no answer for, the names it returned, kept and dropped, and the findings it added. A later scan with no option keeps those names for every text that hasn’t changed, and says how many rows are new or changed and so read by no model. pii report and pii policy say which model read the dataset last. An address that isn’t an endpoint of your workspace is an outside model. It’s asked only with --allow-outside (and --model, the name it serves its model under), it’s sent the masked text, and the command prints a warning line first. --outside-model asks Anthropic or OpenAI and sends the text of every row as it is, also behind a warning line. Leave both off for real customer data. Whichever you use, the report says in its own line whether text left the platform. pii policy shows or sets who reads a dataset’s text as it is (Off, Flag and Mask): off (everyone reads the text as it is), flag (nothing is hidden, and each row says how many findings the scan has in it) or mask (every reader but the owner gets the masked text: a share link, the published manifest, the model that drafts, Insights, a dataset built from this one and the Arena’s import). Mask covers text. Audio still plays as recorded. With no value it prints the policy and how far the scan covers the rows: how many rows there are, how many are flagged, and how many aren’t scanned yet. Under mask the ones not scanned yet are held back from every reader but the owner, and the dataset can’t be published until they’re scanned. Setting mask on a dataset that was never scanned starts a scan. The command follows it and prints its report, and --no-wait comes straight back. pii review is how you check the scan: people read a random sample of rows as they are and settle what the scan found, and the scan is measured against that.
draw picks 300 rows unless --size says otherwise, every row with the same chance. The dataset has to be scanned first, and a sample stays fixed until --redraw, which keeps the reviews already made. status prints how many sampled rows are reviewed and by whom. Once 50 are (or the whole sample when it’s smaller) it prints a table: precision and recall per type and for all types, each with its 95% range and the counts it comes from. A wide range means too few findings of that type were reviewed. The line under the table is the share of personal data Mask really hides, whatever type the scan gave it. Above the table it says which names model the measured scan asked, and whether it kept a model’s names from an earlier scan: a scan that asked no model and kept a model’s names isn’t a scan without one. link makes a link for a reviewer. It’s not an ordinary share link: it shows the original text of the sampled rows, behind an agreement the reviewer has to accept, and it opens nothing else of the dataset. The command prints one warning line first. levers9 dataset links NAME lists every share link of a dataset with what it opens (the rows and labels, or the scan review) and whether it still works. pii export writes the dataset out for training with its personal data taken out (Export for training): one JSON line per row, and a report beside it.
--render surrogate, the default, swaps every name and number for a made-up one of the same kind, the same one for the same person all through a call. Pick it to train a model that should say names and numbers the way people do. --render mask writes placeholders like [NAME], for a file people will read. The first thing the command prints is whether the scan was measured, then the recall for names with its range, the rows written and left out with why, where the two files are on the datasets volume, and what the file doesn’t cover. Audio is never de-identified. An export of a scan nobody measured is made, and says so first. --require-measured refuses it instead. --min-recall TYPE=N refuses it when the review’s recall for a type is under the number, held against the lower end of its 95% range unless you say --by point. A row that holds text no scan has read as it is now stops the export, unless --leave-out-held-back leaves it out. -o FILE also saves the file where you say, with its report beside it (calls-sept.report.json), and won’t write over a file without --force. --new-salt makes the dataset a new salt first, after which earlier surrogate exports no longer line up with new ones: it asks before it acts, unless you add --yes. A large dataset is written on the cluster and the command follows it (--no-wait comes straight back, --on-cluster sends a small one there too). pii exports lists what was made, each file with its render, when, its rows, and whether its scan was measured, with the recall for names when it was. pii export-delete removes one export and its report, and asks first unless you add --yes. export downloads a dataset of any kind as TSV: every row with its latest labels, to stdout or to a file. It’s your own export, so it holds the original text under every policy. --text masked downloads the masked copy instead, which is the file a share link would get under mask.
A decision dataset is a human gold set for a decision model (Decision gold sets). It takes its questions from a file, a Decision API can draft the answers, and it exports as JSON lines and as a schema:
--format is tsv (the default, any kind), jsonl or schema (a decision dataset). --status keeps the rows with one status (pending, validated or flagged). --twins goes with jsonl and adds one record for every counterfactual. --text masked goes with tsv and jsonl. Publishing reads a service-account key from a workspace secret — see Publishing to a bucket. LEVERS9_DASHBOARD_API_URL points these commands at another dashboard (self-hosting, or a local BFF on http://localhost:8100).

Hardware: machines and pools

AI agents

See Skills and MCP for AI agents.

Which workspace a run goes to

Every remote run prints => Workspace NAME · context NAME · <dashboard>/apps first. The default context is what levers9 login wrote and what the dashboard you signed into shows; -c NAME, LEVERS9_TOKEN or CONFIG_PATH send the run elsewhere, and the line says so. LEVERS9_QUIET=1 silences it; LEVERS9_DASHBOARD_URL sets the link when self-hosting.

Where things live

  • ~/.levers9/config: contexts (host, port, token, and controlplane_http_url when REST answers on a different hostname than gRPC, as on the hosted cluster: https://levers9.uselevers.com). Lost it? levers9 login again; the same workspace token comes back. It holds tokens, so the CLI keeps it readable by you only (the file 0600, ~/.levers9 0700); an older one is tightened the next time a command saves it. A --config-path somewhere else gets a 0600 file, and its folder is left as it is.
  • tls in a context says whether its controlplane speaks TLS. levers9 login writes tls = true for the hosted one. Left out, TLS is used on port 443 and not on other ports; LEVERS9_CONTROLPLANE_TLS=true or false decides for contexts that don’t say. When a token would go without TLS to a host that isn’t local or in-cluster, the CLI warns once.
  • levers9 login sends a PKCE challenge, so the browser brings back a one-time code, not the token, and the CLI trades the code for the token over HTTPS. A dashboard from before the exchange still sends the token, and that’s accepted.
  • The current directory is synced into every container at run time; put a .levers9ignore file next to your script to leave things out (same syntax as .gitignore). Files that look like secrets (.env, *.pem, *.key, id_rsa*, .aws/, .netrc, .npmrc, .pypirc) are named in a warning when they’re uploaded; see Parameters.
  • REST: everything the CLI does is also on https://levers9.uselevers.com/api/v1/... with the same token, see the API reference.