| 180 | } |
| 181 | |
| 182 | private computeClassification( |
| 183 | agentStates: Array<{ name: string; state: string; lifecycle: string }>, |
| 184 | ): { classification: LoopClassification; reasons: string[] } { |
| 185 | const reasons: string[] = [] |
| 186 | |
| 187 | if (this.errors > 0) { |
| 188 | reasons.push(`${this.errors} error(s) in this loop`) |
| 189 | return { classification: 'error', reasons } |
| 190 | } |
| 191 | |
| 192 | if (this.taskTransitions > 0 || this.artifactsCreated > 0) { |
| 193 | if (this.taskTransitions > 0) reasons.push(`${this.taskTransitions} task transition(s)`) |
| 194 | if (this.artifactsCreated > 0) reasons.push(`${this.artifactsCreated} artifact(s) created`) |
| 195 | return { classification: 'productive', reasons } |
| 196 | } |
| 197 | |
| 198 | const stuckAgents = Object.entries(this.pendingMessages).filter(([, count]) => count > 0) |
| 199 | if (stuckAgents.length > 0) { |
| 200 | for (const [agent, count] of stuckAgents) { |
| 201 | reasons.push(`${agent} has ${count} pending message(s) while idle`) |
| 202 | } |
| 203 | return { classification: 'backlog_stuck', reasons } |
| 204 | } |
| 205 | |
| 206 | if (this.blockedTasks > 0) { |
| 207 | reasons.push(`${this.blockedTasks} task(s) blocked by dependencies`) |
| 208 | return { classification: 'blocked', reasons } |
| 209 | } |
| 210 | |
| 211 | reasons.push('no state changes detected') |
| 212 | return { classification: 'idle', reasons } |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | class RingBuffer<T> { |