Run goose on a remote server
An agent that keeps working, and keeps its appointments, after you log off.
Shelley handles the setup. About 5 minutes.
Prefer a terminal?
curl -fsSL https://exe.dev/deploy/goose/prompt.txt | ssh exe.dev new --prompt=/dev/stdingoose is an open-source AI agent, started at Block and now part of the Linux Foundation's Agentic AI Foundation. It writes and runs code, uses any MCP server as an extension, and runs recipes: reusable tasks in YAML that it can repeat on a schedule.
On an exe.dev VM, goose gets Claude and GPT through exe.dev's LLM integration: no API keys to paste, and model usage billed to your exe.dev account. The VM stays on, so a conversation carries on after you disconnect, and a recipe scheduled for 9am runs at 9am whether or not your laptop is open.
What you get#
- goose connected to Claude and GPT through exe.dev, with no API key to paste
- A project folder at
~/projecton persistent disk - A
goosetmux session that survives disconnects - goose's server with its recipe scheduler, on localhost only, back after a reboot
What's running, and where
ssh my-goose.exe.xyz
your terminal
ssh -L 3284:localhost:3284 my-goose.exe.xyz
goose Desktop on your computer, through an SSH tunnel
Deploy with Shelley#
Click Deploy and Shelley will set up goose on a new VM, then check that it works. No config-file wrangling required.
Prefer a terminal?
curl -fsSL \
https://exe.dev/deploy/goose/prompt.txt \
| ssh exe.dev new --prompt=/dev/stdinWhat Shelley is told
Set up goose on this VM by following the exe.dev recipe at https://exe.dev/deploy/goose.md from top to bottom.
The recipe's example VM name is my-goose. This VM's name is the output of `hostname`: use it wherever the recipe writes my-goose. Skip the step that creates the VM; you are on it.
You are done when all of these are true. Check each one yourself:
- goose connected to Claude and GPT through exe.dev, with no API key to paste
- A project folder at `~/project` on persistent disk
- A `goose` tmux session that survives disconnects
- goose's server with its recipe scheduler, on localhost only, back after a reboot
Run the VM-side setup in "Start coding" and "Keep goose running". If
`~/.config/goose/config.yaml` already exists, set the provider and model
in it instead of replacing it. Create tmux detached
(`tmux new -d -s goose -c ~/project`) rather than attaching, and reuse
the session if it already exists. Do not launch interactive goose, do not
run `goose doctor` or `goose configure`, and do not schedule any recipe.
Verify from ~/project with
`goose run --no-session -t "Reply with exactly: OK" </dev/null`, check
`tmux has-session -t goose`, that `systemctl is-enabled goose` prints
`enabled`, and that `curl -s http://127.0.0.1:3284/status` prints `ok`.
Skip the later optional instructions and FAQ. Finish with the SSH command
from "Take a break", using this VM's name, and tell the owner to run
`goose session -n main` inside it.
Finish with the URLs I should open.
Also at /deploy/goose/prompt.txt. The recipe it follows is /deploy/goose.md.
Start coding#
On your computer, create a VM and connect. Pick any free name; if you've already clicked Deploy or have a VM, skip the first command and use its name:
ssh exe.dev new --name=my-goose
ssh my-goose.exe.xyz
On the VM, install the goose CLI with its official installer. It puts
goose in ~/.local/bin; CONFIGURE=false skips the interactive setup,
which you don't need:
curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash
goose --version
Point goose at exe.dev's LLM gateway. Each file in
~/.config/goose/custom_providers adds a provider; these two need no API
key, because the gateway at llm.int.exe.xyz only answers your VMs. Then
make Claude Sonnet the default:
mkdir -p ~/.config/goose/custom_providers
cat > ~/.config/goose/custom_providers/exe_claude.json <<'EOF'
{
"name": "exe_claude",
"engine": "anthropic",
"display_name": "Claude via exe.dev",
"base_url": "https://llm.int.exe.xyz",
"requires_auth": false,
"supports_streaming": true,
"models": [
{ "name": "claude-sonnet-5-5" },
{ "name": "claude-opus-5-5" },
{ "name": "claude-haiku-5-5" }
]
}
EOF
cat > ~/.config/goose/custom_providers/exe_openai.json <<'EOF'
{
"name": "exe_openai",
"engine": "openai",
"display_name": "GPT via exe.dev",
"base_url": "https://llm.int.exe.xyz/v1/chat/completions",
"requires_auth": false,
"supports_streaming": true,
"models": [
{ "name": "gpt-6.1-sol" },
{ "name": "gpt-6-luna" }
]
}
EOF
cat > ~/.config/goose/config.yaml <<'EOF'
active_provider: exe_claude
providers:
exe_claude:
enabled: true
model: claude-sonnet-5-5
configured: true
EOF
Make a project folder and check that goose can reach the model. It should
print OK:
mkdir -p ~/project && cd ~/project
goose run --no-session -t "Reply with exactly: OK"
Open a tmux session and start a named goose session inside it:
tmux new -A -s goose -c ~/project
goose session -n main
Give it a small task. For example:
Build a tiny website for a bakery that only sells one kind of bread.
goose runs commands and edits files without asking first; that's its
default auto mode. Type /mode approve to have it ask before each
change, or /mode chat to talk without tools.
Take a break#
Press Ctrl-b, then d to detach. goose keeps working on the VM when you disconnect. On your computer, use this command to hop back in:
ssh -t my-goose.exe.xyz 'tmux new -A -s goose -c ~/project'
A VM restart ends the running session but not the conversation, which goose
keeps in ~/.local/share/goose/sessions. Reconnect and pick it up where
you left off:
goose session --resume -n main
goose session list shows every conversation, including the ones recipes
started.
Keep goose running#
goose serve is goose's server: the same agent, for apps such as goose
Desktop, with a scheduler that runs recipes on time. On the VM, give it a
secret, which apps send to connect, and start it whenever the VM boots. It
listens on localhost only, so nothing outside the VM can reach it:
(umask 077; printf 'GOOSE_SERVER__SECRET_KEY=%s\n' "$(openssl rand -hex 32)" > ~/.config/goose/serve.env)
sudo tee /etc/systemd/system/goose.service >/dev/null <<'EOF'
[Unit]
Description=goose server and recipe scheduler
After=network-online.target
Wants=network-online.target
[Service]
User=exedev
WorkingDirectory=/home/exedev/project
EnvironmentFile=/home/exedev/.config/goose/serve.env
ExecStart=/home/exedev/.local/bin/goose serve --platform desktop --enable-scheduler --host 127.0.0.1 --port 3284
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now goose
Check it. This prints ok:
curl -s http://127.0.0.1:3284/status
Run recipes on a schedule#
A recipe is a task in a YAML file: instructions, a prompt, and parameters
you fill in when you run it. This one summarizes the last day of commits
in ~/project and emails the summary to you with exe.dev's
email API, which can only write to you. On the VM:
mkdir -p ~/recipes
cat > ~/recipes/daily-digest.yaml <<'EOF'
version: 1.0.0
title: Daily digest
description: Summarize the last day of commits in ~/project and email the summary to you
parameters:
- key: email
input_type: string
requirement: required
description: Your exe.dev email address
instructions: |
You run unattended on an exe.dev VM. Nobody is watching, so never ask
questions; make a reasonable choice and say what you chose.
prompt: |
1. In ~/project, run `git log --since="24 hours ago" --stat`.
2. Write a short plain-text summary of what changed and anything that
looks unfinished. If nothing changed, say so in one line.
3. Email the summary to {{ email }} with the subject "Daily digest", by
sending JSON with `to`, `subject` and `body` fields to
http://169.254.169.254/gateway/email/send with curl
(`-H "Content-Type: application/json"`). Build the JSON with jq so the
text is escaped properly.
4. Reply with the HTTP status of that request.
EOF
goose recipe validate ~/recipes/daily-digest.yaml
Run it once now, with your exe.dev email address, and check your inbox:
goose run --recipe ~/recipes/daily-digest.yaml --params email=you@example.com
Then schedule it for 9:00 every day. The VM's clock is UTC, so adjust the hour to your time zone. Stop the server while you change schedules: it keeps its own copy of the list and writes it back after each run, which would undo a change made while it runs.
sudo systemctl stop goose
goose schedule add --schedule-id daily-digest --cron "0 9 * * *" \
--recipe-source ~/recipes/daily-digest.yaml --params email=you@example.com
sudo systemctl start goose
goose schedule list shows each schedule and when it last ran, and
goose schedule sessions --schedule-id daily-digest lists its runs. Open
one with goose session --resume --session-id <id> --history to read what
goose did. To drop a schedule, stop the server again and run
goose schedule remove --schedule-id daily-digest.
Each run is a full agent session billed like any other, so keep schedules to what you'd read. goose's recipe guide covers sub-recipes, retries, and structured output.
Add extensions#
goose's tools come from extensions, which are MCP servers. It starts with
its built-in ones, such as developer for the shell and files. To add a
remote MCP server for good, add it under extensions: in
~/.config/goose/config.yaml. For example, DeepWiki, which answers
questions about public GitHub repositories and needs no sign-in:
extensions:
deepwiki:
type: streamable_http
name: deepwiki
enabled: true
uri: https://mcp.deepwiki.com/mcp
timeout: 300
The built-in extensions stay on. For a single session, pass the server on
the command line instead. Python servers run with uvx, which the VM
already has:
goose session --with-streamable-http-extension https://mcp.deepwiki.com/mcp
goose session --with-extension "fetch:uvx mcp-server-fetch"
Servers that start with npx need Node.js, which a new VM doesn't have.
goose configure → Add Extension walks through the same settings, and
goose's extensions directory lists
more. Restart the server (sudo systemctl restart goose) so scheduled
recipes see new extensions.
Use goose Desktop with the VM#
goose Desktop can drive the server on the VM instead of one on your computer, so its chats and schedules run on the VM. The server listens only on the VM's localhost, so reach it through SSH. On your computer, print the secret, then open a tunnel and leave it running:
ssh my-goose.exe.xyz cat .config/goose/serve.env
ssh -N -L 3284:localhost:3284 my-goose.exe.xyz
In goose Desktop, open Settings → goose Server, turn on
Use external server, and enter http://localhost:3284 as the URL and
the value after GOOSE_SERVER__SECRET_KEY= as the secret key. goose's
remote server guide
describes these settings.
Warning
Whoever has the secret and can reach port 3284 can run any command on the VM. Keep the server on
127.0.0.1and use the tunnel rather than exposing the port.
Make yourself at home#
- Pick a model: in a session, type
/model --provider exe_openai gpt-6.1-solfor GPT, or/model --provider exe_claude claude-opus-5-5for Claude Opus. Changeactive_providerandmodelin~/.config/goose/config.yamlto set the default. To add another model, add it to the provider's file in~/.config/goose/custom_providers; exe.dev keeps a list of available models and prices. - Bring your code: connect a GitHub repo,
clone it onto the VM, and run goose from its directory. Use that folder
instead of
~/projectin the commands above.goose reviewreviews your uncommitted changes, or a range such asmain...HEAD. - Peek at your app: ask goose to serve it on
0.0.0.0:8000, then openhttps://my-goose.exe.xyz/. It's private by default. More about previews. - Give it more VMs: add exe.dev's MCP server as an
extension and goose can create VMs of its own, to try something risky
on a copy or to host what it builds. Recipes run unattended, so use a
token limited to one tag, as
that page shows, in
the extension's
headers. - Script it:
goose run -t "Summarize the TODOs in this repo"runs one task without the interactive screen; add--output-format jsonfor output a program can read.
For everything else, see the goose docs.
Frequently asked questions#
Can I use my own API key or subscription instead?
Yes. Run goose configure on the VM, choose Configure Providers, and pick a provider such as Anthropic, OpenAI, OpenRouter, or ChatGPT Codex; see goose's provider docs. On a VM there is no system keyring, so goose keeps keys in ~/.config/goose/secrets.yaml. To keep keys off the VM entirely, put them in your LLM integration instead and keep the exe_* providers from this page.
How do I update goose?
On the VM, run goose update, then sudo systemctl restart goose so the server and its scheduler use the new version. Your settings, sessions, and schedules stay put.
Something's not working. Where do I look?
goose info -v shows which config files, provider, and extensions goose is using. goose's own logs are in ~/.local/state/goose/logs, and journalctl -u goose shows the server. goose doctor starts a short session in which goose checks its own setup and explains what it finds. If a schedule you added has vanished, see Run recipes on a schedule.
Can I talk to goose from my phone?
goose has an experimental Telegram gateway (goose gateway start telegram) that needs the computer running goose to stay awake, which a VM does. It takes a bot token from @BotFather on the command line, so the token lives on the VM. We haven't tested it on exe.dev.
How do I back up my work?
Commit and push your code. goose session export -n main --format json -o main.json saves one conversation. For a copy of the whole VM, including sessions, recipes, and schedules, run ssh exe.dev cp my-goose my-goose-backup on your computer (choose a free backup name), and connect to the copy with ssh my-goose-backup.exe.xyz. Reattach any VM-specific integrations.
Updated Oct 28, 2026 · tested end to end Oct 28, 2026 · Markdown · manifest.json · Something wrong? Tell us