Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
cca08b6
Add a Private mode toggle that routes Nexus through the attested loca…
Marketen Aug 24, 2026
5467e02
Depend on nexus-local-proxy now that it is a core package
Marketen Aug 25, 2026
e29d976
Revert "Depend on nexus-local-proxy now that it is a core package"
Marketen Aug 25, 2026
2d7157d
Make Nexus privacy mode a runtime switch
Marketen Sep 9, 2026
94acf52
Decide Nexus mode on an exact hostname, not a substring
Marketen Sep 9, 2026
d42cb4e
Follow the proxy package rename to nexus-proxy
Marketen Sep 9, 2026
e77e46d
Plain-language privacy copy, and a Dappstore link when the proxy is m…
Marketen Sep 9, 2026
af1baad
Frame private mode as an offer, and say what it costs
Marketen Sep 9, 2026
33ab3e0
Drop "yet", tighten the privacy copy
Marketen Sep 9, 2026
73f1b5d
Point users at private/ models, and badge them in the picker
Marketen Sep 9, 2026
2745c3c
Render the privacy card on page load, and bind its toggle once
Marketen Sep 10, 2026
13c7ff2
Restyle the privacy card
Marketen Sep 10, 2026
9b1bea4
Say what private mode protects without overstating it
Marketen Sep 10, 2026
7c62832
Ask before restarting Hermes to switch private mode
Marketen Sep 10, 2026
18ba6fc
Centre the private mode confirm dialog
Marketen Sep 10, 2026
43abcbd
Present the switch as confidentiality proofs
Marketen Sep 10, 2026
7c6be22
Point the Anonymous vs. Private models link at its new docs location
Marketen Sep 10, 2026
c9287c6
Rename the local proxy to Nexus Proofs
Marketen Sep 14, 2026
22d5f15
Depend on the Nexus Proofs package
Marketen Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ build_*/
# Node
node_modules/

# Python (dappnode/ is now an importable module)
__pycache__/
*.pyc

# OS files
.DS_Store
Thumbs.db
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ RUN cd /opt/hermes/scripts/whatsapp-bridge && npm install --omit=dev --no-audit
# Copy setup wizard into the image
COPY setup-wizard/ /opt/setup-wizard/

# Copy DAppNode context files (seeded into HERMES_HOME on first boot)
# Copy Dappnode context files (seeded into HERMES_HOME on first boot)
COPY dappnode/ /opt/dappnode/

# v2026.7.1 auto-starts OAuth when BasicAuthProvider is the only dashboard
Expand All @@ -22,7 +22,7 @@ COPY dappnode/ /opt/dappnode/
# package moves to a release containing it.
RUN python3 /opt/dappnode/backport-dashboard-auth.py

# DAppNode s6-overlay customizations: a cont-init bootstrap hook plus the
# Dappnode s6-overlay customizations: a cont-init bootstrap hook plus the
# setup-wizard and ttyd long-run services. We deliberately do NOT override the
# image ENTRYPOINT — the upstream image runs s6-overlay's /init (which handles
# UID remap, chown, config seeding, schema migration, skills sync and drops to
Expand All @@ -39,7 +39,7 @@ ENV HERMES_HOME=/opt/data
# Expose API server, web UI, setup wizard, and web terminal ports
EXPOSE 3000 8080 8081 7681

# Health check for DAppNode monitoring
# Health check for Dappnode monitoring
HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# DAppNodePackage-Hermes-agent

[![DAppNode](https://img.shields.io/badge/DAppNode-Package-blue)](https://dappnode.io)
[![Dappnode](https://img.shields.io/badge/DAppNode-Package-blue)](https://dappnode.io)
[![Upstream](https://img.shields.io/badge/Upstream-NousResearch%2Fhermes--agent-blueviolet)](https://github.com/NousResearch/hermes-agent)

DAppNode package for [Hermes Agent](https://hermes-agent.nousresearch.com/) by [Nous Research](https://nousresearch.com/) — a self-improving AI agent with multi-LLM support, messaging gateway, persistent memory, and skills system.
Dappnode package for [Hermes Agent](https://hermes-agent.nousresearch.com/) by [Nous Research](https://nousresearch.com/) — a self-improving AI agent with multi-LLM support, messaging gateway, persistent memory, and skills system.

## Features

Expand All @@ -16,7 +16,7 @@ DAppNode package for [Hermes Agent](https://hermes-agent.nousresearch.com/) by [

## Getting Started

1. Install the package from the DAppNode Package Store
1. Install the package from the Dappnode Package Store
2. Open the **Setup Wizard** at `http://hermes-agent.dappnode:8080` to configure your AI provider and API key
3. Open the **Gateway Web UI** at `http://hermes-agent.dappnode:3000` to start chatting

Expand All @@ -31,9 +31,9 @@ npx @dappnode/dappnodesdk build
- [Hermes Agent Documentation](https://hermes-agent.nousresearch.com/docs/)
- [Nous Research](https://nousresearch.com/)
- [Upstream Repository](https://github.com/NousResearch/hermes-agent)
- [DAppNode SDK](https://docs.dappnode.io/docs/dev/sdk/overview)
- [Dappnode SDK](https://docs.dappnode.io/docs/dev/sdk/overview)

## License

This DAppNode package wrapper is provided under the same license as DAppNode packages (Apache-2.0).
This Dappnode package wrapper is provided under the same license as Dappnode packages (Apache-2.0).
Hermes Agent itself is licensed under [MIT](https://github.com/NousResearch/hermes-agent/blob/main/LICENSE).
4 changes: 2 additions & 2 deletions dappnode/bootstrap-env.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Repair DAppNode-specific Hermes .env settings before services start."""
"""Repair Dappnode-specific Hermes .env settings before services start."""
from __future__ import annotations

import os
Expand Down Expand Up @@ -102,7 +102,7 @@ def repair_profile_env(profile_home: Path, *, is_default: bool) -> dict[str, str
if not has_usable_secret(env.get("API_SERVER_KEY", "")):
updates["API_SERVER_KEY"] = secrets.token_hex(32)
else:
# DAppNode exposes a single API server on port 3000. Named profile
# Dappnode exposes a single API server on port 3000. Named profile
# gateways can still run messaging/cron, but must not each bind 3000.
if env.get("API_SERVER_ENABLED", "").strip().lower() not in {"false", "0", "no"}:
updates["API_SERVER_ENABLED"] = "false"
Expand Down
69 changes: 60 additions & 9 deletions dappnode/dappnode-nexus/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: dappnode-nexus
description: DAppNode Nexus — Private AI Gateway for Builders. Knowledge about the Nexus platform, its architecture, API compatibility, context-length pitfalls, and integration with DAppNode infrastructure.
description: Dappnode Nexus — Private AI Gateway for Builders. Knowledge about the Nexus platform, its architecture, API compatibility, context-length pitfalls, and integration with Dappnode infrastructure.
category: devops
tags:
- dappnode
Expand All @@ -10,9 +10,9 @@ tags:
- openai-compatible
---

# DAppNode Nexus
# Dappnode Nexus

Nexus is DAppNode's **Private AI Gateway** — a unified, OpenAI-compatible API for accessing private and confidential AI models.
Nexus is Dappnode's **Private AI Gateway** — a unified, OpenAI-compatible API for accessing private and confidential AI models.

## Core Value Proposition

Expand All @@ -23,35 +23,86 @@ Nexus is DAppNode's **Private AI Gateway** — a unified, OpenAI-compatible API

## Architecture

Nexus runs as a service within the DAppNode ecosystem. Users access it via:
Nexus runs as a service within the Dappnode ecosystem. Users access it via:
- **Web UI**: https://nexus.dappnode.com/
- **API endpoint**: `https://nexus-api.dappnode.com/v1`

**Nexus privacy mode** is a switch on the setup wizard's Dashboard tab. It can
be flipped at any time, not only during setup.

Turned on, prompts go through the **Nexus Proofs** package (`nexus-proofs.dnp.dappnode.eth`) on the same Dappnode,
which keeps them encrypted until they reach Nexus, running in a TEE (trusted
execution environment). With a model whose id does not start with `private/`,
Nexus then passes the prompt to that model's provider, which sees it. The proxy verifies that TEE automatically on every
connection; the user can check the proof at
`http://nexus-proofs.dappnode.private:3301/verification`.

It changes **only** `model.base_url`:

| Mode | `model.base_url` |
|---|---|
| Off | `https://nexus-api.dappnode.com/v1` |
| On | `http://nexus-proofs.dappnode.private:3301/v1` |

The API key, provider (`custom`) and model are the same either way, so
switching never needs a key re-entered or the provider reconfigured. Hermes
reads `config.yaml` at startup, so the switch restarts the package itself.

The switch will not turn private mode on while Nexus Proofs is unreachable:
the proxy fails closed, so Hermes would just stop working. Point the user at
the Dappstore to install it.

### Recommend a `private/` model

Private mode protects the prompt from the proxy to the Nexus Gateway. A model
whose id starts with `private/` extends that the rest of the way: the Gateway
reaches those over an attested, encrypted transport that fails closed, so the
prompt is protected end to end.

If the user turns private mode on while using another model, suggest switching
to a `private/` one. `GET /v1/models` marks them.

### What does not work in private mode

Verified against the live TEE Gateway, not assumed:

- **Auto Router (`nexus/auto`)** returns 500 on the TEE Gateway while working
on production. Tell the user to pick a specific model.
- **PII masking** does not apply. The masking service sits outside the TEE and
the TEE may only reach its measured egress routes, which exclude it.

Normal models, the `private/*` models and streaming all work, and both
endpoints serve the same catalog.

## Key URLs

| Resource | URL |
|----------|-----|
| Nexus Web App | https://nexus.dappnode.com/ |
| Nexus API | https://nexus-api.dappnode.com/v1 |
| DAppNode Main Site | https://dappnode.com/ |
| Nexus Proofs endpoint | http://nexus-proofs.dappnode.private:3301/v1 |
| Nexus Proofs verification page | http://nexus-proofs.dappnode.private:3301/verification |
| Dappnode Main Site | https://dappnode.com/ |

## Privacy Guarantees

- Inference runs on DAppNode infrastructure, not external cloud providers
- Inference runs on Dappnode infrastructure, not external cloud providers
- Data does not leave the user's controlled environment
- No logging or retention of prompts by default
- With Private mode on, prompt and completion bodies are additionally encrypted
to the TEE, so nobody in between can read them

## Pitfalls

### Context length defaults to 256K with Nexus provider

When Nexus is configured as the Hermes provider (`nexus-api.dappnode.com`), Hermes may not auto-detect the model's true context length because:

1. `nexus-api.dappnode.com` is not in Hermes' `_URL_TO_PROVIDER` map → treated as an unknown custom endpoint
1. Neither `nexus-api.dappnode.com` nor Nexus Proofs is in Hermes' `_URL_TO_PROVIDER` map → treated as an unknown custom endpoint
2. Hermes may skip provider-aware lookups (Anthropic API, models.dev, hardcoded defaults)
3. Falls back to `DEFAULT_FALLBACK_CONTEXT = 256_000` tokens if auto-detection fails

> **Update**: The `/v1/models` endpoint now returns `context_size` per model. The DAppNode package auto-sets `model.context_length` as a safety net, but you can verify with `hermes config show`.
> **Update**: The `/v1/models` endpoint now returns `context_size` per model. The Dappnode package auto-sets `model.context_length` as a safety net, but you can verify with `hermes config show`.

**Symptom**: Hermes compresses context early, treats a 1M-token model as 256K, or shows `context_length: 256000` in `/usage`.

Expand All @@ -74,4 +125,4 @@ Common Nexus-proxied models and their context lengths:
| `minimax/minmax-m3` | 512,000 |
| `nexus/auto` | Auto-routing (varies) |

> **Note**: `/v1/models` endpoint is now publicly accessible and returns `context_size` per model. Hermes Agent can query this for auto-detection, but the DAppNode package also pre-sets a safe default.
> **Note**: `/v1/models` endpoint is now publicly accessible and returns `context_size` per model. Hermes Agent can query this for auto-detection, but the Dappnode package also pre-sets a safe default.
38 changes: 19 additions & 19 deletions dappnode/dappnode/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
name: dappnode
description: >
DAppNode package operations — HTTPS exposure, port mapping, inter-package
Dappnode package operations — HTTPS exposure, port mapping, inter-package
connectivity, Nexus provider setup, and troubleshooting.
Use when the user asks about networking, exposing services, connecting
to other packages, or configuring AI providers on DAppNode.
to other packages, or configuring AI providers on Dappnode.
version: 1.0.0
author: DAppNode Association
author: Dappnode Association
license: MIT
metadata:
hermes:
Expand All @@ -15,22 +15,22 @@ metadata:
related_skills: [webhook-subscriptions]
---

# DAppNode Package Operations
# Dappnode Package Operations

This Hermes Agent runs as a DAppNode package. This skill covers DAppNode-specific procedures.
This Hermes Agent runs as a Dappnode package. This skill covers Dappnode-specific procedures.

## Exposing a Service via HTTPS

By default, services are only reachable from inside the DAppNode network. To make a service publicly accessible:
By default, services are only reachable from inside the Dappnode network. To make a service publicly accessible:

1. Direct the user to open the DAppNode UI (`http://my.dappnode`), find the Hermes Agent package, and go to its **Network** tab.
1. Direct the user to open the Dappnode UI (`http://my.dappnode`), find the Hermes Agent package, and go to its **Network** tab.
2. They configure:
- **Subdomain**: a name they choose (e.g., `hermes-api`)
- **Port**: which container port to expose (8081 for dashboard, 3000 for API, etc.)
- **Basic auth** (optional but recommended): username and password
3. The resulting public URL will be: `https://<subdomain>.<dyndns-domain>`

This is powered by the `https.dnp.dappnode.eth` package — an Nginx reverse proxy with automatic TLS via DAppNode's dyndns wildcard certificates.
This is powered by the `https.dnp.dappnode.eth` package — an Nginx reverse proxy with automatic TLS via Dappnode's dyndns wildcard certificates.

### Security Notes
- Always recommend basic auth when exposing the dashboard (port 8081)
Expand All @@ -45,13 +45,13 @@ If the user needs a publicly reachable webhook URL:

## Port Mapping to Host

Users can map container ports directly to the host machine's network via the **Network** tab of the Hermes Agent package in the DAppNode UI (`http://my.dappnode`).
Users can map container ports directly to the host machine's network via the **Network** tab of the Hermes Agent package in the Dappnode UI (`http://my.dappnode`).

This allows access from the local network without VPN — useful for LAN-only setups.

## Inter-Package Connectivity

All DAppNode packages share the `dncore_network` Docker bridge network. Packages are reachable via DNS aliases.
All Dappnode packages share the `dncore_network` Docker bridge network. Packages are reachable via DNS aliases.

### DNS Pattern
- Mono-service packages: `<shortname>.dappnode`
Expand All @@ -63,8 +63,8 @@ All DAppNode packages share the `dncore_network` Docker bridge network. Packages
curl -sf http://<package-alias>.dappnode:<port>/ -o /dev/null && echo "reachable" || echo "unreachable"
```

### Using DAppNode Nexus (Recommended)
DAppNode Nexus (`https://nexus.dappnode.com`) is DAppNode's own privacy-focused LLM gateway. It is OpenAI-compatible and prompts are never logged, stored, or used for training. Check the Nexus website for available models and pricing.
### Using Dappnode Nexus (Recommended)
Dappnode Nexus (`https://nexus.dappnode.com`) is Dappnode's own privacy-focused LLM gateway. It is OpenAI-compatible and prompts are never logged, stored, or used for training. Check the Nexus website for available models and pricing.

To configure, use the Setup Wizard at `http://hermes-agent.dappnode:8080` and select Nexus as the provider. Or manually:
1. Sign up at `https://nexus.dappnode.com` and create an API key
Expand All @@ -77,25 +77,25 @@ model:
provider: "nexus"
providers:
nexus:
name: "DAppNode Nexus"
name: "Dappnode Nexus"
base_url: "https://nexus-api.dappnode.com/v1"
key_env: "NEXUS_API_KEY"
default_model: "minimax/minimax-m2.7"
api_mode: "chat_completions"
```

Verify with `hermes doctor`; the resolved provider source should read `custom_provider:DAppNode Nexus`, not `no-key-required`.
Verify with `hermes doctor`; the resolved provider source should read `custom_provider:Dappnode Nexus`, not `no-key-required`.

**Context length pitfall**: Nexus uses a custom domain (`nexus-api.dappnode.com`) that Hermes cannot auto-resolve for context length detection — models default to 256K tokens. The DAppNode package automatically sets `model.context_length` to 1M for new setups, but if you see early context compression, run `hermes config set model.context_length 1000000`. See the `dappnode-nexus` skill for the full root-cause analysis and per-model context lengths.
**Context length pitfall**: Nexus uses a custom domain (`nexus-api.dappnode.com`) that Hermes cannot auto-resolve for context length detection — models default to 256K tokens. The Dappnode package automatically sets `model.context_length` to 1M for new setups, but if you see early context compression, run `hermes config set model.context_length 1000000`. See the `dappnode-nexus` skill for the full root-cause analysis and per-model context lengths.

## Troubleshooting

### Package Not Reachable
- The target package may not be installed or may be stopped — direct the user to the DAppNode UI (`http://my.dappnode`) to check
- Hermes cannot install or manage other DAppNode packages
- The target package may not be installed or may be stopped — direct the user to the Dappnode UI (`http://my.dappnode`) to check
- Hermes cannot install or manage other Dappnode packages

### Configuration
- Environment variables: editable in the **Config** tab of the Hermes Agent package in the DAppNode UI
- Environment variables: editable in the **Config** tab of the Hermes Agent package in the Dappnode UI
- Config file: `/opt/data/config.yaml`
- API keys: `/opt/data/.env`
- Logs: viewable in the **Logs** tab of the Hermes Agent package in the DAppNode UI
- Logs: viewable in the **Logs** tab of the Hermes Agent package in the Dappnode UI
14 changes: 7 additions & 7 deletions dappnode/hermes.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# DAppNode Environment
# Dappnode Environment

This Hermes Agent instance runs as a DAppNode package inside an isolated Docker container.
This Hermes Agent instance runs as a Dappnode package inside an isolated Docker container.

## Network Access

Users connected to the DAppNode (via VPN or WiFi) access services at:
Users connected to the Dappnode (via VPN or WiFi) access services at:

| Service | URL |
|-----------------|------------------------------------------|
Expand All @@ -15,12 +15,12 @@ Users connected to the DAppNode (via VPN or WiFi) access services at:

These services are important, dont kill them.

**IMPORTANT**: Since this instance runs inside a DAppNode package, `localhost` does not work for users. Always give URLs using the DAppNode Hermes namespace: `http://hermes-agent.dappnode:<port>` (e.g., `http://hermes-agent.dappnode:3000` for the API). The user accesses these from their browser while connected to the DAppNode network.
**IMPORTANT**: Since this instance runs inside a Dappnode package, `localhost` does not work for users. Always give URLs using the Dappnode Hermes namespace: `http://hermes-agent.dappnode:<port>` (e.g., `http://hermes-agent.dappnode:3000` for the API). The user accesses these from their browser while connected to the Dappnode network.

## DAppNode Admin
## Dappnode Admin

The user manages this package (environment variables, port mappings, logs, HTTPS exposure) via the DAppNode UI at `http://my.dappnode` — find the Hermes Agent package and use the Config, Network, and Logs tabs.
The user manages this package (environment variables, port mappings, logs, HTTPS exposure) via the Dappnode UI at `http://my.dappnode` — find the Hermes Agent package and use the Config, Network, and Logs tabs.

## External Access

Services are only reachable from inside the DAppNode network by default. The user can expose any port to the public internet with an HTTPS subdomain (and optional basic auth) via the DAppNode network tab. Load the `dappnode` skill for details on how to guide the user through this.
Services are only reachable from inside the Dappnode network by default. The user can expose any port to the public internet with an HTTPS subdomain (and optional basic auth) via the Dappnode network tab. Load the `dappnode` skill for details on how to guide the user through this.
Loading