v3/implementation/adrs/ADR-027-teammate-tool-integration.md
Status: Implemented ✅ Date: 2026-01-25 Updated: 2026-01-25 Author: Claude Flow Architecture Team Version: 1.0.0 Requires: Claude Code >= 2.1.19
The @claude-flow/teammate-plugin package has been fully implemented with:
| Component | Lines | Features |
|---|---|---|
types.ts | ~500 | 30+ interfaces, enums, constants |
teammate-bridge.ts | ~1000 | 15 feature implementations |
mcp-tools.ts | ~350 | 16 MCP tools |
index.ts | ~110 | Public exports |
| Total | ~1960 | - |
| # | Feature | Status | Module |
|---|---|---|---|
| 1 | Core Bridge | ✅ | teammate-bridge.ts |
| 2 | Team Context | ✅ | updateTeamContext(), getTeamContext() |
| 3 | Permission Updates | ✅ | updateTeammatePermissions() |
| 4 | Delegate Mode | ✅ | delegateToTeammate(), revokeDelegation() |
| 5 | Session Memory | ✅ | saveTeammateMemory(), loadTeammateMemory() |
| 6 | Remote Sync | ✅ | pushToRemote(), pullFromRemote(), syncWithRemote() |
| 7 | Transcript Sharing | ✅ | shareTranscript() |
| 8 | Error Handling | ✅ | TeammateError, TeammateErrorCode (18 types) |
| 9 | Teleport | ✅ | teleportTeam(), canTeleport() |
| 10 | Plan Control | ✅ | pausePlanExecution(), resumePlanExecution(), reenterPlanMode() |
| 11 | Version Detection | ✅ | getVersionInfo(), MINIMUM_CLAUDE_CODE_VERSION |
| 12 | Events | ✅ | 20+ event types in TeammateBridgeEvents |
| 13 | Configuration | ✅ | PluginConfig, DEFAULT_PLUGIN_CONFIG |
| 14 | MCP Tools | ✅ | 16 tools with handleMCPTool() |
| 15 | TDD Tests | ✅ | Vitest suite with mocking |
v3/@claude-flow/teammate-plugin/
├── package.json # npm package (requires Claude Code >= 2.1.19)
├── tsconfig.json # TypeScript configuration
├── README.md # Full documentation
├── src/
│ ├── index.ts # Public API exports
│ ├── types.ts # All TypeScript definitions
│ ├── teammate-bridge.ts # Core bridge implementation
│ └── mcp-tools.ts # 16 MCP tools
└── tests/
└── teammate-bridge.test.ts # TDD test suite
This ADR defines the architecture for deep integration between Claude Flow and Claude Code's native TeammateTool multi-agent orchestration system. By leveraging TeammateTool's built-in capabilities for team management, inter-agent communication, and plan approval workflows, Claude Flow can eliminate redundant coordination code and provide seamless native multi-agent experiences.
Analysis of Claude Code v2.1.19 binary revealed a comprehensive multi-agent orchestration system:
| Component | Occurrences | Purpose |
|---|---|---|
TeammateTool | 34 | Core tool implementation |
Teammate | 154 | Agent class references |
spawnTeam | 22 | Team creation |
broadcast | 42 | Inter-agent messaging |
approvePlan/rejectPlan | 25 | Plan approval workflow |
requestJoin/approveJoin | 25 | Team membership |
teammate_mailbox | 3 | Message queue system |
AgentInput Schema (v2.1.19):
interface AgentInput {
description: string;
prompt: string;
subagent_type: string;
model?: "sonnet" | "opus" | "haiku";
resume?: string;
run_in_background?: boolean;
max_turns?: number;
// NEW TEAMMATE FIELDS (v2.1.19+)
allowed_tools?: string[];
name?: string;
team_name?: string;
mode?: "acceptEdits" | "bypassPermissions" | "default" |
"delegate" | "dontAsk" | "plan";
}
ExitPlanModeInput Schema:
interface ExitPlanModeInput {
allowedPrompts?: { tool: "Bash"; prompt: string }[];
pushToRemote?: boolean;
remoteSessionId?: string;
// NEW SWARM FIELDS (v2.1.19+)
launchSwarm?: boolean;
teammateCount?: number;
}
Environment Variables:
CLAUDE_CODE_TEAM_NAME # Team identifier
CLAUDE_CODE_TMUX_SESSION # tmux session name
CLAUDE_CODE_TMUX_PREFIX # tmux prefix key binding
CLAUDE_CODE_TEAMMATE_COMMAND # Spawn command override
Claude Flow currently implements its own multi-agent orchestration via:
This creates redundancy with Claude Code's native TeammateTool, which provides:
Implement a Claude Flow plugin that acts as a bridge to TeammateTool, providing:
| Principle | Implementation |
|---|---|
| Native First | Use TeammateTool when available, fallback to MCP |
| Zero Duplication | Delegate spawning to Claude Code entirely |
| Transparent Bridge | Claude Flow APIs unchanged, backend swapped |
| Version Adaptive | Detect Claude Code version, enable features accordingly |
v3/@claude-flow/teammate-plugin/
├── src/
│ ├── index.ts # Plugin entry point
│ ├── teammate-bridge.ts # Core TeammateTool bridge
│ ├── team-manager.ts # Team lifecycle management
│ ├── mailbox-adapter.ts # Mailbox ↔ Memory bridge
│ ├── spawn-backends/
│ │ ├── tmux-backend.ts # tmux spawn backend
│ │ ├── in-process.ts # In-process backend
│ │ └── backend-selector.ts # Auto-select best backend
│ ├── plan-orchestrator.ts # Plan mode swarm launcher
│ ├── approval-workflow.ts # Plan approval handling
│ ├── discovery.ts # Team discovery service
│ └── types.ts # TypeScript definitions
├── package.json
└── README.md
// types.ts
/**
* TeammateTool operations mapped from Claude Code v2.1.19
*/
export type TeammateOperation =
| 'spawnTeam'
| 'discoverTeams'
| 'requestJoin'
| 'approveJoin'
| 'rejectJoin'
| 'write' // Send message to teammate
| 'broadcast' // Broadcast to all teammates
| 'requestShutdown'
| 'approveShutdown'
| 'rejectShutdown'
| 'approvePlan'
| 'rejectPlan'
| 'cleanup';
/**
* Team configuration
*/
export interface TeamConfig {
name: string;
topology: 'flat' | 'hierarchical' | 'mesh';
maxTeammates: number;
spawnBackend: 'tmux' | 'in_process' | 'auto';
planModeRequired: boolean;
autoApproveJoin: boolean;
messageRetention: number; // ms
}
/**
* Teammate spawn configuration
*/
export interface TeammateSpawnConfig {
name: string;
role: string;
prompt: string;
model?: 'sonnet' | 'opus' | 'haiku';
allowedTools?: string[];
mode?: 'default' | 'plan' | 'delegate' | 'bypassPermissions';
teamName?: string;
runInBackground?: boolean;
}
/**
* Team state
*/
export interface TeamState {
name: string;
createdAt: Date;
teammates: TeammateInfo[];
pendingJoinRequests: JoinRequest[];
activePlans: PlanInfo[];
messageCount: number;
topology: string;
}
/**
* Teammate info
*/
export interface TeammateInfo {
id: string;
name: string;
role: string;
status: 'active' | 'idle' | 'busy' | 'shutdown_pending';
spawnedAt: Date;
messagesSent: number;
messagesReceived: number;
currentTask?: string;
}
/**
* Mailbox message
*/
export interface MailboxMessage {
id: string;
from: string;
to: string | 'broadcast';
timestamp: Date;
type: 'task' | 'result' | 'status' | 'plan' | 'approval';
payload: unknown;
acknowledged: boolean;
}
/**
* Plan for approval
*/
export interface TeamPlan {
id: string;
description: string;
proposedBy: string;
steps: PlanStep[];
requiredApprovals: number;
approvals: string[];
rejections: string[];
status: 'pending' | 'approved' | 'rejected' | 'executing';
}
export interface PlanStep {
order: number;
action: string;
assignee?: string;
tools: string[];
estimatedDuration?: number;
}
// teammate-bridge.ts
import { EventEmitter } from 'events';
import { execSync, spawn } from 'child_process';
import type {
TeamConfig,
TeammateSpawnConfig,
TeamState,
TeammateInfo,
MailboxMessage,
TeamPlan,
TeammateOperation,
} from './types.js';
/**
* Bridge between Claude Flow and Claude Code's TeammateTool
*
* Provides unified API for multi-agent orchestration using
* native TeammateTool capabilities when available.
*/
export class TeammateBridge extends EventEmitter {
private claudeCodeVersion: string | null = null;
private teammateToolAvailable: boolean = false;
private activeTeams: Map<string, TeamState> = new Map();
private mailboxPollers: Map<string, NodeJS.Timeout> = new Map();
constructor(private config: Partial<TeamConfig> = {}) {
super();
}
/**
* Initialize the bridge
* Detects Claude Code version and TeammateTool availability
*/
async initialize(): Promise<void> {
// Detect Claude Code version
try {
const version = execSync('claude --version', { encoding: 'utf-8' }).trim();
const match = version.match(/(\d+\.\d+\.\d+)/);
this.claudeCodeVersion = match?.[1] ?? null;
// TeammateTool requires >= 2.1.19
if (this.claudeCodeVersion) {
const [major, minor, patch] = this.claudeCodeVersion.split('.').map(Number);
this.teammateToolAvailable =
major > 2 ||
(major === 2 && minor > 1) ||
(major === 2 && minor === 1 && patch >= 19);
}
} catch {
this.claudeCodeVersion = null;
this.teammateToolAvailable = false;
}
this.emit('initialized', {
claudeCodeVersion: this.claudeCodeVersion,
teammateToolAvailable: this.teammateToolAvailable,
});
if (!this.teammateToolAvailable) {
console.warn(
`[TeammateBridge] TeammateTool not available. ` +
`Requires Claude Code >= 2.1.19, found: ${this.claudeCodeVersion ?? 'not installed'}`
);
}
}
/**
* Check if TeammateTool is available
*/
isAvailable(): boolean {
return this.teammateToolAvailable;
}
/**
* Get Claude Code version
*/
getClaudeCodeVersion(): string | null {
return this.claudeCodeVersion;
}
// ==================== TEAM MANAGEMENT ====================
/**
* Spawn a new team
*/
async spawnTeam(config: TeamConfig): Promise<TeamState> {
this.ensureAvailable();
const teamState: TeamState = {
name: config.name,
createdAt: new Date(),
teammates: [],
pendingJoinRequests: [],
activePlans: [],
messageCount: 0,
topology: config.topology,
};
// Set environment for team context
process.env.CLAUDE_CODE_TEAM_NAME = config.name;
if (config.planModeRequired) {
process.env.CLAUDE_CODE_PLAN_MODE_REQUIRED = 'true';
}
this.activeTeams.set(config.name, teamState);
// Start mailbox polling for this team
this.startMailboxPoller(config.name);
this.emit('team:spawned', { team: config.name, config });
return teamState;
}
/**
* Discover existing teams
*/
async discoverTeams(): Promise<string[]> {
this.ensureAvailable();
// Teams are stored in ~/.claude/teams/
const teamsDir = `${process.env.HOME}/.claude/teams`;
try {
const { readdirSync } = await import('fs');
const teams = readdirSync(teamsDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name);
return teams;
} catch {
return [];
}
}
/**
* Request to join an existing team
*/
async requestJoin(teamName: string, agentInfo: TeammateInfo): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
team.pendingJoinRequests.push({
agentId: agentInfo.id,
agentName: agentInfo.name,
requestedAt: new Date(),
role: agentInfo.role,
});
this.emit('team:join_requested', { team: teamName, agent: agentInfo });
// Auto-approve if configured
if (this.config.autoApproveJoin) {
await this.approveJoin(teamName, agentInfo.id);
}
}
/**
* Approve a join request
*/
async approveJoin(teamName: string, agentId: string): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const requestIndex = team.pendingJoinRequests.findIndex(
r => r.agentId === agentId
);
if (requestIndex === -1) {
throw new Error(`No pending join request for agent: ${agentId}`);
}
const request = team.pendingJoinRequests.splice(requestIndex, 1)[0];
team.teammates.push({
id: agentId,
name: request.agentName,
role: request.role,
status: 'active',
spawnedAt: new Date(),
messagesSent: 0,
messagesReceived: 0,
});
this.emit('team:join_approved', { team: teamName, agent: agentId });
}
/**
* Reject a join request
*/
async rejectJoin(teamName: string, agentId: string, reason?: string): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
team.pendingJoinRequests = team.pendingJoinRequests.filter(
r => r.agentId !== agentId
);
this.emit('team:join_rejected', { team: teamName, agent: agentId, reason });
}
// ==================== TEAMMATE SPAWNING ====================
/**
* Spawn a teammate using native AgentInput
*/
async spawnTeammate(config: TeammateSpawnConfig): Promise<TeammateInfo> {
this.ensureAvailable();
// Build AgentInput for Claude Code's Task tool
const agentInput = {
description: `${config.role}: ${config.name}`,
prompt: config.prompt,
subagent_type: config.role,
model: config.model,
name: config.name,
team_name: config.teamName ?? process.env.CLAUDE_CODE_TEAM_NAME,
allowed_tools: config.allowedTools,
mode: config.mode,
run_in_background: config.runInBackground ?? true,
};
// The actual spawn happens through Claude Code's Task tool
// This bridge prepares the configuration and tracks state
const teammateId = `teammate-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const teammateInfo: TeammateInfo = {
id: teammateId,
name: config.name,
role: config.role,
status: 'active',
spawnedAt: new Date(),
messagesSent: 0,
messagesReceived: 0,
currentTask: config.prompt.slice(0, 100),
};
// Add to team if team_name specified
if (config.teamName) {
const team = this.activeTeams.get(config.teamName);
if (team) {
team.teammates.push(teammateInfo);
}
}
this.emit('teammate:spawned', {
teammate: teammateInfo,
agentInput,
});
return teammateInfo;
}
/**
* Get spawn configuration for Claude Code Task tool
* Returns the AgentInput object to pass to Task tool
*/
buildAgentInput(config: TeammateSpawnConfig): Record<string, unknown> {
return {
description: `${config.role}: ${config.name}`,
prompt: config.prompt,
subagent_type: config.role,
model: config.model,
name: config.name,
team_name: config.teamName ?? process.env.CLAUDE_CODE_TEAM_NAME,
allowed_tools: config.allowedTools,
mode: config.mode,
run_in_background: config.runInBackground ?? true,
};
}
// ==================== MESSAGING ====================
/**
* Send message to a specific teammate
*/
async sendMessage(
teamName: string,
fromId: string,
toId: string,
message: Omit<MailboxMessage, 'id' | 'from' | 'to' | 'timestamp' | 'acknowledged'>
): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const fullMessage: MailboxMessage = {
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
from: fromId,
to: toId,
timestamp: new Date(),
acknowledged: false,
...message,
};
// Write to mailbox file
await this.writeToMailbox(teamName, toId, fullMessage);
team.messageCount++;
// Update sender stats
const sender = team.teammates.find(t => t.id === fromId);
if (sender) sender.messagesSent++;
this.emit('message:sent', { team: teamName, message: fullMessage });
}
/**
* Broadcast message to all teammates
*/
async broadcast(
teamName: string,
fromId: string,
message: Omit<MailboxMessage, 'id' | 'from' | 'to' | 'timestamp' | 'acknowledged'>
): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const fullMessage: MailboxMessage = {
id: `broadcast-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
from: fromId,
to: 'broadcast',
timestamp: new Date(),
acknowledged: false,
...message,
};
// Write to all teammate mailboxes
for (const teammate of team.teammates) {
if (teammate.id !== fromId) {
await this.writeToMailbox(teamName, teammate.id, fullMessage);
teammate.messagesReceived++;
}
}
team.messageCount += team.teammates.length - 1;
this.emit('message:broadcast', { team: teamName, message: fullMessage });
}
/**
* Read messages from mailbox
*/
async readMailbox(teamName: string, teammateId: string): Promise<MailboxMessage[]> {
this.ensureAvailable();
const mailboxPath = this.getMailboxPath(teamName, teammateId);
try {
const { readFileSync, writeFileSync } = await import('fs');
const content = readFileSync(mailboxPath, 'utf-8');
const messages: MailboxMessage[] = JSON.parse(content);
// Mark as acknowledged and clear
writeFileSync(mailboxPath, '[]', 'utf-8');
return messages;
} catch {
return [];
}
}
// ==================== PLAN APPROVAL ====================
/**
* Submit a plan for approval
*/
async submitPlan(teamName: string, plan: Omit<TeamPlan, 'id' | 'approvals' | 'rejections' | 'status'>): Promise<TeamPlan> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const fullPlan: TeamPlan = {
id: `plan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
approvals: [],
rejections: [],
status: 'pending',
...plan,
};
team.activePlans.push(fullPlan);
// Broadcast plan for approval
await this.broadcast(teamName, plan.proposedBy, {
type: 'plan',
payload: fullPlan,
});
this.emit('plan:submitted', { team: teamName, plan: fullPlan });
return fullPlan;
}
/**
* Approve a plan
*/
async approvePlan(teamName: string, planId: string, approverId: string): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const plan = team.activePlans.find(p => p.id === planId);
if (!plan) {
throw new Error(`Plan not found: ${planId}`);
}
if (!plan.approvals.includes(approverId)) {
plan.approvals.push(approverId);
}
// Check if we have enough approvals
if (plan.approvals.length >= plan.requiredApprovals) {
plan.status = 'approved';
this.emit('plan:approved', { team: teamName, plan });
}
this.emit('plan:approval_added', { team: teamName, planId, approverId });
}
/**
* Reject a plan
*/
async rejectPlan(teamName: string, planId: string, rejecterId: string, reason?: string): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const plan = team.activePlans.find(p => p.id === planId);
if (!plan) {
throw new Error(`Plan not found: ${planId}`);
}
plan.rejections.push(rejecterId);
plan.status = 'rejected';
this.emit('plan:rejected', { team: teamName, plan, rejecterId, reason });
}
/**
* Launch swarm to execute approved plan
* Uses Claude Code's native launchSwarm capability
*/
async launchSwarm(teamName: string, planId: string, teammateCount?: number): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const plan = team.activePlans.find(p => p.id === planId);
if (!plan || plan.status !== 'approved') {
throw new Error(`Plan not approved: ${planId}`);
}
// Build ExitPlanModeInput for swarm launch
const exitPlanInput = {
launchSwarm: true,
teammateCount: teammateCount ?? plan.steps.length,
allowedPrompts: plan.steps.map(step => ({
tool: 'Bash' as const,
prompt: step.action,
})),
};
plan.status = 'executing';
this.emit('swarm:launched', {
team: teamName,
plan,
exitPlanInput,
teammateCount: exitPlanInput.teammateCount,
});
}
// ==================== SHUTDOWN ====================
/**
* Request teammate shutdown
*/
async requestShutdown(teamName: string, teammateId: string, reason?: string): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const teammate = team.teammates.find(t => t.id === teammateId);
if (teammate) {
teammate.status = 'shutdown_pending';
}
this.emit('teammate:shutdown_requested', { team: teamName, teammateId, reason });
}
/**
* Approve teammate shutdown
*/
async approveShutdown(teamName: string, teammateId: string): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
team.teammates = team.teammates.filter(t => t.id !== teammateId);
this.emit('teammate:shutdown_approved', { team: teamName, teammateId });
}
/**
* Reject teammate shutdown
*/
async rejectShutdown(teamName: string, teammateId: string): Promise<void> {
this.ensureAvailable();
const team = this.activeTeams.get(teamName);
if (!team) {
throw new Error(`Team not found: ${teamName}`);
}
const teammate = team.teammates.find(t => t.id === teammateId);
if (teammate) {
teammate.status = 'active';
}
this.emit('teammate:shutdown_rejected', { team: teamName, teammateId });
}
/**
* Cleanup team resources
*/
async cleanup(teamName: string): Promise<void> {
this.ensureAvailable();
// Stop mailbox poller
const poller = this.mailboxPollers.get(teamName);
if (poller) {
clearInterval(poller);
this.mailboxPollers.delete(teamName);
}
// Remove team state
this.activeTeams.delete(teamName);
// Cleanup environment
if (process.env.CLAUDE_CODE_TEAM_NAME === teamName) {
delete process.env.CLAUDE_CODE_TEAM_NAME;
}
this.emit('team:cleanup', { team: teamName });
}
// ==================== UTILITY METHODS ====================
/**
* Get team state
*/
getTeamState(teamName: string): TeamState | undefined {
return this.activeTeams.get(teamName);
}
/**
* Get all active teams
*/
getAllTeams(): Map<string, TeamState> {
return new Map(this.activeTeams);
}
// ==================== PRIVATE METHODS ====================
private ensureAvailable(): void {
if (!this.teammateToolAvailable) {
throw new Error(
`TeammateTool not available. Requires Claude Code >= 2.1.19, ` +
`found: ${this.claudeCodeVersion ?? 'not installed'}`
);
}
}
private getMailboxPath(teamName: string, teammateId: string): string {
return `${process.env.HOME}/.claude/teams/${teamName}/mailbox/${teammateId}.json`;
}
private async writeToMailbox(
teamName: string,
teammateId: string,
message: MailboxMessage
): Promise<void> {
const { mkdirSync, readFileSync, writeFileSync, existsSync } = await import('fs');
const mailboxDir = `${process.env.HOME}/.claude/teams/${teamName}/mailbox`;
const mailboxPath = `${mailboxDir}/${teammateId}.json`;
// Ensure directory exists
if (!existsSync(mailboxDir)) {
mkdirSync(mailboxDir, { recursive: true });
}
// Read existing messages
let messages: MailboxMessage[] = [];
if (existsSync(mailboxPath)) {
try {
messages = JSON.parse(readFileSync(mailboxPath, 'utf-8'));
} catch {
messages = [];
}
}
// Append new message
messages.push(message);
// Write back
writeFileSync(mailboxPath, JSON.stringify(messages, null, 2), 'utf-8');
}
private startMailboxPoller(teamName: string): void {
const pollInterval = 1000; // 1 second
const poller = setInterval(async () => {
const team = this.activeTeams.get(teamName);
if (!team) {
clearInterval(poller);
return;
}
for (const teammate of team.teammates) {
const messages = await this.readMailbox(teamName, teammate.id);
if (messages.length > 0) {
this.emit('mailbox:messages', {
team: teamName,
teammateId: teammate.id,
messages,
});
}
}
}, pollInterval);
this.mailboxPollers.set(teamName, poller);
}
}
/**
* Create and initialize a TeammateBridge instance
*/
export async function createTeammateBridge(
config?: Partial<TeamConfig>
): Promise<TeammateBridge> {
const bridge = new TeammateBridge(config);
await bridge.initialize();
return bridge;
}
// claude-flow-integration.ts
import { TeammateBridge, createTeammateBridge } from './teammate-bridge.js';
import type { TeamConfig, TeammateSpawnConfig, TeamState } from './types.js';
/**
* Integration layer between Claude Flow's swarm system
* and Claude Code's native TeammateTool
*/
export class ClaudeFlowTeammateIntegration {
private bridge: TeammateBridge | null = null;
private fallbackEnabled: boolean = true;
/**
* Initialize integration
* Auto-detects TeammateTool availability
*/
async initialize(): Promise<{
nativeAvailable: boolean;
version: string | null;
mode: 'native' | 'fallback';
}> {
this.bridge = await createTeammateBridge();
const nativeAvailable = this.bridge.isAvailable();
const version = this.bridge.getClaudeCodeVersion();
const mode = nativeAvailable ? 'native' : 'fallback';
return { nativeAvailable, version, mode };
}
/**
* Map Claude Flow topology to team configuration
*/
mapTopologyToTeamConfig(
topology: 'hierarchical' | 'mesh' | 'adaptive',
options: {
maxAgents: number;
planModeRequired?: boolean;
}
): TeamConfig {
return {
name: `cf-team-${Date.now()}`,
topology: topology === 'adaptive' ? 'hierarchical' : topology,
maxTeammates: options.maxAgents,
spawnBackend: 'auto',
planModeRequired: options.planModeRequired ?? (topology === 'hierarchical'),
autoApproveJoin: topology === 'mesh',
messageRetention: 3600000, // 1 hour
};
}
/**
* Map Claude Flow agent type to teammate spawn config
*/
mapAgentToTeammateConfig(
agentType: string,
task: string,
options?: {
model?: 'sonnet' | 'opus' | 'haiku';
allowedTools?: string[];
}
): TeammateSpawnConfig {
// Map common Claude Flow agent types to roles
const roleMap: Record<string, { role: string; defaultTools: string[] }> = {
'coder': { role: 'coder', defaultTools: ['Edit', 'Write', 'Read', 'Bash'] },
'tester': { role: 'tester', defaultTools: ['Read', 'Bash', 'Glob'] },
'reviewer': { role: 'reviewer', defaultTools: ['Read', 'Grep', 'Glob'] },
'researcher': { role: 'researcher', defaultTools: ['Read', 'WebSearch', 'WebFetch'] },
'architect': { role: 'architect', defaultTools: ['Read', 'Glob', 'Grep'] },
'security-architect': { role: 'security', defaultTools: ['Read', 'Grep', 'Bash'] },
'hierarchical-coordinator': { role: 'coordinator', defaultTools: ['Read', 'TodoWrite'] },
};
const mapping = roleMap[agentType] ?? { role: agentType, defaultTools: [] };
return {
name: `${agentType}-${Date.now().toString(36)}`,
role: mapping.role,
prompt: task,
model: options?.model,
allowedTools: options?.allowedTools ?? mapping.defaultTools,
mode: agentType.includes('coordinator') ? 'plan' : 'default',
runInBackground: true,
};
}
/**
* Spawn swarm using native TeammateTool
* Returns AgentInput configurations for Claude Code Task tool
*/
async spawnSwarm(
topology: 'hierarchical' | 'mesh' | 'adaptive',
agents: Array<{ type: string; task: string; model?: 'sonnet' | 'opus' | 'haiku' }>,
options?: { planModeRequired?: boolean }
): Promise<{
teamName: string;
agentInputs: Record<string, unknown>[];
teamState: TeamState;
}> {
if (!this.bridge?.isAvailable()) {
throw new Error('TeammateTool not available');
}
// Create team
const teamConfig = this.mapTopologyToTeamConfig(topology, {
maxAgents: agents.length,
planModeRequired: options?.planModeRequired,
});
const teamState = await this.bridge.spawnTeam(teamConfig);
// Build AgentInput for each agent
const agentInputs = agents.map(agent => {
const config = this.mapAgentToTeammateConfig(agent.type, agent.task, {
model: agent.model,
});
config.teamName = teamConfig.name;
return this.bridge!.buildAgentInput(config);
});
return {
teamName: teamConfig.name,
agentInputs,
teamState,
};
}
/**
* Get bridge for direct access
*/
getBridge(): TeammateBridge | null {
return this.bridge;
}
}
The plugin provides 16 MCP tools for complete TeammateTool integration:
| Tool | Purpose | Priority |
|---|---|---|
teammate_spawn_team | Create new team | Core |
teammate_discover_teams | Find existing teams | Core |
teammate_spawn | Spawn teammate | Core |
teammate_send_message | Direct messaging | Core |
teammate_broadcast | Broadcast to all | Core |
teammate_submit_plan | Submit plan for approval | Core |
teammate_approve_plan | Vote to approve | Core |
teammate_launch_swarm | Execute approved plan | Core |
teammate_delegate | Delegate authority | Extended |
teammate_update_context | Update team context | Extended |
teammate_save_memory | Persist teammate state | Extended |
teammate_share_transcript | Share message history | Extended |
teammate_push_remote | Sync to Claude.ai | Extended |
teammate_teleport | Resume in new context | Extended |
teammate_get_status | Get team status | Utility |
teammate_cleanup | Clean up resources | Utility |
// mcp-tools.ts - Complete implementation in v3/@claude-flow/teammate-plugin/src/mcp-tools.ts
import { TEAMMATE_MCP_TOOLS, handleMCPTool } from '@claude-flow/teammate-plugin';
// List all tools
console.log(TEAMMATE_MCP_TOOLS.map(t => t.name));
// Handle tool call
const result = await handleMCPTool(bridge, 'teammate_spawn_team', {
name: 'my-team',
topology: 'hierarchical',
maxTeammates: 8,
planModeRequired: true,
});
// In Claude Code conversation:
// 1. Initialize team via MCP
mcp__claude-flow__teammate_spawn_team({
name: "feature-dev-team",
topology: "hierarchical",
maxTeammates: 6,
planModeRequired: true
})
// 2. Spawn coordinator first
Task({
description: "Spawn coordinator",
prompt: "You are the team coordinator...",
subagent_type: "hierarchical-coordinator",
team_name: "feature-dev-team",
mode: "plan",
allowed_tools: ["Read", "TodoWrite", "teammate_*"]
})
// 3. Coordinator spawns workers via plan
// ... coordinator creates plan, gets approvals, launches swarm
┌─────────────────┐ broadcast ┌─────────────────┐
│ Coordinator │ ──────────────────►│ Coder #1 │
│ │ │ │
│ Team: dev-team │ mailbox │ Team: dev-team │
│ Role: coord │ ◄────────────────► │ Role: coder │
└─────────────────┘ └─────────────────┘
│ │
│ write │
│ ◄────────────────────────────────────┤
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Memory/HNSW │ │ Tester #1 │
│ (sync point) │ │ │
└─────────────────┘ └─────────────────┘
1. Coordinator submits plan
└─► teammate_submit_plan()
2. Plan broadcast to all teammates
└─► broadcast(type: 'plan')
3. Teammates vote
└─► teammate_approve_plan() / teammate_reject_plan()
4. On approval threshold met
└─► teammate_launch_swarm()
5. Swarm executes plan steps
└─► Claude Code spawns teammateCount agents
| Concern | Mitigation |
|---|---|
| Tool escalation | Explicit allowed_tools per teammate |
| Plan tampering | Approval workflow with quorum |
| Mailbox poisoning | Validate sender in team membership |
| Resource exhaustion | maxTeammates limit per team |
// Teammates inherit sandbox restrictions
const teammateConfig: TeammateSpawnConfig = {
// ...
mode: 'default', // Respects sandbox
// OR
mode: 'bypassPermissions', // Only for trusted scenarios
};
MINIMUM_CLAUDE_CODE_VERSION = '2.1.19')flat, hierarchical, mesh)fallbackToMCP option)launchSwarm)saveTeammateMemory, loadTeammateMemory)delegateToTeammate, revokeDelegation)pushToRemote, pullFromRemote, syncWithRemote)saveTeammateMemory, loadTeammateMemory)updateTeamContext, getTeamContext)teleportTeam, canTeleport)pausePlanExecution, resumePlanExecution, reenterPlanMode)shareTranscript)TeammateError, 18 error codes)// Before: Pure MCP coordination
mcp__claude-flow__swarm_init({ topology: 'hierarchical' })
// After: Native when available, MCP fallback
const integration = new ClaudeFlowTeammateIntegration();
const { mode } = await integration.initialize();
if (mode === 'native') {
// Use TeammateTool
const { agentInputs } = await integration.spawnSwarm('hierarchical', agents);
// Pass agentInputs to Task tool
} else {
// Fallback to MCP
mcp__claude-flow__swarm_init({ topology: 'hierarchical' })
}
| Metric | Target |
|---|---|
| Spawn latency | < 500ms (vs MCP ~2s) |
| Message delivery | < 100ms |
| Plan approval cycle | < 5s for 4 agents |
| Memory overhead | < 50MB per team |
| Code reduction | -3000 lines (vs MCP impl) |
pushToRemote work for Claude.ai integration?sdk-tools.d.ts AgentInput/ExitPlanModeInput schemasStatus: Implemented ✅
Package: @claude-flow/teammate-plugin (v1.0.0-alpha.1)
Location: v3/@claude-flow/teammate-plugin/
npm publish --tag alpha from package directory