Home / Articles

Running Hermes Agent Locally in Rootless Podman with Ollama on an AMD GPU

Running Hermes Agent locally and securely: rootless Podman, one mounted folder, internal networking, Ollama on an AMD GPU with ROCm, and a systemd unit.

Publisher: REACTAPP.TOP20 min read4524 words

This article is published in English.

Sources: Run Hermes Agent Locally (and securely) with Ollama and Rootless Podman on Arch Linux

Running an AI agent against local Markdown and Obsidian notes sounds simple until the question of access comes up. Hermes Agent from Nous Research can run terminal commands, edit files, execute code and call other tools. Handing it a model that is told to touch only one folder does not create a security boundary, because an instruction is just text. Hermes' own documentation draws the same line: its HERMES_WRITE_SAFE_ROOT setting guards the normal file-editing tools, but the terminal tool can still reach everything that the operating-system account running Hermes can reach.

The setup below puts Hermes itself inside rootless Podman, beside an Ollama server running a local model on an AMD GPU. Hermes can edit exactly one folder that is mounted on purpose. The rest of the host filesystem is simply absent from its container namespace, so it cannot wander there. Hermes officially supports running in a container, keeps persistent state under /opt/data, and works with self-hosted inference servers such as Ollama through an OpenAI-compatible endpoint.

The walkthrough was done on EndeavourOS, an Arch derivative. Adapt package commands to other distributions.

Installing rootless Podman

On Arch or EndeavourOS, install Podman with Pacman:

sudo pacman -Syu podman

Pacman may ask which OCI runtime to use, offering these three options:

1) crun
2) krun
3) runc

The choice made here is the first one:

1

That runtime is crun. It pays off later, because Podman's keep-groups feature, which passes supplementary host groups into a rootless container, works with crun.

Confirm the install by printing the version and asking Podman which runtime it uses:

podman --version
podman info --format '{{.Host.OCIRuntime.Name}}'

The second command should answer with the runtime name:

crun

Rootless containers also need subordinate UID and GID mappings for the user, so check that entries exist in both files:

grep "^$USER:" /etc/subuid
grep "^$USER:" /etc/subgid

Run every Podman command from here on as the normal user. Do not prefix them with sudo.

Repairing rootless OverlayFS when needed

The first image pull on this machine failed with an error saying the kernel did not support overlay on an ext filesystem:

kernel does not support overlay fs:
'overlay' is not supported over extfs

When that happens, install the userspace implementation:

sudo pacman -S fuse-overlayfs

Podman recommends fuse-overlayfs for rootless setups where native overlay is unavailable, and it can be named explicitly as the rootless mount program. Check that it is installed:

which fuse-overlayfs

The expected path is:

/usr/bin/fuse-overlayfs

Configuration lives in a per-user file:

~/.config/containers/storage.conf

If it already exists, edit it instead of replacing it blindly. The storage section should select the overlay driver:

[storage]
driver = "overlay"

And a second section should name the mount program:

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"

Then verify the effective settings by filtering Podman's debug output:

podman info --debug | grep -Ei 'graphDriverName|mount_program|overlay'

Moving storage to a bigger drive

For this project the step became necessary almost at once. The Ollama container image alone is several gigabytes, models often add another 5 to 20 GB or more, and Podman also uses temporary disk space while downloading and unpacking layers. The first Ollama pull eventually died with a message about no space left on a path under the temporary directory:

write /var/tmp/container_images_storage...:
no space left on device

Two separate locations are involved, which is easy to miss. Permanent rootless storage for images, containers and volumes normally sits under the home directory:

$HOME/.local/share/containers/storage

Downloaded layers, however, land temporarily in:

/var/tmp

Changing Podman's --tmpdir does not relocate that download directory. Podman's documentation says to use the TMPDIR environment variable instead.

With a larger local filesystem available, create a layout with one directory for storage and one for temporary files:

/path/to/large-drive/podman/
├── storage/
└── tmp/

Create both and hand ownership to the current user:

sudo mkdir -p /path/to/large-drive/podman/storage
sudo mkdir -p /path/to/large-drive/podman/tmp
sudo chown -R "$USER:$USER" /path/to/large-drive/podman

Then open the storage configuration for editing:

mkdir -p ~/.config/containers
nano ~/.config/containers/storage.conf

Point the graph root at the new storage directory while keeping the overlay driver:

[storage]
driver = "overlay"
graphroot = "/path/to/large-drive/podman/storage"

And keep the fuse-overlayfs mount program setting:

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"

Choose a local filesystem for this. Podman's documentation cautions that NFS and other network filesystems misbehave as rootless container storage.

Temporary downloads need relocating as well, so set TMPDIR for the current shell:

export TMPDIR=/path/to/large-drive/podman/tmp

To make it permanent in Bash, append it to the profile and reload it:

echo 'export TMPDIR=/path/to/large-drive/podman/tmp' >> ~/.bashrc
source ~/.bashrc

Verify the new locations before deleting anything from the old one. Print the graph root and filter the debug output for the temp directory and mount program:

podman info --format 'GraphRoot: {{.Store.GraphRoot}}'
podman info --debug | grep -Ei 'graphRoot|imageCopyTmpDir|mount_program'

Only on a fresh Podman installation, and only when the new graph root is definitely correct, should the abandoned storage in the old location be considered for deletion:

~/.local/share/containers/storage

Do not do that casually on an existing installation. It is also why the storage location should be settled before the Ollama model volume is created.

One folder for Hermes, two volumes for state

Pick the single host directory Hermes may touch, and pick it deliberately. Export its path in a variable and create the folder:

export HERMES_WORKSPACE="$HOME/path/to/Hermes-Workspace"
mkdir -p "$HERMES_WORKSPACE"

With Obsidian, a good choice is one dedicated folder inside a vault, such as a research directory sitting next to personal and work folders:

Obsidian Vault/
├── Personal/
├── Work/
├── Research/
└── Agent Workspace/       ← only this folder is exposed

Everything else in the vault stays invisible to the agent.

Next, create two volumes that Podman manages itself:

podman volume create hermes-data
podman volume create ollama-models

The first holds Hermes' configuration, memory and persistent state. The second holds the local model. Neither requires exposing any further host directory to the agent.

Giving the container AMD GPU access

This setup targets AMD ROCm, not NVIDIA CUDA. On an AMD card, do not install nvidia-container-toolkit, a detour that was taken here before the mistake was noticed. Ollama's container instructions use the ollama/ollama:rocm image for AMD GPUs and pass two device nodes through to the container:

/dev/kfd
/dev/dri

Check that they exist on the host:

ls -l /dev/kfd
ls -l /dev/dri/render*

Then look at the account's groups:

groups

If the account is missing from the video and render groups, add it:

sudo usermod -aG video,render "$USER"

Then log out completely and back in. Running the group listing again in the same old terminal will not show the new membership, which causes some needless head-scratching:

groups

After a fresh login, run it once more:

groups

The output should now include both groups:

video render

Podman's --group-add keep-groups option later passes these supplementary groups into the rootless container so it can use the GPU devices.

Checking the TUN device

Another failure came from rootless networking. Pasta, Podman's default rootless network mechanism, complained that it could not open the TUN device:

pasta failed with exit code 1:
Failed to open() /dev/net/tun: No such device

Look for the device node:

ls -l /dev/net/tun

If it is missing, load the kernel module:

sudo modprobe tun

Then check again. If modprobe says the module does not exist for the running kernel, and the system was upgraded recently, compare the running kernel version against the module directories:

uname -r
ls /usr/lib/modules/

On a rolling distribution like Arch the machine may simply still be running an older kernel whose modules were removed during the upgrade. Reboot and retry.

Choosing a local model

For cards around 16 GB of VRAM, Ollama currently recommends gemma4 as a local model for Hermes, and systems near 24 GB can look at models like qwen3.6. The example here takes a 12-billion-parameter gemma4 variant:

gemma4:12b

Store it in a variable, adjusting the model for the actual hardware:

export HERMES_MODEL="gemma4:12b"

The other number that matters is context length. Ollama defaults GPUs with less than 24 GiB of VRAM to only a 4K context, while both projects advise a minimum of 64,000 tokens whenever agents are involved. That gets configured explicitly further on.

Pulling images and the model

Three images are needed: the regular Ollama image, the ROCm build and the Hermes image:

podman pull docker.io/ollama/ollama:latest
podman pull docker.io/ollama/ollama:rocm
podman pull docker.io/nousresearch/hermes-agent:latest

The regular Ollama image handles the one-time model download. The ROCm image becomes the permanent AMD inference server.

Download the model into its persistent volume using a throwaway container. First clear any stale one:

podman rm -f ollama-bootstrap 2>/dev/null

Then start the downloader with the model volume mounted and nothing else:

podman run -d \
  --name ollama-bootstrap \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:latest

If rootless pasta networking still refuses to work, host networking is acceptable for this temporary downloader only. Remove the container again:

podman rm -f ollama-bootstrap 2>/dev/null

And start it with the host network:

podman run -d \
  --network host \
  --name ollama-bootstrap \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:latest

No Hermes workspace is mounted into this container. Pull the model through it:

podman exec -it ollama-bootstrap \
  ollama pull "$HERMES_MODEL"

Confirm with a listing:

podman exec ollama-bootstrap ollama list

The chosen model should show up:

gemma4:12b

Remove the temporary container. The model stays in the volume:

podman rm -f ollama-bootstrap

If Podman complains that the container name is already taken:

container name "ollama-bootstrap" is already in use

do not keep inventing new names. Clear the stale container instead:

podman rm -f ollama-bootstrap

or add the --replace flag.

An internal network and a pod

The first design used no network at all:

--network none

That was changed to a dedicated internal Podman bridge. Hermes and Ollama get a shared network namespace in which they can talk to each other, while Podman restricts access to the outside. The --internal option exists specifically to limit external access on bridge networks. Create it:

podman network create \
  --ignore \
  --internal \
  hermes-internal

Then create the pod on that network:

podman pod create \
  --name hermes-local \
  --network hermes-internal \
  --userns=keep-id:uid=10000,gid=10000

The keep-id option places the unprivileged host account inside the container's user namespace, and the numbers it shows up as inside can be chosen. The Hermes container runs as 10000:10000, which is why those values appear.

Starting Ollama with ROCm

One of the less obvious problems turned up here. The command that finally worked looks like this:

podman run -d \
  --name ollama \
  --pod hermes-local \
  --device /dev/kfd \
  --device /dev/dri \
  --group-add keep-groups \
  -e HOME=/root \
  -e OLLAMA_MODELS=/root/.ollama/models \
  -e OLLAMA_CONTEXT_LENGTH=64000 \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:rocm

Its AMD-specific lines are:

--device /dev/kfd
--device /dev/dri
--group-add keep-groups
ollama/ollama:rocm

In this Podman layout, two environment variables should not be omitted:

HOME=/root
OLLAMA_MODELS=/root/.ollama/models

The reason is a puzzling failure. The model had downloaded correctly, the blobs and manifest existed and the volume was mounted properly. Yet listing models returned nothing at all:

podman exec ollama ollama list

The container's log told the story:

podman logs ollama

It showed Ollama using a home directory of the filesystem root and a models path derived from it:

HOME=/
OLLAMA_MODELS=/.ollama/models

while the model actually lived in:

/root/.ollama/models

Ollama builds its default model path from the home directory plus .ollama/models unless OLLAMA_MODELS overrides it. So the model was never missing. Ollama was looking in the wrong place. Setting both variables explicitly fixed it at once:

HOME=/root
OLLAMA_MODELS=/root/.ollama/models

Now the listing should show the model:

podman exec ollama ollama list

Confirming the GPU is used

Search the log for ROCm and GPU lines:

podman logs ollama 2>&1 | grep -Ei 'gpu|rocm|amd|gfx'

A working AMD setup identifies a ROCm compute device. Then run a quick test prompt that asks the model for an exact reply:

podman exec -it ollama \
  ollama run gemma4:12b \
  "Reply with exactly: AMD GPU test successful"

Immediately afterwards, look at the running models:

podman exec ollama ollama ps

Two columns deserve attention:

PROCESSOR
CONTEXT

Ideally the processor column shows the GPU and not CPU offload, and the context is around:

64000

Ollama itself recommends checking its process listing to see both the allocated context and the offloading. A model that advertises a huge native context is not necessarily getting that much allocated.

Starting Hermes with one mount

Now create the Hermes container inside the same pod:

podman run -d \
  --name hermes \
  --pod hermes-local \
  --security-opt=no-new-privileges \
  --pids-limit 512 \
  -v hermes-data:/opt/data \
  -v "$HERMES_WORKSPACE:/opt/data/workspace:rw,nodev,nosuid" \
  -w /opt/data/workspace \
  docker.io/nousresearch/hermes-agent:latest \
  sleep infinity

What matters is what is missing from it. None of these appear:

/:/host
/home:/home
~/.ssh
~/.config
Docker socket
Podman socket

The only arbitrary host bind is the workspace folder, mapped into the container's data directory:

$HERMES_WORKSPACE
        ↓
/opt/data/workspace

Hermes' own state lives in the named volume, which is mounted at its data path:

hermes-data
        ↓
/opt/data

The official container uses /opt/data for persistent state and sets HERMES_WRITE_SAFE_ROOT to it automatically. That write guard counts as defence in depth, not the main sandbox, since Hermes' documentation warns that terminal commands can bypass its file-tool restrictions. The real boundary is the Podman filesystem namespace.

Verifying the mount boundary

Inspect what the host actually exposes to Hermes:

podman inspect hermes \
  --format '{{range .Mounts}}{{println .Type .Source "->" .Destination}}{{end}}'

Expect two lines, one for the data volume and one for the allowed folder:

Podman volume -> /opt/data
your allowed folder -> /opt/data/workspace

The home directory and the container-runtime socket must not appear. Check the socket explicitly:

podman exec hermes sh -c \
  'test ! -S /var/run/docker.sock && echo "No Docker socket exposed"'

Host-sensitive paths can be probed too, for instance asking whether an SSH directory is visible:

podman exec hermes sh -lc \
  'test -e "$HOME/.ssh" && echo "SSH directory visible" || echo "Host SSH directory not visible"'

Remember that the container has its own system directories. Seeing them is normal, since they belong to the container, not the host.

Hermes reaching Ollama

Both containers share the pod's network namespace, so they communicate over loopback. Test Ollama's OpenAI-compatible API from inside the Hermes container:

podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("http://127.0.0.1:11434/v1/models").read().decode())'

The reply should be JSON containing the local model. A first attempt used a Python heredoc and printed nothing:

podman exec hermes python - <<'PY'
...
PY

The cause was simple. Python reads its program from stdin in that form, but podman exec had not been told to attach stdin. Adding the interactive flag makes the heredoc version work:

podman exec -i hermes python - <<'PY'
import urllib.request
print(
    urllib.request.urlopen(
        "http://127.0.0.1:11434/v1/models"
    ).read().decode()
)
PY

That missing flag cost a few extra minutes.

Proving the outside world is unreachable

Try fetching an external website from inside Hermes' container:

podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("https://example.com", timeout=5).read())'

It should fail, and that is intentional. Hermes can reach Ollama inside the pod, but the network gives no normal external access.

Do not panic if the container listing:

podman ps

shows a port next to Ollama:

11434/tcp

That does not mean the port was published to the host. A published port would look more like this:

0.0.0.0:11434->11434/tcp

No -p 11434:11434 is used anywhere in this setup.

Pointing Hermes at local Ollama

Start Hermes' setup as the container's user, working in the workspace:

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes model

The provider picker lists many cloud services. Do not choose the cloud option:

Ollama Cloud

Choose the manual endpoint entry instead:

Custom endpoint (enter URL manually)

For the base URL, use the loopback address and the v1 path:

http://127.0.0.1:11434/v1

If an API key is requested, leave it blank. Ollama's current Hermes integration notes use exactly this custom-endpoint flow with an empty key for a local server. When asked about API compatibility, pick the OpenAI-compatible chat-completions path. For the model name, enter:

gemma4:12b

and for the context length:

64000

Hermes' provider documentation says local Ollama should use the v1 endpoint and needs at least 64K of context for agent workflows that use tools.

Using the local terminal backend, inside the container

This sounds odd at first. Hermes normally warns that its local terminal backend setting:

terminal:
  backend: local

runs terminal commands directly on the same machine as Hermes with no isolation. On a directly installed Hermes that would be dangerous. Here Hermes itself is already inside Podman, so the local backend resolves to the Hermes container:

Hermes "local"
      ↓
Hermes container

and not to the workstation:

Hermes "local"
      ↓
your Arch workstation

Set the backend:

podman exec --user 10000:10000 hermes \
  hermes config set terminal.backend local

And the working directory:

podman exec --user 10000:10000 hermes \
  hermes config set terminal.cwd /opt/data/workspace

A preference for a profile-based home mode is also worth setting:

podman exec --user 10000:10000 hermes \
  hermes config set terminal.home_mode profile

Do not configure Hermes' Docker backend for this design. It would require handing Hermes access to a container runtime, which undoes one of the main isolation decisions.

Launching Hermes

An interactive session starts with the same exec pattern used for setup:

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes

For note cleanup, a prompt might ask Hermes to review the Markdown notes in the workspace, tidy the structure and headings, fix formatting and grammar, keep every technical detail, command, link and Obsidian wikilink intact, and never cut real content merely to make a note shorter. Changes land directly in the single mounted host folder.

Starting the environment at boot

Once everything works interactively, the pod should return after a reboot without manually starting Ollama and Hermes. Create a user systemd unit:

mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/hermes-local.service

The unit begins with a description and an ordering line:

[Unit]
Description=Hermes Local AI Pod
After=default.target

The service section is a one-shot that starts the pod, stops it with a 30-second grace period and allows two minutes to start:

[Service]
Type=oneshot
RemainAfterExit=yesExecStart=/usr/bin/podman pod start hermes-local
ExecStop=/usr/bin/podman pod stop -t 30 hermes-localTimeoutStartSec=120
TimeoutStopSec=60[Install]
WantedBy=default.target

If Podman's graph root lives on a separate drive, that drive must be a reliable boot-time mount and not something the desktop mounts interactively after login. An optional ExecStartPre mount check suited to the filesystem can be added.

Reload systemd:

systemctl --user daemon-reload

Enable the unit:

systemctl --user enable hermes-local.service

Start it:

systemctl --user start hermes-local.service

And check it:

systemctl --user status hermes-local.service

Because it is a oneshot service with RemainAfterExit set, this status is normal:

Active: active (exited)

A newer Quadlet mechanism exists in Podman for defining containers declaratively under systemd. With an environment already assembled and tested by hand, though, a tiny user service that starts the pod known to work disturbed the least.

To let the rootless user service start at boot even before logging into the desktop, enable lingering:

sudo loginctl enable-linger "$USER"

Verify it:

loginctl show-user "$USER" -p Linger

The expected result:

Linger=yes

Systemd's linger feature makes the user's service manager start at boot and stay available outside normal login sessions. After a reboot, the container listing:

podman ps

should already show the pod with Ollama and Hermes running. An interactive Hermes session still starts with the exec command:

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes

The boot service starts the environment. It does not open an interactive chat.

Problems that actually occurred

Here is the condensed list of failures met along the way, each as a problem and a fix.

An overlay error from the kernel:

Problem:
kernel does not support overlay fs

was solved by installing fuse-overlayfs and setting it as the overlay mount program:

Fix:
Install fuse-overlayfs and configure it as the overlay mount program.

Running out of space under the temporary directory:

Problem:
no space left on device under /var/tmp

needed both a moved graph root where appropriate and a TMPDIR setting, since Podman's tmpdir flag is a different thing:

Fix:
Move Podman's graphroot if needed AND set TMPDIR.
Changing Podman's --tmpdir is not the same thing.

A name conflict for the bootstrap container:

Problem:
ollama-bootstrap name already in use

is fixed by removing the stale container:

Fix:
podman rm -f ollama-bootstrap

Pasta failing to open the TUN device:

Problem:
pasta cannot open /dev/net/tun

is fixed by loading the tun module, or rebooting if installed modules do not match the running kernel:

Fix:
sudo modprobe tun
If the installed modules don't match the running kernel, reboot.

An NVIDIA toolkit installed on an AMD machine:

Problem:
Installed nvidia-container-toolkit on an AMD machine

should simply not be done, using the ROCm image with the kfd and dri devices instead:

Fix:
Don't.
Use ollama/ollama:rocm with /dev/kfd and /dev/dri.

New video and render group memberships that did not show up in the group listing:

Problem:
Added myself to video/render but `groups` still didn't show them

need a logout and login, because the existing session keeps its original groups:

Fix:
Log out and back in.
The existing login session retains its original supplementary groups.

Model files and manifest present while Ollama's listing is empty:

Problem:
Ollama model files and manifest exist, but `ollama list` is empty

is diagnosed by reading the log and, if the home and models paths point at the filesystem root while the volume is mounted under the root user's directory, setting both variables explicitly:

Fix:
Check:
podman logs ollamaIf Ollama is using:
OLLAMA_MODELS=/.ollama/modelswhile the volume is mounted under:
 /root/.ollamaset explicitly:
HOME=/root
OLLAMA_MODELS=/root/.ollama/models

A Python test from Hermes to Ollama that silently printed nothing:

Problem:
Hermes → Ollama Python test silently prints nothing

was fixed by adding the interactive flag when using a heredoc, or simply using the inline command form:

Fix:
If using `python -` with a heredoc, add `podman exec -i`.
Or simply use `python -c`.

A provider menu with no obvious local Ollama entry:

Problem:
The Hermes provider menu doesn't contain "local Ollama"

was resolved by choosing the manual custom endpoint and entering the local v1 URL:

Fix:
Choose:
Custom endpoint (enter URL manually)Then:
http://127.0.0.1:11434/v1

And Hermes complaining about an inadequate context window even though everything else worked:

Problem:
Everything works, but Hermes reports an inadequate context window

is solved by setting the context length server-side to 64000, configuring Hermes for the same value and checking the real allocation with Ollama's process listing:

Fix:
Set OLLAMA_CONTEXT_LENGTH=64000 server-side and configure Hermes for the same value.
Verify the real allocation with:
ollama ps

Where the trust boundary should live

The main lesson here is not really about Ollama or ROCm. It is about deciding where the trust boundary lives. A prompt saying never to read anything outside a directory is a behavioural instruction. A Podman container that was never given the rest of the host filesystem is an operating-system boundary. Hermes' write-safe root is useful, but Hermes itself describes it as an extra layer and not a true sandbox, since shell commands are able to sidestep the file-tool guard.

The resulting trust model stacks the layers from weakest to strongest, starting with prompt restrictions and Hermes' file-write protection and ending at the container boundary:

Prompt restrictions
        ↓
Hermes file write protections
        ↓
Hermes container filesystem
        ↓
Rootless Podman user namespace
        ↓
Host filesystem permissions

Even if the model receives a malicious instruction hidden in a Markdown file, the interesting question is not whether it will obey the original prompt. It is what the compromised agent can actually reach. In this design, the answer is intentionally small.

Debug in layers

Hermes and Ollama both change quickly. Hermes' current documentation supports self-hosted OpenAI-compatible endpoints, Ollama included, but there have also been upstream reports about tool-call behaviour through custom Ollama endpoints. If ordinary chat works yet Hermes seems to describe tool calls without executing them, check the current Hermes issue tracker before assuming the Podman sandbox is broken.

That is the reason to test the stack as a chain of questions, from whether Podman sees the GPU, through whether Ollama sees the model, and onward to whether Hermes can talk to Ollama:

Does Podman see the GPU?
        ↓
Does Ollama see the model?
        ↓
Does Ollama actually use the GPU?
        ↓
Is the context really 64K?
        ↓
Can Hermes reach /v1/models?
        ↓
Can Hermes perform an actual file operation?
        ↓
Can Hermes reach anything it shouldn't?

Do not try to debug all six layers at once.

What the finished setup gives

The resulting configuration provides local inference, AMD GPU acceleration, persistent Hermes memory and one writable host workspace:

Local inference                YES
AMD GPU acceleration           YES
Hermes persistent memory       YES
One writable host workspace    YES

And it needs no cloud model, exposes no host filesystem, exposes no Podman or Docker socket and has no normal internet egress:

Cloud LLM required             NO
Host filesystem exposed        NO
Podman/Docker socket exposed   NO
Normal Internet egress         NO
Entire Obsidian vault exposed  NO

That is a far more comfortable way to experiment with a capable local agent. Hermes still has enough access to be useful. It just does not get to decide for itself what enough access means.