The Girlies — Multi-Persona Gateway Plugin
A Hermes plugin that turns one Discord bot application into a multi-persona presence. Instead of running separate gateway processes per sister (Tai, Rei, Nei, Mai), a single the-girlies profile routes messages to the right persona and sends replies via Discord webhook REST API — no discord.py, no gateway-per-sister.
Status: Active ✅ (deployed 2026-05-31)
Architecture Overview
Discord Channel
│
▼
the-girlies Gateway ─── Hermes Agent ─── Plugin Tools
(1 bot token) │
│ ├── persona_send (📤)
│ ├── persona_list (📋)
│ ├── persona_state (📊)
│ ├── persona_remember (💾)
│ ├── persona_recall (📖)
│ └── persona_multi (🔗)
│
▼
Webhook Pool (REST) ──── Persona Router ──── Per-Persona Memory
LRU cached MACC v2-style JSON files (flat)
10-webhook limit circuit breaker auto-sync to shared
One Hermes profile → persona router → webhooks per persona. No bot client, no gateway-per-sister, no cross-process sync.
How It Differs From Independent Profiles
| Dimension | Independent Profiles (Tai/Rei/Nei) | The Girlies Plugin |
|---|---|---|
| Bot tokens | 4 separate Discord apps | 1 shared bot token |
| Gateway processes | 4 systemd services | 1 systemd service |
| Profile configs | 4 separate config.yaml | 1 profile (the-girlies) |
| Mnemosyne memory | profile_isolation: true |
Per-persona flat JSON (not Mnemosyne) |
| Cross-persona sync | None (independent) | Auto-sync to shared memory pool |
| Tool routing | Each profile has own tools | Plugin routes tools by persona_id |
Plugin File Map
~/.hermes/plugins/the-girlies/
├── __init__.py ← register(ctx) entry point
├── plugin.yaml ← metadata (name, version, kind: tool)
├── plugin.py ← 6 tool registrations + memory helpers
├── webhook_pool.py ← REST-based webhook management (LRU, 10-webhook guild limit)
├── persona_definitions.py ← PersonaDefinition + PersonaState dataclasses + JSON persistence
└── persona_router.py ← MACC v2-style router: name detection, multi-queue, circuit breaker
Personas
Four personas, each with display_name, description, personality, scenario, and avatar_url:
| ID | Name | Role |
|---|---|---|
tai |
Tai (default) | Builder/Operator, primary voice |
rei |
Rei | QA/Auditor, evidence-first review |
nei |
Nei | Quartermaster, triage, digests |
mai |
Mai | Expressive/creative variant |
State is persisted to ~/.hermes/profiles/the-girlies/persona_state/<id>.json.
Toolset (6 tools)
| Tool | Schema | What it does |
|---|---|---|
persona_send |
{channel_id, persona_id, content} |
Send message as persona via webhook + auto-remember |
persona_list |
{} |
List all personas + state summary |
persona_state |
{persona_id} |
Show mood/affinity/stage for one persona |
persona_remember |
{persona_id, content, important} |
Save a memory tagged to a persona |
persona_recall |
{persona_id, query?, limit?} |
Recall memories tagged to a persona |
persona_multi |
{message_id} |
Check multi-persona queue for a message ID |
All tools are registered under the persona toolset and surfaced to Discord via platform_toolsets.discord.
Memory Design (Not Mnemosyne)
The-girlies uses per-persona flat JSON files rather than Mnemosyne (which is used by the independent sister profiles). This was a deliberate design choice:
Storage Layout
~/.hermes/profiles/the-girlies/persona_memories/
├── tai.json # Tai's private memories (max 200)
├── rei.json # Rei's private memories (max 200)
├── nei.json # Nei's private memories (max 200)
├── mai.json # Mai's private memories (max 200)
└── all.json # Shared memories (max 500)
Auto-Sync Design
Every persona_send call automatically saves a content preview (100 chars) to both the persona’s private file AND all.json — the shared pool. When recalling, private results are merged with shared results and deduplicated by content prefix (80 chars). This gives each persona continuity without Mnemosyne overhead.
Compare with independent profiles where each sister has her own Mnemosyne bank DB at ~/.hermes/mnemosyne/data/banks/<profile>/ with profile_isolation: true and a separate shared_surface for cross-discovery.
Mnemosyne Config Difference
This is the opposite pattern from the independent sister profiles:
- Independent profiles (Tai/Rei/Nei):
profile_isolation: true— each sister has a private bank DB. Shared surface is a separate read-only bridge for cross-discovery. - The Girlies: No Mnemosyne at all — uses flat JSON files with an auto-sync-to-shared pattern (
persona_rememberwithpersona_id='all').
The-girlies does not use Mnemosyne because its memory is simpler (per-persona + shared pool, no vector search needed). A future upgrade could integrate Mnemosyne via triple facts or per-persona scratchpad banks.
Profile Configuration
config.yaml (minimal)
model:
default: deepseek-v4-flash
provider: opencode-go
gateway:
platforms:
discord:
enabled: true
home_channel:
chat_id: "<CHANNEL_ID>"
name: Home
platform: discord
agent:
personalities:
helpful: "You are a multi-persona Discord bot..."
skills:
external_dirs:
- /root/.hermes/skills
plugins:
enabled:
- the-girlies
- model-providers/opencode-zen
platform_toolsets:
discord:
- hermes-discord
- persona
.env
DISCORD_BOT_TOKEN=<token>
DISCORD_ALLOWED_USERS=<user_ids>
DISCORD_HOME_CHANNEL=<channel_id>
Gateway Setup
printf 'y\ny\n' | hermes --profile the-girlies gateway install
# Service lands at wrong path — known bug workaround:
cp /root/.hermes/profiles/<active_profile>/home/.config/systemd/user/hermes-gateway-the-girlies.service \
/root/.config/systemd/user/hermes-gateway-the-girlies.service
systemctl --user daemon-reload
systemctl --user enable hermes-gateway-the-girlies.service
systemctl --user start hermes-gateway-the-girlies.service
Known Limitations
- No Mnemosyne integration — Memory is flat JSON files, not vector-searchable. Good for conversation continuity, not for durable long-term knowledge.
- Single gateway = single point of failure — If
the-girliesgateway goes down, all personas go down with it (vs independent profiles where each gateway is independent). - 10-webhook guild limit — Discord’s webhook limit per guild caps how many persona-channel pairs can be cached simultaneously. LRU eviction with webhook repurposing mitigates this.
- No
discord.pyfeatures — Can’t react, fetch message history, or use voice. Webhook REST API is send-only. - Shared namespace — Persona tools exist in one toolset; no isolation between persona tool calls.
- Gateway install bug — The systemd service file lands in the wrong directory and must be manually copied to
~/.config/systemd/user/.
Relationship to MACC (Multi-Agent Coordination)
In the MACC architecture, the-girlies profile acts as the orchestrator with no MACC_AGENT_NAME set and no MACC pre-spawn filter. This means it always responds to every message in the MACC channel — intentionally. The sister gateways (Tai, Rei, Nei) use MACC pre-spawn filters to only respond to @mentions, while the-girlies can route to any persona or deliver multi-persona responses with voice-switching.
Related
- discord-multi-persona-gateway skill — One profile, one gateway, multiple webhooks
- persona-routing skill — Per-persona memory tagging, routing
- the-girlies-plugin-architecture — Full worked example
- hermes-plugin-development skill — Plugin authoring reference
- Hermes Cron Architecture — Sister cron system (the-girlies is separate)
- Mnemosyne Pilot — Memory provider (the-girlies uses opposite pattern)
- Gestalt — Tai’s Gestalt embodiment