Skip to content

Latest commit

 

History

History
342 lines (297 loc) · 87.1 KB

cli.md

File metadata and controls

342 lines (297 loc) · 87.1 KB

Command reference

agentlab --help and agentlab <command> --help are authoritative; this page is the map. Every command runs against one lab — its agentlab.yaml (written by configure; on a terminal, a missing file starts the form), certs/ and state/ — and talks to its kind cluster through the cluster's own exported kubeconfig, state/kubeconfig, never your shell's current-context.

Where things live

agentlab keeps to the freedesktop folders, like every other program on the machine (each XDG_* variable counts when it holds an absolute path):

What Where
The binary ~/.local/bin/agentlab ($XDG_BIN_HOME), where self-update installs it
A lab's configuration ~/.config/agentlab/labs/<lab>/agentlab.yaml ($XDG_CONFIG_HOME)
A lab's state: the lab CA and certificates (certs/), kubeconfigs and rendered manifests (state/) ~/.local/state/agentlab/labs/<lab>/ ($XDG_STATE_HOME), the lab's directory; its agentlab.yaml links to the configuration
The registry of the labs ~/.config/agentlab/labs.yaml
The update check's cache ~/.cache/agentlab/ ($XDG_CACHE_HOME)

On macOS the configuration and the state are under ~/Library/Application Support/agentlab/, the cache under ~/Library/Caches/agentlab/. A lab's directory is then its configuration's folder too, and its agentlab.yaml is the file itself, not a link. $XDG_STATE_HOME, when set to an absolute path, moves the state out of it: an existing lab's certs/ and state/ move to the new folder on its next command, links left in their place, and its agentlab.yaml stays where it is, linked from the new directory.

A lab is its name: it keeps its directory, and with it its CA, across down and up. A lab from before these folders — a checkout, a directory of its own — moves into them on the first command that enters it: its agentlab.yaml to the configuration folder, certs/, state/, kubeconfig.oidc and .token into its directory, each by a rename, nothing copied or dropped, relative paths in agentlab.yaml made absolute first. The command says so (Moved lab agentlab from … to …). Every moved file leaves a link at its old path: the kind node of a running lab mounts certs/ and state/node by their old paths, and an older agentlab run in the old directory still finds its lab. A lab on another file system than the state folder stays where it is, said on stderr. A lab already in its directory whose agentlab.yaml there is a file, not the link (the layout before the configuration folder, or an older release's save over the link), has the file moved to the configuration folder byte for byte on its first command, the link in its place (Moved lab agentlab's agentlab.yaml to …); a copy equal to the configuration folder's file gives way to the link, and one that differs is refused, naming both files and the newer, until one of the two is moved aside.

Which lab

configure and up register their lab under its clusterName, so the other commands find it from any directory, in this order:

  1. --lab <name> (every command), refused naming the registered labs when no lab of that name is registered;
  2. the agentlab.yaml in the current directory: the lab its clusterName names (a copy of a registered lab's file, or the link a moved lab left, enters that lab), else this directory's lab, which settles in the folders — always this directory for render, which writes the files of the agentlab.yaml it reads: a copy renders a scratch lab beside it and never rewrites the registered lab's certs/ or state/;
  3. a picker among the registered labs on a terminal, and off one a refusal that names them and --lab.

No lab is a default, not even the only one registered: a command runs against the lab --lab, the directory or the person names, or refuses. An agentlab.yaml without a clusterName is refused too: the file names its lab.

A lab entered from another directory is named on stderr (Lab agentlab (~/.local/state/agentlab/labs/agentlab)). configure and up skip step 3: in a directory without agentlab.yaml they create a new lab in the folders, named agentlab — or the first free agentlab-<n> when another lab holds that name — unless configure --cluster-name <name> names it. A clusterName another lab holds is refused, naming both directories — the two would collide on the kind cluster and its ports. down keeps the registration (the lab still exists), and down --purge removes it with the lab; a lab whose directory or agentlab.yaml is gone is neither offered nor listed, and back once its agentlab.yaml is (the next registration drops the entries still gone). agentlab list also shows every kind cluster no registered lab names, marked lab directory unknown: any command run in a lab's directory (agentlab pods) registers the lab. self-update, completion and help touch no lab.

The commands that only read a lab — trust, untrust, list, status, open, pods, logs — read an agentlab.yaml a newer agentlab wrote: a field this release does not know is read past with a note naming the release that wrote it, and nothing is written. Every command that writes the file refuses it instead, so a newer release's switch is never dropped.

Headless use

Nothing asks without a terminal (stdin not a TTY): no first-run question, no lab picker, no trust or open offer. A missing agentlab.yaml is refused with a pointer to agentlab configure --defaults, which writes the canonical lab without a question; registered labs and no agentlab.yaml here are refused naming them and --lab; up --trust/--open pre-answer the end of a boot. CI, coding agents and scripts write the config first and name the lab.

The sections below are the groups agentlab --help prints, in the same order.

Setup

Command What it does
up Check docker's CPUs and memory against this configuration's floors, then create the kind cluster, deploy Dex and the enabled components, and verify the OIDC chain end to end. Idempotent: unchanged re-runs are no-ops. On a terminal it ends by asking what the summary used to only describe: whether to trust the lab CA while it is untrusted, then whether to open the portal. --trust and --open (or --trust=false/--open=false) pre-answer both for scripted runs; off a terminal nothing is asked. See TLS.
configure Discover this machine, then ask for the lab configuration (or keep it with --defaults) and save agentlab.yaml. Without an agentlab.yaml yet, on a terminal, it first asks one question — the detected defaults, or customize — and only Customize runs the form; up (and every command that has to create the file on the way) asks the same. With an existing file the form opens with its values. Flags below.
trust Install the lab CA into the system and browser trust stores (one sudo prompt; reversible). See TLS.

Everyday

Command What it does
open <portal|agents|prometheus> Open a lab URL in the browser and print it (so it can be copied where the opener finds no browser — SSH, WSL): portal is Backstage, agents the kagent UI, prometheus the lab Prometheus's web UI on the edge (https://observability.<domain>/prometheus, the path Backstage queries). The target is required; without one, the refusal names them. Refused when the target is disabled in agentlab.yaml, when the cluster is not running, or when the target does not answer yet (a short reachability probe, before any trust question, so a sudo prompt is never spent on a command that then opens nothing) — a plain fact instead of an error page in the browser. While the lab CA is untrusted, open portal and open prometheus offer agentlab trust first on a terminal and warn off one; open agents is plain HTTP on loopback and asks nothing.
list (ls) Every lab registered on this machine: its name, directory, cluster state (running, exited, not created), enabled components with the chart, the portal and muster URLs (with the port suffix when the edge is not on 443), and whether its lab CA is trusted here; * marks the current directory's. Docker and the files answer, so it works while a lab is down. -o json for scripts. It never asks which lab.
status The lab's live state, for the lab here (or --lab): the chart the cluster runs — its version, whether it is a dev chart (a chart directory's build or a branch's dev build), its Helm revision and which agentlab installed it from where — against the chart agentlab.yaml installs; when the two differ, the drift with both versions and which side is behind (an agentlab.yaml put back from a copy without a platform run pins an older release than runs) and the two ways to end it — agentlab platform installs the configured chart over the one in place, agentlab configure --adopt-chart writes the chart in place into the config; and the platform HelmReleases, how many are Ready and every other one with helm-controller's message. An overlay line per platform.valuesFiles entry: the file and every value it sets (components.kagent.versionRange=1.15.0-rc.4; the first eight, then a count), an unreadable one named as such, one the release in place does not carry yet marked, and one the release in place was installed with that agentlab.yaml no longer lists (every platform run records its overlays in the revision's description, which the chart line shows) — so a lab handed on with a proof's pins never reads as the chart's own component ranges; overlays and staleOverlays in JSON. Refused with agentlab up when the cluster is not running. A registry line: the lab registry container (<cluster>-registry) running, stopped, or missing, which agentlab platform recreates (registry in JSON). -o json for scripts (drift carries the line). With platform.workspaces, a workspaces line: the NFS server and its node, the NFS CSI controller with its mTLS proxy (without it while Agent Substrate is off), the node plugin, the StorageClass, and whose CSIDriverConfig registers the driver with Substrate (the chart's, the lab's, or one applied by hand), worded as the platform summary words it.
pods [-n <namespace>] The lab's pods across namespaces through the embedded client, shaped like kubectl get pods -A (NAMESPACE, NAME, READY, STATUS with a waiting reason such as ImagePullBackOff, RESTARTS, AGE); -n narrows to one namespace. Refused with agentlab up when the cluster is not running.
logs <component> Tail a component's logs: backstage, dex, klaus-gateway, mcp-prometheus, muster or prometheus.
portal dev / portal build Run your own giantswarm/backstage checkout against the lab (--checkout, default ../backstage, resolved against the directory you run the command from). dev runs the checkout's yarn start with hot reload — frontend http://localhost:3000, backend on backstage.localDevPort (--port, default 7017), signed in through the lab Dex, talking to the lab's muster, kagent and apiserver. build builds the image the way the checkout's CI does, records it as platform.devImages.backstage and rolls it in (--no-apply only records it). See agentlab as a development environment.
login [email] Log in as a lab user: the headless password grant, or --browser for the real Dex login page (authorization-code flow, which asks for the user itself). Either way it prints the token claims and writes .token and kubeconfig.oidc for that user. --password overrides the one in agentlab.yaml. --audience (repeatable, default kubernetes, headless only) names the token's audience: kubernetes for the apiserver, agent-platform for muster and the agent-platform MCP servers behind it (workspace-manager, agent-manager, model-manager), dex-k8s-authenticator for muster's own Kubernetes writes. Any set besides kubernetes alone is minted by the agent-platform Dex client, so its token carries agent-platform too; kubeconfig.oidc is written only when the token carries kubernetes.
turn (--list | --template <name> <prompt> | --session <id> --delete) One conversation with an agent as a lab user, the way the surfaces drive it: the user's Dex id_token on the kagent controller's gRPC route through the edge. --list prints the roster that user sees — ListAgents as that person (each Agent with the AgentTemplate and the Harness it pairs), or the gRPC status when the controller refuses. With --template and one prompt it starts a Session, through agent-manager's start_session as that user, of the Agent that pairs the template with a Harness — the template's Agent that reports Ready, the one the portal picks; --harness picks the Agent pairing the template with that Harness — streams one turn, prints the answer and the terminal task state, and deletes the session. --user picks the lab user (default: the first admin). --keep leaves the session for a later --session <id> turn (--instance is the former name) and prints the command that deletes it: --session <id> --delete deletes a kept session as the --user, its creator, the way a turn's own cleanup does (DeleteSession, retried while its runtime work settles, releasing its actor and snapshot), reads it back gone, and refuses an id that user cannot see — another user's, or one already gone; and --session … --suspend suspends it to the snapshot store and resumes it before the turn, the proof that a conversation survives the snapshot. The terminal status message's metadata (a harness's usage of the turn) is printed when present. A turn that pauses for tool approval prints the request and stops; --decide approve or --decide reject (with --reason) answers every request until the task settles. --events <file> writes every streamed A2A event as one JSON object per line, the raw material of a parser fixture. --session <id> --share-with <email> has the --user share the session read-write with another lab user (--share-ttl bounds how long the share's token grants access; default until revoked), runs the turn as that user with the share token (their own Dex token plus the token, which adds to their identity), and revokes the share afterwards.

kubectl --kubeconfig kubeconfig.oidc after login is the OIDC path — the way to verify what a specific user can do. The kind admin context bypasses the platform and OIDC entirely.

login --audience agent-platform is the headless way to call muster as a lab user, the token a person's portal sign-in carries: send .token as the bearer to https://muster.<domain>/mcp, and the agent-platform MCP servers answer (x_workspace-manager_list_workspaces among them). Add --audience kubernetes when the same call reaches mcp-kubernetes or kubectl.

Testing

Command What it does
test Assert RBAC for every configured user: a token from Dex, then one SelfSubjectAccessReview per expectation of each group — kubectl auth can-i, asked of the apiserver in-process with that token alone.

The rest of the group are the lab's end-to-end proofs. Each one logs in to Dex headlessly, drives the real components through the same paths a person would, asserts the result and leaves nothing behind — with one documented exception, models-test on a server that cannot delete a model (see below). For its length a proof holds the platform's component version ranges, so Flux upgrades nothing under it, and it returns once a release published meanwhile has rolled (docs/platform.md, the range hold). Where a command takes [email], that user runs the proof (default: the admin). They trust certs/ca.crt directly, so they need neither agentlab trust nor any Node setting. The agent proofs run on kagent API v2, the one line the platform ships: every agent a HelmRelease of the Generic agent chart (1.x) rendering an AgentTemplate on the platform Harness, created through one helper — see Agents.

Command What it proves
platform-test [email] Dex → muster → mcp-kubernetes → apiserver; the APIs the lab provides itself served; the fleet's flux-multi-tenancy policy enforcing on HelmReleases in org-lab — four shapes as server-side dry runs, denied with the fleet's message where an installation denies (no tenant; a tenant installing into another namespace without a kubeconfig) and admitted where it admits (serviceAccountName: automation; kubeConfig.secretRef); the chart's Kyverno PolicyExceptions admitted by the lab's Kyverno (the polex-test stage) (the identity proof: a viewer's kube-system Secrets Forbidden by the apiserver, a forged x-user-id changing nothing), with agents on the kagent controller route's JWT Strict policy (a call without a token refused at the edge; a valid token with a forged x-user-id attributed to the token's subject by the controller) and agent-manager writing as the caller (a viewer's create refused under the viewer's name), the atelet's image-cache policy and the two halves of Agent Substrate on one release (the atelet's image against the WorkerPool's worker image — a skew boots no golden actor) and the WorkerPool's workers Running on a node that carries its node selector (a Pending worker fails the proof with the scheduler's reason), with workspaces on the workspace-manager (registered with muster and reachable: Connected, Awaiting Session or Auth Required; rolled out, list_providers as the admin naming every configured provider instance), with platform.musterPodNetwork muster's two Ready replicas on the pod network and one of them deleted while an MCP session keeps calling through the edge without a failed call (muster on the pod network), the per-server OAuth sign-in challenge, the infrastructure families (the lab's cluster as the kubernetes and prometheus members, tool-group: infrastructure, no family-less mcp-kubernetes) and, with observability on, mcp-prometheus and Backstage's metrics endpoint. Every stage is named with its outcome and duration in a summary before the verdict; a stage the lab's state cannot run fails the proof naming the stage, and only the stages agentlab.yaml leaves without a subject (agents or observability off, the 3.x line) are listed as SKIP with the reason. See The agent platform.
polex-test [--chart-version <release>] The chart's Kyverno PolicyExceptions as the fleet's Kyverno answers them: the meta chart and every component release it carries rendered offline with the lab's values, kyvernoPolicies.enabled: true and, with agents on, tenants.organizations on, every rendered PolicyException created as a server-side dry run in its namespace (policy-exceptions, made sure to exist) against the lab's Kyverno; a refusal fails with the apiserver's message (spec.match.any[].subjects: Forbidden: Usage of user info is forbidden for subjects in an exception with background on); no exception is created. --chart-version judges a release the lab does not run yet. platform-test runs the same stage on the chart agentlab.yaml installs. See The agent platform.
vm-manager-test [email] The vm-manager pod as the person: GET /api/v1/host from inside the cluster refused without a token and answered with the person's Dex id_token (the token muster forwards); the x_vm-manager_* tools through muster with their annotations intact (get_host read-only, delete_vm destructive), get_host, list_images, list_networks; the MCPServer with the agent-platform tool group, Connected; then create_vm on the newest image followed with get_vm to ready (the installer boot, the installed boot, READY=1), the attestation verdicts where the image policy has golden values, the console tail, delete_vm. --skip-vm boots nothing; --vm-timeout bounds the boot (default 6m). See vm-manager.
models-test [email] Managed models through muster's x_model-manager_* tools: 401 at muster without a token, then pull → ModelConfig → every write's dry run, the gitops_owned refusal → agent turn → MCP via muster → unload → delete, one backend per run. On lmstudio the teardown is the unsupported refusal plus an unwire, since LM Studio serves no delete, and that run leaves its model downloaded. --backend picks one of platform.modelManager.backends (default: the first); --model a small, tool-calling capable model. See Models.
serving-test [email] Model serving on llm-d (platform.serving): the llm-d controller, the well-known template and the models Gateway up; 401 at muster without a token, then model-manager's tools through muster as the person; the lab preset listed among the shipped ones and fitting the node (a CPU preset, the allocatable budget) → load → the LLMInferenceService Ready on the CPU runtime, no accelerator requested → the ModelConfig wired at the model's route on the models Gateway → a completion through the Gateway (401 without a token, 200 with the person's) → the agent turn on the wired ModelConfig must answer (Substrate's egress gateway trusts the lab CA through substrate.atenetEgress.upstreamTrust; with the LLM endpoint on, the ModelConfig rides the endpoint under the public name and the turn's tokens must show up in the data plane's per-model token metric) → unload, nothing left behind. --preset serves another published preset, --skip-chat skips the agent turn, --ready-timeout bounds the serve (default 20m). See Model serving on llm-d.
workspaces-test [email] The workspaces proof (platform.workspaces): the NFS server, the NFS CSI controller with its mTLS proxy, the node plugin and StorageClass agentlab-workspaces in place → a 1Gi read-write-many claim of the class bound → a bare mirror seeded on it from a repository with an executable and a symbolic link → session a: a pod with its own sessions/a sub-path read-write and mirrors/ read-only, a git clone --shared of the mirror with the executable bit and the link intact, a commit, a write into the mirrors refused → session b likewise, nothing of session a visible → the whole volume mounted read-only, a write refused → the controller endpoint refused without Substrate's client certificate (the TLS alert quoted) → the actor-level mount through ate-api-server on the Substrate chart version it names: an ActorTemplate with two existing volumes, two actors on the claim's volume at sessions/a and sessions/b with the mirrors read-only, each seeing only its own session and refused a write into the mirrors, a pause and resume keeping the process and the content, both deleted with the volume and its files left, an unknown driver and a missing handle refused at create (a Substrate without existing volumes refuses the template: the skip, with ate-api-server's reason) → the namespace agentlab-workspaces-test removed (first and last) and the volume's directory gone from the export (another live volume's directory named and left in place). Before that, unless --storage-only: a Session admitted on the volume (on the claude Harness; --harness codex --model-config <name> runs it on a coding Harness the lab renders, with that ModelConfig, such as the lab's ChatGPT login) with a workspace grant the manager's key set verifies (a Session without a volume, the refusals of no grant, a foreign key, another person, another namespace's Agents and no Agent namespace, the Session's turn in its own directory with the mirror read-only and its clone, its file read back on the volume); --ready-timeout bounds each wait (default 5m). --part sync runs the sync part instead on the --provider instance (default github) and its proofRepositories: the first cycle, a no-op, a push and a force-push to a scratch branch fetched alone with a borrowing clone kept whole, a dropped repository kept while borrowed and deleted after, the update, the delete. See The sync part. --part sessions (--sessions n, default 10) starts Sessions on the claude and the kagent Harness together with a requested sync cycle: each fetches the pushed repository once from the provider (its setup record), isolated, deleted, cleaned up past the window. See The sessions part. --part git proves git and gh as the person on GitHub from a workspace Session on both Harnesses: a push and a pull request whose author and push actor GitHub names the person, no GitHub token in the sandbox, a Session suspended on its question resumed in the same directory, a Session without a workspace refused. See The git part. A GitHub App's grant the admin lacks waits for muster's sign-in link opened in a browser. --part gitlab runs the GitLab part instead, on the provider instance gitlab as the admin and [email] (default dev@lab.local): list_repositories for the group → the token exchange refusing [email] with the connect link → connect_provider through GitLab's sign-in and consent → the exchange releasing the admin's GitLab token, which GitLab's API and git accept → a workspace over the project, its mirror at GitLab's head → start_session with the grant, the clone in the Session's directory, no GitLab credential in it → the Session's push (skipped until kagent's token broker is wired) → a Session without a workspace refused at GitLab → disconnect_provider revoking the token; without the instance every stage skips with its reason. See The GitLab part.
workspaces-measure Measures workspaces on the cluster --kube-context and --muster-url name (with --organization, --provider, --small-repos and --large-repo), through its muster as the person muster auth login signed in, their token renewed before it expires: a small and a large workspace created and deleted --runs times (default 5): the first sync, a no-op and a changed cycle, authorize_workspace, with --agent and --agentgateway-url a Session's directory set up through start_session (from the call and from the first turn to the setup's record) and the on-demand fetch in the Session's sandbox after a push, a first checkout, git status and a cold build (its download through the module proxy, --goproxy, and its compile, two figures) in the Session's clone on the volume and the same on the node's local disk, the session directory's size; the first run also times one online expansion where the StorageClass allows it; the last reads the claim's capacity, the volume's use, each mirror against the provider's size (the factor against 1.5) and whether the headroom max(4 GiB, 50 %) held. Per figure what it measured from and to, median, p90 and max, with the StorageClass and driver, as JSON and a Markdown table in state/workspaces-measure-<time>/ (--run-dir) and printed; a figure the cluster cannot report is listed as not available with the reason, and a failed probe is listed while the run goes on. The pushes go to a scratch branch on the provider with the person's git credentials (--git-url, default https://github.com), deleted afterwards; everything else it created is removed. See The measurements.
agents-test [email] agent-manager through muster as the signed-in user, on the Generic chart line agent-manager declares (its --agent-chart-semver, the 1.x or the 2.x line): get_info on that line and the platform Harness; create with a toolset and a git skill (refused without a toolset) → the HelmRelease as agent-manager's write (its field manager, values.toolset, the skill pinned) next to the OCIRepository on the declared line → the render (the AgentTemplate with the Harness label and the display-name and icon-url annotations, the agent's RemoteMCPServer with the toolset header) → Ready on the Harness with the installed chart on the declared line (a chart off it fails, naming both), get_agent_status agreeing with status.harnesses[] → a turn as the person answering from the skill → update → refreshSkills re-pinning to list_skills' head → Agent Plugins on kagent: update_agent binding the upstream example package agentplugins/agent-plugins-example by commit with migrate-agent-plugin selected, Ready on a new revision, a new Session's agent listing that skill; the selection emptied, Ready again, the skill no longer listed → delete removing the release and the render; a template no Harness admits reported failed with the reason; a viewer's create Forbidden by the apiserver; the ServiceAccount holds no RBAC of its own beyond exactly what the lab's switches grant it: with agent-manager.skills.mintGitAuthSecret the Role on the minted skills Secret(s) (secrets, get and update, by name), with tenants.organizations.enabled the tenant reconciler's ClusterRole (the copies' kinds, the default ModelConfig's key Secret by name, Secret create, bind on agent-platform-tenant-flux, and the network policy kind of networkPolicy.flavor where the policy is on); a switch's grant missing in part fails, as does any row beyond them.
agents-gitops-test [email] Editing an agent applied from git, as the portal does it: a release agent-manager composed, applied with a Flux Kustomization's provenance labels and read back managed: gitops; the change's dry run on the platform's agent-manager gitops_owned in mode apply and unsupported in mode commit (no GitHub App pinned there), update_agent refused, the live release untouched. See Agents.
organizations-test [--skip-slack] [--gateway-image <image>|--gateway-binary <path>] Agent Platform organizations (platform.organizations; tenants-test is an alias): two AgentPlatformOrganizations in the root namespace agentlab-organizations, alice@lab.local and bob@lab.local each an editor of one by the group in their token: each creates and runs an agent in their own organization as themselves → every surface (the Kubernetes API, the kagent API through the edge, Slack; the Dev Portal skipped until giantswarm/backstage#3363) lists each member the same agents → the negative cross-organization tests, one verdict per surface and boundary, each with an admitted twin: Kubernetes RBAC (agents, templates, connectors, models, releases, Secrets, sessions), kagent's authorizer and A2A, Slack, muster's listing and calls, the other organization's model, in-cluster endpoints on 443 through the egress gateway → the management-cluster profile: the object-count ResourceQuota, a Kyverno PolicyException for agents-* that does not lift the sandbox admission policies, the credential provider's grants, each component's Secret reads (ate-controller, both credential providers, the connectivity hooks), the erasure of an Orphaned organization (skipped, HACKS.md U41). The two organizations stay for the next run. See The organizations proof.
toolsets-test [email] Declared toolsets end to end: agent-manager requires one and writes it as values.toolset on the agent's HelmRelease, the chart renders it as the header on the agent's own RemoteMCPServer (none for preset:none; none on a release without a toolset — implicit full access), muster resolves and refuses per request, agents see their toolset through a turn on kagent, a session-scoped per-server sign-in scopes a server's tools to the token's session (never the agent's), a subject-scoped one reaches the person's agents, the portal's Tools step and its create path through the portal's muster backend. --model-config picks the kagent ModelConfig the throwaway agents run on (a host model without an Anthropic key); --skip-chat skips the turns that need a model to answer; --skip-portal skips the portal's create path. See Toolsets.
egress-test [email] The agent egress proof: an agent on the platform Harness created through agent-manager as [email] (default the admin) with the read-only toolset and one declared origin (--origin, default https://example.com), Ready with that spec.egress → one turn whose bash tool probes from inside the actor: the declared origin reached; https://proxy.golang.org/, undeclared, answered 403 by the egress gate, its access log naming actor egress policy denied; muster (the agent's RemoteMCPServer URL) answering with its own status, no egress entry needed; raw TCP to github.com:22, by name and by address, reading no SSH banner (one fails the proof, naming the destination). --without-egress is the control (no egress entry: the declared stage fails); --ready-timeout bounds the boot (default 10m); --model-config names the ModelConfig. See The egress proof.
signin-test [email] The sign-in proof: on the platform Harness and the claude, codex and omp coding Harnesses (--harness picks), an agent created through agent-manager as [email] (default the admin) with the lab-oauth-fixture toolset, in a Session started through start_session as the person, calls a tool of the fixture before the person signed in → the turn pauses at input-required on muster's sign-in link (url_elicitation_request); decline completes it without the call, and after a headless sign-in accept resumes the same task and the call answers; a next turn completes after each. --model-config names the ModelConfig of the kagent, claude and omp agents; --timeout bounds each agent's boot (default 5m). See The sign-in proof.
chatgpt-test Codex on a person's own ChatGPT subscription (platform.chatgptSubscription), judged from the egress gateway's record: the ModelConfig chatgpt-subscription without a Secret and muster's App chatgpt → the admin's grant (--connect starts OpenAI's device-code sign-in and prints the page and the code, --connect-wait waits for it, default 15m; 0 prints and ends) → a codex agent's turn as the admin completes on their login, its backend requests forwarded, the sandbox holding placeholders and no refresh token → a developer's Session of the same agent refused by the credential provider → a non-codex agent's request to the backend refused by its egress policy → core_auth_logout app=chatgpt, the admin's next turn refused. --unconnected runs what needs no connection; --ready-timeout, --turn-timeout bound the boots and turns. See The ChatGPT subscription proof.
coding-test [email] The coding agents proof (platform.coding) on one coding Harness, --harness claude (the default), codex or omp, which the lab must render (platform.coding.harnesses): the Harness Ready from the platform values (kagent.harnesses; on claude no spec.claude.limits on it) → agent-manager's write-time checks: validate_agent on the Harness accepting a toolset with a muster write tool ([preset:read-only, tool:x_agent-manager_create_agent]; refusing it below agent-manager 1.14.0) and refusing an egress origin outside the Harness's list → Agent Plugins at write time: validate_agent on every Harness of the namespace accepting the upstream example package agentplugins/agent-plugins-example pinned by commit with migrate-agent-plugin selected; Anthropic's public Claude-format explanatory-output-style refused as Claude-format by validate_agent and create_agent on codex, nothing written; on kagent the same, SKIP naming agent-manager's plugin check on kagent where validate_agent accepts the package (gated on the capability, not the version); on omp the same with the run's Anthropic ModelConfig, SKIP naming agent-manager's plugin check on omp where validate_agent accepts the package, and on a codex run → create_agent on the Harness as [email] (default the admin) with that toolset (preset:read-only below 1.14.0) and the example package (on claude with explanatory-output-style beside it), Ready, its RemoteMCPServer Accepted with the toolset header, the Agent's spec.egress the Harness's origins → one Session started through start_session as [email] and bound to them in muster on every Harness and one turn whose shell tool probes the sandbox, judged stage by stage: on claude, Claude Code as uid 65532 cannot read the root harness's /proc/<pid>/environ or memory; the direct muster binding holds a placeholder credential; muster refuses x_agent-manager_delete_agent through the actor's binding as outside the toolset; on every Harness no forge token or bearer in the environment or under /data and /workspace, the model key kagent's placeholder (on codex the KAGENT_CODEX_MCP_CREDENTIAL_* too); a lookup of example.org through the cluster resolver and at 1.1.1.1 recorded (a record, not a gate); proxy.golang.org denied 403 at the egress gate and api.github.com, one of the Agent's origins, reached → the plugin skills: the agent lists migrate-agent-plugin and no other known plugin skill, from its context in a turn that calls no tool (on the mockllm the requests the runtime sent carry it) → on claude: explanatory-output-style's SessionStart hook ran, update_agent moving the agent to codex refused as Claude-format with the HelmRelease unchanged, the move to kagent refused the same, SKIP with the kagent row's reason, the move to omp the same, SKIP with the omp row's reason → an MCP call: the agent calls x_agent-manager_list_agents through its binding and finds itself listed → the turn gap: a heartbeat one turn starts in the sandbox does not tick in the 90 s without a turn and ticks again in the next → on claude the instruction-file fixture: one turn writes CLAUDE.md, AGENTS.md, .claude/settings.json with a SessionStart hook and permissions.allow, a skill, a subagent, a command and .mcp.json into the Session's directory, the next records what Claude Code loaded (a record) → the Session suspended and resumed, a turn reading the first turn's file and recalling its code word → the resumed turn's usage on its task status → a read-write share of the Session refused → update_agent with the example package's selection empty: Ready on a new revision, a new Session's agent without its skill. The agents and Sessions are removed on every exit path; --muster-tools meta (or tool names) gives the agent a tool selection on its muster binding, as a platform agent has (the Generic chart's muster.tools); --ready-timeout bounds each boot and every wait outside a turn (default 10m); --turn-timeout bounds each turn, by default the budget of the agent's model: 40m on a model a host model server serves (a model-manager ModelConfig, or one whose endpoint is host.agentlab.internal), 10m on a hosted one; every turn prints its deadline and where it comes from; --model-config names the ModelConfig the agent runs on (default on codex the in-cluster mockllm, the ModelConfig agentlab-mockllm on a mock Responses API scripted for these turns, which needs no OpenAI or Bedrock account, closing with a model stage: every model call answered by the script; --mockllm-binary names the static linux agentlab its container runs; else agent-manager's default; a real codex model is an OpenAI Responses API or Bedrock gpt-* one, such as codex-host, which configure --codex-host-model wires from qwen3-coder:30b or gpt-oss:20b on the host's Ollama; omp takes the Anthropic one claude uses). See The coding agents proof.
coding-test --part devctl platform.devctl: a claude agent created through agent-manager with x_devctl_pr_wait, x_devctl_pr_merge and x_devctl_release_wait as its toolset opens a pull request on --repository (default giantswarm/devctl-mcp-proof) from a workspace Session as the person, then calls the three tools in one turn, each once after the verdict before it, and reads the merge record back: merged by the person; beside it one pr_wait through muster with a progressToken answering green in one call past the MCPServer's timeout, its progress delivered. With --harness codex or omp the proof opens the pull request from this host as the person and that Harness's agent (codex on the mockllm unless --model-config) waits for its CI with one x_devctl_pr_wait call, devctl's server logging it as the person, no muster or GitHub token in the sandbox. Real GitHub and CircleCI. See The devctl part.
coding-test --part toolchains On --harness (claude by default, codex on the mockllm unless --model-config), as [email]: start_session with --toolchains (default go) mounts exactly those, pinned by digest, and one turn finds each one's tool under /opt/kagent/tools/<name> on its PATH answering its version; with go a fixture module builds offline; the Session's actor template in Substrate mounts exactly those digests. --other-version name@version adds a second Session with that toolchain at that version, which mounts that digest beside the first's, a suspend and resume of the first, which keeps its revision and digests, and a move of the agent to a new revision while the first is suspended, after which its resume runs a new prepared revision with the same digests. Needs agent-manager's toolchains.repository and a kagent controller that mounts session tool images and admits it. See The toolchains part.
skills-test [email] The golden boot under Substrate's egress gate: an AgentTemplate on the Go ADK Harness with one git skill pinned to a full commit of a public repository (giantswarm/agent-skills), written below the agent chart on purpose; Ready on the Harness, then one turn through the edge as the user that names the skill and answers a fact only its SKILL.md has. A failed boot prints the evidence (the Harness conditions and warnings, Substrate's ActorTemplate, actor and worker as the controller reports them, the controller, atenet and worker log lines, the versions) and boots the same template without the skill as the control. A second turn in a new Session has the agent's bash tool run git ls-remote of the source from the sandbox, without Authorization and with kagent's placeholder, to show whether the Session's egress carries the source's credential. --skill-fixture boots the skill from the lab's own private git host behind the edge (skillhost.<domain>, no GitHub token; a Secret the proof creates and never prints) and prints golden fetch: credential sent and session request: no credential from the host's record of every request, or FINDING: session request: credential sent with the request that carried it. --skill-repo and its siblings boot another fixture, --skill-secret a private one; there the probe reports the host's exit codes. --ready-timeout bounds the boot (default 10m); --model-config picks the kagent ModelConfig the agent runs on (a host model without an Anthropic key). See The skills proof.
a2a-test [email] Turns over native gRPC through the edge as Swarmgeist and the portal backend drive them: the GRPCRoute and its JWT policy Accepted; no token refused at the edge (Unauthenticated; a plain POST gets 401); a forged x-user-id beside a valid token replaced; ListAgents listing the proof's Agent Ready with its display-name and icon-url annotations; start_session idempotent on requestId; SendStreamingMessage with the HITL extension streaming an answer; a requireApproval tool call paused at input-required → approved → completed with muster logging the call under the person, rejected → ended without the call; CancelTask on a running turn canceled server-side and a following turn answered. --ready-timeout bounds the fixture agent's boot (default 4m). See Turns through the edge.
substrate-step-test [email] --to <release> Agents keep their conversation and durable volume across a Substrate release: the Substrate before the step read off the atelet, the WorkerPool and ate-api-server; an agent on the platform Harness with a conversation of three turns (a codeword, a marker its bash tool writes to /data, the codeword asked back), suspended; the substrate and substrate-crds components pinned to --to through a values overlay of the run's own and the platform upgraded in place, by default under a drain (from the candidate's ate-api-server Ready, every worker pod deleted every 8 s for a minute, the Session's resume issued during it once the release before's ate-api-server pods are gone, and the candidate's RPC log read: a ResumeActor of the Session's actor must have arrived while the drain ran; --no-drain skips it), the workers rolled onto the new worker image; the Session resumed, answering the first turn's codeword and reading the marker back; the template's golden taken again on the new release (in the drain case every golden of the lab, with no terminal error) and a new Session answering. PASS or FAIL with the cause per step (Substrate 1.8.0's refusal of a 1.7 snapshot named as the restore refusal), the agent removed and the lab back on the chart's Substrate (--stay leaves it on --to). --step-dev-image <target>=<image> adds dev images for the step alone, --ready-timeout and --retake-timeout bound the golden boot and the re-take (default 10m each), --model-config the ModelConfig. See The Substrate step proof.
kagent-step-test [email] --to <kagent release> | --chart <agent-platform release> An agent, its conversation and its durable volume survive a step of the kagent line: the kagent line before the step read off the meta chart, the kagent HelmRelease and the controller; an agent on the platform Harness with a conversation of two turns (a codeword, a known file written to its durable volume), suspended; the kagent and kagent-crds components pinned to --to through a values overlay of the run's own, the meta chart moved to the candidate --chart (a chart-only candidate that keeps kagent included), or both, and the platform upgraded in place; the Session resumed, a turn answering the codeword and reading the file back unchanged, a suspend and a resume more, the Session and the agent deleted. PASS per step, or FAIL naming the stage that failed (step, resume, turn, suspend, delete) with the cause; the lab back on its own configuration (--stay leaves it on the step). --step-dev-image <target>=<image> adds dev images for the step alone, --ready-timeout bounds the golden boot, --model-config the ModelConfig. See The kagent step proof.
harness-matrix-test What each Harness does with a Shared subagent binding (tools[].subAgent) and, on claude, with a Claude-format package's subagents and commands. Per Harness (--harness, default kagent, claude, codex, omp) and case (--case, default all): shared-prompt (a child with a prompt and a model), shared-skill (the child selects the example package's migrate-agent-plugin), shared-mcp (the child binds kagent's kagent-api server), shared-nested (the child binds a child) and, on claude only, plugin-parts (anthropics/claude-code plugins/feature-dev at the lab's pinned commit: the agent lists its plugin commands and subagents and invokes code-explorer). Each cell records the controller's refusal (the Agent's condition) or one turn, judged by a codeword that only the child's system prompt holds, so a reply carrying it shows the child ran. Every cell is an observation, not a pass or fail; the table is printed and written to state/harness-matrix.txt. On codex without --codex-model-config the shared-prompt turn runs on the mockllm, whose script spawns the child with spawn_agent, waits for it, and answers the child's own request with the codeword its developer instructions carry; the other codex cells boot and take no turn. --mockllm-binary names the static linux agentlab its container runs; --ready-timeout bounds each golden boot of a parent (default 10m); a boot kagent reports crashed and starts over gets the timeout again, so a Harness whose every boot crashes reads REFUSED with kagent's ActorTemplateFailed once its retry budget is spent. The agents are removed on every exit path. See The harness matrix.
substrate-tunnel-test A gRPC call through atenet-router to an HTTP/1.1-only actor fails at once: an ActorTemplate on the lab's alpine (busybox nc on port 80, closing on the HTTP/2 preface unanswered) on the platform Harness's worker pool, reached through a port-forward to the router's HTTP port with the ate-target-actor header; GET /readyz over HTTP/1.1 and h2c answered (h2c downgraded at the actor); a POST with content-type: application/grpc over h2c must fail with HTTP 502 or grpc-status: 14 within --promptly (5s), not with --client-timeout (30s), which is atunnel's relay keeping the tunnel open after the actor closed. --substrate <release> pins the substrate and substrate-crds components to the release for the run, unless the lab and its worker pods (where atunnel relays, read off the pods) run it already, and returns the lab to the chart's Substrate after (--stay leaves it); --ready-timeout bounds the golden boot and the route (5m). The actor and template removed by the same run. See The Substrate tunnel-close proof.
klaus-gateway-test [email] The Swarmgeist proof through klaus-gateway's Slack adapter: the gateway runs on the host (by default the release the lab's meta chart runs as its klaus-gateway component — the version its HelmRelease applied, else the newest the chart's range admits, printed at the start — --gateway-image overrides it; a local build with --gateway-binary) against the lab's public gRPC target grpcs://agentgateway.<domain>:<port> with the lab CA and the JWT policy at the edge, Slack in events mode on a fake Web API the proof serves, the person linked by a record in its OBO link store carrying the user's Dex id_token. Messages are signed Events API callbacks, the Ask an agent here shortcut a message_action, Approve and Deny Block Kit clicks: @bot agents lists the fixture's AgentTemplate and hides one no Harness admits, the picker its Select button opens hides it too, and a submitted selection of it is refused with the reason; an unlinked person invoking the Ask an agent here shortcut is told to sign in first, gets the Sign in button from @bot login and reaches no controller; every conversation opens through that shortcut and the picker (klaus-gateway 4.13.0 and later answer a mention outside a conversation with the help); one streamed turn under the template's display name and icon binds the thread to one Session and is attributed to the person at muster; a requireApproval binding pauses the task at input-required, Approve resumes it to completed, Deny in a second thread ends one without the call; a stop reply has the task cancelled at the controller and the thread goes on; a restart on the stores keeps the thread → Session mapping; a restart in the middle of a turn, with the edge out of the restarted gateway's reach for 45 s, still posts the answer in its thread without a reply. --gateway-port (default the lab's own block: 18090, plus ten per port its apiServerPort sits above 6443, so two labs run the proof at the same time; the admin endpoints and the in-process fake take the next two, the loopback gate in front of the edge the third), --slack-fake-binary (the static Linux agentlab the fake's container runs while the component is on; default this binary), --run-dir keeps the stores, keys and the gateway's log, --ready-timeout bounds the fixture's boot (default 10m), --model-config picks the kagent ModelConfig the fixture runs on (a host model without an Anthropic key), --harness the Harness whose admission labels the fixture carries and whose Ready entry the boot waits for (default the platform Harness kagent; a coding Harness such as claude with --model-config naming a ModelConfig it accepts). A 0.x gateway image is the documented negative. With platform.klausGateway on, the meta chart's klaus-gateway component follows: the Role scoped to the OBO link Secret and no store volume, two links written through the gateway's store package with the lab's key, the pod deleted and its replacement Ready with the same links within 60 s, one Slack turn through the pod over the in-cluster target, the fake then a container on the kind network; then an agent whose egress declares one origin fetches an undeclared host, and the component's egress refusal notice must reach its creator as one direct message naming the host and port as plain text (agent-platform 4.154.0 and later); --refusal-only runs that step alone. --part refresh runs the refresh race on the component instead: a real sign-in through muster and Dex, two replicas, and rounds in which both refresh the person's expired link at the same instant; every pod must still answer signed in, the refresh token rotate each round and muster's log record no reuse (The refresh race). See The Swarmgeist proof and klaus-gateway.
decisions-test [email] The proof of klaus-gateway's decisions (POST /decisions): the gateway on the host (--gateway-image, default the release the lab's klaus-gateway component runs, as for klaus-gateway-test — every release from 4.1.0 serves them; --gateway-binary for a branch) with its reviews endpoint, a ServiceAccount token with audience klaus-gateway the lab's API server vouches for (TokenReview), the fake Slack Web API. A team decision answered by a Choose click, a person's (found by email, a direct message) in the modal with an option and own words, a team decision by a reply in its thread — each answer muster's core_mcpserver_list called as the user, attributed at muster; a tool's refusal as the status line with the decision open; a close as defaulted that refuses a later click. No agent, no model.
beekeeper-decisions-test The proof of beekeeper's decisions in Slack: beekeeper serve on the host (--beekeeper-version, default the first release that puts decisions to people, its binary and CRDs; --beekeeper-binary for a branch) over the lab cluster's beekeeper resources, with its mailboxes in a throwaway Postgres container, registered in muster as the MCPServer beekeeper with the person's token forwarded; klaus-gateway and the fake Slack as decisions-test runs them, beekeeper calling the gateway with a ServiceAccount token of audience klaus-gateway. A decision for the admin filed through muster arrives as a direct message: another user's click is refused, the admin's answers it; a decision for team:agentlab is answered by another member's thread reply; one nobody answers closes with its default at its due time and refuses a late click; one its filer withdraws (note_done) loses its buttons; note.answered (via slack) and note.defaulted Events in beekeeper-agentlab. The admin's guide (local:agentlab/Guide, registered through muster) converses with them: its converse opens a direct message, their reply in its thread reaches the guide's mailbox via slack, its answer lands in the same thread; another user's send_message to the guide is refused, and with the guide's RosterEntry gone a reply gets the thread's not-delivered note. With --local-beekeeper <host build>, step 9 runs the admin's own machine: a configuration and state of its own whose central context is the lab's muster, its watch --standby (the host's chores off) and a background Claude Code session (claude --bg, haiku) registered as its guide; the guide's beekeeper converse opens a direct message, the admin's reply in its thread reaches the running guide session through the watch's subscriptions/listen on beekeeper://mailbox/<person>, stamped Message from <person> via slack, and the guide's own beekeeper converse answer lands in the same thread. --guide-only runs steps 6 to 9 without the decisions. --team-group makes the team's Slack user group @team-agentlab, the admin alone in the fake's usergroups.list, who answers its decisions (serve.groups): step 2 refuses the other member's note_answer through muster, and the admin's thread reply answers it. It needs a beekeeper with serve.groups and a klaus-gateway with GET /groups/{group}/members/{email}. Without --local-beekeeper, no agent, no model.
beekeeper-central-test The proof of beekeeper's central resources in the local binary: beekeeper serve on the host as beekeeper-decisions-test runs it (--beekeeper-version, default the first release whose local binary lists, answers and withdraws the central instance's notes, parks on and folds into one; --beekeeper-binary for a branch, used by serve and both machines), with the Environment agentlab-shared and its MergeLane, and two local beekeepers, one per lab user, each with a configuration and state of its own whose central context is the lab's muster. A lease the admin's machine claims is refused to the other user's with its holder and listed there; a hold on the central lane set on one machine stops the other's merges and is listed as central; two merges into the lane take their turns through muster, the second's once the first merged and left, and the other user cannot take the first out; the admin's registered agent appears on the central roster after one watch --once; with the central instance unreachable a central claim exits 69 and the machine's own resource is still claimed; a decision the admin files for the team is listed for the other user, who answers it, and it is closed for both; a decision the admin's CLI files with note add --for the other user is listed and queued as agentlab#<n> on the other's machine, refused to its filer's note answer and answered there, and the admin's standby watch, down while it was answered, prints its answer.recorded line from the central feed exactly once across two restarts; a second one is refused to its addressee's note done and withdrawn by its filer; the admin's registered agent, given a task, folds a decision into an open central one with note add --fold-into agentlab#<n> (the other user lists one note with both questions) and parks on it with agents park --on agentlab#<n>, which watch --once leaves parked while it is open and, once the other user answers, says AGENT RESUMABLE exactly once with the answer word for word. --part notes runs those last two steps alone. --chart <dir or oci:// reference> runs serve in the lab cluster from the beekeeper chart instead, as an installation runs it: the chart's CRDs, the Environment and lane from its environments and lanes values, the mailboxes in a CloudNativePG database of its own (beekeeper/beekeeper-pg, read through database.secretName), the lab Dex through the dex-localhost sidecar and the lab CA (initContainers, extraEnv, extraVolumes), and muster reaching it at its Service; everything it created, the CRDs the lab lacked included, is removed afterwards. Claude Code session variables the proof inherits are dropped from the machines' environment. No agent, no model.
beekeeper-subscriptions-test The proof of resource subscriptions relayed per user by muster (MCP 2026-07-28 subscriptions/listen): beekeeper serve on the host as beekeeper-central-test runs it (--beekeeper-version, default a release whose serve offers subscribable resources; --beekeeper-binary for a branch) behind the lab's muster with the person's token forwarded, and two lab users who each open a subscriptions/listen stream on muster's edge for their own beekeeper://mailbox/<email>. Both streams are acknowledged; a message into one person's mailbox reaches that person's stream, with its subscription ID, and the other's next update is its own; the second user's subscription to the first's mailbox is refused by beekeeper, which muster reached as that user. Needs muster with the relay (muster#1348). No agent, no model.
backstage-test [email...] The headless Backstage sign-in and the muster hop with that user's own forwarded token, including the per-server Sign in challenge and the MCP servers page's grouping; with agents on, the Dev Portal's Agent Platform pages on kagent API v2 as a person drives them — the wizard's create path through agent-manager (skill discovery, the dry run, Deploy as the first platform-admin), the agents list per user, a streamed and a resumed conversation, HITL and Stop, edit, skills update and delete; with managed models, the Models page's model-manager read and write through the same muster hop as the person and a viewer's write refused; everything it creates removed on every path (default: every user). --agent <namespace>/<name> runs the Agent Platform half against an existing agent instead: create, HITL, edit and delete skipped, the roster and the chat proven on it, the agent left in place. See Backstage.

Cleanup

Command What it does
down Destroy the kind cluster. certs/ is kept and the trust stores are untouched. With --purge, remove the lab for good: the cluster, then its CA from the trust stores (sudo may prompt), then its directory, its configuration and its registration, so list no longer shows it. It asks first on a terminal; off one it needs --yes. A lab outside agentlab's folders (a checkout down would otherwise move) is refused before anything changes.
untrust Remove exactly the lab CA from the system and browser trust stores.
platform-down Remove the agent platform in the chart's ordered teardown, leaving Dex and the cluster alone. The workspace storage goes with it (its namespace, classes, CSIDriver and CRDs), and the driver's mounts and volume bytes are cleaned off the node; down cleans the node the same way before the cluster is deleted.

Advanced

Command What it does
platform Install the agent platform on a running cluster: the lab Dex re-applied first (the users and the GitHub sign-in as agentlab.yaml has them; unchanged, a no-op), then one idempotent upgrade-or-install of the agent-platform chart in its lab shape through the embedded Helm (the helm upgrade --install --wait of Helm 4, in-process), then wait for every component. A re-run that would install the same chart version with the same values writes no Helm revision. up runs this when the platform is enabled. On the dev channel it first re-resolves the branch's newest build (and installs Substrate); --pin freezes the recorded build instead, --pin=false follows the branch again. Like up, it ends with the trust and portal questions on a terminal, and takes the same --trust/--open.
certs Generate the lab CA and Dex server cert, re-minting only what config or policy require. --force regenerates everything and breaks a running cluster's trust.
render Render every manifest from agentlab.yaml into state/ without applying anything. It renders the agentlab.yaml of the current directory into that directory, a copy of a registered lab's file included (named on stderr); that lab renders from its own directory or with --lab.
reload Re-render and re-apply the Dex config after editing agentlab.yaml (users, passwords, groups, the GitHub sign-in), without touching the platform.
self-update Replace the binary with the latest GitHub release, once its cosign Sigstore bundle verifies (see below). --check only reports the running and the latest version, exit status 125 when a newer one exists. --pre follows release candidates too (below).

Backstage has no command of its own: it deploys with the platform (backstage.enabled in agentlab.yaml and agentlab up), and agentlab open portal is how you reach it.

configure flags

configure probes the machine on every run and applies what it finds before asking anything — see what it discovers. The flags pin a value regardless of the discovery, with or without --defaults; nothing else in an existing file is touched.

Flag Effect
--defaults Skip the form; keep the current values (or write the canonical lab) plus what the discovery finds.
--cluster-name <name> The lab's name (clusterName): its kind cluster, its registration and --lab <name>. A new lab defaults to agentlab, or to the first free agentlab-<n> when another lab on this machine holds it; a name another lab holds is refused.
--accessible Prompt-per-question form mode, for screen readers and plain terminals.
--platform[=false] Enable or disable the agent platform. Off gives a bare kind + Dex OIDC sandbox.
--agents[=false] Enable or disable the agents runtime (kagent, part of the platform install).
--observability[=false] Enable or disable the observability stack (Prometheus + mcp-prometheus).
--backstage[=false] Enable or disable Backstage (implies the platform).
--telemetry[=false] Enable or disable the anonymous usage data the lab and its portal send to TelemetryDeck (telemetry, on by default); the form asks the same as its last question. See Usage data.
--portal <mode> The Backstage portal mode (backstage.portal): agent-platform (the default) serves the Agent Platform shell, classic the classic Platform Portal alone. The next agentlab platform rolls Backstage onto it. See Portal mode.
--installations-page The portal's Installations page (backstage.installationsPage): page:gs/installations and its nav item join the lab's enabled extensions, for the portal's installations e2e specs (AGENTLAB_INSTALLATIONS_PAGE=1). The next agentlab platform rolls Backstage onto it; --installations-page=false turns it off. See The portal's browser suite.
--chart-version <x.y.z> The agent-platform chart release to install, an exact version (default: the release this agentlab was verified with, config.DefaultChartVersion).
--chart-path <dir> Install the agent-platform chart from a local checkout's helm/agent-platform directory instead of the pinned release, with the checkout's connectivity chart beside it (pushed into the lab registry at the meta chart's version; refused without the sibling directory); --chart-path "" clears it. See Installing an unreleased chart.
--chart-branch <branch> The dev channel: follow this agent-platform branch's newest dev build — resolved now and on every up/platform, written to chartVersion; --chart-branch "" returns to the stable channel and, unless --chart-version pins one in the same call, puts the default release in place of the branch's build the resolver left in chartVersion — a lab that leaves the dev channel does not keep its last dev build. Mutually exclusive with --chart-path; implies Substrate. See Dev channel.
--upgrade-seed[=false] Seed an upgrade proof: a released chart below agentlab's floor (agent-platform 4.93.0) installs in the shape of its line instead of being refused; --upgrade-seed=false clears it. See Upgrade proofs from an older line.
--released The restore step before handing a lab on: drop the proof state — every platform.valuesFiles overlay, platform.devImages, a chartPath, the dev channel (a branch's dev build in chartVersion goes back to the default release) and platform.vmManager.imageDir — printing each dropped setting; a release or release candidate pin stays. The following agentlab platform installs the released platform, and agentlab status shows no overlay line. Not with --chart-path, --chart-branch, --adopt-chart or --vm-manager-image-dir.
--adopt-chart Write the chart the running lab cluster carries into chartVersion, so agentlab.yaml says what the cluster runs — the restore step for a config put back from a copy apart from its cluster (agentlab status names the drift). A release puts the lab on the stable channel at that release (a chartPath or chartBranch the config named gives way); a dev build of the followed branch pins the dev channel at it. Refused for a chart directory's build, whose version is a placeholder, and when the lab is not running. Not with --chart-version, --chart-path or --chart-branch.
--model-manager[=false] Pin managed models on or off (needs agents). Without the flag a first configure follows the host model servers the discovery finds, and a later one keeps the recorded choice: configure --defaults never turns on what the lab was configured without. Off is the chart's component off too: the rendered values state components.model-manager.enabled: false, since the meta chart runs model-manager by default from 4.24.0 (with no backend).
--model-manager-backends ollama,lmstudio Pin the host model servers, in order (ollama, lemonade, lmstudio); the first is model-manager's default backend.
--vm-manager[=false] Run the platform's VM provisioner (vm-manager) as a pod of the node, or turn it off; needs /dev/kvm and /dev/vhost-vsock on this machine (a machine without them turns it off on its own).
--vm-manager-image-dir <dir> The image directory the vm-manager pod boots from: a vm-manager checkout's images/build after make -C images, mounted into the node at agentlab up (agentlab down && up after changing it).
--muster-pod-network[=false] Run muster as an installation does — on the pod network at two replicas behind the edge, a PodDisruptionBudget of minAvailable: 1, the issuer through the dex-localhost sidecar — or return it to the host network at one replica (the default). See muster on the pod network.
--klaus-gateway[=false] Run Swarmgeist (klaus-gateway) as the meta chart's in-cluster component, or turn it off: A2A on the in-cluster controller target, Slack on a placeholder Secret (its Web API the proof's fake), the OBO link store in a Secret (needs agents). See klaus-gateway.
--github[=false] Register GitHub's hosted MCP server with muster as MCPServer github, signed in to as the person through an OAuth App or GitHub App client read from the Secret platform.github.secret names (default agent-platform/github-oauth-client, keys client-id and client-secret), or remove the server. agentlab never reads the client's values: the operator places the Secret. See GitHub as the person.
--github-signin-client-id <id> GitHub sign-in through the lab Dex: the GitHub App's client id, which turns the sign-in on ("" turns it off). The App's callback URL is the lab Dex's own callback (<issuer>/callback); its client secret is the Secret dex/github-signin-client (key client-secret), placed by the operator's secret tooling and never read by agentlab; platform.githubSignIn.secret names another Secret in the dex namespace. See GitHub sign-in.
--github-signin[=false] Turn the GitHub sign-in on (once a client id is recorded) or off; the client id and the Secret stay for the next time.
--github-signin-orgs giantswarm,other GitHub sign-in: admit members of these GitHub organizations only, their teams as the token's groups (<org>:<team-slug>); empty admits any GitHub account.
--devctl[=false] devctl's MCP server from its released chart (--devctl-version, default the release agentlab names), registered with muster (x_devctl_*) and acting on GitHub as the person with their grant of the workspaces' github instance App (needs agents and that instance), or off: the release uninstalled. coding-test --part devctl proves it. See devctl's MCP server.
--coding[=false] Coding agents, or off: the chart's kagent.harnesses renders the coding Harnesses (claude, Claude Code run headless, unless --coding-harnesses names others), which coding-test proves (needs agents; agent-platform 4.110.0 or newer). See The coding agents proof.
--chatgpt-subscription[=false] A person's own ChatGPT subscription as a codex model, or off: the ModelConfig chatgpt-subscription (chatgptOAuth, no apiKeySecret; an object platform applies from agent-platform 4.175.0-rc.22, a kagent.modelConfigs entry before it), muster's token encryption, and kagent and muster at the first releases that serve it where the chart's ranges admit older ones; --chatgpt-subscription-model <model> names the model (default gpt-5.6-luna). chatgpt-test proves it (needs the codex coding Harness and --workspaces; agent-platform 4.164.0 or newer). See The ChatGPT subscription proof.
--organizations[=false] Agent Platform organizations (on by default), or off: the chart's agentPlatformOrganizations.enabled with the LLM listener installs agent-platform-operator and the organizations' roles and admission policies, up keeps the default organization agentlab-organizations/default, and --organizations adds organizations-test's members alice@lab.local and bob@lab.local (agentlab reload renders them into Dex). Needs agents and agent-platform 4.175.0-rc.4 or newer; on an older chart it has no effect. See Organizations.
--coding-harnesses <runtimes> The coding Harnesses --coding renders, by runtime: claude, codex, omp or several (claude,codex,omp), each named after its runtime; "" returns to claude alone. codex needs a kagent line whose chart takes runtime: codex; omp (oh-my-pi, a BYO Harness on the kagent line's omp-harness adapter) one whose chart takes runtime: byo and an image from --coding-images. A dev build of a coding Harness's image swaps in through platform.devImages claude-harness / codex-harness / omp-harness; claude-harness and codex-harness need kagent from the same build, whose controller pins the CLI version (Coding Harness targets).
--coding-images <runtime>=<image> The image of a coding Harness the chart has none of its own for, pinned by digest (platform.coding.images): omp=gsoci.azurecr.io/giantswarm/kagent/omp-harness@sha256:…, the runtimeImages.ompHarness.digest the release's kagent chart stamps. A dev image (platform.devImages omp-harness) wins over it.
--serving[=false] Serve models on llm-d in the lab, or turn it off: the KServe llmisvc controller and its CRDs, the well-known runtime configs, the connectivity chart's serving slice with the models Gateway, model-manager's kserve backend and one CPU preset of the lab's (needs agents; installs cert-manager; agent-platform 4.44.0 or newer). See Model serving on llm-d.
--workspaces[=false] Install the workspace storage, or turn it off: an in-cluster NFS server, the NFS CSI driver nfs.csi.k8s.io with its controller behind an Envoy mTLS proxy only Agent Substrate's API server may reach (its pod-identity certificate), and the read-write-many StorageClass agentlab-workspaces; needs the host kernel's nfsd and nfs modules (available, not loaded by hand: the kernel loads them); under rootless Podman on Linux the hostpath CSI driver backs the volumes instead (--workspaces-storage). Needs the agents (--agents): the workspaces live in kagent's namespace, and the lab refuses them without it. See Workspaces.
--workspaces-provider <github|gitlab|github,gitlab> The workspace-manager's provider instances: github (the default, "" too), a GitHub App of the lab's own on github.com, the instance once the App's ids are set and the Secret agent-platform/workspace-github carries private-key and client-secret; gitlab, a GitLab's OAuth application; or both. With workspaces on, the base URL https://workspace-manager.<domain>:<gatewayPort> and its route are in place regardless; each instance's App registers muster's /oauth/proxy/callback. See Workspaces.
--workspaces-csi-companion[=false] With workspaces on, the connectivity chart's CSI companion is Substrate's workspace driver (platform.workspaces.csiCompanion): its efs preset running the lab's NFS plugin, the lab's NFS driver serving the volumes as efs.csi.aws.com — the lab path for proving a companion chart change; needs agent-platform 4.128.0 or newer. Switching takes the workspace storage apart first, its volumes with it. See The CSI companion.
--workspaces-storage <nfs|moosefs|hostpath> What backs the workspaces' volumes (platform.workspaces.storage): nfs, an NFS server in the cluster and csi-driver-nfs, which needs a rootful engine on Linux; moosefs, a MooseFS master and chunkserver in the cluster and moosefs-csi, mounted through FUSE, which runs under rootless Podman on every node; hostpath, csi-driver-host-path, a directory of the single node per volume, bind-mounted, which runs under rootless Podman but cannot let a pod and an actor hold one volume; "" (the default) picks moosefs under rootless Podman on Linux without the companion and nfs elsewhere. moosefs and hostpath refuse --workspaces-csi-companion, hostpath also substrateNodes above 0. See Rootless Podman: the moosefs and hostpath storages.
--workspaces-cleanup-after <duration> How long a Session's directory on a workspace volume outlives its last turn (platform.workspaces.sessionsCleanupAfter, the workspace-manager's sessions.cleanupAfter): a Go duration; "" returns to the chart's 30 days.
--workspaces-refresh-after <duration> The refresh threshold (platform.workspaces.syncRefreshAfter, the workspace-manager's sync.refreshAfter): a Session started on a workspace whose last sync is older requests a sync; a Go duration; "" returns to the manager's 1h.
--workspaces-github-app-id, --workspaces-github-client-id The public ids of the github instance's App (platform.workspaces.github); its private key and client secret never pass through agentlab.
--ai-key-source <ref> Where the Anthropic key of the agents' default ModelConfig lives: a reference beekeeper secret copy resolves (op://<vault>/<item>/<field>, or <file>#<path> of a SOPS file), never a value. Every up and platform place it into the Secret kagent/kagent-anthropic through beekeeper (on PATH, or configure refuses), so a recreated lab carries the key without a manual step; --ai-key-source "" clears it (the key then comes from $ANTHROPIC_API_KEY, else a placeholder). See The model.
--github-token-source <ref> Where the GitHub token of the portal's skill discovery and agent-manager's skill resolution lives: a reference beekeeper secret copy resolves, as for --ai-key-source, never a value. Every up and platform place it into the Secret agentlab-github-token in agent-platform through beekeeper, so the lab calls GitHub authenticated while agentlab never reads the value; --github-token-source "" clears it (GitHub is then called as the skills GitHub App when one is recorded, else unauthenticated). The connectivity chart's migrate run, a suspended CronJob a post-install or post-upgrade hook starts, reads no token. See Agents.
--github-app-id <id>, --github-app-installation-id <id>, --github-app-private-key-source <ref>, --github-app-client-id <id>, --github-app-client-secret-source <ref> The skills GitHub App agent-manager resolves skills with (githubApp): its public ids and a reference to its private key that beekeeper secret copy resolves, never a value. Every up and platform write the Secret agentlab-github-app in agent-platform (the key placed by beekeeper) and wire agent-manager.skills.github.app. --github-app-installation-id alone takes the App id and private key of the workspaces GitHub App (platform.workspaces.github), so a lab whose workspaces App is installed records one public id; with the workspaces App's client id and client secret source recorded, the portal's skill discovery calls GitHub as the same App (Secret agentlab-portal-github-app). Not together with --github-token-source; "" on all three clears it.

Environment variables

Variable Read by Effect
ANTHROPIC_API_KEY up, platform Becomes the Secret kagent/kagent-anthropic at deploy time while agentlab.yaml records no aiKey.source; never written to agentlab.yaml or state/. With a source the Secret is placed from it through beekeeper secret copy --to-secret instead, and without either it holds a placeholder (the ModelConfig resolves, agent turns fail until the key is placed). See Agents.
GH_TOKEN, GITHUB_TOKEN self-update, the update check Read GitHub's releases with the token, GH_TOKEN first, instead of under GitHub's anonymous rate limit; without either, the token gh auth token prints. up, platform, discover, backstage-test, agents-test and the migration rehearsal never read it: the lab's consumers get a token only from githubToken.source (or call GitHub as the skills GitHub App). See Agents.
<name> per extraModels[].apiKeyEnv up, platform The key for that model config, same handling. See Models.
NODE_USE_SYSTEM_CA=1 Node >= 22.15, Claude Code Makes Node honor the system trust store after agentlab trust. Older Node: NODE_EXTRA_CA_CERTS=~/.local/state/agentlab/labs/<lab>/certs/ca.crt. See TLS.
KUBECONFIG your shell Never read by the lab: its embedded Helm and Kubernetes client are built from state/kubeconfig alone. KUBECONFIG=state/kubeconfig kubectl ... is the lab's view from a shell. A file it names that is a copy of this lab's admin kubeconfig (every entry kind-<clusterName> — a lab lease holds one) is refreshed by up and every other cluster-facing command once the cluster behind it was recreated, so a copy taken before down and up reaches the new cluster without a manual step; your own kubeconfig, ~/.kube/config and another lab's are never written, except that up removes a kind-<clusterName> context, cluster and user of the recreated lab whose CA is the previous cluster's (the lab's own kubeconfig, or its lease's copy, is the one to use).
AGENTLAB_TELEMETRY_OPTOUT, DO_NOT_TRACK=1 every command Disable the anonymous usage signals. See Usage data.
AGENTLAB_TELEMETRY_TESTMODE=1 every command File the signals as test data, for work on the lab itself.
AGENTLAB_TELEMETRY_DEBUG=1 every command Log what happened to the signals (a failed or rejected delivery, an endpoint out of reach) on stderr; telemetry prints nothing otherwise.
AGENTLAB_NO_UPDATE_CHECK=1 every command Silence the newer-release hint (below).

Keeping agentlab current

agentlab self-update installs the latest GitHub release for your OS and architecture — the command muster and mcp-kubernetes have too — at ~/.local/bin/agentlab ($XDG_BIN_HOME). A release installed anywhere else (~/go/bin, a download folder) moves there on the first self-update, also when nothing is newer, and leaves a link at its old path, so either order of the two folders on PATH runs the one binary; self-update says when ~/.local/bin is not on PATH. agentlab self-update --check only reports the running and the latest version, with exit status 125 when a newer one exists (for scripts). A binary without a release version (agentlab --version says dev) is refused: reinstall it from a release or with go install. A go build from a checkout carries Go's pseudo-version (v0.19.3-0.20260908…-8536d36) and is treated as what it is: after the tag before it, before the tag after it.

Every release binary is verified before it is installed. CI (the architect orb) signs each agentlab-<os>-<arch> with cosign — keyless, the CircleCI pipeline's identity, recorded in the Rekor transparency log — and publishes the signature next to it as agentlab-<os>-<arch>.bundle. self-update downloads both and installs the binary only after the bundle verifies against the Sigstore public-good trust root for a CircleCI build of github.com/giantswarm/agentlab (the shared selfupdate-cosign validator, the one muster and the other Giant Swarm CLIs use). A release without a bundle for your platform is refused before anything is downloaded; a download that does not match its signature is refused before anything is written. Either way the installed binary stays as it is, and the error says why. The hint below installs nothing, so it does not need the bundle.

Release candidates

Release candidates (v0.97.0-rc.1, published as GitHub prereleases) are left out unless you ask for them: agentlab self-update --pre installs the newest of the release candidates and the stable releases, in semver order (v0.96.0-rc.6 < v0.96.0 < v0.97.0-rc.1), verified like any release. A release candidate installed by hand thereby moves to the next one, or to the stable release that follows it; --check --pre reports the same without installing. Without --pre nothing changes: only stable releases count, and an rc binary with nothing stable newer is pointed at --pre. The hint below names stable releases only.

Every write of agentlab.yaml records the agentlab that wrote it in the header (# Written by agentlab v0.97.0-rc.1.), a comment older releases read past. A field this binary does not know is refused, naming that version and the command that installs it: agentlab self-update, or agentlab self-update --pre when the file was written by a release candidate.

Every command also starts with a one-line hint on stderr while a newer release is out — devctl's per-command check, with two deliberate differences:

  • A hint, never a gate. An outdated agentlab runs every command the same; nothing waits for you to update.
  • It gives up fast. The GitHub round trip is capped at two seconds, and its answer is cached for an hour under your user cache directory (~/.cache/agentlab/latest-release.json on Linux, ~/Library/Caches/agentlab/ on macOS). A failed attempt is remembered for ten minutes, so a machine without internet is not held up on every command, and the last known answer keeps being shown meanwhile.

self-update and the hint read the releases with a token when one is at hand: GH_TOKEN, else GITHUB_TOKEN, else the token of gh's signed-in account (gh auth token). Without one the call is anonymous, under GitHub's limit of 60 calls an hour shared by every machine behind the same address; self-update says which it used, and when that limit is spent its error says the call was anonymous and which variable to set.

AGENTLAB_NO_UPDATE_CHECK=1 silences the hint (self-update itself always works); dev builds never check.