# M5 Multi-Tool Coordination Patterns
## Overview
M5 (Multi-Tool Orchestration) enables multiple tools to coordinate through shared conversation context with per-tool permission scoping. This document describes the patterns, best practices, and API for building coordinated multi-tool workflows.
## Architecture
### Core Components
1. **Tool Registry** (`src/core/tool-registry.ts`)
- Dynamic discovery from `dist/tools/` directory
- Capability-based metadata caching
- Hot-reload support for development
2. **Intent Router** (`src/core/intent-router.ts`)
- Routes actions to tools based on capability matching
- Returns routing confidence scores (0.0-1.0)
- Supports explicit tool override for testing
3. **Shared Context** (`src/core/shared-context.ts`)
- Cross-tool resource coordination
- Resource ownership tracking (createdBy, timestamps)
- Conversation-scoped resource namespace
4. **Scoped Permissions** (`src/core/conversation-migration.ts`)
- Per-tool permission levels (independent)
- Backward compatible with M4 global permissions
- Three levels: 1=read, 2=write, 3=execute
## Coordination Patterns
### Pattern 1: Resource Producer-Consumer
**Use Case:** One tool creates data, another tool reads and processes it.
**Example:**
```typescript
// data-tool creates a resource
await manager.negotiate(conversationId, 'create-resource', {
name: 'user-data',
data: { userId: 123, name: 'Alice' }
});
// analytics-tool reads and processes the resource
await manager.negotiate(conversationId, 'read-resource', {
name: 'user-data'
});
```
**Key Points:**
- Producer tool needs write permission (level 2)
- Consumer tool only needs read permission (level 1)
- Resources persist in conversation state
### Pattern 2: Pipeline Coordination
**Use Case:** Multiple tools process data sequentially, each adding their contribution.
**Example:**
```typescript
// Step 1: data-tool creates initial data
await manager.negotiate(conversationId, 'create-resource', {
name: 'pipeline-data',
data: { raw: [...] }
});
// Step 2: transform-tool enriches the data
await manager.negotiate(conversationId, 'update-resource', {
name: 'pipeline-data',
data: { raw: [...], transformed: [...] }
});
// Step 3: export-tool reads final data
await manager.negotiate(conversationId, 'read-resource', {
name: 'pipeline-data'
});
```
**Key Points:**
- Each tool updates the same resource
- Resource metadata tracks creation and updates
- Pipeline coordination emerges from shared context
### Pattern 3: Capability Composition
**Use Case:** Complex tasks requiring multiple specialized tool capabilities.
**Example:**
```typescript
// Let intent router discover appropriate tools
await manager.negotiate(conversationId, 'greet'); // → example-tool
await manager.negotiate(conversationId, 'create-resource'); // → data-tool
await manager.negotiate(conversationId, 'list-resources'); // → data-tool
```
**Key Points:**
- No explicit tool selection needed
- Router matches capabilities to actions
- Emergent multi-tool workflows
### Pattern 4: Permission Isolation
**Use Case:** Tools with different security requirements operating independently.
**Example:**
```typescript
// Upgrade only data-tool to write level
await manager.negotiate(conversationId, 'upgrade:data-tool:level-2');
// data-tool can now write
await manager.negotiate(conversationId, 'create-resource', {...});
// example-tool still at read level (independent)
await manager.negotiate(conversationId, 'greet'); // Works without upgrade
```
**Key Points:**
- Each tool has independent permission level
- Principle of least privilege
- Fine-grained security control
## API Reference
### ConversationManager.negotiate()
**Signature (M5):**
```typescript
async negotiate(
conversationId: string,
action: string,
args?: any,
explicitTool?: string
): Promise<NegotiationResult>
```
**Parameters:**
- `conversationId`: Unique conversation identifier
- `action`: Action to execute (e.g., 'greet', 'create-resource', 'list-tools')
- `args`: Optional action-specific arguments
- `explicitTool`: Optional tool override (bypasses routing)
**Special Actions:**
- `list-tools`: Returns all registered tools and their capabilities
- `upgrade:tool-name:level-N`: Upgrades specific tool permission level
**Returns:**
```typescript
interface NegotiationResult {
success: boolean;
output?: any;
error?: string;
requiresApproval?: boolean;
approvalReason?: string;
alignment?: 'aligned' | 'agnostic' | 'contradiction';
}
```
### SharedContext API
**Creating Resources:**
```typescript
createResource(name: string, data: any, toolName: string): void
```
**Reading Resources:**
```typescript
getResource(name: string): Resource | undefined
```
**Updating Resources:**
```typescript
updateResource(name: string, data: any, toolName: string): boolean
```
**Listing Resources:**
```typescript
listResources(): Resource[]
```
**Resource Schema:**
```typescript
interface Resource {
name: string;
data: any;
createdBy: string; // tool name
createdAt: number; // timestamp
updatedAt: number; // timestamp
}
```
### Tool Implementation Guide
**Minimal Tool Structure:**
```typescript
import { BaseTool, type ToolContext, type ToolResult } from '../core/tool-interface.js';
class MyTool extends BaseTool {
constructor() {
super({
name: 'my-tool',
version: '1.0.0',
capabilities: ['action1', 'action2', 'action3'],
});
}
async execute(action: string, context: ToolContext): Promise<ToolResult> {
// Access shared context
const sharedContext = context.sharedContext;
// Access tool name for resource creation
const toolName = context.toolName;
// Access action arguments
const args = context.args;
switch (action) {
case 'action1':
return this.handleAction1(sharedContext, args, toolName);
default:
return { success: false, error: `Unknown action: ${action}` };
}
}
private handleAction1(context: SharedContext, args: any, toolName: string): ToolResult {
// Create or read resources
if (!args.name) {
return { success: false, error: 'Required parameter: name' };
}
context.createResource(args.name, args.data, toolName);
return {
success: true,
output: { message: `Resource created by ${toolName}` }
};
}
}
// Export with static identity
const MyToolClass: ToolClass = Object.assign(MyTool, {
identity: {
name: 'my-tool',
version: '1.0.0',
capabilities: ['action1', 'action2', 'action3'],
}
});
export default MyToolClass;
```
## Permission Levels
| Level | Name | Capabilities |
|-------|---------|-------------------------------------------|
| 1 | Read | Read resources, query data |
| 2 | Write | Create/update resources, modify state |
| 3 | Execute | Execute commands, system operations |
**Alignment Mapping:**
- `aligned` actions: Level 1 (read)
- `agnostic` actions: Level 2 (write)
- `contradiction` actions: Denied (never execute)
## Best Practices
### 1. Tool Capability Design
**Do:**
- Use descriptive capability names (`create-user`, `send-email`)
- Group related actions in single tool
- Keep capabilities atomic and focused
**Don't:**
- Use generic capability names (`do-thing`, `process`)
- Mix unrelated capabilities in one tool
- Create capability name collisions across tools
### 2. Resource Naming
**Do:**
- Use hierarchical names (`user:123`, `session:abc:data`)
- Include type prefix (`config:api-key`, `cache:results`)
- Keep names URL-safe (alphanumeric + `:-_`)
**Don't:**
- Use generic names (`data`, `temp`, `result`)
- Include sensitive data in resource names
- Use special characters that need escaping
### 3. Permission Requests
**Do:**
- Request minimum required level
- Document permission requirements in tool docs
- Provide clear upgrade messages
**Don't:**
- Request execute level unless truly needed
- Assume permission without checking
- Skip permission validation
### 4. Error Handling
**Do:**
- Return descriptive error messages
- Check resource existence before reading
- Validate input parameters
**Don't:**
- Expose internal implementation details in errors
- Fail silently on permission denial
- Return stack traces to users
## Testing Multi-Tool Workflows
**Example Test Pattern:**
```typescript
import { ConversationManager } from './dist/core/conversation-manager.js';
import { ConversationStore } from './dist/core/conversation-store.js';
async function testMultiToolWorkflow() {
// Use in-memory store for testing
const store = new ConversationStore({ dbPath: ':memory:' });
const manager = new ConversationManager(store);
// Wait for tools to load
await manager.waitForToolsLoaded();
const conversationId = 'test-conversation';
// Upgrade permissions
await manager.negotiate(conversationId, 'upgrade:data-tool:level-2', {});
// Execute coordinated workflow
const result1 = await manager.negotiate(conversationId, 'create-resource', {
name: 'test-data',
data: { value: 42 }
});
const result2 = await manager.negotiate(conversationId, 'read-resource', {
name: 'test-data'
});
// Verify results
assert(result1.success);
assert(result2.success);
assert(result2.output.data.value === 42);
}
```
## Migration from M4
### Code Changes
**Before (M4):**
```typescript
const result = await manager.negotiate(
conversationId,
ExampleToolClass, // ← Tool class passed explicitly
action,
args
);
```
**After (M5):**
```typescript
const result = await manager.negotiate(
conversationId,
action, // ← Tool discovered via routing
args,
explicitTool // ← Optional override
);
```
### Permission Migration
M4 conversations with `currentLevel` automatically migrate:
```sql
-- M4 format
{
"currentLevel": 2,
"identity": {"toolName": "example-tool"}
}
-- M5 format (after migration)
{
"currentLevel": 2, -- Kept for fallback
"toolPermissions": {
"example-tool": {"level": 2, "upgradedAt": 1234567890}
},
"sharedContext": {"resources": []}
}
```
## Troubleshooting
### "No tool found for action: X"
**Cause:** No registered tool declares the action as a capability.
**Solution:**
1. Run `list-tools` to see available capabilities
2. Check tool's capabilities array in constructor
3. Rebuild tools: `npm run build`
4. Verify tools loaded: Check server logs for "Loaded: tool-name"
### "Permission upgrade required"
**Cause:** Action requires higher permission level than currently granted.
**Solution:**
```typescript
// Upgrade tool to required level
await manager.negotiate(
conversationId,
'upgrade:tool-name:level-2',
{}
);
```
### "Resource not found"
**Cause:** Resource doesn't exist in shared context.
**Solution:**
1. Check resource name spelling
2. Ensure creating tool completed successfully
3. Use `list-resources` to see all resources
4. Verify conversation ID matches
### "SharedContext not available"
**Cause:** Tool context missing sharedContext field.
**Solution:**
- Update ConversationManager to M5 version
- Ensure negotiate() passes sharedContext in ToolContext
- Check tool's execute() method receives context parameter
## Performance Considerations
### Tool Loading
- Tools loaded once on startup (cached)
- Hot-reload supported during development
- Production: Pre-build and deploy tools
### Resource Storage
- Resources stored in conversation state (JSON)
- Persisted to database on each update
- Consider resource size (< 10KB recommended)
### Routing Performance
- Capability matching is O(n) where n = tool count
- Exact matches cached
- Typical overhead: < 1ms for 10 tools
## Security Notes
### Resource Isolation
- Resources scoped to conversation
- No cross-conversation access
- Tool name recorded for audit trail
### Permission Enforcement
- Checked before every tool execution
- Per-tool granularity
- Immutable permission history
### Input Validation
- Tools responsible for validating args
- Resource names sanitized
- JSON injection prevented
## Future Enhancements
### M6 Roadmap
- **Resource Transactions**: Atomic multi-resource updates
- **Event Subscriptions**: Tools notified of resource changes
- **Resource TTL**: Automatic cleanup of old resources
- **Query Language**: Advanced resource filtering
- **Tool Dependencies**: Explicit tool coordination contracts
---
## Quick Reference Card
```typescript
// List available tools
await manager.negotiate(conversationId, 'list-tools', {});
// Upgrade tool permission
await manager.negotiate(conversationId, 'upgrade:tool-name:level-2', {});
// Create resource
await manager.negotiate(conversationId, 'create-resource', {
name: 'resource-name',
data: { key: 'value' }
});
// Read resource
await manager.negotiate(conversationId, 'read-resource', {
name: 'resource-name'
});
// Update resource
await manager.negotiate(conversationId, 'update-resource', {
name: 'resource-name',
data: { updated: true }
});
// List resources
await manager.negotiate(conversationId, 'list-resources', {});
```
**Permission Levels:**
- Level 1: Read-only
- Level 2: Read + Write
- Level 3: Read + Write + Execute
**Alignment Actions:**
- `aligned` → Level 1
- `agnostic` → Level 2
- `contradiction` → Denied