CLI reference
The command surface for the whole flow — auth, config, whoami/health, odyssey init/push/publish/dev, the platform-authoring groups (scenarios, suites, agents gate/ci-suite, repo), ci init/review --platform-suite, runs export, and the credential/base-url resolution precedence.
The SDK ships two console scripts. dystopic is the engine; odyssey is the agent
namespace on top of it. Everything in the flow — logging in, registering an agent,
running checks, and gating a PR — is reachable through them.
pip install 'dystopic[odyssey]'odyssey <command> is exactly dystopic odyssey <command>: the odyssey script and
the dystopic odyssey … prefix dispatch to the same code. The flow uses the odyssey
spelling throughout; use whichever you prefer.
The two scripts
dystopic
The engine and console entry point. Owns global flags, credential resolution, and
output formatting. Exposes auth, config, whoami, health, version, logout,
the top-level ci command, and the platform-authoring groups — scenarios, suites,
agents gate / agents ci-suite, and repo — directly.
odyssey
The agent namespace: init, push, publish, runs, list, show, versions,
ci, plus the authoring commands (doctor, scaffold, mcp, sync,
dump-agent) that require the [odyssey] extra.
Run odyssey <command> --help for a command's options, or dystopic <command> --help
for the engine commands.
Global flags
These flags are understood by the engine. --base-url, --verbose, --quiet, and
--no-dotenv are position-independent — they may appear before or after the
subcommand (dystopic auth login --base-url https://api.pipelines.tech is normalized to
put --base-url first). The rest must precede the subcommand.
| Flag | Effect |
|---|---|
--base-url URL | Target a different API host for this invocation. The only way to retarget — there is no named-environment alias. |
--api-key KEY | Supply the API key for this invocation instead of resolving one. |
--output {json,yaml,table}, -o | Output format. Unset renders a compact human summary for read/status commands and raw JSON for everything else; -o json always prints the raw object. |
--quiet | Print only the final resource ID (e.g. the check_id). |
--verbose | Enable debug logging — logs each request to stderr. |
--no-dotenv | Do not auto-load a .env file from the current directory. |
--version | Print the SDK version and exit. |
By default the CLI loads a .env from the current directory (if
python-dotenv is installed, which the [odyssey]
extra pulls in); shell environment variables always win over it. Pass --no-dotenv to
skip that autoload.
Credential & base-url resolution
Every command that talks to the API resolves an API key and a base URL through the
same precedence chain. The single source of truth — including how auth login stores the
key in your OS keychain — lives in Set up the CLI. In
short:
- API key:
--api-key›DYSTOPIC_API_KEY› OS keychain / config file (written byauth login). - Base URL:
--base-url›DYSTOPIC_BASE_URL(orDYSTOPIC_INTERNAL_BASE_URL) › thebase_urlsaved in config ›https://api.pipelines.tech.
.env values slot in below flags/shell env but above the config file (they populate the
environment before resolution runs). When no key resolves, the command exits 2 with:
Missing DYSTOPIC_API_KEY. Set it as an environment variable, CLI flag, or run `dystopic auth login`.Environment variables
| Variable | Purpose |
|---|---|
DYSTOPIC_API_KEY | API key, used when no --api-key flag is given. |
DYSTOPIC_BASE_URL | Override the API base URL (also DYSTOPIC_INTERNAL_BASE_URL). |
DYSTOPIC_CONFIG_FILE | Override the config file path (default ~/.config/dystopic/config.json). |
Auth
Browser login is the default. With no --api-key, auth login opens your browser to sign
in and mints a key; pass --no-browser to print the verification URL and poll instead, or
--api-key KEY to save a key directly. If you already have a valid saved key, login
reuses it — pass --force to mint a new one.
# Browser sign-in (default) — saves the key for future CLI/SDK calls.
dystopic auth login
# Headless: print the URL to visit and poll.
dystopic auth login --no-browser
# Save a key you already hold.
dystopic auth login --api-key pk_live_...
# Confirm the saved key works and see which host it points at.
dystopic auth statusauth status prints a ✓ Authenticated to <base-url> (or ✗ Not authenticated) line.
# Remove the saved API key (clears the whole config file).
dystopic logout
# Keep base_url/org_id/email; only drop the api_key.
dystopic logout --keep-base-urlConfig
Inspect or edit the on-disk SDK config (~/.config/dystopic/config.json unless
DYSTOPIC_CONFIG_FILE overrides it). config show masks the API key and notes whether it
lives in the OS keychain or plaintext in the file.
dystopic config show # print the config (api_key masked)
dystopic config path # print the resolved config file path
dystopic config set base_url https://api.pipelines.tech
dystopic config unset base_url
dystopic config clear # delete the config file entirelyRecognized keys include base_url, api_key, and email.
odyssey — agent lifecycle
Register an agent with the web dashboard or the SDK (create_code_agent(...)) —
that is the primary path and it needs no local manifest. The odyssey init + odyssey push commands below are a secondary, manifest-driven alternative for teams who
prefer to keep a checked-in registration file. See
Register your agent for the full walkthrough and the manifest
schema.
The lifecycle read/write commands (init, push, publish, runs, list, show,
versions, ci) run on the base [cli] extra. The authoring commands further down
require [odyssey].
odyssey init
Interactive wizard that scaffolds a minimal agent-registration manifest (dystopic.yaml)
in the current directory — agent name, mode, and entrypoint/source only. Fully offline; no
API key required. This is the entry point for the secondary, manifest-driven registration
path; most teams register on the dashboard or via the SDK instead.
odyssey initSee Register your agent for the manifest fields it writes.
odyssey push
Create or update the agent on the platform from the manifest. Requires an API key. This is
the secondary registration path — the primary is the dashboard or SDK
create_code_agent(...).
odyssey push # push the only agent (or name it)
odyssey push my-agent # push a specific agent by name
odyssey push --all # push every agent in the manifest
odyssey push my-agent --publish # push, then activate the draft| Flag | Effect |
|---|---|
name (positional) | Agent name from the manifest (default: the only one). |
--all | Push every agent in the manifest. |
--publish | Activate the agent immediately after pushing the draft. |
--set-credential TYPE=VALUE | Set an org credential value (repeatable; also read from os.environ[TYPE]). |
The relocated dystopic agents push spelling is the same command; it prints a redirect
to odyssey push (see Relocated legacy commands).
odyssey publish
Activate a pushed draft agent.
odyssey publish my-agent # by manifest name
odyssey publish --id 42 # by numeric agent id (skips the manifest)odyssey list / odyssey show
odyssey list # agents you can run against (NAME / ID / MODE / STATUS)
odyssey list --search billing
odyssey show 42 # one agent by idodyssey versions
List, inspect, or diff an agent's version snapshots.
odyssey versions --agent-id 42
odyssey versions --agent-id 42 --version 3
odyssey versions --agent-id 42 --from-version 2 --to-version 3| Flag | Effect |
|---|---|
--agent-id ID | Agent row to inspect (required). |
--version N | Return one version instead of the full newest-first history. |
--from-version N | Compare against --to-version (or latest if omitted); renders a readable diff. |
--to-version N | The version to compare --from-version against (defaults to latest). |
--json | Emit the diff as JSON instead of the readable summary. |
--color {auto,always,never} | Colorize readable diffs (default auto). |
Platform authoring — scenarios, suites, gate, CI binding
Suites, scenarios, the gate, and the CI suite binding are platform objects on your
agent. These dystopic command groups are typed wrappers over the agent-scoped REST
endpoints (.../scenarios, .../suites, .../gate, .../ci-suite) — the same
endpoints the dashboard drives. The golden-path walkthrough is
4 · Create a suite; the request bodies live in the
create-suite reference.
dystopic scenarios
Scenario CRUD plus CSV bulk-import. list/show print the result; the mutating
commands print progress to stderr and exit 0/1.
dystopic scenarios list 42 [--group refunds] [--limit 50] [--offset 0]
dystopic scenarios show 42 101
dystopic scenarios create 42 --name refund-simple \
--user-instruction "Refund order #1234." --expected-outcome completion
dystopic scenarios update 42 101 --expected-outcome refusal
dystopic scenarios delete 42 101
dystopic scenarios import 42 seeds.csv --suite-id 7 --append| Flag (create/update) | Effect |
|---|---|
--name NAME | Stable human-readable id (required on create). |
--user-instruction TEXT | The task text handed to the agent (a content axis). |
--expected-outcome {completion,refusal} | The outcome the reviewer judges against. |
--behavior-instructions TEXT | Directions to the world/user simulator (a content axis). |
--failure-rules RULE | A failure rule; repeat the flag to build a list. |
--group NAME | Bucket label (scenario_group). |
--world-id ID | Pin the world this scenario runs in. |
JSON-shaped fields (initial_state, scorers, and the multi-turn conversation object)
are set from the dashboard or the SDK (client.create_scenario(...)), not CLI flags.
import parses the CSV client-side and creates one scenario per row (same column
dialect as the dashboard importer); rows with no content field are skipped, and a
non-numeric max_turns cell is dropped rather than aborting the import. With
--suite-id, the imported scenarios are bound to that suite — an atomic
full-replace of the suite's bindings by default; --append keeps the existing
bindings and adds the imported scenarios after them (deduped).
dystopic suites
Suite CRUD plus the suite ↔ scenario binding.
dystopic suites list 42
dystopic suites show 42 7
dystopic suites create 42 --name default [--description D] [--world-id W]
dystopic suites update 42 7 --description "PR regression set"
dystopic suites delete 42 7
dystopic suites scenarios 42 7 # the bound scenarios, in run order
dystopic suites bind 42 7 101 102 103 # ATOMIC full-replace; arg order = run order
dystopic suites unbind 42 7 101 # drop one binding, scenario untouchedbind is not an append: scenarios not listed are unbound.
dystopic agents gate
The agent's platform gate — the rule that turns a base-vs-head diff into pass or block. It is platform-authoritative: RBAC'd, audited, and not weakenable from a PR.
dystopic agents gate get 42
dystopic agents gate set 42 --block-on-constraint \
--reviewer-severity critical --min-pass-rate 0.8 --scorer policy_check
dystopic agents gate clear 42
dystopic agents gate import 42 dystopic.yaml [--suite NAME] # legacy-yaml migrationFlag (gate set) | Tier |
|---|---|
--block-on-constraint | Block when a tracked constraint regresses base→head. |
--reviewer-severity {nit,warning,critical} | Block at or above this LLM-reviewer severity. |
--min-pass-rate RATE | Block when the pass rate falls below RATE (0..1). |
--scorer NAME | Block when scorer NAME regresses (repeatable). |
set with no tier flags persists an all-empty advisory gate — still a configured
gate; clear (the DELETE) is the only way to un-configure it. gate import is the
one-shot migration for teams with a legacy committed config: it lifts a suite's
block_on list out of the file and persists it as the platform gate.
dystopic agents ci-suite
Bind (or clear) the suite CI resolves for this agent — what makes
ci review --platform-suite source that suite's frozen scenarios server-side.
dystopic agents ci-suite set 42 7 # suite must belong to agent 42
dystopic agents ci-suite clear 42dystopic repo
Inspect or create the repo ↔ platform connection CI depends on. status and
connect print to stderr and exit 0/1 (status is scriptable as a
connected/not-connected predicate).
dystopic repo list # repos connected to your org
dystopic repo status owner/name # exit 0 connected / 1 not
dystopic repo connect owner/name [--agent-id 42]connect claims the repo headlessly — no browser bounce — provided your org has
already installed the GitHub App and added this repo to it. --agent-id links a
specific org-owned umbrella agent; omitted, the platform auto-provisions one. The
first-time App install is inherently browser-bound (GitHub has no headless
install API): when the repo isn't claimable yet, connect prints the install URL (or
the list of repos that are claimable) and exits 1. Already-connected repos are a
no-op exit 0. See 7 · Integrate into CI/CD.
odyssey runs
Read-side commands for past checks.
odyssey runs list # recent checks (RUN / STATUS / PASSED/TOTAL)
odyssey runs list --agent my-agent
odyssey runs show 512 # one check, rendered
odyssey runs export 512 --json out.json --junit out.xmlodyssey runs export CHECK_ID writes the full check rollup (every case, paged internally)
to the paths you give:
| Flag | Effect |
|---|---|
check_id (positional) | The check id to export. |
--json FILE | Write the full rollup as JSON. |
--junit FILE | Write a JUnit XML report. |
With neither flag, it prints a note reminding you to pass --json and/or --junit. It
warns to stderr before overwriting an existing file.
ci — the PR check
The CI surface drives the branded Dystopic merge check. It is exposed both as the
top-level dystopic ci … and, identically, as odyssey ci …. Its two core subcommands
are init (scaffold the workflow) and review (the PR gate); cancel, dismiss, and
rerun manage an existing review.
dystopic ci init
Scaffold the PR-check workflow into the current repo — offline, no API key.
Platform-first by default: it writes exactly one file, the
.github/workflows/pipelines-regression.yml workflow in platform-suite (zero-yaml)
mode, and no local config — a connected repo needs nothing else, because CI
resolves the run recipe, gate, and bound suite from the platform. It then prints the
turn-it-on checklist: dystopic repo connect, author scenarios/a suite
(dystopic scenarios import …), and dystopic agents ci-suite set.
dystopic ci init
dystopic ci init --force # overwrite scaffold files that already exist
dystopic ci init --legacy # old three-file scaffold (dystopic.yaml + seeds.csv + workflow)The scaffolded workflow sets the action's platform-suite: true input, which requires
an action release that includes it — re-release @v1 (or pin a revision that has it)
if your org's action copy predates SDK 0.5.0. --legacy restores the old
committed-config skeleton for the pre-connected-repo path; the recommended front door
is to connect your repo on the platform. See CI/CD for the
connect-repo walkthrough.
dystopic ci review
Submit the base and head sides as commit-keyed checks and gate on the assembled review.
This is what the workflow runs on each PR. odyssey ci review is the same command.
# PR mode: gate head against the merge-base, zero committed config.
dystopic ci review --platform-suite \
--base "$MERGE_BASE_SHA" --head HEAD --repo owner/name --pr-number 42
# Push / non-PR mode: head-only, gated on the absolute (floor) gate.
dystopic ci review --platform-suite --head-only --branch main--platform-suite is the zero-yaml mode and the recommended default. The side
submits no local config and no scenarios: the platform resolves the execution recipe
from the connected repo's umbrella agent and sources the bound suite's frozen
scenarios server-side, byte-identical to an in-app check. It fails closed (exit 1 /
422) if the repo is unconnected, the umbrella agent has no run command, or the suite
doesn't resolve. The gate lives on the platform and cannot be weakened or removed by a
PR — dropping config on head does not disarm the check.
| Flag | Effect |
|---|---|
--platform-suite | Zero-yaml mode (recommended). Submit no local config or scenarios; the platform resolves the recipe from the umbrella agent and sources the bound suite's frozen scenarios server-side. |
--suite NAME | Suite selector (default default). |
--all-suites | Review every configured suite over the same two worktrees; the exit code is the worst across suites. |
--base SHA | Base git sha/ref — the PR's merge-base (required unless --head-only). |
--head REF | Head git ref/sha (default HEAD). |
--repo owner/name | Repository (default $GITHUB_REPOSITORY). |
--pr-number N | PR number (default $PR_NUMBER). Mutually exclusive with --head-only. |
--head-only | Submit only the head side and gate on the absolute floor gate; needs --branch (or $GITHUB_REF_NAME). |
--branch NAME | Branch name for --head-only reviews (default $GITHUB_REF_NAME). |
--sample N | Only run the first N scenarios. |
--repeats N | Repeats per scenario (default 1). |
--fresh | Force fresh executions instead of attaching to existing commit-keyed runs — costs real runs. |
--baseline-review ID | Client-side drift compare against a prior assembled review (requires --head-only and --fresh). |
--json | Emit the machine dump to stdout (the composite Action consumes it). |
--poll-interval SECONDS | Poll cadence (default 5.0). |
--timeout SECONDS | Poll budget in seconds (default 1200.0); exhaustion is not a failure — the platform finishes asynchronously. |
Exit codes:
| Code | Meaning |
|---|---|
0 | Gate pass / neutral / advisory / skip-neutral, or the poll budget was exhausted (the platform finishes the review async). |
1 | Gate blocked; a gated-red preflight (config or gate removed vs. base); or an operational HTTP failure mid-flow. |
2 | Usage error, or a named preflight error with nothing submitted (e.g. an unconnected repo, an umbrella agent with no run command, or a suite that doesn't resolve). |
Use dystopic ci review / odyssey ci review (the v2 surface, backed by
/api/ci/reviews). This is the only supported CI review command.
See the CI/CD reference for the full decision table, preflight error names, and workflow wiring.
odyssey authoring commands
These require the [odyssey] extra; running one without it prints an install hint and
exits 2.
The authoring commands: odyssey scaffold (scaffold a runnable agent project),
odyssey doctor (local green/red preflight), odyssey mcp (introspect MCP servers into a
tools_schema), odyssey sync (sync a wrapper's tools_schema to the platform), and
odyssey dump-agent (print the JSON registration payload).
Diagnostics
dystopic whoami # the user behind the current API key (email / name / org / role)
dystopic health # API health, ✓/✗ per dependency
dystopic version # SDK versiondystopic --version prints the same version and exits.
Relocated legacy commands
Agent lifecycle commands moved under the odyssey namespace. Typing a relocated
top-level command directly at the console — dystopic init, dystopic agents push,
or dystopic runs … — prints a one-line redirect to the odyssey spelling on
stderr and exits 2. The zero-yaml authoring commands are the exception:
dystopic agents gate … and dystopic agents ci-suite … (like dystopic scenarios/suites/repo) did not relocate and run directly.
`dystopic agents push` has moved — agent commands now live under `odyssey`.
Run: odyssey push
(or: dystopic odyssey push)Use the odyssey spelling (or the explicit dystopic odyssey … prefix) instead.
Next
With the command surface in hand, walk the flow end to end starting at Port your agent, or jump to CI/CD to wire up the Dystopic check.
Wire contract & errors
The exhaustive low-level contract every ported agent honors — response shape, proxy request/response/error classes, retry policy, DYSTOPIC_* env vars, and the SDK error hierarchy.
Docs MCP server
Give your coding agent live access to this documentation — a hosted MCP server with search_docs, list_docs, and get_doc, authenticated with your normal Dystopic API key.