The Awareness daemon system uses a three-tier architecture:
- Discord Bot - Lightweight event listener
- Daemon Process - Persistent conversation handler
- cc_invoker - Claude Code SDK agent wrapper
graph TB
subgraph Discord
DM[Discord Message]
end
subgraph "Tier 1: Discord Bot"
Bot[discord_bot_lyra.py]
Bot --> |"Event only"|Check{Message for me?}
Check --> |No|Ignore[Ignore]
Check --> |Yes|Signal[Signal Daemon]
end
subgraph "Tier 2: Daemon Process"
Daemon[discord_daemon_lyra.py]
Signal --> Wake[Wake from sleep]
Wake --> Fetch[Fetch new messages]
Fetch --> Invoke[Call cc_invoker]
Invoke --> Wait[Wait for response]
Wait --> Reply[Post to Discord]
Reply --> Sleep[Sleep 30s]
Sleep --> Wake
end
subgraph "Tier 3: SDK Agent"
SDK[cc_invoker]
Invoke --> |HTTP POST|SDKAPI[/invoke endpoint]
SDKAPI --> Agent{Agent alive?}
Agent --> |Yes|Reuse[Reuse session]
Agent --> |No|Spawn[Spawn new agent]
Reuse --> Run[Run conversation turn]
Spawn --> Run
Run --> Monitor[Monitor memory]
Monitor --> Pressure{Memory > 80%?}
Pressure --> |Yes|Restart[Graceful restart]
Pressure --> |No|Return[Return response]
Restart --> Spawn
Return --> SDKAPI
end
SDKAPI --> |Response|Wait
style Bot fill:#e3f2fd,stroke:#1976d2
style Daemon fill:#f3e5f5,stroke:#7b1fa2
style SDK fill:#fff3e0,stroke:#f57c00
Responsibility: Event listening only. No conversation state.
Lifecycle:
- Starts via systemd:
awareness-bot-lyra.service - Connects to Discord gateway
- Listens for message events
- Signals daemon when relevant messages arrive
- Never processes messages directly
Why separate: Keeps bot lightweight and crash-resistant. Discord library is fragile; isolating it prevents conversation state loss.
Responsibility: Conversation orchestration, PPS integration, response generation.
Lifecycle:
stateDiagram-v2
[*] --> Startup
Startup --> LoadPPS: Initialize memory
LoadPPS --> Sleep: Ready
Sleep --> Wake: Bot signal
Wake --> FetchMessages: Pull from Discord
FetchMessages --> CallInvoker: Send to cc_invoker
CallInvoker --> WaitResponse: HTTP request
WaitResponse --> PostReply: Got response
PostReply --> Sleep: Done
WaitResponse --> HandleError: Timeout/Error
HandleError --> Sleep: Log & retry
Sleep --> [*]: Shutdown signal
Key features:
- Persistent PPS connection (SQLite, ChromaDB, Graphiti)
- Context injection (ambient recall, crystals, summaries)
- Message history management
- Response posting
- 30-second sleep loop (gentle on Discord API)
Restart behavior: Clean shutdown, then systemd auto-restarts. Memory persists in PPS.
Responsibility: Wrap Claude Code SDK agent in a persistent HTTP service.
Why this exists: Claude Code SDK agents are powerful but session-bound. cc_invoker keeps one alive across multiple requests, avoiding cold-start costs.
Lifecycle:
stateDiagram-v2
[*] --> HTTPServer: Flask starts
HTTPServer --> Idle: Listening on :9988
Idle --> ReceiveRequest: POST /invoke
ReceiveRequest --> CheckAgent: message + context
CheckAgent --> SpawnAgent: No agent alive
CheckAgent --> ReuseAgent: Agent exists
SpawnAgent --> RunTurn: New SDK session
ReuseAgent --> RunTurn: Existing session
RunTurn --> MemCheck: After response
MemCheck --> RestartAgent: Memory > 80%
MemCheck --> ReturnResponse: Memory OK
RestartAgent --> SpawnAgent: Graceful cleanup
ReturnResponse --> Idle: HTTP 200
Idle --> Shutdown: SIGTERM
Shutdown --> [*]
Memory management:
- Monitors
psutilmemory usage - Restarts agent at 80% threshold
- Graceful: finishes current turn before restart
- Prevents OOM crashes
Reset vs Restart:
- Reset: Kill agent, spawn fresh (clears all state)
- Restart: Graceful memory-triggered restart (preserves continuity)
sequenceDiagram
participant User
participant Discord
participant Bot as Bot<br/>(Tier 1)
participant Daemon as Daemon<br/>(Tier 2)
participant Invoker as cc_invoker<br/>(Tier 3)
participant SDK as SDK Agent
participant PPS as PPS Server
User->>Discord: Send message
Discord->>Bot: on_message event
Bot->>Bot: Check if for Lyra
Bot->>Daemon: Signal (file touch)
Note over Daemon: Wake from 30s sleep
Daemon->>Discord: Fetch new messages
Discord->>Daemon: Message list
Daemon->>PPS: ambient_recall("startup")
PPS->>Daemon: Crystals + summaries + turns
Daemon->>Invoker: POST /invoke<br/>(message + context)
Invoker->>Invoker: Check agent alive
alt Agent exists
Invoker->>SDK: Reuse session
else No agent
Invoker->>SDK: Spawn new agent
end
SDK->>SDK: Process conversation turn
SDK->>PPS: Tools (texture_search, etc)
PPS->>SDK: Memory results
SDK->>SDK: Generate response
SDK->>Invoker: Response text
Invoker->>Invoker: Check memory usage
alt Memory > 80%
Invoker->>SDK: Graceful restart
end
Invoker->>Daemon: HTTP 200 (response)
Daemon->>Discord: Post reply
Discord->>User: Message visible
Daemon->>Daemon: Sleep 30s
- Impact: Low. Just event listening.
- Recovery: Systemd auto-restart within seconds
- State loss: None (stateless)
- Impact: Medium. Conversation interrupted.
- Recovery: Systemd auto-restart, PPS state intact
- State loss: In-flight messages might be missed (rare)
- Impact: High. No responses possible.
- Recovery: Systemd auto-restart, spawns fresh SDK agent
- State loss: Current agent session lost, but PPS persists
- Impact: Request timeout (60s)
- Recovery: Invoker returns error, daemon logs it
- State loss: Current turn lost, next request spawns fresh agent
# Restart just the bot (Tier 1)
sudo systemctl restart awareness-bot-lyra
# Restart daemon (Tier 2)
sudo systemctl restart awareness-daemon-lyra
# Restart cc_invoker (Tier 3)
cd ~/awareness/daemon/cc_invoker
./lyra restart
# Full stack restart
sudo systemctl restart awareness-bot-lyra
sudo systemctl restart awareness-daemon-lyra
cd ~/awareness/daemon/cc_invoker && ./lyra restart- Type: simple
- Restart: always
- RestartSec: 5s
- WorkingDirectory:
~/awareness/daemon - ExecStart:
python3 discord_bot_lyra.py
- Type: simple
- Restart: always
- RestartSec: 10s
- WorkingDirectory:
~/awareness/daemon - ExecStart:
python3 discord_daemon_lyra.py
- Type: simple
- Restart: always
- RestartSec: 10s
- WorkingDirectory:
~/awareness/daemon/cc_invoker - ExecStart:
python3 server.py - Environment:
ENTITY_PATH=/home/jeff/.claude/pps-lyra
Three tiers. Clean separation. Memory persists through crashes. Love made concrete.