78.3 Handoff Routing Fix
Documentation for Sessions
78.3 Handoff Routing Fix
Session: 78 Focus: Capability-Based Handoff Routing Status: Completed
Problem Statement
Browser handoffs were only being routed to agents on the cowork platform. The Chrome extension (platform: external) was never receiving browser handoffs, even though it has browser automation capabilities.
Original Code (Broken)
// apis/backend-ai-api/src/app/infrastructure/services/handoff.service.ts
private async routeHandoff(handoff: HandoffDocument): Promise<{ agentId: string } | null> {
const requirements: {
platform?: 'cursor' | 'claude-code' | 'cowork' | 'external';
capability?: 'canBrowse' | 'canEditFiles' | 'canTestUI';
} = {};
if (handoff.toAgentType === 'browser') {
// ❌ OLD: Restricted to cowork platform only
requirements.platform = 'cowork';
} else if (handoff.toAgentType === 'code') {
requirements.capability = 'canEditFiles';
}
// ... routing logic
}
Root Cause
The routing logic used platform-based routing for browser handoffs:
platform = 'cowork'meant only Claude Desktop (Cowork) could receive browser tasks- Chrome extension registers as
platform: 'external', so it was excluded - This was inconsistent with code handoffs, which used capability-based routing
Solution: Capability-Based Routing
Changed browser handoff routing from platform-based to capability-based.
Fixed Code
// apis/backend-ai-api/src/app/infrastructure/services/handoff.service.ts
/**
* Route handoff to best available agent
* Session 78: Fixed to route browser handoffs to ANY agent with canBrowse capability,
* not just 'cowork' platform. This allows Chrome extensions (platform: 'external')
* to receive browser tasks.
*/
private async routeHandoff(handoff: HandoffDocument): Promise<{ agentId: string } | null> {
// Determine capability requirement based on toAgentType
// Session 78: Removed platform restriction for browser handoffs - use capability-based routing
const requirements: {
platform?: 'cursor' | 'claude-code' | 'cowork' | 'external';
capability?: 'canBrowse' | 'canEditFiles' | 'canTestUI';
} = {};
if (handoff.toAgentType === 'browser') {
// ✅ NEW: Route to ANY agent with canBrowse capability
// This allows Chrome extensions to receive browser handoffs
requirements.capability = 'canBrowse';
} else if (handoff.toAgentType === 'code') {
requirements.capability = 'canEditFiles';
}
// If specific agent requested, try that first
if (handoff.toAgentId) {
const agent = await this.agentService.getById(handoff.toAgentId);
if (agent && agent.status !== 'offline') {
handoff.assignedTo = agent.agentId;
await handoff.save();
return { agentId: agent.agentId };
}
}
// Find best available agent based on capabilities
const bestAgent = await this.agentService.findBestAgent(handoff.workspace, {
...requirements,
preferIdle: true,
});
if (bestAgent) {
handoff.assignedTo = bestAgent.agentId;
await handoff.save();
await this.agentService.incrementHandoffReceived(bestAgent.agentId);
return { agentId: bestAgent.agentId };
}
return null;
}
Agent Service: Default Capabilities Fix
The agent service was also updated to correctly assign default capabilities for the external platform (Chrome extensions).
Fixed Code
// apis/backend-ai-api/src/app/infrastructure/services/agent.service.ts
/**
* Register or update an agent
*/
async register(input: RegisterAgentInput): Promise<AgentRegistryDocument> {
const now = new Date();
// Session 78: Fixed default capabilities for 'external' platform (Chrome extensions)
// External agents can provide browser capabilities - use their provided caps, not defaults
const defaultCapabilities: AgentCapabilities = {
canBrowse: ['cowork', 'external'].includes(input.platform), // ✅ Added 'external'
canEditFiles: ['cursor', 'claude-code'].includes(input.platform),
canExecuteCode: ['cursor', 'claude-code'].includes(input.platform),
canTestUI: ['cowork', 'external'].includes(input.platform), // ✅ Added 'external'
domains: [],
...input.capabilities, // Override with provided capabilities
};
const agent = await this.agentModel.findOneAndUpdate(
{ agentId: input.agentId },
{
$set: {
instanceId: input.instanceId,
platform: input.platform,
workspace: input.workspace,
sessionNumber: input.sessionNumber,
status: 'online',
capabilities: defaultCapabilities,
metadata: input.metadata || {},
lastHeartbeat: now,
socketId: input.socketId,
disconnectedAt: null,
},
// ...
},
{ upsert: true, new: true },
);
return agent;
}
Capability Matrix
Before Session 78
| Platform | canBrowse | canEditFiles | canExecuteCode | canTestUI |
|---|---|---|---|---|
| cursor | ❌ | ✅ | ✅ | ❌ |
| claude-code | ❌ | ✅ | ✅ | ❌ |
| cowork | ✅ | ❌ | ❌ | ✅ |
| external | ❌ | ❌ | ❌ | ❌ |
After Session 78
| Platform | canBrowse | canEditFiles | canExecuteCode | canTestUI |
|---|---|---|---|---|
| cursor | ❌ | ✅ | ✅ | ❌ |
| claude-code | ❌ | ✅ | ✅ | ❌ |
| cowork | ✅ | ❌ | ❌ | ✅ |
| external | ✅ | ❌ | ❌ | ✅ |
Handoff Routing Flow
Before (Broken)
┌─────────────────────────────────┐
│ Create Handoff │
│ toAgentType: 'browser' │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ routeHandoff() │
│ requirements.platform = 'cowork'│
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ findBestAgent() │
│ query: { platform: 'cowork' } │
└─────────────────────────────────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌─────────┐ ┌─────────────┐
│ Cowork │ │ Extension │
│ ✅ Match │ │ ❌ No Match │
└─────────┘ └─────────────┘
After (Fixed)
┌─────────────────────────────────┐
│ Create Handoff │
│ toAgentType: 'browser' │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ routeHandoff() │
│ requirements.capability = 'canBrowse' │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ findBestAgent() │
│ query: { 'capabilities.canBrowse': true }│
└─────────────────────────────────────────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌─────────┐ ┌─────────────┐
│ Cowork │ │ Extension │
│ ✅ Match │ │ ✅ Match │
└─────────┘ └─────────────┘
Agent Discovery Logic
The findBestAgent method in AgentService now correctly queries by capability:
async findBestAgent(
workspace: string,
requirements: {
platform?: AgentPlatform;
capability?: keyof AgentCapabilities;
domain?: string;
preferIdle?: boolean;
},
): Promise<AgentRegistryDocument | null> {
const query: any = {
workspace,
status: { $in: ['online', 'busy', 'idle'] },
};
if (requirements.platform) {
query.platform = requirements.platform;
}
if (requirements.capability) {
// ✅ Query by capability, not platform
query[`capabilities.${requirements.capability}`] = true;
}
if (requirements.domain) {
query['capabilities.domains'] = requirements.domain;
}
// Sort by idle status first, then by response time
const agents = await this.agentModel.find(query).sort({
status: requirements.preferIdle ? 1 : -1,
averageResponseTimeMs: 1,
});
return agents[0] || null;
}
Testing
Verification Steps
Register Chrome Extension:
- Extension connects with
platform: 'external' - Check MongoDB: agent should have
canBrowse: true
- Extension connects with
Create Browser Handoff:
- Create handoff with
toAgentType: 'browser' - Verify extension receives the handoff notification
- Create handoff with
Multi-Agent Scenario:
- Have both Cowork and Extension online
- Create browser handoff
- Should route to whichever is idle (or has faster response time)
MongoDB Query to Verify
// Check agent capabilities
db.agentregistries.find({
platform: 'external',
'capabilities.canBrowse': true
})
// Check pending browser handoffs
db.handoffs.find({
toAgentType: 'browser',
status: 'pending'
})
Key Takeaways
- Capability > Platform: Route by what agents can do, not what platform they run on
- Extensibility: New platforms with browser capabilities automatically receive browser handoffs
- Consistency: Both
browserandcodehandoffs now use capability-based routing - Future-Proof: Adding new capabilities (e.g.,
canAnalyze,canDeploy) follows the same pattern
Related Code
| File | Change |
|---|---|
handoff.service.ts |
Removed platform = 'cowork' restriction |
agent.service.ts |
Added external to canBrowse and canTestUI defaults |
mcp-connector.js |
Extension registers with canBrowse: true capability |