fix(container): gate poll loop on trigger=1 to honor accumulate contract

A warm container picks up every pending messages_in row on each poll tick
and calls markProcessing → agent.query → markCompleted. Before this, that
included trigger=0 rows (ignored_message_policy='accumulate' context),
causing the agent to wake and potentially respond to messages the wiring
had explicitly opted out of engaging on — defeating accumulate's "store
as context, don't engage" contract.

Gate the main poll loop with `messages.some(m => m.trigger === 1)` —
mirrors host-side countDueMessages which is already gated. If the batch
has no wake-eligible row, sleep and leave them pending. They ride along
via the same getPendingMessages query the next time a real trigger=1
lands, which is the intended accumulate behavior.

The concurrent active-turn poll (line ~290) is unchanged on purpose —
once the agent has engaged, pushing in accumulate rows mid-turn as
additional context is desired.

initTestSessionDb was missing the trigger and series_id columns on
messages_in, out of sync with the live migration. Added both so the new
tests (and any future trigger-aware tests) can run.

Four tests cover the data contract: trigger=0 rows are returned by
getPendingMessages (so they ride along), the gate predicate correctly
identifies accumulate-only batches, mixed batches pass the gate, and the
schema default of 1 applies when the column is omitted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-20 11:23:47 +03:00
parent c38e5b11a8
commit 31f2da9585
3 changed files with 64 additions and 4 deletions

View File

@@ -72,6 +72,19 @@ export async function runPollLoop(config: PollLoopConfig): Promise<void> {
continue;
}
// Accumulate gate: if the batch contains only trigger=0 rows
// (context-only, router-stored under ignored_message_policy='accumulate'),
// don't wake the agent. Leave them `pending` — they'll ride along the
// next time a real trigger=1 message lands via this same getPendingMessages
// query. Without this gate, a warm container keeps processing
// (and potentially responding to) every accumulate-only batch, defeating
// the "store as context, don't engage" contract. Host-side countDueMessages
// gates the same way for wake-from-cold (see src/db/session-db.ts).
if (!messages.some((m) => m.trigger === 1)) {
await sleep(POLL_INTERVAL_MS);
continue;
}
const ids = messages.map((m) => m.id);
markProcessing(ids);