v0.5.1-alpha.2 is outsee the release →
All guides
[ DOCS ]

Quickstart

docs/quickstart.md on GitHub →

Zero to a finished agent run in about ten minutes, solo.

Two machines are involved, though they can be the same one:

  • the server box - a Linux machine with Docker and git. Agents run here.
  • your machine - Linux, macOS, or Windows. Where you type.

Your machine needs git and, unless both machines are on a tailnet (see step 3), an SSH key. Aether uses ~/.ssh/id_ed25519 and your ssh-agent. For a key somewhere else, pass aether link --key <path>; for a passphrase-protected one, ssh-add it first. Windows paths and the OpenSSH agent service are in install.md.


1. Install

On the server box and on a Linux or macOS machine:

curl -fsSL https://raw.githubusercontent.com/3xDevOps/Aether/main/scripts/install.sh | sh

The script asks what this machine is. Answer server on the server box and client on your own machine; Enter takes the sensible default.

That answer decides what you get. A server gets aether and aether-server in /usr/local/bin, with sudo. A client gets the aether CLI alone in ~/.local/bin, without sudo, so the desktop app can replace it when it updates; if that directory is not on your PATH, the script prints the one line that adds it, and the app finds it either way. macOS is a client platform, so it only ever gets aether. Later, aether update upgrades whatever is installed.

It then finishes that side's setup: step 2 on the server, the desktop app (step 7) on a client. To install the binaries and stop there, add --role none; see install.md.

On Windows, download and review the PowerShell installer, then run it:

Invoke-WebRequest -UseBasicParsing -Uri https://raw.githubusercontent.com/3xDevOps/Aether/main/scripts/install.ps1 -OutFile "$env:TEMP\aether-install.ps1"
& "$env:TEMP\aether-install.ps1"

It installs the CLI and desktop app by default, without administrator access. Use -Role none for the CLI alone. Desktop setup requires a release containing the Windows installer fixes (v0.4.0-alpha.6 or newer); the installer refuses older desktop builds. See install.md for script-policy requirements, version selection, upgrades, and manual installation.

2. Start the server

Answering server in step 1 already ran the command below on the server box. It writes the config and the systemd unit but deliberately starts nothing, so the activation line it printed is still yours to run. Run setup by hand if you skipped the question:

sudo aether-server setup

It asks for the listen address, data directory, and tailnet policy - plus, on a host that already runs tailscaled, the dashboard's HTTPS port on the tailnet (Enter accepts each default) - then prints:

systemctl daemon-reload && systemctl enable --now aether-server

Run that and the server is live on :2222. The SSH host key is generated on first start, and the SSH port is the only thing exposed unless you answered the dashboard question. Change any option later with aether-server config set <key> <value>, then restart.

To try it in the foreground first, sudo aether-server serve runs until Ctrl-C. install.md covers unattended installs, running unprivileged, and every serve option.

aether link <server-host>:2222

Output:

linked to <server-host>:2222 as admin (admin)

The first identity to link a fresh server becomes the admin. That is the whole account setup - there is no signup, no password, no config file to edit. The link is saved to ~/.config/aether/config.json, or %AppData%\aether\config.json on Windows. (Joining over a tailnet, the display name comes from your tailnet login instead of the literal admin; the role is the same. Change any display color with aether member color <#rrggbb>.)

How you were identified depends on the network:

  • On a tailnet: Tailscale already knows who you are and the server asks it. No SSH key, no invite code, nothing to copy. See networking.md.
  • Anywhere else: your SSH public key (~/.ssh/id_ed25519, or any key in your ssh-agent) is registered as the admin's key. Generate one first with ssh-keygen -t ed25519 if you do not have one. On Windows that is %USERPROFILE%\.ssh\id_ed25519 and the OpenSSH agent service; ssh-keygen ships with Windows OpenSSH.

On first contact aether records the server's host key in ~/.ssh/known_hosts (%USERPROFILE%\.ssh\known_hosts on Windows) and prints its fingerprint. Compare that against what the server printed if you care to.

Then tell Aether who to put on your commits:

aether member git --name "Ada Lovelace" --email ada@example.com

Every commit an agent makes in your runs, and every commit Aether makes for them, is authored as that name and address, so branches you merge upstream credit your account. Without it the fallback is your display name at <member-id>@aether.local, which maps nowhere. The local dashboard's onboarding wizard asks for the same two fields in its Git identity step, right after Link, prefilled from this machine's git config user.name and user.email. aether member git with no flags shows what is set.

4. Create a workspace and push your repo

A workspace is the repo plus a server-owned scope for runs and shells. Creating one is an admin operation. Every container for a member starts from that member's saved image, or the server's standard image when none is saved:

aether workspace init myproject

Use --base <branch> when runs should branch from something other than main. The dashboard's create-workspace form uses the same command. Install tools in the environment terminal, then press Save environment so runs get them too; see environments.md.

Now point your local clone at it and seed the repo:

aether link <server-host>:2222 --repo ~/code/myproject
cd ~/code/myproject
git push -u aether main

link --repo adds an aether git remote - a normal git remote over the same SSH port, no separate credentials. With multiple workspaces, add --workspace <name-or-id> (aether workspace list shows them). The push sends the workspace's base branch; replace main if you created the workspace with --base.

The first link --repo also reads your clone's own origin URL and records it as the workspace's checkout Origin, printing workspace origin -> <url>. Every run checkout created afterwards gets an origin remote pointing there, so an agent in a run can git push origin <branch> and open a pull request once you have connected GitHub (step 5). The local dashboard's Repository step does the same. Recording happens only when you may push - a viewer may not - and your clone's origin is one the server accepts; otherwise the link still succeeds, prints no workspace origin -> line, and records nothing. aether workspace origin shows what was recorded and takes a URL or --clear to change it. A github.com origin you cloned over SSH is recorded in its https://github.com/... form, because that is the form a run can push to; teams.md has the rule for other hosts.

This checkout Origin is a push destination, not the source used to refresh a workspace base. A workspace is local-only by default. To have Aether fetch a protected base from an upstream repository, configure the optional source mirror below.

In the local dashboard's onboarding wizard, the Repository step does both for you. Point it at your clone: type the absolute path, or, in the desktop app, pick it with Choose folder. The step adds the remote, then its Push now button compares your clone with the workspace and pushes when there is something to push, keeping git's own output on the page. It runs the same push with --no-follow-tags, so the command above also sends your tags if you have push.followTags set. The step remembers the repository it connected, so walking back to it shows the connected clone and its push result with Use a different repository to re-point, not an empty form.

A fresh workspace has no branch there yet, so the button pushes. A member joining a workspace someone else already seeded gets a report instead of a failed push: the same commit on both sides; the workspace ahead of the clone, where a Fast-forward my clone button catches the clone up, fast-forward only; or the two diverged, where the wizard prints the git commands to resolve it by hand. A fast-forward writes no merge commit and never rewrites your own commits, and nothing here force-pushes. See teams.md for that case.

For optional source-mirror setup, see the detailed instructions.

Optional: configure source control

An administrator can make the workspace's base a read-only mirror of an upstream branch. Configure it from the CLI or local dashboard only after the initial workspace push or after reconciling the two repositories:

# Public GitHub or any public HTTPS repository:
aether workspace mirror configure --workspace myproject \
  --source https://github.com/acme/myproject.git --branch main --auth public

# Private GitHub: Aether generates a read-only deploy key.
aether workspace mirror configure --workspace myproject \
  --source https://github.com/acme/private.git --branch main --auth deploy-key

# Generic SSH: provide a known_hosts file; do not use unverified TOFU.
aether workspace mirror configure --workspace myproject \
  --source ssh://git.example.com/acme/myproject.git \
  --branch main --auth deploy-key --known-hosts-file ~/.ssh/known_hosts

--branch defaults to the workspace base branch. Public mode accepts credential-free HTTPS. Deploy-key mode accepts a GitHub HTTPS URL or a generic ssh:// URL. For GitHub, configure prints the public key and: GitHub deploy-key settings: https://github.com/<owner>/<repo>/settings/keys/new and Open Settings > Deploy keys > Add deploy key. Follow that URL, paste the public key as a repository deploy key, leave Allow write access off, and then verify it. The private key never appears in CLI output or the dashboard.

Configuration is initially pending and does not fetch. Verify it with:

aether workspace mirror refresh --workspace myproject
aether workspace mirror status --workspace myproject

Forward-only upstream changes become ready and advance the mirrored base. Rewrites or a base that diverged locally retain an observed candidate and leave the accepted base alone; review the candidate and explicitly adopt it:

aether workspace mirror adopt --workspace myproject --generation <n> --yes
aether workspace mirror disable --workspace myproject --yes

The local dashboard's onboarding wizard offers this same setup inline only to administrators and only when capability workspace.mirror.status exists. The entry is prefilled from checkout Origin when available and opens this existing Source control flow for public HTTPS or deploy-key setup and verification. Choosing it configures the server-owned read-only upstream; new runs refresh it before launch. Skipping it leaves the workspace's current source settings unchanged. If no source mirror is configured, it remains local-only; configure it later from Workspace > Source control. On the dashboard's Workspace page, the flow is admin-only: its one-time Configure form is prefilled from Workspace.Origin; choose public or deploy-key authentication, copy the public key and follow the GitHub link, then use Verify or Refresh. Adopt candidate and Disable both ask for explicit confirmation. Disable returns the workspace to local-only mode; it does not revoke a deploy key at GitHub, so remove that key there.

Every launch refreshes a configured mirror before creating the run. An authentication, network, missing-source, rewrite, divergence, or other refresh failure creates no run and never silently uses a stale base. If the failure reports an accepted commit, the CLI offers an explicit retry:

aether run "add a health check endpoint" --agent claude \
  --cached-base <accepted-commit>

The --cached-base override is request-scoped: it applies only to the launch request where it is supplied and is never inherited by later launches. Repeating the same override is accepted while the SHA still matches the accepted commit and the workspace base has not moved; a mismatched SHA or moved base is rejected.

5. Set up your agent

Choose an agent once:

aether agent add claude

For a shipped agent, the local dashboard's Agents step opens the live environment terminal dock and types its vendor install script. The dock says Starting your environment container while Docker starts it, then the shell appears. When you press I've installed and logged in, the wizard checks the executable is on the server and runs env save for you, naming the saved image. It cannot check the vendor login; the agent does that when it starts. From the CLI, open the terminal, run the script, install into ~/.local/bin, and complete the vendor login there:

aether terminal

For a name Aether does not ship, the command first asks for interactive and headless launch templates. Install that executable into ~/.local/bin using the vendor's instructions, then complete its login in the environment terminal. Return to the dashboard when finished.

The member home persists the executable and vendor login state across containers. Configuration can be imported once from the browser and then edited in Files. See the environment terminal guide for tab and stop behavior.

Your own configuration is separate from vendor login and image setup. In the local dashboard's Agents step, choose one directory such as ~/.claude, ~/.codex, or ~/.pi with Choose directory. Review the preview, select a destination when the basename is unknown or ambiguous, and click Import configuration. This is explicit and one-time; there is no local directory watcher or AI inventory. The server-hosted dashboard has no local directory picker; use aether gui for this step.

Known credential names in any path component and runtime/history defaults are skipped in the browser before upload. Remaining bytes are uploaded and server-scanned, so never assume all secret content stays on your machine. Empty files and arbitrary binary regular files are preserved. Imports are limited to 1 MiB per file, 20 MiB decoded total, and 2,000 files. Browser imports create files with mode 0644; executable mode and symlinks cannot be preserved, so a script may need chmod in the remote terminal.

The imported files go into your authenticated member's persistent home, which is mounted read-write in the environment terminal and in runs using that account. Changes are immediately visible, including to active runs; the agent may need to reload. This is a shared home, not an isolated per-run profile. The snapshot pin records launch provenance, not an isolated writable copy or a promise that home edits wait for later runs. Importing or editing configuration does not rebuild the installed-agent image.

Open Files to browse your own configuration beside workspace base and live-run files. The editor supports JSON, JavaScript, TypeScript, Markdown, Python, and TOML syntax highlighting, plus find/replace. Save explicitly with Save, Commit to , or Ctrl/Cmd-S. Dirty tabs remain in memory across routes, the browser warns before unloading them, and there is no autosave or force-save. A failed or stale save keeps the draft; Reload from server replaces it with current server content.

Configuration editing accepts complete UTF-8 text up to 64 MiB. Binary and oversized files are read-only. New configuration files accept nested relative paths and refuse overwrites. config.* methods require Launch and target only your authenticated member home; there is no admin/member selector. For workspace files, Commit to creates one commit on the base branch but does not push upstream; live-run writes modify the uncommitted checkout. Base saves require Push and run saves require Steer.

Connect GitHub

Optional, and worth doing before the first run: connect GitHub once and your runs can push their branch to the origin recorded in step 4, open a pull request, and sign their commits as you.

In the dashboard, the Agents step has a Connect GitHub button. It opens the same terminal dock, types the login command, and its I've logged in button runs the rest, reporting the account and the registered key.

From the CLI it is two commands. In the environment terminal:

gh auth login --hostname github.com --git-protocol https --web \
  --scopes admin:ssh_signing_key

Then, back on your machine:

aether github connect

What each step writes, how to re-run it, and how to revoke are in environment-home.md and security.md.

6. Launch a run

aether run "add a health check endpoint" --agent claude

The run gets its own container and checkout while using your persistent home.

run 01m04mhf114eap4k85n2mgcped running

A run is one agent execution with its own container, git worktree, and branch; aether runs lists them. Scoped commands default to the only workspace when there is exactly one, which is why nothing above named it.

7. Watch it

aether gui

This serves the dashboard from your own machine and opens a browser tab already carrying a per-process token. It rides your SSH key, so everything the CLI can do works from the page, plus local verbs like pulling a run branch into your clone. Leave it running; Ctrl-C stops the gateway and the token dies with it. aether gui --url prints the URL instead of opening a browser. See local-gateway.md.

On a tailnet, the server can host the dashboard instead, so a phone or any other tailnet device opens https://<the server's MagicDNS name>/ with nothing installed and no token. Set web-port and restart the server; see networking.md. This server-hosted dashboard has no machine-local verbs or onboarding wizard, but its authenticated Files view can edit the shared member home.

Prefer a native window?

aether gui in a browser tab is the whole dashboard. If you would rather it lived in its own window - with desktop notifications and a dock badge when a run parks in needs-attention, plus aether://run/<id> deep links - build the desktop app. Answering client in step 1 already did this. Nothing has to be installed first - the CLI fetches its own Node.js copy when the machine has none, which makes the first build longer:

aether gui build

That installs Aether into your application menu (Linux), your Applications folder (macOS; ~/Applications without administrator rights, and the command prints the path), or the Start Menu (Windows). Open it like any other app.

Two things to know:

  • There is no download. No release publishes an installer. aether gui build packages the Electron shell on your machine from sources carried in the CLI. Details in install.md.
  • It is not a standalone client. The app does not bundle aether; it launches aether gui from your PATH, and the dashboard lives inside that CLI binary. Install the CLI (step 1); on an unlinked machine, the app opens its local onboarding wizard and links from there. When you update the CLI, the window picks up the new dashboard without rebuilding the app.

In the dashboard: a workspace switcher over the runs in scope, a board bucketed by what needs attention, a live terminal mirror per run, the diff timeline, the workspace feed. Read-only by default; typing into a run needs the steer capability, which as the owner you have.

The terminal escape hatch is aether attach <run-id> - a raw byte-for-byte passthrough where every native keybind and theme of the agent's own TUI works. Detach without killing anything: the PTY lives on the server.

To nudge a running agent without attaching:

aether inject <run-id> "also update the README"

The message appears in the transcript as a banner in your member color, and everyone watching sees who said it.

8. Pull the result

When the TUI agent exits, the run stays alive in a login shell. Start another installed agent in the same checkout if needed; exiting that shell opens another login shell, so the run remains live until you explicitly close it. Close commits and publishes the latest work to the run's branch.

aether pull <run-id>

If your checkout is already on the run branch, Aether fast-forwards it. If not, Aether creates or updates the local run branch without switching your checkout:

Branch aether/run-add-a-health-check-endpoint-mgcped is ready. Switch with: git switch aether/run-add-a-health-check-endpoint-mgcped

The branch name is aether/run-<slug>-<short-id>: the task slugified, then the last six characters of the run ID. Aether never switches branches or merges the run into your base branch. Review it, diff it, then merge it by hand:

git log --oneline aether/run-add-a-health-check-endpoint-mgcped
git diff main...aether/run-add-a-health-check-endpoint-mgcped

If the local checkout has uncommitted changes, the pull still fetches the run branch and reports that the checkout is dirty. Commit or stash those changes before switching to the run branch or merging it.

When the review is complete, merge locally. In a local-only workspace, push the reviewed base back to Aether:

git switch main
git merge aether/run-add-a-health-check-endpoint-mgcped
git push aether main

In a mirrored workspace, the Aether base is protected. Push the reviewed result to the configured source branch using credentials for that upstream:

git push <source-remote> main:<configured-source-branch>

Here <source-remote> is a local remote (or URL) for the configured source; the mirror's server-side credentials are read-only. The checkout Origin is optional and independent of the source mirror. It may be a different repository, and is a review destination for publishing the run branch (for example, to open a pull request); it is not the destination for a mirrored base update.

Then close the run out so it leaves the attention board:

aether close <run-id> --outcome merged      # or --outcome abandoned

Closing retains the exact TUI container, checkout, run row, member account and coordination surfaces for --run-container-ttl (default 1h). Before that retention expires, reopen the same run with:

aether relaunch <run-id>

Relaunch does not create a new run or container and expired or unavailable runs cannot be relaunched. Kill and Delete remain immediate cleanup operations.

The local daemon is optional. It fetches server-owned run branches as agents commit and can push your local base branch in local-only workspaces. It does not watch agent configuration directories; configuration is imported explicitly in the local dashboard (aether gui) and edited in Files. It does not fetch a mirror source or forward a checkout's origin into the workspace. In a mirrored workspace, a base push attempt is rejected because the mirror owns that branch; use aether workspace mirror refresh and, when needed, explicit aether workspace mirror adopt instead. aether pull remains the review path in every mode.

aether daemon install --server <server-host>:2222 --repo ~/code/myproject
systemctl --user daemon-reload && systemctl --user enable --now aether-daemon

That second line is the Linux one. daemon install prints the activation command for whatever platform you are on: launchctl load on macOS, schtasks /Create on Windows. A unit installed by an older release that still contains --sync-origin will fail after upgrade; reinstall it with the command above and activate the newly written unit. There is no recurring dashboard Sync from origin action.


Prove the plumbing without an agent subscription

No vendor login yet? Aether ships a deterministic agent named fake for exactly this: it runs a script from your repo instead of an agent, so you can drive the whole lifecycle end to end and see a real branch come back.

Start the server with the fake agent's command in its environment. If the systemd unit from step 2 is already running, stop it first (sudo systemctl stop aether-server) - two servers cannot share :2222:

AETHER_FAKE_AGENT="sh /workspace/agent.sh" \
  aether-server serve --data-dir /var/lib/aether --addr :2222

/workspace is where the run's checkout is mounted, so agent.sh is just a file in your repo. Create a throwaway one:

mkdir demo && cd demo
git init -b main
git config user.name "You" && git config user.email you@example.com   # if git has no identity yet
cat > agent.sh <<'EOF'
echo "agent starting"
printf 'hello from the agent\n' > result.txt
echo "agent done"
EOF
echo "# demo" > README.md
git add -A && git commit -m seed

Then run steps 3, 4, 6 and 8 above with the default standard image and --agent fake instead of --agent claude. Skip step 5: fake has no agent login. Step 7 (aether gui) works too if you want to watch.

Launching it is the CLI's job, though. fake is a server-side registration rather than an executable installed in your account, and the local dashboard's two launch surfaces - the launch form and the wizard's First run step - offer only agents installed in the account, plus the custom harness a deployment pins, so fake never appears in either.

aether link <server-host>:2222
aether workspace init demo
aether link <server-host>:2222 --repo "$PWD"
git push -u aether main
aether run "write a result file" --agent fake
aether runs
aether pull <run-id>

aether runs shows the run reaching needs-attention within seconds, and the pulled branch carries a commit adding result.txt. That is the full path - container, worktree, PTY, commit, fetch - with nothing mocked but the agent.

When something does not work

Symptom Cause
not linked; run aether link <addr> CLI commands that need a server have no saved link. The desktop app opens its onboarding wizard and can link from there.
no Aether member for this key The server already has an admin, so you are not the first member. Get an invite: teams.md.
unable to authenticate, attempted methods [none] The CLI had no key to offer: none at ~/.ssh/id_ed25519 and no ssh-agent. The same error names the key when one was found but could not be used - read the rest of the line. On Windows, check Get-Service ssh-agent and look for the key at %USERPROFILE%\.ssh\id_ed25519.
<path> is passphrase-protected; add it to ssh-agent (ssh-add <path>) or pass --key <unencrypted key> The key exists but the CLI cannot decrypt it; it does not prompt for a passphrase. Run ssh-add <path>, or point at an unencrypted key with aether link <addr> --key <path>.
parse ssh key <path> The file at that path is not an SSH private key (a public key, or a truncated file). Pass the private key with aether link <addr> --key <path>.
link --key: stat <path> / ssh key <path>: open <path>: no such file The key path you chose is not there. A chosen key is never skipped in favor of the agent, so re-link with the right --key. Re-linking without --key keeps the saved one; to go back to ~/.ssh/id_ed25519, delete the key line from ~/.config/aether/config.json.
host key mismatch / REMOTE HOST IDENTIFICATION HAS CHANGED on aether link The server was reinstalled and generated a new host key, but your known_hosts still trusts the old one. Clear it: ssh-keygen -R '[<server-host>]:2222'.
tailnet identity unavailable; key authentication required Informational, not an error. The server has Tailscale but this connection did not arrive over the tailnet, so it fell back to your SSH key.
membership pending admin approval You joined over a tailnet on a server that requires approval. An admin runs aether member approve <your-member-id>.
no workspace yet; skip git remote Run aether workspace init first, then re-run aether link --repo.
multiple workspaces available; specify --workspace Pass --workspace <name> to aether link or another command that accepts a workspace selector. Agent setup is member-scoped.
Run reaches failed immediately The agent started and exited. aether timeline --run <run-id> shows the exit code; aether attach only works while a run is alive.
self-update is not supported on Windows Expected. Re-download the release binary: install.md.

Starting over

Testing the whole path from a clean slate, or handing the box to someone else? install.md has the full removal order for the server, the client, and your linked repos. Two things bite people: run containers outlive the server unit and must be removed separately, and a reinstalled server gets a new host key, so stale known_hosts entries have to go or the next aether link fails.

Next