CedarCopilot wants to merge 2 commits into staging from fix/chat-field-descriptions-usd-190624
Live on prod, no production signal yet
Strict CloudWatch fallback found 1 prod failure log lines for [SUBSCRIPTION] Error setting up Gmail watch (invalid_grant), but this domain-wide failure family is not tied to this intent.
Libra has verdicts on 0 of 1 tracked behaviors on prod; 1 is still being checked. Libra checks hourly for 3 days after each deploy.
The chat agent reaches this tool as find → update-fields. It never calls get-conversation, and find returns a flat summary with no field semantics:
{"name": "Squared Circles_Consumer Research_Research Team",
"status": "6_negotiation", "dealValue": 65000, "isOwnedByCurrentUser": true}
So when Willem asked to set the deal to $52,500, the agent had to guess the unit. It guessed cents:
{ "name": "dealValue", "value": 5250000,
"reasoning": "User explicitly requested changing the deal value to $52,500 (stored in cents)." }
That went to Salesforce. When he said the number looked wrong, it re-derived the same value , "Need to verify the correct cents value: $52,500 = 5,250,000 cents" , and told him Cedar was correct and Salesforce was lagging.
The description that would have prevented this already existed and was already right: 'Deal value in USD (annual contract value)...'. But it lives inside extractConversationFields, whose only callers are event-execution paths (orchestrator-dispatch-tools.ts:232, handleExecuteFromClientSend.ts:486, initial-sync-conversation-update-workflow.ts:112) and fetchConversationTool.ts:471. Nothing on the chat write path could reach it.
1. One home for core field descriptions. CORE_FIELD_BASE_DESCRIPTIONS in core-fields.ts now holds the six core fields whose description is static. extractConversationFields reads from it rather than inlining the literals, so read and write paths cannot drift. status and priority interpolate their enum options at runtime and deliberately stay put. A coreFieldBaseDescription() helper throws rather than emitting an empty description if a field is ever added without one , an undescribed field is exactly how an agent ends up guessing a unit.
2. update-conversation-fields echoes back the description of every field it wrote, as a new optional fieldDescriptions on the result. Core fields come from the registry; custom fields come from the AOP defs via buildFieldPermissions, which now collects description (the aop-schema.ts:360 field definition already carried it , the local cast in the tool had simply dropped it). This is the only point on the chat path where field semantics reach the agent, and it arrives in the same turn, early enough to self-correct before replying.
3. The dealValue text states the unit three ways, because the failure mode is an agent guessing:
Deal value in WHOLE US DOLLARS (annual contract value). NEVER cents. This is a native Cedar field and its unit is always dollars regardless of how the external CRM stores it , do NOT multiply by 100. $52,500 is written as 52500, not 5250000.
The "regardless of how the external CRM stores it" clause is there because that is the inference that produced the bug.
It does not make a wrong value impossible. The same agent wrote plain dollars (40000) to this same conversation five days earlier, also without the description , the model is usually right and occasionally confidently wrong. A magnitude guard on the write is the belt-and-braces fix and is deliberately not in this PR, so the two can be reviewed on their own merits.
Scope check for reassurance: across 120 days there are 13 chat-sourced dealValue writes and only the two on 2026-08-27 carry the 100x. This is a one-off, not a broken code path.
pnpm --filter @zero/server exec vitest run \
src/mastra/utils/context-formatting/__tests__/core-field-descriptions.test.ts
Three new tests: the dealValue text asserts all four unit statements; a coverage test fails if any static core field is added without a description; a triviality test rejects stub descriptions.
tsc --noEmit 0 errors · vitest on tools/conversation, utils/context-formatting, services/crm, tools/task → 16 + 72 files, 1040 tests passing, 0 failures · pnpm deps:check no violations (1576 module
Libra has not measured any production surfaces for this change yet.