---
title: "Run coding agents in herdr on a Lens Agents sandbox — Lens Blog — Lens"
description: "Drive Claude Code and Codex from herdr on your laptop while they run in a governed Lens Agents sandbox, with persistent panes and default-deny egress."
url: https://lenshq.io/blog/herdr-on-lens-agents/
---

[← Blog](https://lenshq.io/blog/)

[Tutorials](https://lenshq.io/blog/category/tutorials/)

# Run coding agents in herdr on a Lens Agents sandbox

Jari Kolehmainen October 27, 2026 8 min read

**TL;DR**

- [herdr](https://herdr.dev) is a terminal workspace manager for AI coding agents. Its panes run in a background server, and `herdr --remote` attaches to that server over SSH.
- A Lens Agents sandbox speaks SSH through `nexusctl`, so it can be that remote. No sshd in the image, no open port.
- The result: you work in herdr on your laptop, while Claude Code and Codex run in a sandbox with default-deny egress and secrets they never see.

Running several coding agents at once has two practical problems. You want them to keep working when you close the laptop, and you don't want them running on your laptop with your credentials and an open network.

herdr solves the first one. A Lens Agents sandbox solves the second. This post connects them.

## How the pieces fit

herdr keeps every pane in a server process. When you run `herdr --remote <host>`, the server runs on the remote host and owns the agents, while your local herdr draws the UI: sidebar, theme, and your own keybindings. Detach, and the agents keep running. Attach again from any machine, and they're where you left them.

`herdr --remote` uses plain OpenSSH. That's the only thing it needs from the remote side.

A Lens Agents sandbox doesn't run an SSH server. Instead, `nexusctl sandbox ssh-proxy` acts as one on your machine, as an OpenSSH `ProxyCommand`. Each SSH session becomes one exec in the sandbox, authenticated with your Lens Agents login. Shells, commands, PTYs, and window resizes work, so a full-screen TUI like herdr works too.

## What you need

- Lens Agents 0.34.0 or later. SSH access to sandboxes arrived in that release. If you don't have a platform yet, follow the [getting started guide](https://docs.lenshq.io/lens-agents/getting-started/).
- `nexusctl`, installed from your Lens Agents platform and logged in with `nexusctl auth login`.
- The ADMIN role on the project where the sandbox lives. SSH access requires it.
- herdr on your laptop. See the [herdr install guide](https://herdr.dev/docs/install/).
- A [fine-grained GitHub token](https://github.com/settings/personal-access-tokens/new) with access to only the repositories the agents work on.

## Step 1: Define the sandbox

The sandbox is a home directory on a persistent volume, with the agent CLIs and herdr installed into it. Everything installs into `$HOME`, so tools, logins, and repositories survive a restart, and the install scripts skip what's already there.

```
name: Dev Box
image: ubuntu:24.04
command: sleep infinity
cpu: "4"
memory: 16Gi

user: ubuntu
workdir: /home/ubuntu/work

volumes:
  - mountPath: /home/ubuntu
    size: 30Gi

env:
  MISE_YES: "1"

scripts:
  - when: pre-start
    description: install base packages
    user: root
    run: |
      apt-get update -qq
      DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
        curl git ca-certificates unzip xz-utils less >/dev/null
  - when: pre-start
    description: give the home volume to the agent user
    user: root
    run: chown ubuntu:ubuntu /home/ubuntu
  - when: pre-start
    description: set up the shell
    run: |
      [ -f ~/.bashrc ] || cp -rT /etc/skel ~
      grep -q 'mise activate' ~/.bashrc || cat >> ~/.bashrc <<'EOF'
      export PATH="$HOME/.local/bin:$PATH"
      eval "$(mise activate bash)"
      EOF
  - when: pre-start
    description: install mise
    run: |
      [ -x ~/.local/bin/mise ] && exit 0
      curl -fsSL https://mise.run -o /tmp/install-mise.sh
      sh /tmp/install-mise.sh
  - when: pre-start
    description: install Claude Code
    run: |
      [ -x ~/.local/bin/claude ] && exit 0
      curl -fsSL https://claude.ai/install.sh -o /tmp/install-claude.sh
      bash /tmp/install-claude.sh
  - when: pre-start
    description: install Codex
    run: |
      ~/.local/bin/mise which codex >/dev/null 2>&1 && exit 0
      ~/.local/bin/mise use -g codex@latest
  - when: pre-start
    description: install herdr
    run: |
      [ -x ~/.local/bin/herdr ] && exit 0
      curl -fsSL https://herdr.dev/install.sh -o /tmp/install-herdr.sh
      sh /tmp/install-herdr.sh
  - when: pre-start
    description: open herdr panes in bash
    run: |
      [ -f ~/.config/herdr/config.toml ] && exit 0
      mkdir -p ~/.config/herdr
      printf '[terminal]\ndefault_shell = "/bin/bash"\n' > ~/.config/herdr/config.toml

policy:
  name: Dev Box
  networkDefaultVerdict: deny
  allowedDomains:
    - { pattern: github.com, verdict: allow, transport: direct }
    - { pattern: "*.github.com", verdict: allow, transport: direct }
    - { pattern: "*.githubusercontent.com", verdict: allow, transport: direct }
    - { pattern: api.anthropic.com, verdict: allow, transport: direct }
    - { pattern: console.anthropic.com, verdict: allow, transport: direct }
    - { pattern: claude.ai, verdict: allow, transport: direct }
    - { pattern: platform.claude.com, verdict: allow, transport: direct }
    - { pattern: api.openai.com, verdict: allow, transport: direct }
    - { pattern: auth.openai.com, verdict: allow, transport: direct }
    - { pattern: chatgpt.com, verdict: allow, transport: direct }
    - pattern: "*"
      verdict: allow
      transport: direct
      description: Read-only internet
      rules:
        - { method: GET }
        - { method: HEAD }
  credentials:
    - { credentialName: gh-token, envVarKey: GH_TOKEN }

credentials:
  gh-token:
    value: ${GH_TOKEN}
    injections:
      - domain: api.github.com
        headerName: Authorization
        headerFormat: "Bearer {value}"
      - domain: github.com
        headerName: Authorization
        headerFormat: "Basic {base64:x-access-token:{value}}"
```

A few things to note:

- **`command: sleep infinity`** keeps the sandbox up. herdr starts its own server the first time you attach, and that server keeps running after the SSH session ends.
- **`ubuntu:24.04`** is the official image. It has no curl or git, so the first script installs them as root. The agent user must exist in the image, and `ubuntu` does.
- **herdr goes to `~/.local/bin`**, because that is where `herdr --remote` looks for it. The last script makes herdr open panes in bash, so `~/.bashrc` puts the mise tools on the `PATH`.
- **The policy denies by default.** The agents can reach GitHub and their model providers. For the rest of the internet, only `GET` and `HEAD` pass, which cuts off the obvious upload paths.
- **The GitHub token never enters the sandbox.** `$GH_TOKEN` inside the sandbox holds a placeholder. The real token is added to requests to GitHub at the platform boundary. The agents can still push to any repository the token can reach, so scope it to the repositories they need.

## Step 2: Create the sandbox

```
read -rs GH_TOKEN && export GH_TOKEN
nexusctl sandbox create --project <org>/<project> -f sandbox.yaml
```

Paste the token at the prompt. `read -s` keeps it off the screen and out of your shell history, and `create` expands `${GH_TOKEN}` from your environment. The first start runs the install scripts, so give it a minute.

## Step 3: Make the sandbox an SSH host

Let `nexusctl` write the SSH config entry for every sandbox in the project:

```
nexusctl sandbox ssh-config --project <org>/<project> >> ~/.ssh/config
```

The entry matches `*.<project>.<org>.nexus` and routes through `ssh-proxy`:

```
Host *.<project>.<org>.nexus
  User sandbox
  ProxyCommand nexusctl sandbox ssh-proxy %n --project <org>/<project> --server <url>
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null
  LogLevel ERROR
```

Host key checking is off because the proxy is a local process with a throwaway key. Trust comes from your Lens Agents login and TLS to the platform, not from the SSH host key.

Check that plain SSH works first. It's the first thing herdr's own troubleshooting asks you to check:

```
ssh <sandbox>.<project>.<org>.nexus '~/.local/bin/herdr --version'
```

## Step 4: Attach with herdr

```
herdr --remote <sandbox>.<project>.<org>.nexus
```

herdr finds the binary in `~/.local/bin`, starts the server in the sandbox, and opens the workspace. Open a pane, run `claude` or `codex`, and log in once. The login is stored on the volume, so you do it only once.

To let herdr show when an agent is working, waiting for you, or done, install its integration inside the sandbox:

```
herdr integration install claude
herdr integration install codex
```

Detach with `ctrl+b q`. The agents keep running in the sandbox. Run the same `herdr --remote` command later, from this laptop or another one, to pick up where you left off.

### Keep the sandbox next to your local work

If you also run herdr locally, save the sandbox as a machine instead:

```
herdr machine add <sandbox>.<project>.<org>.nexus --label "Lens Agents"
```

The sandbox then shows in the herdr sidebar next to Local, and the agent list covers both, so you can see which agent needs an answer wherever it runs.

## What the sandbox adds

herdr gives you persistence and one place to watch every agent. The sandbox decides what those agents can do:

- **Off your laptop.** The agents run in the sandbox, so they can't read your files, SSH keys, or cloud credentials.
- **Default-deny egress** with per-domain, per-method rules, enforced outside the agent.
- **No secrets in the sandbox.** Secrets that you store in Lens Agents, such as the GitHub token, reach the agents only as placeholders. The real values are added at the boundary. Your Claude and ChatGPT logins are different: they are stored on the sandbox volume, as on any machine where you log in.
- **Audit.** Every request the sandbox proxy handles is recorded, with the policy decision.

Your workflow stays the same: the same herdr, the same agents, the same keybindings. Only the place the agents run changes.

For why these controls matter, read [how to run Hermes Agent securely](https://lenshq.io/blog/how-to-run-hermes-agent-securely/) and [the OpenClaw fallout](https://lenshq.io/blog/banning-ungoverned-ai-agents/).

## Run your coding agents governed

Run Claude Code, Codex and other compatible agents with identity, policy, and audit built in.

[Book a demo](https://lens-agents-contact.lenshq.io/)

## FAQ

**Does the sandbox need an SSH server?**

No. `nexusctl sandbox ssh-proxy` acts as the SSH server on your machine and turns each SSH session into an exec in the sandbox.

**Do my agents stop when I close the laptop?**

No. The herdr server and its panes run in the sandbox. Closing the laptop only disconnects the UI.

**Can I use SSH agent forwarding for Git inside the sandbox?**

No. The proxy refuses agent forwarding. Use a token credential instead, as in the example: the sandbox sees a placeholder and the platform adds the real token to requests to GitHub.

**What happens to my work when the sandbox restarts?**

The home volume stays, so repositories, tool installs, and agent logins stay too. Running processes, including the herdr server, start again from nothing, and herdr restores the saved layout of its session.
