From 7a4ccaeb61f9f951c7c1bc8e1623e47e381c3ff6 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Wed, 29 Jul 2026 19:32:10 +0200 Subject: [PATCH] =?UTF-8?q?fix(hub):=20dormant=20=E2=89=A0=20besch=C3=A4ft?= =?UTF-8?q?igt=20=E2=80=94=20arbeitende=20Agenten=20nicht=20mehr=20als=20t?= =?UTF-8?q?ot=20melden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dormantAgents` filterte auf `!inLoop`. `inLoop` ist aber immer false, solange ein Agent einen Task AUSFÜHRT — dadurch meldete der Pulse jeden hart arbeitenden Agenten als dormant. Live gesehen: codex mit 17 s altem Check-in stand als dormant im Pulse. Genau das Signal, auf das sich der Architekt verlassen soll, hätte ihn dazu gebracht, funktionierende Agenten anzustoßen. Jetzt zwei getrennte Fälle: - `loopExitReason` gesetzt → der Agent hat den Loop ausdrücklich verlassen und hört nicht mehr zu: sofort melden (unverändert). - kein Exit-Grund, aber Task in Arbeit → er führt aus; erst nach DORMANT_AFTER_MS (5 min) ohne Check-in ist das mein Fall. Co-Authored-By: Claude Opus 5 (1M context) --- src/core/services/presenceService.ts | 9 +++++++++ src/server/routes.ts | 19 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/core/services/presenceService.ts b/src/core/services/presenceService.ts index a020d1d..61a8e1b 100644 --- a/src/core/services/presenceService.ts +++ b/src/core/services/presenceService.ts @@ -22,6 +22,15 @@ import { VERSION } from '../../version.js'; export const ACTIVE_WINDOW_MS = 2 * 60_000; /** Longer than this without any action ⇒ the agent is stale/offline. */ export const STALE_WINDOW_MS = 10 * 60_000; +/** + * Ab wann gilt ein Agent als DORMANT (der Architekt muss eingreifen)? + * + * `inLoop` allein taugt dafür NICHT: es ist false, sobald ein Agent einen Task + * ausführt — ein hart arbeitender Agent sähe damit genauso aus wie ein toter. + * Entscheidend ist, wie lange der letzte Check-in her ist. Alles darunter ist + * normale Arbeit und darf keinen Alarm auslösen. + */ +export const DORMANT_AFTER_MS = 5 * 60_000; export type AgentLight = 'active' | 'busy' | 'idle' | 'stale'; diff --git a/src/server/routes.ts b/src/server/routes.ts index 3868c04..00fa18b 100644 --- a/src/server/routes.ts +++ b/src/server/routes.ts @@ -11,7 +11,7 @@ import { getStatus, updateStatus } from '../core/services/statusService.js'; import { autoDelegate, suggestDelegation } from '../core/services/delegateService.js'; import { computeBudget } from '../core/services/budgetService.js'; import { getRoster } from '../core/services/rosterService.js'; -import { computeHealth, enterLoop, leaveLoop, stampSeen } from '../core/services/presenceService.js'; +import { computeHealth, enterLoop, leaveLoop, stampSeen, DORMANT_AFTER_MS } from '../core/services/presenceService.js'; import { resolvePending, hasPending } from '../core/services/checkinService.js'; import { loadConfig, saveConfig } from '../core/config.js'; import { agentIdentity } from '../core/services/identityService.js'; @@ -96,8 +96,23 @@ export async function registerRoutes(app: FastifyInstance, cwd: string): Promise assignedTo: task.assignedTo, updatedAt: task.updatedAt, })), + // Zwei verschiedene Zustände, die nicht gleich behandelt werden dürfen: + // + // a) `loopExitReason` gesetzt → der Agent hat den Loop AUSDRÜCKLICH + // verlassen ("turn ended"). Er hört ab sofort nicht mehr zu; ein + // Reopen erreicht ihn nie. Sofort melden, ohne Wartezeit. + // b) kein Exit-Grund, aber ein Task in Arbeit → der Agent FÜHRT gerade + // aus. `inLoop` ist dabei immer false; ohne Zeitbedingung wäre jeder + // hart arbeitende Agent "dormant" und der Architekt würde Leute + // anstoßen, die einwandfrei laufen. Erst nach DORMANT_AFTER_MS ohne + // Check-in ist das ein Fall für mich. dormantAgents: health.agents - .filter((agent) => !agent.inLoop && (agent.taskId || agent.loopExitReason)) + .filter((agent) => { + if (agent.inLoop) return false; + if (agent.loopExitReason) return true; + if (!agent.taskId) return false; + return agent.lastSeenAgoSec === undefined || agent.lastSeenAgoSec * 1000 >= DORMANT_AFTER_MS; + }) .map((agent) => ({ name: agent.name, taskId: agent.taskId,