Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions apps/hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,25 @@ export async function createHub(config: HubConfig) {
chatOrchestratorDeps.memory = memoryHandle.memory;
}
const chatOrchestrator = createChatOrchestrator(chatOrchestratorDeps);
// CL-6644: a loud, unconditional boot confirmation that message intake
// is actually wired — a composition-root mistake here (an import
// dropped, a construction reordered, an argument omitted) type-checks
// fine but produces a hub that accepts messages into a void: no
// dispatch, no error, no notice, just a message that persists and is
// never asked of anyone. This can't detect every such mistake (the
// pieces below are non-optional local bindings, not feature-flagged),
// but it turns "intake is wired" from an assumption nothing checks
// into a line every boot log carries — the next investigation starts
// by grepping for this instead of re-deriving the whole call chain.
getLogger(["hub", "chat-intake"]).info(
"Chat message intake wired: turnQueue={hasTurnQueue} " +
"chatOrchestrator={hasOrchestrator} chatPlatform={hasPlatform}",
{
hasTurnQueue: turnQueue !== undefined,
hasOrchestrator: chatOrchestrator !== undefined,
hasPlatform: chatPlatform !== undefined,
},
);
// A room participant that died with its sidecar is otherwise silently
// dead until somebody writes into it, and the turn the crash
// interrupted never surfaces at all — the run that died never sends
Expand Down
30 changes: 30 additions & 0 deletions packages/chat/src/workbench-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,25 @@ async function routeToRecipients(
if (host !== undefined) recipientSet.add(host.address);
}
const recipients = [...recipientSet];
// CL-6644: unconditional, not gated on failure — the silent gap this
// investigation found is that nothing at all logs between "message
// persisted" and either a dispatch failure or a successful reply,
// so a turn that resolves zero recipients (a workbench with no host
// participant, a stale settings row) or one that stalls before ever
// reaching `dispatchTurnBatch`'s own error handling looks identical
// to total silence in the logs. This line exists so the next person
// chasing an "agent never replied" report can tell, from logs alone,
// whether routing ever ran and what it resolved to.
fanoutLog.info(
"Routed workbench {workbenchId}'s message {messageId} to {count} " +
"recipient(s): {recipients}",
{
workbenchId: input.workbenchId,
messageId,
count: recipients.length,
recipients,
},
);

const contextText =
!isDefaultRouting && recipients.length > 0
Expand Down Expand Up @@ -1202,6 +1221,17 @@ async function dispatchTurnBatch(
if (last === undefined) return;
const messageIds = batch.map((turn) => turn.messageId);

// CL-6644: unconditional entry marker — see the matching note on the
// caller's own recipient-resolution log. This is the one line that
// proves execution reached turn dispatch at all; its absence for a
// message known to have persisted narrows a future "no reply"
// report to upstream of here without needing a live repro first.
fanoutLog.info(
"Dispatching workbench {workbenchId}'s turn for message(s) " +
"{messageIds} to {count} recipient(s): {recipients}",
{ workbenchId, messageIds, count: recipients.length, recipients },
);

// Concurrent: agents are independent, and a dispatch that has to wake
// its target pays a full redeploy — serially, one slept agent would
// delay every agent mentioned after it.
Expand Down
Loading