Tentaflake — Agent Management Guide
Source:
docs/02-agent-tips.mdat2081f31a099e· docs version 0.4.0
This guide contains historical direct-network and direct-secret development examples. They require the explicit
devsecurity profile. Installed hosts now default tobalanced, where shared policy enforces gVisor, non-root execution, read-only root, no published services, and no real credential files. Networking isnoneunless an exact broker policy is declared. See security profiles before copying an example.
Day-to-day operations for your Hermes agents: state, logs, secrets, updates, security.
State Directories
Section titled “State Directories”Each agent gets an isolated state directory at /var/lib/hermes-<name>:
/var/lib/hermes-coding/├── workspace/ # Agent working directory — files, clones, output├── skills/ # Loaded skill files├── cron/ # Scheduled task definitions└── ... # Hermes internal state (config, sessions, logs)Inside the container, HERMES_HOME points here. All agent persistence lives
in this tree.
List all agent state dirs:
ls -la /var/lib/ | grep hermesManaging Agents
Section titled “Managing Agents”Via systemd
Section titled “Via systemd”Docker containers are managed by systemd. List agent services:
sudo systemctl list-units | grep hermesExpected:
docker-hermes-coding.service loaded active running Docker Application Container hermes-codingStart / stop / restart:
sudo systemctl start docker-hermes-codingsudo systemctl stop docker-hermes-codingsudo systemctl restart docker-hermes-codingsudo systemctl status docker-hermes-codingVia Docker
Section titled “Via Docker”# List running agent containerssudo docker ps --filter "name=hermes-"
# List all (including stopped)sudo docker ps -a --filter "name=hermes-"
# View logssudo docker logs hermes-codingsudo docker logs --tail 50 -f hermes-coding # tail + follow
# Restartsudo docker restart hermes-coding
# Enter container shellsudo docker exec -it hermes-coding bash
# Run Hermes command directlysudo docker exec -it hermes-coding hermes chatsudo docker exec -it hermes-coding hermes modelInspect container config
Section titled “Inspect container config”sudo docker inspect hermes-coding | jq '.[0].Config.Env'sudo docker inspect hermes-coding | jq '.[0].Mounts'Adding / Removing Agents
Section titled “Adding / Removing Agents”Add an agent:
-
Edit
my-agents.nix— append an attrset to thehermesAgentslist:{name = "personal";envFile = "/run/secrets/hermes-personal.env";}See the commented reference agent in
my-agents.nix.examplefor every availablesettings/ volume / container option.A second runtime, ZeroClaw, works the same way via the
zeroclawAgentslist (agents get container/state dirzeroclaw-<name>, config from asettingsattrset serialized to TOML instead of YAML). See the commented reference entry inmy-agents.nix.exampleandzeroclaw.env.examplefor its env-file convention. -
For the explicit
devprofile only, create its direct env file. Balanced rejects real provider env files and uses the broker flow in brokered egress:Terminal window sudo cp /etc/nixos/hermes.env.example /run/secrets/hermes-personal.envsudo chmod 600 /run/secrets/hermes-personal.envsudo vi /run/secrets/hermes-personal.env -
Rebuild:
Terminal window sudo nixos-rebuild switch --flake /etc/nixos#<hostname>
Remove an agent: Delete or comment out the agent block, rebuild. Container, system user, and state dir remain on disk. Clean up manually:
sudo rm -rf /var/lib/hermes-<name>sudo userdel hermes-<name>sudo groupdel hermes-<name>Secrets rotation
Section titled “Secrets rotation”In balanced, rotate the broker-owned provider credential through the
deployment’s runtime secret mechanism, then restart only the exact LLM broker.
The controller retains its revocable virtual key and never receives the real
provider value. Verify /healthz and audit readiness; do not print the key or
inspect it through the agent environment. Direct /run/secrets/*.env rotation
is a compatibility procedure for explicitly selected dev systems only.
Multiple Agents
Section titled “Multiple Agents”Each balanced agent receives a separate, policy-bounded capsule. This reduces cross-agent authority; it is not a claim of complete isolation from kernel, runtime, or image vulnerabilities.
| Aspect | Isolation |
|---|---|
| Container | Separate Docker container |
| System user | hermes-<name> with own UID/GID |
| State dir | /var/lib/hermes-<name> (0700) |
| Credentials | Per-agent virtual broker key; real provider key stays host-side |
| Configuration | Separate HERMES_HOME |
| Network | network=none or one dedicated internal broker network |
Run agents of any type: coding, research, personal, automation, monitoring.
The generated mount policy and private host permissions deny direct access to
another agent’s state. dev may deliberately weaken these properties and must
not be treated as an untrusted 24/7 boundary.
Logging
Section titled “Logging”Docker logs
Section titled “Docker logs”sudo docker logs hermes-codingsudo docker logs --tail 100 -f hermes-codingJournald (systemd view)
Section titled “Journald (systemd view)”sudo journalctl -u docker-hermes-codingsudo journalctl -u docker CONTAINER_NAME=hermes-codingAgent internal logs
Section titled “Agent internal logs”Inside the container, Hermes writes to:
sudo docker exec hermes-coding cat $HERMES_HOME/logs/errors.logsudo docker exec hermes-coding cat $HERMES_HOME/logs/gateway.logLocal observability
Section titled “Local observability”The optional observability profile sends the systemd journal to local Loki via Alloy and provisions Grafana with Loki and Prometheus data sources. It replaces the removed custom audit database and web console without enlarging the core. See observability and detection.
Backups
Section titled “Backups”Save these for disaster recovery:
/etc/nixos/ # Full system config (flake + modules)/var/lib/hermes-*/workspace/ # Agent working files (selective)/var/lib/hermes-*/cron/ # Scheduled task definitionsEnv files are on tmpfs (/run/secrets/) — lost on reboot.
Store keys in a password manager.
Quick backup script:
#!/usr/bin/env bashBACKUP="/root/backup-$(date +%Y%m%d-%H%M%S)"mkdir -p "$BACKUP"cp -r /etc/nixos "$BACKUP/nixos"for d in /var/lib/hermes-*; do [ -d "$d/cron" ] && cp -r "$d/cron" "$BACKUP/$(basename $d)-cron"donetar czf "$BACKUP.tar.gz" "$BACKUP"echo "Backup: $BACKUP.tar.gz"Updating Containers
Section titled “Updating Containers”The default agent images are pinned directly in their builders by OCI manifest
digest. The image digests are independent of flake.lock, so update them
deliberately:
# Print the current upstream digest for every tracked image./scripts/update-agent-images.sh
# Review the output, edit lib/constants.nix by hand, then rebuildsudo nixos-rebuild switch --flake /etc/nixos#<hostname>The bump stays manual on purpose: a script that rewrites the pin for you is a mutable tag with extra steps.
If you override image, it must be digest-pinned too — mkHermesAgent and
mkZeroClawAgent reject an unpinned reference at eval time, so a mutable tag
fails the build instead of silently producing a different deployment.
Write the reference as registry/repository@sha256:digest, not
repository:tag@sha256:digest. The docker CLI tolerates carrying both a tag and
a digest, but podman and skopeo reject it outright:
Docker references with both a tag and digest are currently not supportedSince tentaflake.containerBackend supports podman, the tag-plus-digest form
would break those hosts; keep the version in a comment instead.
For an image you build locally, there is no registry digest to pin to. On an
explicitly selected dev host only, set allowMutableImage = true; to
acknowledge it is not reproducible. Balanced/strict reject this escape hatch:
(mkHermesAgent { name = "coding"; image = "my-hermes:local"; allowMutableImage = true;})Performance Tuning
Section titled “Performance Tuning”Memory limits
Section titled “Memory limits”For balanced, set the shared enforced limits in the security profile. The
container policy appends these flags after caller configuration, so
extraContainerConfig cannot weaken or replace them:
tentaflake.security.resources = { memory = "4g"; memorySwap = "4g"; # no additional swap cpus = "0.5"; nofile = 4096;};extraContainerConfig resource overrides are a dev compatibility technique,
not a way to tune a secure capsule.
Process limits
Section titled “Process limits”Every agent container gets --pids-limit=512 by default — a fork-bomb ceiling
generous enough for compile jobs. A balanced agent may raise it only to another
positive ceiling; null (unlimited) is rejected by the secure profile:
(mkHermesAgent { name = "coding"; pidsLimit = 1024; # heavy parallel builds; must stay positive in balanced})Capabilities
Section titled “Capabilities”balanced always enforces cap-drop=ALL; it is not opt-in and cannot be
removed with extraContainerConfig. Build dependencies into a reviewed,
digest-pinned image or run them through the disposable worker rather than
granting Linux capabilities to a long-lived controller. The dev profile is
the only compatibility path for experiments requiring broader authority.
Resource monitoring
Section titled “Resource monitoring”# Per-container statssudo docker stats hermes-coding
# System-widehtopsudo journalctl -u docker-hermes-coding --since "1 hour ago" | grep -i oomDisk usage
Section titled “Disk usage”du -sh /var/lib/hermes-*/Security Notes
Section titled “Security Notes”Env files and credentials
Section titled “Env files and credentials”/run/secrets/ is a tmpfs mount — contents never written to disk. It is a
valid location for the broker’s Agenix-decrypted provider credentials, but
not for credentials mounted into a balanced or strict agent. Such agents
receive only their revocable virtual broker key and use the configured broker
endpoint; the broker retains the real provider, GitHub, and fetch credentials.
Direct agent env files are a dev-profile compatibility path only. They are
lost at reboot unless recreated by Agenix or another secret manager. Never put
them in /etc/nixos/: that can put their contents in the world-readable Nix
store.
Docker isolation
Section titled “Docker isolation”balanced agents run in their own internal capsule network, not with host
networking. The shared builders enforce an explicit non-root uid/gid,
cap-drop=ALL, no-new-privileges, a read-only root filesystem, gVisor
runsc, private tmpfs paths, and CPU/RAM/swap/PID/file limits. Only the
agent’s State and Workspace mounts are writable. The broker is the only
configured external authority.
The dev profile intentionally retains a compatibility path with broader OCI
options. It is not suitable for an untrusted 24/7 agent and must never be used
as an implicit fallback from balanced.
System user security
Section titled “System user security”Each agent has its own system user hermes-<name> with:
- No login shell (isSystemUser)
- Home directory = state dir
- No sudo access
Detection and evidence
Section titled “Detection and evidence”Journald is the primary host and container-unit evidence source. The optional observability profile retains it in Loki; the separate Falco profile adds kernel runtime detection. Neither profile is container isolation.
Config Tips (settings parameter)
Section titled “Config Tips (settings parameter)”When using the settings parameter on mkHermesAgent, keep these in mind.
Provider configuration
Section titled “Provider configuration”For balanced, define the permitted provider/models and real credentials in
tentaflake.broker.agents.<name> and configure the runtime to use that local
LLM endpoint with its virtual agent key. Do not add OpenRouter, Groq,
Firecrawl, GitHub, or other real provider keys to an agent env file.
The following direct variables are legacy dev configuration only:
| Setting | Direct variable |
|---|---|
model.provider = "openrouter" |
OPENROUTER_API_KEY |
stt.provider = "groq" |
GROQ_API_KEY |
web.backend = "firecrawl" |
FIRECRAWL_API_KEY |
Model provider
Section titled “Model provider”Always add model.provider alongside model.default:
model = { default = "deepseek/deepseek-v4-flash"; provider = "openrouter"; # ← required, not inferred from model name};Compression
Section titled “Compression”Protect recent context and system prompt from compression:
compression = { enabled = true; threshold = 0.50; # compress at 50% context fill target_ratio = 0.20; # compress to 20% of original protect_last_n = 20; # keep last 20 messages uncompressed protect_first_n = 3; # keep first 3 (system prompt) uncompressed};Without protect_last_n/protect_first_n, the agent’s system prompt and
recent conversation get compressed — losing identity and continuity.
MCP Servers & Node.js
Section titled “MCP Servers & Node.js”The default Hermes agent container (docker.io/nousresearch/hermes-agent) is
Python-based and may not include Node.js. MCP servers using npx
will fail with “command not found”. Solutions:
-
Build a custom Docker image extending the Hermes one with Node.js
-
Use a Python-based MCP server (e.g.
mcp-server-filesystemPython package) -
Build a pinned custom agent image containing Node.js when it is required. Do not bind-mount
/usr/bin/nodeor other host binaries into a secure capsule. -
Use the broker fetch gateway for web retrieval in
balanced. It constrains destinations, redirects, DNS answers, response size, and provenance. The optional Hive Research module (modules/optional/hive-research.nix) is outside the core and may be used by an operator or an explicitdevintegration, but a balanced capsule cannot reach host loopback and must not be given its key-bearing endpoint:services.hive-research = {enable = true;package = inputs.hive-research.packages.${pkgs.system}.default;keyFiles.BRAVE_API_KEY_FILE = "/run/agenix/hive-brave-api-key";};A
dev-profile agent can explicitly configure its MCP client with:mcp_servers:hive-research:url: "http://127.0.0.1:7815/mcp"
Optional Piper voice files
Section titled “Optional Piper voice files”Piper is not part of the core. A secure agent image must contain any required
voice assets at build time and be pinned by digest; mounting host voice paths
is a dev-only compatibility option. Online TTS also needs an explicit
brokered/policy-controlled integration rather than direct agent egress.
Toolsets
Section titled “Toolsets”["all"] enables every toolset including risky ones (Docker, package
management, system operations). Prefer explicit:
toolsets = [ "terminal" "web" "memory" "file" "skills" ];Provider timeouts
Section titled “Provider timeouts”DeepSeek models can have long generation times. Set explicit timeouts:
settings = { providers.openrouter = { request_timeout_seconds = 1800; stale_timeout_seconds = 300; };};