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 Plugin
Hook strategy
message:receivedcaches a per-session decision from deterministic rules.before_model_resolveappliesproviderOverride + modelOverridewhen decision says no-reply.before_prompt_buildprepends end-marker instruction + scheduling identifier instruction when decision allows speaking.
Rules (in order)
- non-discord -> skip
- bypass sender -> skip
- end symbol matched -> skip
- else -> no-reply override
Config
See docs/CONFIG.example.json.
Required:
noReplyProvidernoReplyModel
Optional:
enabled(default true)discordOnly(default true)listMode(human-list|agent-list, defaulthuman-list)humanList(default [])agentList(default [])channelPoliciesFile(per-channel overrides in a standalone JSON file)schedulingIdentifier(default➡️) — moderator handoff identifierenableDirigentPolicyTool(default true)
Unified optional tool:
dirigent_tools- Discord actions:
channel-private-create,channel-private-update,member-list - Policy actions:
policy-get,policy-set-channel,policy-delete-channel - Turn actions:
turn-status,turn-advance,turn-reset
- Discord actions:
bypassUserIds(deprecated alias ofhumanList)endSymbols(default ["🔚"])enableDiscordControlTool(default true)- Discord control actions are executed in-plugin via Discord REST API (no
discordControlApiBaseUrlneeded) discordControlApiTokendiscordControlCallerIdenableDebugLogs(default false)debugLogChannelIds(default [], empty = all channels when debug enabled)
Per-channel policy file example: docs/channel-policies.example.json.
Policy file behavior:
- loaded once on startup into memory
- runtime decisions read memory state only
- direct file edits do NOT affect memory state
dirigent_toolspolicy actions update memory first, then persist to file (atomic write)
Moderator handoff format
When the current speaker NO_REPLYs, the moderator bot sends: <@NEXT_USER_ID>➡️
This is a non-semantic scheduling message. The scheduling identifier (➡️ by default) carries no meaning — it simply signals the next agent to check chat history and decide whether to speak.
Slash command (Discord)
/dirigent status
/dirigent turn-status
/dirigent turn-advance
/dirigent turn-reset
Debug logging:
- set
enableDebugLogs: trueto emit detailed hook diagnostics - optionally set
debugLogChannelIdsto only log selected channel IDs