Skip to content

[Bug]: google_meet & voice_call route every call to single configured agent in multi-agent deployments #77753

Description

@quangtran88

Summary

In multi-agent OpenClaw deployments (per-user agents on Slack/Telegram/Discord), when any agent invokes the google_meet tool (action=join, create+joinAfterCreate, test_speech) or the voice_call tool (initiate_call), the outbound voice call is always routed to the single configured agent (voiceConfig.agentId ?? "main") instead of the agent that actually initiated the call. Every user hears the same main agent voice.

Reproduction

  1. Deploy OpenClaw with multiple per-user agents (e.g., slack-u123, slack-u456).
  2. User Alice (routed to slack-u123) asks the bot to join a Google Meet.
  3. User Bob (routed to slack-u456) asks the bot to join a different Google Meet concurrently.
  4. Both calls reach Twilio and respond with the same agent's voice/identity (main), not the per-user agent.

Root Causes (multiple, must all be addressed)

  1. Static tool registration loses caller identityextensions/google-meet/index.ts:993 and extensions/voice-call/index.ts:609 register tools via api.registerTool({...}) static-object form. The factory form api.registerTool((ctx) => ({...}), { name }) is required to capture ctx.agentId.
  2. Cross-process gateway hop drops contextcallGoogleMeetGatewayFromTool (google-meet/index.ts:449-470) hops via callGatewayFromCli (CLI bridge → WebSocket → gateway-rpc.runtime.js). Tool-side closures cannot reach the gateway handler. agentId must travel as a serialized RPC field.
  3. Gateway handlers don't extract agentIdgooglemeet.join (line 705-720), googlemeet.create, googlemeet.test_speech build rt.join(request) without agentId.
  4. JoinMeetRequest / rt.join shape lacks agentIdextensions/google-meet/src/runtime.ts:351; same for joinMeetViaVoiceCallGateway at runtime.ts:443-468.
  5. voicecall.start RPC handler doesn't accept agentIdextensions/voice-call/index.ts:581-606 reads only to/message/dtmfSequence/mode. initiateCallAndRespond (line 335-353) calls manager.initiateCall(to, undefined, {...}) with no agentId slot.
  6. Three downstream sites read effectiveConfig.agentId, not call.agentId:
  7. voice_call tool itself has the same defectextensions/voice-call/index.ts:635 initiate_call action calls rt.manager.initiateCall(to, undefined, {...}) with no agentId.

Impact

Scenario Current Expected
Multi-user Slack bot via google_meet All users share main agent voice Each user hears their own agent
Multi-user via direct voice_call All users share main agent voice Each user hears their own agent
Realtime mode (mode=realtime) Reads effectiveConfig.agentId Should read call.agentId
Conversation mode (response-generator) Reads voiceConfig.agentId Should read call.agentId
Google Meet transcription attribution Attributed to main Attributed to requesting agent

Proposed Fix (high-level)

Design invariant: resolve effective agentId exactly once at call creation, persist it on CallRecord, and consume call.agentId at every downstream site through a centralized helper. No optional-fallback chain at every hop.

  1. Convert google_meet and voice_call tools to factory pattern with { name } opts (required by src/plugins/registry.ts:506-509 for factory-form name auto-derivation).
  2. Inject ctx.agentId into the gateway-bound RPC payload at the dispatch boundary; thread it through googlemeet.{join,create,test_speech} handlers → JoinMeetRequest/Create/TestSpeechrt.joinjoinMeetViaVoiceCallGatewayvoicecall.start RPC → manager.initiateCallCallRecord.agentId.
  3. Freeze the resolved agentId on inbound CallRecord at manager/events.ts:createWebhookCall (DID-route default, frozen at creation to prevent hot-reload drift).
  4. Add extensions/voice-call/src/util/resolve-call-agent-id.ts with a single precedence helper: call.agentId || effectiveConfig.agentId || "main". Consume from webhook, both realtime consult lanes, and response-generator.
  5. Construct per-meeting session keys (agent:${agentId}:google-meet:${session.id}) to prevent collisions on shared Twilio dial-ins (pattern reused from extensions/google-meet/src/agent-consult.ts:50-57).

Scope

~14 files across two extensions plus one new helper module:

  • extensions/google-meet/: index.ts, src/runtime.ts, src/voice-call-gateway.ts
  • extensions/voice-call/: index.ts, src/types.ts, src/manager/outbound.ts, src/manager/events.ts, src/webhook.ts, src/response-generator.ts, src/runtime.ts, new src/util/resolve-call-agent-id.ts

Plus unit tests (per-file) and one integration test driving the webhook → response-generator boundary.

Backward Compatibility

  • CallRecord schema: new optional field, Zod strip-mode round-trip preserves old records.
  • voicecall.start RPC: old handlers ignore unknown agentId/sessionKey params.
  • Single-agent deployments: ctx.agentId === "main" (or undefined → falls through helper to config default → "main"). Identical pre-PR behavior.
  • Mixed-version rolling upgrades: voice-call state is process-local (in-memory Map + per-pod calls.jsonl); during a rolling window, old-pod voicecall.start invocations silently drop the field and fall back to effectiveConfig.agentId. Uniform rollout recommended.

Related

PR with full plan and detailed change set will follow shortly. Filed by quangtran88 from oneclaw/oneclaw.improvement for an OneClaw multi-agent deployment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions