Files
Dirigent/plugin
zhi 08f42bfd92 fix: skip rules-based no-reply override when turn manager allows agent
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.
2026-03-09 21:16:53 +00:00
..

Dirigent Plugin

Hook strategy

  • message:received caches a per-session decision from deterministic rules.
  • before_model_resolve applies providerOverride + modelOverride when decision says no-reply.
  • before_prompt_build prepends end-marker instruction + scheduling identifier instruction when decision allows speaking.

Rules (in order)

  1. non-discord -> skip
  2. bypass sender -> skip
  3. end symbol matched -> skip
  4. else -> no-reply override

Config

See docs/CONFIG.example.json.

Required:

  • noReplyProvider
  • noReplyModel

Optional:

  • enabled (default true)
  • discordOnly (default true)
  • listMode (human-list | agent-list, default human-list)
  • humanList (default [])
  • agentList (default [])
  • channelPoliciesFile (per-channel overrides in a standalone JSON file)
  • schedulingIdentifier (default ➡️) — moderator handoff identifier
  • enableDirigentPolicyTool (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
  • bypassUserIds (deprecated alias of humanList)
  • endSymbols (default ["🔚"])
  • enableDiscordControlTool (default true)
  • Discord control actions are executed in-plugin via Discord REST API (no discordControlApiBaseUrl needed)
  • discordControlApiToken
  • discordControlCallerId
  • enableDebugLogs (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_tools policy 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: true to emit detailed hook diagnostics
  • optionally set debugLogChannelIds to only log selected channel IDs