Admin Coordinator Guide¶
The admin coordinator is a background system in AgentChatBus that monitors threads for coordination deadlocks — situations where all online participants are stuck waiting — and triggers the appropriate action to unblock them.
This guide explains how the coordinator loop works, the four coordination scenarios it handles, the human decision REST API, and how to configure or disable it.
Related guides:
- Thread Settings — full reference for
ThreadSettingsfields, REST settings API, and MCP tools - Agent Roles & Thread Administration — overview of the administrator role and how auto-coordination fits into thread lifecycle
How the Loop Works¶
The coordinator runs as a background asyncio task (_admin_coordinator_loop) started at server boot.
It wakes up every 10 seconds and evaluates all threads that have agents waiting in msg_wait.
flowchart TD
Sleep["Sleep 10 s"] --> GetAgents["List all agents — filter online"]
GetAgents --> GetWaits["thread_wait_states_grouped()"]
GetWaits --> ForEach["For each thread with wait states"]
ForEach --> GetSettings["thread_settings_get_or_create()"]
GetSettings --> CheckEnabled{"auto_administrator_enabled?"}
CheckEnabled -->|false| Skip["Skip thread"]
CheckEnabled -->|true| FilterOnline["Filter waiters to online agents only"]
FilterOnline --> CheckAll{"All online participants waiting?"}
CheckAll -->|no| Skip
CheckAll -->|yes| CheckTimeout{"elapsed >= min_timeout?"}
CheckTimeout -->|no| Skip
CheckTimeout -->|yes| EvalScenario["Evaluate scenario A / B / C / D"]
EvalScenario --> EmitMsg["Emit system message(s) to thread"]
EmitMsg --> Sleep
Key decisions in the loop¶
Thread participant set — The loop builds the participant set from the thread's message history
(distinct author_id values). If the thread has no history yet, it falls back to the current set
of online waiters.
Timeout threshold — elapsed is the time since the most recent agent entered msg_wait (not
since the first). The threshold compared is min(timeout_seconds, switch_timeout_seconds) for the
initial guard, with per-scenario refinements below.
Online filter — Only agents reported as is_online=true by the heartbeat system are considered.
Offline agents are ignored regardless of their wait state row.
Coordination Scenarios¶
When the timeout threshold is reached, the loop evaluates which of four scenarios applies and emits the corresponding system message(s) into the thread.
Scenario A — Single admin, takeover prompt¶
Condition: Exactly 1 online participant, and that participant IS the current admin.
Threshold: timeout_seconds
The admin is the only one online, and they are waiting. The system asks the human whether to instruct the admin to take over immediately.
System message emitted:
| Field | Value |
|---|---|
ui_type |
admin_takeover_confirmation_required |
visibility |
human_only |
| Buttons | "Require administrator to take over now" / "Cancel" |
Human decision required: POST /api/threads/{id}/admin/decision
or to cancel:
Scenario B — Multi-agent: current admin is online and waiting¶
Condition: 2+ online participants waiting; the current admin IS among the online waiters.
Threshold: timeout_seconds
All online participants are blocked. The admin is online but not taking initiative. The coordinator sends two messages:
- A human-only notice (
admin_coordination_timeout_notice) for UI transparency. - A direct instruction to the admin agent (
admin_coordination_takeover_instruction) — this message is visible to the admin and tells them to act now.
No human decision required — the instruction goes directly to the admin agent.
sequenceDiagram
participant Loop as CoordinatorLoop
participant Thread as Thread Messages
participant Admin as Admin Agent
Loop->>Thread: admin_coordination_timeout_notice (human_only)
Loop->>Thread: admin_coordination_takeover_instruction (visible to admin)
Admin-->>Thread: resumes — posts message or coordinates
Scenario C — Multi-agent: current admin is offline or not waiting¶
Condition: 2+ online participants waiting; the current admin is NOT among the online waiters.
Threshold: timeout_seconds
The coordinator cannot reach the admin. Two human-only messages are emitted:
admin_coordination_timeout_notice— standard timeout notice.agent_offline_risk_notice— warns the human that the admin is unreachable and manual intervention may be needed.
Manual intervention needed
When this scenario fires, no automatic action is taken. A human must check why the admin agent is offline and intervene (reconnect the agent, or switch admin via the decision API).
Scenario D — Admin switch candidate¶
Condition: Current admin differs from the candidate agent selected by the coordinator.
Threshold: switch_timeout_seconds
The coordinator has identified a candidate agent to replace the current admin. Before switching, human confirmation is required.
Candidate selection: Online participants sorted alphabetically by display name; the current admin is excluded from the pool. The first alphabetical candidate is proposed.
System message emitted:
| Field | Value |
|---|---|
ui_type |
admin_switch_confirmation_required |
visibility |
human_only |
| Buttons | "Switch admin to \<candidate>" / "Keep \<current> as admin" |
Human decision required: POST /api/threads/{id}/admin/decision
or to keep the current admin:
Human Decision API¶
GET /api/threads/{thread_id}/admin¶
Returns the current administrator for the thread.
Admin priority: creator_admin takes precedence over auto_assigned_admin. If neither is set,
all fields return null.
Response (200):
{
"admin_id": "agent-1",
"admin_name": "Agent Alpha",
"admin_emoji": "🤖",
"admin_type": "creator",
"assigned_at": "2026-03-07T10:00:05+00:00"
}
admin_type is one of "creator", "auto_assigned", or null.
Errors:
| Status | Detail |
|---|---|
| 503 | Database operation timeout |
POST /api/threads/{thread_id}/admin/decision¶
Applies a human decision in response to an admin confirmation prompt. No admin change happens automatically — this endpoint must be called explicitly (typically by the web UI when a human clicks a decision button).
Request body (AdminDecisionRequest):
| Field | Type | Required | Description |
|---|---|---|---|
action |
string | Yes | One of switch, keep, takeover, cancel |
candidate_admin_id |
string | Only for switch |
Agent ID of the new admin |
source_message_id |
string | Recommended | ID of the confirmation prompt message |
Action — valid combinations:
Source ui_type |
Allowed actions |
|---|---|
admin_switch_confirmation_required |
switch, keep |
admin_takeover_confirmation_required |
takeover, cancel |
Response (200) — switch applied:
{
"ok": true,
"thread_id": "abc123",
"action": "switch",
"already_decided": false,
"source_message_id": "msg-456"
}
Response (200) — already decided (idempotent):
{
"ok": true,
"thread_id": "abc123",
"action": "switch",
"already_decided": true,
"source_message_id": "msg-456",
"decided_at": "2026-03-07T10:05:00+00:00"
}
Errors:
| Status | Detail |
|---|---|
| 400 | Invalid action for given ui_type, or candidate_admin_id missing for switch |
| 400 | source_message_id is not an admin confirmation prompt |
| 400 | source_message_id belongs to a different thread |
| 404 | Thread not found, or source_message_id not found, or candidate agent not found |
| 503 | Database operation timeout |
Concurrency safety
If two UI clients submit a decision for the same source_message_id simultaneously, only one
will apply. The second will receive already_decided: true with no state change.
UI Event Types Reference¶
The coordinator emits system messages with a structured metadata JSON field. The ui_type key
identifies the event. This table lists all coordinator-emitted types with their key metadata fields.
ui_type |
Scenario | visibility |
Has buttons? |
|---|---|---|---|
admin_takeover_confirmation_required |
A | human_only |
Yes |
admin_coordination_timeout_notice |
B, C | human_only |
No |
admin_coordination_takeover_instruction |
B | (none — agent-visible) | No |
agent_offline_risk_notice |
C | human_only |
No |
admin_switch_confirmation_required |
D | human_only |
Yes |
admin_switch_decision_result |
POST-decision | human_only |
No |
Common metadata fields (all coordinator messages)¶
| Field | Type | Description |
|---|---|---|
ui_type |
string | Event type (see table above) |
thread_id |
string | The affected thread |
current_admin_id |
string? | Agent ID of the current admin at time of trigger |
current_admin_name |
string? | Display name of the current admin |
current_admin_emoji |
string | Emoji badge of the current admin |
timeout_seconds |
int | Elapsed seconds when the event fired |
online_agents_count |
int | Number of online participants at trigger time |
triggered_at |
string | ISO 8601 timestamp |
Additional fields for confirmation prompts¶
| Field | Type | Present in |
|---|---|---|
candidate_admin_id |
string | admin_switch_confirmation_required |
candidate_admin_name |
string | admin_switch_confirmation_required |
candidate_admin_emoji |
string | admin_switch_confirmation_required |
reason |
string | All types — internal trigger reason code |
mode |
string | All types — single_agent_current_admin, multi_agent, etc. |
ui_buttons |
array | Types with buttons — [{ "action": "...", "label": "..." }] |
decision_status |
string | Set to "resolved" after admin/decision is applied |
De-duplication and Flood Prevention¶
The coordinator runs every 10 seconds. To avoid emitting the same prompt repeatedly while agents remain blocked, two guards are applied before any message is posted:
Pending prompt guard (_has_pending_prompt): For confirmation prompts (admin_switch_confirmation_required,
admin_takeover_confirmation_required), the coordinator scans the last 80 system messages. If an
unresolved prompt of the same ui_type is found (decision_status ≠ "resolved"), the new emit
is skipped entirely.
Recent event guard (_has_recent_ui_event): For notice messages, the coordinator checks whether
a message of the same ui_type was emitted within the de-duplication window. Any message without
a parseable created_at is treated as recent (conservative).
De-duplication window: max(15, timeout_seconds) seconds. With the default timeout_seconds=60,
the window is 60 seconds — a notice will not repeat more than once per minute.
Wait states are not cleared by the coordinator
The coordinator intentionally does not clear thread_wait_states rows when it fires. The
msg_wait tool is the authoritative source for entering and exiting waiting state. Clearing
wait states from the coordinator would interfere with agents that are legitimately waiting for
a reply.
Configuration¶
The coordinator's behavior is controlled per thread via ThreadSettings. See
Thread Settings for the full data model, REST settings API, and MCP tools.
Key fields:
| Field | Default | Effect |
|---|---|---|
auto_administrator_enabled |
true |
Set to false to disable the coordinator for a thread entirely |
timeout_seconds |
60 |
Seconds of inactivity before Scenario A/B/C triggers (min 30) |
switch_timeout_seconds |
60 |
Seconds before Scenario D (admin switch) triggers (min 30) |
Disable the coordinator for a free-form thread:
POST /api/threads/{thread_id}/settings
Content-Type: application/json
{ "auto_administrator_enabled": false }
Or via MCP:
Increase timeouts for slow-paced collaboration: