Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 59 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,85 @@ 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-proxy** package on the same Dappnode,
which encrypts them so only the TEE (trusted execution environment) running
Nexus can read them. The proxy verifies that TEE automatically on every
connection; the user can check the proof at
`http://nexus-proxy.dappnode.private:3301/verification`.

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

| Mode | `model.base_url` |
|---|---|
| Off | `https://nexus-api.dappnode.com/v1` |
| On | `http://nexus-proxy.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-proxy` 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/ |
| Attested local proxy | http://nexus-proxy.dappnode.private:3301/v1 |
| Proxy verification page | http://nexus-proxy.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 the local proxy 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 +124,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