CedarCopilot wants to merge 1 commit into staging from fix/next-steps-backstop-db-check
Not deployed
Libra has no production signal for this change yet because it has not deployed. Libra checks hourly for 3 days after each deploy.
runNextStepsBackstop (apps/server/src/mastra/workflows/event-execution/on-event-agent-execution-workflow.ts) decided whether to dispatch update-next-steps-and-tasks based solely on orchestratorCalledNextSteps: didCallNextSteps(result) , an in-memory read of the orchestrator's generate() result. didCallNextSteps's own docstring already admits the tool-call shape "differs between provider adapters and neither one is guaranteed."
Real production evidence (Weiver customer, conversation d9b0de02-1208-4fa9-9272-b62d65a32cb9, run cd603b06-2b49-4c1f-9729-e780d658db71): the orchestrator DID call update-next-steps-and-tasks directly at 2026-09-13 10:02:30, creating task c6f051d2-3eea-4ae4-99b8-7933cceba95c. 39 seconds later, runNextStepsBackstop fired anyway , its guard evaluated orchestratorCalledNextSteps as false, a false negative , creating a second, overlapping duplicate task 876c8405-13b4-436b-ac59-9b89bdeb7e0c with contradictory framing (one calls itself the deal's "first" follow-up, the other references "a second email"). Both tasks trace to the same run_id.
Added didDispatchNextStepsInDb(runId) , a query against agent_tool_calls (run_id, tool_name) , as a second, authoritative signal, OR'd defensively with the existing in-memory check inside runNextStepsBackstop. It only runs when the in-memory check reads false, and can only suppress a false-positive backstop fire; it introduces no new failure mode since the original check is untouched.
The original docstring on didCallNextSteps warns that querying agent_tool_calls at that call site would race with the async tool-call logger. This is a different call site: by the time runNextStepsBackstop's guard runs, several other steps (including save-execution-summary) have already completed in the same run , consistent with the observed 39-second gap in the Weiver case , so the write is very likely to have landed.
Added a createStructuredLog('info', '[orchestratorAgentStep] next-steps backstop suppressed by DB check', ...) line for when the DB check is what caught the false negative, distinguishing "in-memory check works" from "in-memory check is broken and the DB check saved us" , that tells whoever reads the logs later whether didCallNextSteps itself still needs a deeper fix.
Also added unit tests for the new didDispatchNextStepsInDb (exported, mocking createDb the same way apps/server/src/mastra/tools/__tests__/tool-grant.test.ts does), covering both the row-found and no-row branches.
apps/server/src/mastra/workflows/event-execution/__tests__/next-steps-backstop.test.ts , 9 tests passing, including the two new didDispatchNextStepsInDb cases.cedar-prod), watch for [orchestratorAgentStep] next-steps backstop suppressed by DB check , any occurrence means the in-memory didCallNextSteps check produced a false negative that the DB check caught. If this line trends non-zero, didCallNextSteps itself likely needs a deeper fix (e.g. covering more provider adapter shapes) rather than relying on the DB check as a permanent crutch.run_id for outbound-send executions with open tasks (cross-reference user_tasks.creation_run_id for duplicates).š¤ Generated with Claude Code
https://claude.ai/code/session_019jTe7TuDHKYaFL6Ny2Z5h6
This PR adds a database-backed second signal to prevent the next-steps backstop from dispatching duplicate work when provider-specific result shapes defeat the in-memory tool-call check.
agent_tool_calls by run ID and tool name when the in-memory check is negative.Libra has not measured any production surfaces for this change yet.