When the turn manager determines it's an agent's turn (checkTurn.allowed), the rules engine's evaluateDecision() could still override the model to no-reply with reason 'rule_match_no_end_symbol'. This happened because: 1. The sender of the triggering message (another agent) was not in the humanList, so the rules fell through to the end-symbol check. 2. getLastChar() operates on the full prompt (including system content like Runtime info), so it never found the end symbol even when the actual message ended with one. Fix: return early from before_model_resolve after the turn check passes, skipping the rules-based no-reply override entirely. The turn manager is the authoritative source for multi-agent turn coordination. Tested: 3-agent counting chain ran successfully (3→11) with correct NO_REPLY handling when count exceeded threshold.
Dirigent
Rule-based no-reply gate + turn manager for OpenClaw (Discord).
Formerly known as WhisperGate. Renamed to Dirigent in v0.2.0.
What it does
Dirigent adds deterministic logic before model selection and turn-based speaking for multi-agent Discord channels:
-
Rule gate (before_model_resolve)
- Non-Discord → skip
- Sender in bypass list / human list → skip
- Message ends with configured end symbol → skip
- Otherwise → route to no-reply model/provider
-
End-symbol enforcement
- Injects instruction:
Your response MUST end with 🔚… - In group chats, also injects: "If not relevant, reply NO_REPLY"
- Injects instruction:
-
Scheduling identifier (moderator handoff)
- Configurable identifier (default:
➡️) used by the moderator bot - Handoff format:
<@TARGET_USER_ID>➡️(non-semantic, just a scheduling signal) - Agent receives instruction explaining the identifier is meaningless — check chat history and decide
- Configurable identifier (default:
-
Turn-based speaking (multi-bot)
- Only the current speaker is allowed to respond
- Others are forced to no-reply
- Turn advances on end-symbol or NO_REPLY
- If all bots NO_REPLY, channel becomes dormant until a new human message
-
Agent identity injection
- Injects agent name, Discord accountId, and Discord userId into group chat prompts
-
Human @mention override
- When a
humanListuser @mentions agents, temporarily overrides turn order - Only mentioned agents cycle; original order restores when cycle completes
- When a
-
Per-channel policy runtime
- Policies stored in a standalone JSON file
- Update at runtime via
dirigent_policy_set/dirigent_policy_deletetools
-
Discord control actions (optional)
- Private channel create/update + member list
- Via
dirigent_channel_create,dirigent_channel_update,dirigent_member_listtools
Repo layout
plugin/— OpenClaw plugin (gate + turn manager + moderator presence)no-reply-api/— OpenAI-compatible API that always returnsNO_REPLY- Discord admin actions are now handled in-plugin via direct Discord REST API calls (no sidecar service)
docs/— rollout, integration, run-mode notes, turn-wakeup analysisscripts/— smoke/dev/helper checksMakefile— common dev commands (make check,make check-rules,make test-api,make smoke-discord-control,make up)CHANGELOG.md— milestone summary
Quick start (no Docker)
cd no-reply-api
node server.mjs
Then render config snippet:
node scripts/render-openclaw-config.mjs
See docs/RUN_MODES.md for Docker mode.
Discord extension capabilities: docs/DISCORD_CONTROL.md.
Runtime tools & commands
Tools (6 individual tools)
Discord control:
dirigent_discord_channel_create— Create private channeldirigent_discord_channel_update— Update channel permissionsdirigent_discord_member_list— List guild members
Policy management:
dirigent_policy_get— Get all policiesdirigent_policy_set— Set/update channel policydirigent_policy_delete— Delete channel policy
Turn management is internal to the plugin (not exposed as tools).
See
FEAT.mdfor full feature documentation.
Slash command (Discord)
/dirigent status
/dirigent turn-status
/dirigent turn-advance
/dirigent turn-reset
Config highlights
Common options (see docs/INTEGRATION.md):
listMode:human-listoragent-listhumanList,agentListendSymbolsschedulingIdentifier(default➡️)waitIdentifier(default👤) — agent ends with this to pause all agents until human replieschannelPoliciesFile(per-channel overrides)moderatorBotToken(handoff messages)enableDebugLogs,debugLogChannelIds
Development plan (incremental commits)
- Task 1: project docs + structure
- Task 2: no-reply API MVP
- Task 3: plugin MVP with rule chain
- Task 4: sample config + quick verification scripts
- Task 5: plugin rule extraction + hardening
- Task 6: containerization + compose
- Task 7: plugin usage notes
- Task 8: sender normalization + TTL + one-shot decision
- Task 9: auth-aware no-reply API
- Task 10: smoke test helpers
- Task 11: plugin structure checker
- Task 12: rollout checklist