👤 Modelo de usuário — Como o Hermes separa contextos
O Hermes usa um conceito de tenant (=usuário lógico) que carrega ID, namespace de memória, profile e quota. Toda mensagem entra pelo router de canal, é resolvida para um tenant e aí tudo — memória, skills, secrets, logs — fica escopado a ele.
Diagrama: 1 instância → N usuários → N namespaces
┌──────────────────────────┐
│ Hermes Agent (1 proc) │
│ port 7777, PID 14523 │
└─────────────┬────────────┘
│
┌─────────────────────────┼─────────────────────────┐
│ │ │
┌────▼────┐ ┌─────▼────┐ ┌─────▼────┐
│ tenant: │ │ tenant: │ │ tenant: │
│ alice │ │ bob │ │ carol │
│ (mãe) │ │ (filho) │ │ (pai) │
└────┬────┘ └─────┬────┘ └─────┬────┘
│ │ │
┌────▼────────┐ ┌──────▼──────┐ ┌───────▼─────┐
│ memory/ │ │ memory/ │ │ memory/ │
│ alice.db │ │ bob.db │ │ carol.db │
│ skills/alice│ │ skills/bob │ │ skills/carol│
│ secrets: │ │ secrets: │ │ secrets: │
│ OR_KEY_A │ │ OR_KEY_B │ │ OR_KEY_C │
│ quota: $20 │ │ quota: $5 │ │ quota: $50 │
└─────────────┘ └─────────────┘ └─────────────┘
💡 Resolução de tenant
Toda mensagem que chega num canal passa por tenant_resolver. A ordem padrão:
- 1.Match exato em
users.<canal>.<id>no config - 2.Fallback pra tenant
default(se permitido) - 3.Rejeição com mensagem "user not authorized" (se
strict_users: true)
Feature × tipo de isolamento
| Feature | Isolamento | Onde mora |
|---|---|---|
| Histórico de chat | Total | ~/.hermes/memory/<tenant>.db |
| Memória semântica | Total | ~/.hermes/embeddings/<tenant>/ |
| API keys (OpenRouter etc) | Total | Keyring + secrets/<tenant>.enc |
| Skills privadas | Total | skills/<tenant>/ |
| Skills compartilhadas | Compartilhado | skills/shared/ |
| Modelo / provider | Override por user | profiles/<tenant>.toml |
| Logs de auditoria | Compartilhado (tagged) | logs/audit.jsonl |
🔑 API keys por usuário — OpenRouter sub-keys ou múltiplas
Você tem 3 caminhos, do mais simples ao mais escalável. A escolha errada vira factura inflada no fim do mês.
1 chave master + limite por user (mais barato)
Você mantém UMA sk-or-... da conta principal e o Hermes aplica quota.monthly_usd por tenant.
Bom pra: família, equipe <5 pessoas, confiança alta.
OpenRouter sub-keys (recomendado)
Em openrouter.ai/settings/keys crie 1 sub-key por pessoa, com limite mensal próprio. Isolamento de verdade — fatura mostra gasto por chave.
Bom pra: time, clientes, qualquer ambiente com auditoria.
Cada usuário traz a própria chave (BYOK)
Cada pessoa cria conta no OpenRouter e cadastra a chave dela via hermes user secrets set. Você não paga nada além do hosting.
Bom pra: SaaS interno, instalação compartilhada open source.
Comando real — cadastrando chave por user
# Cria tenant
hermes user create alice --display "Alice Maldaner" --quota 20.00
# Cadastra chave da Alice (vai pro keyring com namespace dela)
hermes user secrets set alice OPENROUTER_API_KEY sk-or-v1-alice-************
# Override de modelo padrão só pra Alice
hermes user config set alice model.default "anthropic/claude-sonnet-4.5"
# Lista
hermes user list
NAME QUOTA USED MODEL STATUS
alice $20.00 $3.42 anthropic/claude-sonnet-4.5 active
bob $5.00 $0.18 meta-llama/llama-3.3-70b:free active
carol $50.00 $47.10 openai/gpt-4o ⚠ near-limit
✓ FAZER
- ✓Sub-keys OpenRouter por pessoa (B)
- ✓Quota mensal hard no Hermes E no provider
- ✓Rotacionar chaves a cada 90 dias
- ✓Alertar usuário em 80% da quota
✗ NÃO fazer
- ✗1 chave compartilhada sem quota — fatura explode
- ✗Salvar chaves em
.envversionado - ✗Confiar só em quota local (sem limite no provider)
- ✗Reusar chave entre prod e dev
💾 Memória isolada — DB por usuário ou namespace
Hermes oferece dois modos de isolar memória. Escolha errado e a Alice recebe lembrança de conversa do Bob — vazamento clássico em multi-tenancy.
📦 DB por usuário (default, seguro)
Cada tenant tem arquivo SQLite próprio. Impossível query cruzar.
~/.hermes/memory/
alice.db
bob.db
carol.dbPro: zero risco. Contra: difícil migrar/agregar.
🏷️ DB único + namespace
Tudo num hermes.db, toda query tem WHERE tenant_id = ?.
SELECT * FROM messages
WHERE tenant_id = 'alice'
ORDER BY ts DESC;Pro: backup único, escala melhor. Contra: bug no WHERE = vazamento.
Configurando o modo
# ~/.hermes/config.toml
[memory]
mode = "per_user" # ou "namespaced"
path = "~/.hermes/memory"
backend = "sqlite" # sqlite | postgres | duckdb
auto_vacuum = true
encryption = "age" # opcional, criptografa em repouso
[memory.semantic]
enabled = true
embeddings_model = "text-embedding-3-small"
per_user_index = true # NUNCA false em multi-tenant
⚠️ Vazamento entre usuários
O bug #1 em setups multi-user é per_user_index = false na memória semântica. O retrieval traz embeddings de TODOS os tenants — a Alice pergunta "onde guardei a senha do banco" e recebe a senha do Bob. Sempre teste:
hermes audit leak-check --tenant alice
✓ memory.sqlite: isolated
✓ memory.embeddings: isolated
✓ skills.private: isolated
✗ logs.shared: tagged but globally readable (expected)
💡 Dica — backup seletivo
Com DB por usuário, backup vira cp alice.db alice.db.bak. Você consegue dar pra Alice o backup só da memória dela quando ela pedir, sem expor dados dos outros. Compliance amigável.
🧩 Skills compartilhadas vs privadas — Estratégias
Skills (tools, prompts especializados, fluxos) podem ser globais (todos veem) ou privadas (só dono usa). Saber dividir reduz duplicação e vazamento.
Layout de diretórios
~/.hermes/skills/
├── shared/ # disponível a TODOS os tenants
│ ├── web-search/
│ ├── calculator/
│ └── translate-ptbr/
├── alice/ # só Alice vê
│ ├── meu-banco-api/ # tem credenciais privadas
│ └── agenda-trabalho/
├── bob/ # só Bob vê
│ └── jogos-steam/
└── disabled/ # tirou de circulação sem deletar
└── legacy-thing/
Comandos de gerência
# Instala skill compartilhada (acessível a todos)
hermes skill install https://github.com/foo/web-search --scope shared
# Instala skill privada da Alice
hermes skill install ./meu-banco-api --scope user --user alice
# Promove de privada pra compartilhada
hermes skill promote alice/agenda-trabalho --to shared
# Lista skills visíveis pra cada user
hermes skill list --user alice
SCOPE NAME SOURCE
shared web-search github.com/foo/web-search
shared calculator builtin
user meu-banco-api local
user agenda-trabalho local
# Audita o que cada skill acessa
hermes skill audit alice/meu-banco-api
Network calls: api.meubanco.com.br
Filesystem: read ~/.hermes/secrets/alice/banco.enc
Subprocess: none
Tokens used: BANCO_TOKEN (encrypted, alice-only)
✓ Compartilhar quando
- ✓Skill é genérica (web search, calculadora)
- ✓Não tem credenciais embutidas
- ✓Output é seguro pra qualquer audiência
- ✓Você quer evitar 3 cópias idênticas
✗ NUNCA compartilhar
- ✗Skills com token de banco/email pessoal
- ✗Skills que escrevem em path pessoal
- ✗Skills com prompt que vaza identidade
- ✗Skills experimentais que podem crashar
📊 Quotas e budget por usuário — Limites práticos
Sem quota, um único filho perguntando "explica detalhadamente toda física quântica" zera o budget familiar. Hermes tem 3 níveis de limite: tokens/dia, custo/mês e rate.
Config completa de família (4 pessoas, real)
# ~/.hermes/config.toml
[multitenancy]
enabled = true
strict_users = true # rejeita ID não cadastrado
default_tenant = false # sem fallback inseguro
[users.alice] # mãe - power user
display = "Alice"
quota = { monthly_usd = 30.0, daily_tokens = 500000, rate_per_min = 30 }
model_default = "anthropic/claude-sonnet-4.5"
secrets_namespace = "alice"
channels = { telegram = [123456789], discord = ["alice#0001"] }
[users.bob] # filho 14 anos - limitado
display = "Bob"
quota = { monthly_usd = 5.0, daily_tokens = 100000, rate_per_min = 10 }
model_default = "meta-llama/llama-3.3-70b-instruct:free"
secrets_namespace = "bob"
channels = { telegram = [987654321] }
content_filter = "strict" # bloqueia NSFW
[users.carol] # pai - corporativo
display = "Carol"
quota = { monthly_usd = 50.0, daily_tokens = 1000000, rate_per_min = 60 }
model_default = "openai/gpt-4o"
secrets_namespace = "carol"
channels = { slack = ["U12345"], telegram = [555444333] }
[users.daniel] # filha 8 anos - kid mode
display = "Daniel"
quota = { monthly_usd = 2.0, daily_tokens = 30000, rate_per_min = 5 }
model_default = "anthropic/claude-haiku-4.5"
content_filter = "kids"
channels = { telegram = [111222333] }
[quota.alerts]
warn_at_pct = 80 # avisa user em 80%
hard_stop_at_pct = 100 # corta acesso em 100%
notify_admin = "alice" # quem recebe alerta
Setup família 4 pessoas — timeline
Min 0-5 · Cria os 4 tenants
Para cada um: hermes user create <nome> --quota X
Min 5-15 · Sub-keys OpenRouter
openrouter.ai/settings/keys → 4 sub-keys com limite. hermes user secrets set <u> OPENROUTER_API_KEY ...
Min 15-25 · Pega chat_id de cada um
Cada pessoa abre @userinfobot no Telegram, manda o ID pra você, você adiciona em channels.telegram.
Min 25-35 · Bot único no Telegram
hermes channel start telegram — o bot resolve cada chat_id para o tenant correto automaticamente.
Min 35-45 · Teste e audit
hermes audit leak-check --all-tenants + cada pessoa manda "oi, qual meu nome?" e confirma a resposta certa.
📊 Dashboard de uso
Rode hermes usage --month e veja consumo por user:
USER TOKENS_IN TOKENS_OUT COST_USD REQUESTS MODEL_TOP
alice 1,240,512 382,109 $12.40 412 claude-sonnet-4.5
bob 92,103 14,221 $0.18 89 llama-3.3-70b:free
carol 3,012,440 980,331 $47.10 203 gpt-4o
daniel 22,310 4,012 $0.42 54 claude-haiku-4.5🚪 Roteamento por canal — Telegram chat_id, Discord user_id
Cada canal entrega um identificador próprio. O tenant resolver mapeia esse ID externo → tenant interno. Errar isso = mensagem da Alice cai no contexto do Bob.
Identificadores por canal
| Canal | ID estável | Como obter |
|---|---|---|
| Telegram | chat_id (int) | @userinfobot |
| Discord | user.id (snowflake) | Dev Mode ON → click direito → "Copiar ID" |
| Slack | user_id (U...) | Perfil → Mais → "Copiar member ID" |
| CLI (TUI) | $USER do SO | whoami |
| HTTP API | Header X-Hermes-Tenant | Cliente envia explicitamente |
Config do roteador
# ~/.hermes/config.toml — bloco de routing
[router]
strategy = "exact_match" # exact_match | regex | callback
unknown_action = "deny" # deny | default_tenant | prompt_admin
[router.telegram]
# chat_id -> tenant
mapping = {
123456789 = "alice",
987654321 = "bob",
555444333 = "carol",
111222333 = "daniel"
}
[router.discord]
mapping = {
"281234567890123456" = "alice",
"291234567890123456" = "bob"
}
[router.slack]
mapping = {
"U01ABCDE" = "carol" # só Carol usa Slack
}
# Caso especial: grupo no Telegram com vários users
[router.telegram.groups]
"-1001234567890" = "carol" # grupo "Trabalho", tudo cai no tenant carol
📊 Como testar o roteamento
hermes route test telegram 123456789→ mostra qual tenant resolveriahermes route trace --tail→ loga em tempo real toda resoluçãohermes audit cross-tenant-msgs --last 7d→ detecta se algo escapou pro tenant errado
⚠️ Grupo do Telegram = caso ESPECIAL
Em group chat o chat_id é do GRUPO, não da pessoa. Se mapear pro tenant errado, todo mundo no grupo vira "aquela pessoa". Decida: ou força from_user.id como chave (cada um tem contexto próprio), ou aceita que o grupo todo é UM tenant (contexto compartilhado). Configure com router.telegram.group_strategy = "per_user" | "shared".
💡 Dica — auditoria contínua
Adicione hermes audit weekly-report --email admin@familia.local no cron. Toda sexta você recebe: gastos por user, top skills usadas, alertas de quota, anomalias de roteamento, vazamentos suspeitos. 10 minutos pra prevenir 10 horas de bagunça.
✅ Resumo do Módulo
1 instância, N usuários, cada um com namespace próprio
Sub-keys OpenRouter por pessoa com limite no provider
per_user DB + per_user_index nos embeddings
Genéricas compartilhadas, com credencial sempre privadas
tokens/dia, custo/mês, rate/min — com alerta em 80%
hermes route test + audit cross-tenant-msgs
Próximo módulo:
3.4 — 🚨 Troubleshooting de setup — Top 20 erros: sintoma → diagnóstico → fix.