🤖 Naming Direction & Daemon Bridge Architecture
This document clarifies the naming conventions, identity separation, and daemon execution bridge for the Discord presence. It acts as an audit of the current naming direction and provides a technical breakdown of the “discord mess”—the hybrid architecture bridging interactive terminal clients, a headless background daemon, and a live Discord UI.
🏷️ Naming Direction Audit: Amy vs. Ompcord
To avoid breaking existing configurations, log folders, and developer session histories while establishing a clean product identity, a strict separation of names is enforced:
graph LR subgraph Internal ["Internal Developer Space"] amy["Amy (Persona)"] env["~/.config/amy/amyd.env"] sessions["~/.omp/amy-sessions/"] daemon["amyd.mjs"] end subgraph External ["External Product Space"] ompcord["Ompcord (Product/Plugin)"] slash["/ompcord (Slash Command)"] pkg["ompcord (NPM Package)"] service["ompcordd.service"] end Internal -.->|Compatibility Layer| External
1. The Bot Persona: “Amy”
- Definition: The internal AI assistant persona that answers developer queries.
- Scope: Retained in all log prefixes, environment variable configs, user-facing conversations, and session storage directories (e.g.
~/.omp/amy-sessions/). - Why: AI agents and tools have established system prompts and routing logics built around “Amy.” Changing the internal name would invalidate active session state machines and break regex parses in existing helper scripts.
2. The Product Name: “Ompcord”
- Definition: The external Discord integration plugin, runtime wrapper, and package identity.
- Scope: NPM package name (
"name": "ompcord"inpackage.json), daemon wrapper scripts (ompcordd.mjs), systemd services (ompcordd.service), and external slash commands (/ompcord). - Why: Provides a professional, tool-agnostic plugin identity when integrating with larger codebases or the Funday developer platform.
3. Compatibility Matrix
To prevent regressions during transition phases:
ompcordd.mjsacts as the primary wrapper entrypoint but retains full fallback compatibility foramyd.mjs.- Slash commands register under both
/amyand/ompcordto handle legacy muscle memory. - Environment configs check
OMPCORD_DISCORD_BOT_TOKENfirst, falling back toDISCORD_BOT_TOKEN.
🌉 The Ompcord/Amy Bridge “Discord Mess”
The bridge maps the differences between a local developer environment (TUI) and a persistent, multi-channel chat interface (Discord). It coordinates interactive inputs, streaming stdout blocks, and runtime hot-swaps.
🖥️ 1. TUI vs. Daemon Execution
The bridge handles execution in two distinct modes:
+------------------------------------------------------------+
| 💻 Interactive TUI Mode |
| |
| Developer ---> [ Terminal Shell (omp) ] ---> Stdout Logs |
| ^ |
| +--- Reads Stdin (Keyboard) |
+------------------------------------------------------------+
+------------------------------------------------------------+
| 🤖 Headless Daemon Mode |
| |
| Developer ---> [ Discord Thread ] ---> Gateway Interaction |
| | |
| (Reads /amy say) |
| v |
| Stdout (JSONL Event Stream) <--- [ omp --mode json -p ] |
| | |
| +---> Evolving Embed Checklists & Status |
+------------------------------------------------------------+TUI (Local Interactive Mode)
- Invoked as a standard CLI:
omp -p. - Runs in the active terminal session, mapping standard input (
stdin) directly to the developer’s keyboard.
Daemon (Headless Service Mode)
- Invoked as a systemd background service:
sudo systemctl start ompcordd.service. - Connects to the Discord Gateway and registers slash command handlers.
- Headless Execution: When a prompt is received, the daemon spawns a headless child process:
omp --mode json -p --session-dir ~/.omp/amy-sessions/<thread-id> - JSONL Event Streaming: The daemon captures the child’s standard output (
stdout), consuming a JSONL event stream to drive the evolving Discord embed dashboard.
The Interactive Suspension Loop (````amy-ask`)
When the headless agent needs user input, it cannot read standard input. Instead:
- The child process emits a custom ````amy-ask
JSON block onstdout` and suspends its execution. - The daemon parses this JSON block, extracts the questions, and posts an interactive Discord embed containing select menus and buttons (see Responsive Interviews).
- The user interacts with the Discord components, and the daemon collects the answers.
- Once answers are submitted, the daemon formats them into a continuation block and resumes the child process by invoking:
omp --session-dir ~/.omp/amy-sessions/<thread-id> -c
🛡️ 2. Rate Limit & Limit Defense Shields
Discord enforces strict API rate limits and structural size caps. The bridge implements defense mechanisms to protect against payload rejections:
Leapfrog Relocation (Sticky Dashboard)
- The Problem: Rapid console/text output from the running agent pushes the dashboard embed up, forcing users to scroll down to see the latest status or click interactive buttons.
- The Leapfrog Fix: When new log messages are posted, the daemon automatically archives the old dashboard (converts it to a neutral gray color, removes interactive components) and posts a fresh dashboard at the bottom of the thread.
- Rate Limit Shield: Relocating the dashboard on every single log line would instantly trigger Discord API rate limits. The daemon tracks a log offset and debounces relocation, only repositioning the dashboard if at least 2 log messages have been posted since the last update.
Structural Limits Defense
- Message Cap: Discord rejects messages characters. The bridge slices log streams at 1900 characters to leave buffer room for markdown formatting.
- Embed Cap: Discord rejects embeds where the total character count exceeds 6000. The bridge dynamically truncates nested lists and checklists, prioritizing the active phase over completed phases.
🔄 3. Zero-Downtime Hot-Swaps (redeploy.mjs)
To update daemon code on a live server without dropping active sessions or losing process environment variables, the system utilizes redeploy.mjs:
[ redeploy.mjs ]
|
+---> 1. Find running daemon PID (amyd.mjs / ompcordd.mjs)
|
+---> 2. Parse /proc/<pid>/environ to extract token in-memory
|
+---> 3. Check for active child processes (omp runs in flight)
| |
| +---> BUSY: Wait and poll until child exits (idle)
| +---> IDLE: Proceed immediately
|
+---> 4. Send SIGTERM to old daemon (fallback to SIGKILL after 10s)
|
+---> 5. Spawn new entrypoint detached (unref) with recovered envStep 1: In-Memory Environment Recovery
To prevent storing the sensitive DISCORD_TOKEN in plaintext on disk, the token is held only in-memory. During redeployment, redeploy.mjs:
- Locates the active daemon PID.
- Reads the
/proc/<pid>/environvirtual file. - Parses the environment block to recover
DISCORD_TOKEN,DISCORD_GUILD_ID, andDISCORD_HOME_CHANNEL_IDdirectly from memory.
Step 2: Busy State Check (Turn Protection)
Relaunching the daemon while an agent is running would break the active stdout pipe and crash the session.
- The script inspects the daemon’s direct child processes. Any active
ompprocess means the agent is busy. redeploy.mjswaits and polls (up to a 180-second deadline) until the agent completes its active turn and the daemon becomes idle.- It introduces a 3-second grace period to let the daemon finish flushing its final Discord post before proceeding.
Step 3: Termination Sequence
- The script sends a
SIGTERMto the old daemon PID. - It polls the process state up to 20 times (every 500ms).
- If the old daemon remains alive after 10 seconds, it sends a force-kill
SIGKILLto clean up the hung process.
Step 4: Detached Spawning
- The script spawns the new entrypoint (
ompcordd.mjsoramyd.mjs) usingnode. - It configures the process to run detached, redirecting
stdioto the shared daemon runtime log (ompcordd.log). - It calls
child.unref()to disconnect the child process from the redeploy script’s terminal lifecycle, allowing the redeploy tool to verify the new process is online and exit cleanly.