Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(chat): propagate the real agentId on Path B instead of hardcoded literals

merged#2699CedarCopilot

CedarCopilot wants to merge 2 commits into staging from fix/chat-path-agentid-propagation

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Sep 6, 2026, 2:44 PM
  2. Sep 6, 2026, 3:05 PM
  3. Merged
    Sep 6, 2026, 3:15 PM
  4. Live on prod
    Sep 6, 2026, 3:15 PM
  5. Observed 0 hours
    Sep 6, 2026, 3:15 PM
  6. Unobserved

    Live on prod, no production signal yet

    Sep 6, 2026, 3:15 PM
  7. Pipelines steady after this deploy
    Sep 6, 2026, 3:15 PM

Behaviors Libra is checking

Bound-thread chat tool calls use the thread's real bound agentId instead of the hardcoded chat-agent literal, allowing field-ownership checks and tool attribution to resolve to the owning agent.Not checked
prod
User MCP server names that collide with reserved Cedar keys emit a structured warning while allowing the chat turn to continue.Not checked
prod
Reserved Cedar MCP servers always take precedence over user-connected MCP servers with the same name, preventing a user server from shadowing shared or per-agent Cedar tool access.Not checked
prod
Named subagents can successfully propose and execute conversation-field mutations for fields they own because their Cedar tool contexts carry the subagent's actual agentId.Not checked
prod
Frontend tool-call rendering treats Cedar tools from per-agent delegate servers the same as tools from the shared Cedar server.Not checked
prod
Each named Task-delegated playbook subagent receives a dedicated Cedar MCP server configured with its own real agentId and can no longer route Cedar tool calls through the shared session server.Not checked
prod

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

Summary

Phase 1 of docs/design/unify-agent-dispatch-paths.md.

On the autonomous dispatch path ("Path A" , on-event triggers, cron, before-meeting), every dispatch tool resolves the real per-agent UUID and sets it on RequestContext before running. That's what lets updateConversationFieldsTool's field-ownership check (ownerId !== runningAgentId) tell who's actually calling.

On the chat/on-demand path ("Path B"), this was broken: run-chat-agent-sdk.ts and chat-workflow.ts set agentId to hardcoded literal strings ('chat-agent-sdk-apply' / 'chat-agent') regardless of which named agent was actually bound or delegated to. Since runningAgentId could never equal a real stamped ownerAgentId, field-ownership enforcement failed closed , silently blocking every agent from writing an owned field (statusOverview, nextSteps), including the legitimate owner , for every chat interaction, always.

Fix

  • Bound-thread path: request-context.ts already resolved the bound agent's real id (an earlier commit, confirmed via git blame) , added an explicit override hook and propagated it through the two remaining hardcoded-literal call sites.
  • Task-delegation path (the harder case , one chat turn can delegate to several different named agents, so a single context value can't track "which one is running right now"): gives each named delegate its own dedicated in-process MCP server keyed to its real playbook agentId (delegate-tool-scoping.ts), since the Agent SDK's tool handler has no per-invocation correlator back to the calling subagent otherwise.

Risk

This entire code path is already gated behind the OFF-by-default cedar-claude-code-sdk PostHog flag , this fix doesn't introduce new production exposure, it closes a real gap inside an already-conservative gate.

One residual, documented limitation: the per-agent AgentDefinition.tools/mcpServers scoping mechanism is built from the SDK's documented type surface, but can't be exercised against a live @anthropic-ai/claude-agent-sdk query() run in this sandbox (no network/API key in CI). Recommend a manual smoke test in staging (with the flag on for a test account) before merging, since this is the one part static review and unit tests can't fully close the loop on.

Test plan

  • pnpm --filter @zero/server run types , clean
  • pnpm exec vitest run across every touched test dir , 145 tests, all passing
  • pnpm dlx oxlint@1.78.0 --deny-warnings , clean
  • pnpm deps:check , 0 violations
  • thermo-review run against the full diff , 0 blocking, 0 should-fix (one self-caught issue , stale references to a function name that was never created , fixed before this PR)
  • Manual smoke test in staging with cedar-claude-code-sdk on for a test account, verifying a bound-chat and a Task-delegated agent each get the correct field-ownership behavior

šŸ¤– Generated with Claude Code

https://claude.ai/code/session_01E3AhwqADAFeqe9wEhz6Mk7

Greptile Summary

This PR propagates real playbook agent IDs through both bound-chat and named-delegate execution, preserves that provenance across deferred mutation approval, and normalizes per-agent MCP tool names for frontend rendering.

  • Resolves bound-thread tool calls to the bound agent rather than a fixed chat identifier.
  • Creates a dedicated Cedar MCP server and tool scope for each named delegate.
  • Stores proposal-time agent provenance with pending mutations and restores it during approval.
  • Adds focused tests for context propagation, delegate scoping, pending mutation application, and legacy tool-name mapping.
  • The generated MCP namespace currently collides with unrestricted user MCP server names.

Confidence Score: 4/5

The PR should not merge until generated delegate server names cannot be overwritten by user MCP servers a

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/workflows/chat/__tests__/agent-bound-chat.test.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/__tests__/apply-pending-mutation-agentid.test.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/chat-workflow.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/__tests__/delegate-tool-scoping.test.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/__tests__/legacy-tool-name-map.test.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/__tests__/request-context.test.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/delegate-tool-scoping.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/legacy-tool-name-map.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/mcp-server.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/request-context.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/subagents.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/run-chat-agent-sdk.tsno production surface mapped
  • apps/server/src/services/chat/pending-mutations.tsno production surface mapped