A coding agent in a Luxor sandbox, one command away. Run Claude Code, Codex or any of 26 coding agents in a box of their own: one command makes the box, starts the agent and drops you in. Close the laptop and it keeps working. Pick it up again from your phone. Let it run with approvals off, in a box that is nobody's machine.
lux # pick an agent (or create one) and jump into it
lux claude # your Claude Code agent, reachable from claude.ai/code
lux codex # Codex
lux crush # or copilot, gemini, qwen, goose, kimi, omp, adal, hermes, openclaw
lux opencode-web # a web UI instead: opencode-web, open-webui, hermes-web, openclaw-web, durable-pi
lux grok # a box from a Luxor image: grok, amp, droid, pi, prime-agent, tinyfish, anchor, hotdataInstall (macOS and Linux; needs the Luxor CLI and jq):
curl -fsSL https://luxcode.org/install.sh | sh
tenki login
luxOne bash script. The command is lux; the Luxor CLI it drives is still the
tenki command (Luxor is tenki.cloud's new name). The same script is also
published as colincode: curl -fsSL https://colincode.org/install.sh | sh
installs it as colin, with its config in ~/.config/colincode and
COLINCODE_* in place of LUX_*. The command's own name picks the brand;
build-site.sh renders the site for either.
The harnesses come from agentcamp (fifteen terminal agents and five web UIs) plus Luxor's eight pre-built images (Grok Build, Amp, Droid, Pi, Prime Agent, TinyFish, Anchor, Hotdata).
The Luxor CLI (still the tenki command; Luxor is tenki.cloud's new name)
already has every primitive (create, ssh, resume, exec),
and the Luxor base image already ships Claude Code, Codex and opencode. What it
lacks is the glue:
- no single command that does create-or-resume → wait → launch the agent → attach
- nothing auto-resumes a paused box — every command just fails against one
- no way to point an agent at a different inference provider without editing config files inside the box
You never create or resume a box by hand. Run any lux command and the box
is simply there: made if it does not exist yet, woken if it was paused, waited
for if it is still starting. A paused box costs you a few seconds, not an error.
An agent is a name plus a harness, an inference provider and a model. Each
agent gets its own sandbox named after it. lux with no arguments
shows your agents in an arrow-key menu, plus "new agent", which walks through:
- Terminal or web — a terminal agent in tmux that you attach to over ssh, or a web UI served from the box and tunnelled to your browser
- Harness — one of the terminal agents, Luxor's pre-built images, or the web UIs below
- Provider — the harness default (its own sign-in or in-app setup), or one of the providers below
- Model — the provider's recommended coding models, or type an id
- Size — small 2 vCPU/4 GiB · medium 4/8 (default) · large 8/16 · xlarge 12/32 · xxlarge 16/64
- Volume — a persistent volume (10/25/50 GiB, or none; Luxor workspaces cap volumes, 50 GiB on ours) mounted at
~/work. The agent starts there. It survives pause,rmand a box resize; the root disk does not. - Name — leave empty for the harness name on its default provider
(
claude,opencode-web,tinyfish), or<provider>-<harness>on a third-party one (nebius-crush,openrouter-opencode-web)
lux <harness> picks your only agent for that harness, offers a menu if you
have several, or starts the creation flow if you have none. lux <name>
targets a specific agent. Agents live in ~/.config/luxcode/agents/.
lux pick an agent (or create one) and jump into it
lux new [cli|web|harness] create an agent: terminal or web → harness → provider → model → size → volume → name
lux <harness> [action] a harness (see Harnesses): your agent for it, or a new one
lux <agent> [action] a specific agent by name
up (default) create or resume the box, start the agent, attach
ssh same as up (attach to the running agent)
shell plain shell in the box
run -- <prompt> one-shot headless prompt
status agent, provider, box state
provider switch inference provider (and model)
model [id] switch model
size [name] box size; applies when the box is (re)created
volume [GiB|none] add or drop the persistent volume at ~/work
url [off] a public Luxor link: the web UI, or a browser terminal on a terminal agent
(anyone with the link gets in — share it like a password); off removes it
auth sign in / store or replace the provider key
down | pause pause the box (auto-resumed next time)
rm | stop | kill terminate the box; the volume is kept and `lux <name>` brings the agent
back on it (rm --purge deletes the volume too)
lux image [build [--full]] the golden image new boxes start from (see Golden image)
lux ls list agents and their boxes, and kept volumes
lux volumes every volume: which agent is on it, kept ones, strays
lux volumes rm <name|id> delete a volume for good (confirmed by name)
lux pause --all pause every agent's box
lux rm --all terminate every box and forget every agent
All three harnesses run inside tmux, so they survive SSH disconnects and
sandbox pauses. lux <agent> attaches you to that session every time; detach
with Ctrl-b d and the agent keeps running.
The set agentcamp runs in its sandboxes, plus Luxor's own pre-built images. Claude Code, Codex and opencode come with Luxor's base image; the others are installed into the box on first use (a minute or two, once per box), except the two image harnesses, whose box is created from that image.
Terminal harnesses run inside tmux, so they survive SSH disconnects and
sandbox pauses. lux <agent> attaches you to that session every time;
detach with Ctrl-b d and the agent keeps running.
| Harness | Command | Default provider | Headless run |
Other providers |
|---|---|---|---|---|
claude |
Claude Code | Anthropic (claude auth login in the box, Remote Control) |
claude -p |
env (Anthropic API or proxy) |
codex |
Codex CLI | OpenAI (codex login in the box) |
codex exec |
-c flags |
opencode |
opencode | opencode (configure inside) | opencode run |
env |
copilot |
GitHub Copilot CLI | GitHub (/login inside, Copilot plan) |
copilot -p |
env (its own-model settings) |
gemini |
Gemini CLI | Google (sign in inside) | gemini -p |
— (speaks only Google's API) |
qwen |
Qwen Code | Qwen OAuth inside | qwen -p |
injector |
crush |
Crush (Charm) | configure inside | crush run |
injector |
goose |
Goose (Block) | goose configure inside |
goose run -t |
injector |
kimi |
Kimi Code CLI | Moonshot (kimi login inside) |
— | injector |
pi |
Pi, from Luxor's tenki/pi image |
/login inside (subscriptions) |
pi -p |
injector |
omp |
oh-my-pi | configure inside | omp -p |
injector |
droid |
Droid, from Luxor's tenki/droid image |
FACTORY_API_KEY, or sign in inside |
droid exec |
injector (added to /model) |
adal |
AdaL | AdaL account / ChatGPT subscription | — | — |
hermes |
Hermes (Nous) | hermes setup inside |
— | injector |
openclaw |
OpenClaw (openclaw chat) |
openclaw onboard inside |
— | injector |
grok |
Grok Build, from Luxor's tenki/grok image |
xAI (XAI_API_KEY, or grok login --device-auth inside) |
grok -p |
— |
amp |
Amp, from Luxor's tenki/amp image |
Amp (AMP_API_KEY, or amp login inside) |
amp -x |
— |
prime-agent |
Prime Agent, from Luxor's tenki/prime-agent image |
Prime Intellect (PRIME_API_KEY, or log in inside) |
prime-agent -p |
— (other providers' keys in the box env) |
tinyfish |
TinyFish toolkit, from Luxor's tenki/tinyfish image |
TinyFish (TINYFISH_API_KEY) |
tinyfish … (raw args) |
— |
anchor |
Anchor Browser toolkit, from Luxor's tenki/anchor image |
Anchor (ANCHOR_API_KEY) |
— (a shell; playwright-core, anchor-smoke) |
— |
hotdata |
Hotdata toolkit, from Luxor's tenki/hotdata image |
Hotdata (HOTDATA_API_KEY) |
hotdata … (raw args) |
— |
The entries "from Luxor's image" are Luxor's pre-built images
(docs) rather than programs
installed into the base box: the agent's box is created from that image, so
they sit in the same menu as the other harnesses. Where the image's agent
also has a device-code or browser login, the key can be left empty at
creation and the sign-in done inside. The toolkits (tinyfish, anchor,
hotdata) open a shell in the box with the key set; lux tinyfish run -- fetch content get https://… runs the CLI directly. LUX_IMAGE still
sets a custom image for every other harness.
Web UIs are a server started in the box (in tmux, like a terminal agent)
and reached through an ssh tunnel: lux <agent> forwards the box port to
localhost, opens your browser on it and holds the tunnel until Ctrl-C; the
UI keeps running. Nothing is exposed to the internet unless you ask:
lux <agent> url binds a Luxor preview URL to the port, a sticky public link
with no password of its own (OpenClaw's UI takes a token, which the link
carries), and url off removes it.
| Web UI | Port | Default provider | Other providers |
|---|---|---|---|
opencode-web |
8086 | opencode (configure inside) | env, like opencode |
open-webui |
8083 | configure a connection inside | env (OPENAI_API_BASE_URL), single user, no login screen |
hermes-web |
8088 | hermes setup inside |
injector |
openclaw-web |
8087 | openclaw onboard inside |
injector; a per-agent gateway token in the link |
durable-pi |
8092 | pi's /login |
injector (pi's config); sessions kept in ~/work/.durable-pi |
"Injector" means agentcamp's inject-provider.sh, fetched from agentcamp.dev
into the box and run for that one harness: it merges the provider's base URL,
key and model into the CLI's own config file (the provider shows up as
lux in its pickers) and switches the CLI to its no-approval mode, as the
agentcamp sandboxes do — the box is yours alone. LUX_INJECT_URL points
it elsewhere. Claude Code, Codex, opencode, Copilot and Open WebUI are wired
through environment variables instead, with nothing written to their config.
Only Claude Code has Remote Control, so only a claude agent on the
Anthropic provider shows up at claude.ai/code and in the mobile app. Remote
Control needs a full-scope login made with claude auth login inside the box;
long-lived tokens from claude setup-token and API keys are inference-only and
Claude Code refuses Remote Control with them. The first lux claude opens
that sign-in over SSH (it prints a URL, you paste a code back). The login is
stored under $HOME in the box and survives pauses.
Every provider below works with every harness that takes a custom endpoint (the two tables above); the columns show the three original harnesses.
| Provider | claude |
codex |
opencode |
Key | Default model |
|---|---|---|---|---|---|
| each harness's default | its own sign-in or key (see the harness tables above) | — | its own | ||
| Nebius Token Factory | ✓ (via proxy) | ✓ | ✓ | NEBIUS_API_KEY |
moonshotai/Kimi-K3 |
| Boundless | ✓ | ✓ | ✓ | BOUNDLESS_API_KEY |
claude-fable-5.1 |
| Baseten | ✓ (beta) | ✓ | ✓ | BASETEN_API_KEY |
moonshotai/Kimi-K3 |
| Friendli | ✓ | ✓ | ✓ | FRIENDLI_TOKEN |
zai-org/GLM-5.3 |
| OpenRouter | ✓ | ✓ | ✓ | OPENROUTER_API_KEY |
anthropic/claude-fable-5.1 |
Defaults are the most advanced model each provider serves (by its own pricing).
The model menu shows about ten recommended models first, then every model the
provider's /v1/models endpoint returns (cached for an hour); type to filter,
or pick "other" and enter any id.
How each harness is wired, with nothing written to the agent's own config:
- Claude Code gets
ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKENand the model inANTHROPIC_MODELand theANTHROPIC_DEFAULT_*_MODELslots. Any third-party base URL disables Remote Control — the menu says so. Providers with no Anthropic Messages API (Nebius) get a translation proxy in the box: a stdlib-only Python script embedded inlux, written next toagent.env, run in a second tmux session (claude-proxy) on127.0.0.1:8787, serving/v1/messages(streaming and tool calls included) in front of the provider's/chat/completions. Claude Code is pointed at it. The proxy restarts with the agent on a provider or model change and is brought back bylux <agent>if it died. Debug it withlux <agent> shell, thentmux attach -t claude-proxy.LUX_PROXY_PORTmoves it. - Codex gets a
model_provideron the command line (-cflags) withwire_api = "responses", which is the only wire API Codex accepts for custom providers; all providers above expose a Responses endpoint. - opencode gets
OPENCODE_CONFIG_CONTENTselecting<provider>/<model>from its built-in catalog.
Provider keys are asked for once (hidden input) and kept in
~/.config/luxcode/env (mode 0600); the environment wins if the variable is
already set. At every lux <agent> the provider settings are written into the
box as ~/.config/luxcode/agent.env (0600, sent over ssh stdin, never on a
command line) and sourced by the tmux launch. Changing provider or model just
restarts the agent — no rm needed.
Settings live in ~/.config/luxcode/env (~/.config/luxcode/env for lux),
next to the provider keys. lux config shows every setting and key with where
its value comes from; lux config size large saves a default (the same names
as the variables below, prefix optional); lux config unset size drops it;
lux config edit opens the file. A variable set in the environment wins for
that one run. lux auth nebius stores or replaces a provider key (Enter keeps
the current one); lux <agent> auth does the same for that agent's provider,
or runs the harness's own sign-in.
| Variable | Default | Meaning |
|---|---|---|
LUX_HARNESS |
claude |
harness used when you omit one |
LUX_SIZE |
medium |
default box size offered (small … xxlarge) |
LUX_CPU / LUX_MEMORY_MB |
4 / 8192 |
an exact default instead of a preset |
LUX_VOLUME_GB |
10 |
default volume size offered (0 = none) |
LUX_DISK_GB |
20 |
root disk (Luxor recommends 20 for agents) |
LUX_IMAGE |
(none) | force one registry image for every harness without its own |
LUX_BASE_IMAGE |
03q08p/colincode-base:stable |
the published golden image looked for on first use |
LUX_RESUME_TIMEOUT |
3m |
how long to wait for a resume |
LUX_MAX_DURATION |
2h |
a box pauses this long after each create or resume, whatever it is doing (Luxor caps it at 2h) |
LUX_PAUSE_RETENTION |
720h |
how long Luxor keeps a paused box |
LUX_STICKY |
0 |
1 creates sticky boxes that never pause by themselves |
Luxor's base image has no tmux, so a fresh box used to spend its first seconds
on apt. Boxes without an image of their own now start from a golden image:
Luxor's base plus tmux (and python3 and curl for the Claude proxy). The first
lux that creates a box looks for the published one, 03q08p/colincode-base:stable,
and remembers it in ~/.config/luxcode/image; a workspace that cannot see
it builds its own on the spot (a temporary 2 vCPU box, apt, snapshot, publish
to that workspace's registry, about a minute, once). The harnesses from Luxor's
own images (pi, droid, grok, amp, prime-agent, tinyfish, anchor, hotdata) keep
theirs.
lux image which image is in use, and whether it still resolves
lux image build build and publish a fresh one in your workspace
lux image build --full also preinstall copilot, gemini, qwen, crush, goose, kimi, omp, hermes, openclaw
lux image build --public publish it for every workspace (how the default one is made)
lux image forget go back to Luxor's base image (the registry image is kept)
LUX_BASE_IMAGE names a different published image to look for;
LUX_IMAGE forces one image for every harness without its own.
Boxes are not sticky. Luxor pauses one two hours after it was created or last
resumed (the workspace cap on --max-duration; LUX_MAX_DURATION can
only lower it). The Luxor CLI has no idle-timeout flag, and a box created
without one does not pause for being idle, so the timer is the pause. lux <agent> down pauses it now. Every lux command resumes a paused box first, so a
pause costs a few seconds, not an error, and the timer starts again from the
resume. tmux and the agent's session survive it; /tmp and open TCP
connections do not, so an agent paused mid-request retries when the box is
back. For a job that must run unattended for longer than that, create the
agent with LUX_STICKY=1: a sticky box never pauses by itself. Boxes
made by earlier versions are sticky and stay that way; status says so, and
rm then lux <agent> recreates one that pauses (the volume is kept).
- Pausing clears
/tmpand drops TCP connections. Keep work in~/work(the volume) or$HOME. tmux survives; anything in/tmpdoes not. - Luxor boxes can't be resized in place and volumes keep their size for
life.
sizetakes effect on the next create (rm, thenup; the volume is kept and reattached). To change a volume's size,volume nonedeletes it, thenvolume <GiB>makes a new one. - Native
codex/opencodecredentials from~/.config/luxcode/envare injected at create time only and stored in plaintext session metadata; to rotate them,rmthenup. Provider keys for the other providers are re-sent on everyup, so rotating those is just editing the env file. runis headless and skips approval prompts.- Boxes created by older versions (
luxcode-<harness>) are adopted as agents automatically.