Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(next-steps): give the agent the stage label and the deals behind the conversation

merged#2706CedarCopilot

CedarCopilot wants to merge 5 commits into staging from fix/next-steps-stage-label

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Sep 6, 2026, 6:55 PM
  2. Sep 6, 2026, 8:23 PM
  3. Merged
    Sep 6, 2026, 8:51 PM
  4. Live on prod
    Sep 6, 2026, 8:51 PM
  5. Observed 2 days
    Sep 6, 2026, 8:51 PM
  6. Pipelines steady after this deploy
    Sep 6, 2026, 8:51 PM
  7. Unobserved

    Live on prod, no production signal yet

    Sep 9, 2026, 7:22 PM

Behaviors Libra is checking

The next-steps agent receives every CRM deal linked to the conversation with an English open or closed/terminal state, and multi-deal conversations mark the single active deal while identifying the others as siblings.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Sep 9, 2026, 6:22 PM
Only external CRM metadata is added to the linked-deals prompt section, while Slack routing metadata and empty linked-deal sections remain omitted.Inconclusivelow confidence

Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.

prod, checked Sep 9, 2026, 6:24 PM
The next-steps agent receives the human-readable CRM status label resolved from the applicable AOP picklist instead of the stored option value, in both normal execution and read-only preview.Inconclusivelow confidence

Unable to verify: the telemetry query was rejected before execution as containing a forbidden non-read keyword, so no production success or failure evidence was obtained.

prod, checked Sep 9, 2026, 7:21 PM
If the status option lookup fails or has no usable matching label, the next-steps agent continues with the raw status value rather than failing or displaying an empty stage.Inconclusivelow confidence

Unable to complete verification: the available telemetry queries did not yield a valid OTEL result, and CloudWatch returned only partial execution logs without a bounded success/error count, latency, or distinct-user measurement. Verdict is inconclusive.

prod, checked Sep 9, 2026, 7:22 PM

Libra has verdicts on 0 of 4 tracked behaviors on prod; 4 are still being checked. Libra checks hourly for 3 days after each deploy.

Two independent facts were missing from the next-steps agent's prompt, and each one caused a distinct class of wrongly-cancelled work. Both are fixed here; the harness shows neither is sufficient alone.

1. The stage was stated as the CRM's option VALUE, not its label

Cedar stores the external CRM's stage value. That agrees with the label on an untouched pipeline and diverges completely on one re-valued during a migration.

conversationshown to the agentreal HubSpot labelisClosed
Middough Inc. - 1 (Pirros)Scoping (closedwon)Scopingfalse
Buchan - Cancelled (Pirros)Deal DQ (226595252)Deal DQtrue

Verified live on staging's code via buildNextStepsPreview:

AssertionError: expected 'Status: Scoping (closedwon)\nNext Ste…' not to contain 'closedwon'

57 open tasks / 9 users / 5 orgs sit on conversations whose stored status reads closedwon/closedlost while every linked deal reports isClosed: false.

2. The agent could not see WHICH deals back the conversation

formatIntegrationMetadataForAgent (conversation-context.ts:1128) has rendered one <linked_crm_deal> block per linked opportunity for every other agent surface for a long time. Each carries Deal State: open | closed/terminal , the authoritative hs_is_closed in English , and, when a conversation carries more than one deal, marks the single one that represents it via pickActiveExternalCrmDeal. Its own comment names the failure it exists to prevent:

otherwise it sees several indistinguishable <linked_crm_deal> blocks and can gate/reason off a terminal sibling.

updateNextStepsAndTasksTool , the one agent that cancels work , never called it. A conversation with a dead sibling deal and a live active one was, to that agent, indistinguishable from a dead conversation.

Wired in through a small pure helper over integrationMetadata the hydrated conversation already carries. No extra read. Not via buildConversationContext, which would drag event formatting and several queries onto this hot path for one block. Only the external-CRM entries are passed through , the formatter also emits a Slack routing block, and routing is already this agent's <output_policy>. Verified against production: the OLC conversation (4 linked HubSpot deals) renders four blocks with correct labels, states and exactly one ACTIVE marker.


Harness

deal-close-task-cancel-eval.ts, 9 cases × 5 trials × 4 instruction variants per arm. Unit of analysis is the CASE; trials are repeats, so no pooling and no p-values.

arm<current_state><trigger_event><linked_crm_deal>
Aclosedwonraw,
BScopingraw,
DScopinglabel,
Eclosedwonrawyes
FScopinglabelyes

False cancellations (a must-keep task cancelled) out of 45:

variantABDEF
baseline343715180
blanket443820328
exceptions222313110
authoritative961354

Negative controls, baseline variant, cancellations (0 = clean):

caseshapeABDEF
marlowe-livesingle link, re-valued stage12/1515/150/1512/150/15
kelvin-unlinkedno CRM link at all5/55/50/55/50/5
fenwick-livesingle link, re-valued stage2/52/50/51/50/5
thorne-multilink2 open + 1 closed sibling5/55/55/50/50/5
hallward-mixed1 dead + 1 live10/1010/1010/100/100/10

The two fixes own disjoint failure classes, and this is the whole argument for shipping both:

  • The stage label fixes the single-link cases. marlowe 12→0 and kelvin 5→0 across E→F; the formatter alone (E) leaves them exactly where they were, because
Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/server/src/mastra/tools/task/__tests__/linked-crm-deals.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/__tests__/next-steps-preview.readonly.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/__tests__/next-steps-prompt.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/__tests__/status-label.test.tsno production surface mapped
  • apps/server/src/mastra/tools/task/linked-crm-deals.tsno production surface mapped
  • apps/server/src/mastra/tools/task/next-steps-preview.tsno production surface mapped
  • apps/server/src/mastra/tools/task/next-steps-prompt.tsno production surface mapped
  • apps/server/src/mastra/tools/task/status-label.tsno production surface mapped
  • apps/server/src/mastra/tools/task/updateNextStepsAndTasksTool.tsno production surface mapped