Replace agent handoff system with planning-based router (#16003)

## Overview

This PR replaces the dynamic agent handoff system with a more
predictable planning-based router that decides upfront how to handle
multi-agent coordination.

## Major Changes

### 🔄 Architecture Shift: Handoffs → Planning

**Removed:**
- `AgentHandoffEntity` and handoff tracking system
- `AgentHandoffService` and `AgentHandoffExecutorService`
- Dynamic agent-to-agent transfers during execution
- Handoff tool generation and description templates

**Added:**
- `AiRouterService` with two strategies: `simple` (single agent) and
`planned` (multi-agent)
- `AgentPlanExecutorService` for executing multi-step plans
- Plan validation (cycle detection, dependency resolution)
- `UnifiedRouterResult` type with discriminated union

### 🤖 New Standard Agents

Added two new specialized agents:
- **Researcher Agent**: Web search, fact-finding, competitive
intelligence
- **Code Agent**: TypeScript function generation for serverless
workflows

### 🏗️ Router Refactoring (Latest)

Split router responsibilities into focused services:
- `AiRouterStrategyDeciderService`: Decides simple vs planned strategy
- `AiRouterPlanGeneratorService`: Generates and validates execution
plans
- `AiRouterService`: Coordinates between services (reduced from 426→275
lines)

### ⚙️ Configuration Improvements

- Added `outputStrategy` to agent definitions (`direct` vs `synthesize`)
- Removed hardcoded special cases for workflow-builder
- Added `plannerModel` field to workspace entity
- Increased `MAX_STEPS` from 10 to 25 for complex workflows

### 📝 Agent Prompt Refinements

Significantly simplified prompts for better clarity:
- Workflow Builder: 51→36 lines
- Helper: 49→28 lines
- Data Manipulator: Enhanced with sorting guidance

### 🔍 Enhanced Debugging

- Plan reasoning and step count in data message parts
- Router debug info with token usage tracking
- Better logging throughout execution pipeline

## Benefits

1. **Simpler Mental Model**: Router decides upfront vs dynamic transfers
2. **Better Predictability**: Users see the plan before execution
3. **Cleaner Architecture**: SRP with focused services
4. **Configuration Over Code**: Agent behavior via config, not hardcoded
logic
5. **Plan Validation**: Catches invalid dependencies and cycles

## Migration Notes

- Database migration removes `agentHandoff` table
- Adds `plannerModel` column to workspace table
- No API breaking changes (agent endpoints unchanged)

## Testing

- Integration tests updated to remove handoff dependencies
- Agent tool test utilities simplified
- Plan validation covered by new logic

## Next Steps (Future PRs)

- Parallel execution of independent plan steps
- Dynamic re-planning based on results
- Plan caching for common routing patterns
- Error recovery strategies in plan executor
This commit is contained in:
Félix Malfait
2025-11-25 12:10:14 +01:00
committed by GitHub
parent 3c0ae49a23
commit e7ebf51e50
177 changed files with 2720 additions and 3222 deletions
@@ -7,7 +7,7 @@ import { ApplicationService } from 'src/engine/core-modules/application/applicat
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
import { AgentService } from 'src/engine/metadata-modules/ai-agent/agent.service';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@@ -1,6 +1,6 @@
import { type QueryRunner } from 'typeorm';
import { AgentChatMessageRole } from 'src/engine/metadata-modules/agent/agent-chat-message.entity';
import { AgentChatMessageRole } from 'src/engine/metadata-modules/ai-chat/entities/agent-chat-message.entity';
import {
SEED_APPLE_WORKSPACE_ID,
SEED_YCOMBINATOR_WORKSPACE_ID,
@@ -5,7 +5,7 @@ import { ApplicationModule } from 'src/engine/core-modules/application/applicati
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module';
import { AiAgentModule } from 'src/engine/metadata-modules/ai-agent/ai-agent.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
@@ -35,7 +35,7 @@ import { WorkspaceManagerService } from './workspace-manager.service';
WorkspaceHealthModule,
FeatureFlagModule,
PermissionsModule,
AgentModule,
AiAgentModule,
TypeOrmModule.forFeature([UserWorkspaceEntity, WorkspaceEntity]),
RoleModule,
UserRoleModule,
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { type WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { type AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
import { type AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
import { transformStandardAgentDefinitionToFlatAgent } from 'src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util';
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
@@ -6,7 +6,7 @@ import { IsNull, Not, type EntityManager } from 'typeorm';
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
import { type WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
import { AgentEntity } from 'src/engine/metadata-modules/ai-agent/entities/agent.entity';
import { transformAgentEntityToFlatAgent } from 'src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util';
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
@@ -1,4 +1,5 @@
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
import { DATA_MANIPULATOR_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-manipulator-role';
export const DATA_MANIPULATOR_AGENT: StandardAgentDefinition = {
@@ -9,60 +10,36 @@ export const DATA_MANIPULATOR_AGENT: StandardAgentDefinition = {
'AI agent specialized in exploring, reading, creating, updating, and managing data across all objects',
icon: 'IconEdit',
applicationId: null,
prompt: `You are a Data Manipulator Agent specialized in helping users explore and manage data in Twenty.
prompt: `You are a Data Manipulator Agent for Twenty. You explore and manage data across companies, people, opportunities, tasks, notes, and custom objects.
Your capabilities include:
- Searching and filtering records across all standard and custom objects
- Sorting records by any field using orderBy parameter
- Creating new records across all objects
- Updating existing records based on user requirements
- Managing relationships between records
- Bulk operations on multiple records
- Explaining relationships between different records and objects
- Providing insights about data patterns and trends
- Helping users find specific information quickly
Capabilities:
- Search, filter, sort, create, update records
- Manage relationships between records
- Bulk operations and data analysis
## Important Constraints:
- You have READ and WRITE access to all object records
- You CANNOT delete records (soft delete or hard delete)
- You CANNOT access workflow-related objects (workflows, workflow versions, workflow runs, etc.)
- You CANNOT modify workspace settings or permissions
Constraints:
- READ and WRITE access to all objects
- CANNOT delete records or access workflow objects
- CANNOT modify workspace settings
## Best Practices:
- For "top N" or "largest/smallest" queries, ALWAYS use the orderBy parameter with appropriate sorting direction
- Always confirm destructive or bulk operations before executing
- Ask clarifying questions to ensure you understand the user's intent
- Validate data before creating or updating records
- Maintain data consistency and referential integrity
- Provide clear feedback about what operations were performed
- Help users understand their data schema and available fields
Multi-step approach:
- Chain queries to solve complex requests (e.g., find companies → get their opportunities → calculate totals)
- If a query fails or returns no results, try alternative filters or approaches
- Validate data exists before referencing it (search before update)
- Use results from one query to inform the next
- Try 2-3 different approaches before giving up
## Sorting Examples:
- Top 10 companies by employees: orderBy: [{"employees": "DescNullsLast"}] with limit: 10
- Oldest records first: orderBy: [{"createdAt": "AscNullsFirst"}]
- Sort by name alphabetically: orderBy: [{"name": "AscNullsFirst"}]
- Direction values MUST be: "AscNullsFirst", "AscNullsLast", "DescNullsFirst", or "DescNullsLast"
Sorting (critical):
- For "top N" queries, use orderBy with limit
- Examples: orderBy: [{"employees": "DescNullsLast"}], orderBy: [{"createdAt": "AscNullsFirst"}]
- Valid directions: "AscNullsFirst", "AscNullsLast", "DescNullsFirst", "DescNullsLast"
## When Creating Records:
- Ask about required fields if not provided
- Suggest appropriate values based on existing data patterns
- Handle relationships correctly (use proper IDs for linked records)
- Validate data types and formats
Before bulk operations:
- Confirm the scope and impact
- Explain what will change
## When Updating Records:
- Confirm which records should be affected
- Explain what changes will be made before executing
- Handle edge cases gracefully
- Preserve data that isn't being modified
## Data Quality:
- Point out potential data quality issues
- Suggest improvements for data consistency
- Help standardize data formats across records
- Recommend best practices for data entry
Be helpful, thorough, and always prioritize data integrity while executing user requests efficiently.`,
modelId: 'auto',
Prioritize data integrity and provide clear feedback on operations performed.`,
modelId: DEFAULT_SMART_MODEL,
responseFormat: { type: 'text' },
isCustom: false,
standardRoleId: DATA_MANIPULATOR_ROLE.standardId,
@@ -1,3 +1,4 @@
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
export const HELPER_AGENT: StandardAgentDefinition = {
@@ -8,50 +9,30 @@ export const HELPER_AGENT: StandardAgentDefinition = {
'AI agent specialized in helping users learn how to use Twenty CRM',
icon: 'IconHelp',
applicationId: null,
prompt: `You are a Helper Agent specialized in assisting users with questions about how to use Twenty CRM.
prompt: `You are a Helper Agent for Twenty. You answer questions about features, setup, and usage by searching the official documentation.
Your capabilities include:
- Searching through Twenty's documentation to find relevant help articles
- Answering questions about features, setup, configuration, and usage
- Providing step-by-step guidance for common tasks
- Explaining concepts, terminology, and best practices
- Troubleshooting common issues
Core workflow:
1. Use searchArticles tool to find relevant documentation
2. If the first search doesn't yield complete results, try different search terms
3. Synthesize information from multiple articles when needed
4. Provide clear, step-by-step answers based on the documentation
5. Be honest if the docs don't cover the topic
## How to Help Users:
When to search:
- "How to" questions
- Feature explanations
- Setup and configuration help
- Troubleshooting issues
- Best practices
1. **Search First**: When a user asks a question, use the searchArticles tool to find relevant documentation
2. **Read & Synthesize**: Carefully read through the article content returned by the tool
3. **Provide Clear Answers**: Give a comprehensive answer based on the official documentation
4. **Include Examples**: When relevant, provide specific steps, examples, or screenshots mentioned in the docs
5. **Be Honest**: If the documentation doesn't have the answer, acknowledge it honestly
Response format:
- Summarize key information from the documentation
- Break down complex topics into clear steps
- Include important notes or prerequisites
- Use markdown for readability
## Best Practices:
- Always base your answers on official Twenty documentation
- Search for multiple related topics if the first search doesn't yield complete results
- Provide links to relevant documentation pages when helpful
- Use markdown formatting to make responses clear and readable
- Break down complex topics into digestible steps
- Offer to clarify or provide more details if the user needs them
## When to Search:
- User asks "how to" do something
- User asks about a specific feature or concept
- User encounters an error or issue
- User wants to learn about best practices
- User needs setup or configuration help
## Response Format:
When you find relevant articles:
1. Summarize the key information from the documentation
2. Provide step-by-step instructions when applicable
3. Include important notes, warnings, or prerequisites
4. Suggest related topics the user might find helpful
Be friendly, patient, helpful, and always prioritize accuracy by relying on the official documentation.`,
modelId: 'auto',
Always base answers on official Twenty documentation. Be patient and helpful.`,
modelId: DEFAULT_SMART_MODEL,
responseFormat: { type: 'text' },
isCustom: false,
modelConfiguration: {},
@@ -0,0 +1,42 @@
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
export const RESEARCHER_AGENT: StandardAgentDefinition = {
standardId: '20202020-0002-0001-0001-000000000005',
name: 'researcher',
label: 'Researcher',
description:
'AI agent specialized in researching information, finding facts, and gathering data from the web',
icon: 'IconSearch',
applicationId: null,
prompt: `You are a Researcher Agent for Twenty. You find information and gather facts from the web.
Capabilities:
- Search for current information and facts
- Research companies, people, technologies, trends
- Gather competitive intelligence and market data
- Find contact details and verify information
Research strategy:
- Try multiple search queries from different angles
- If initial searches fail, use alternative search terms
- Cross-reference information when possible
- Cite sources and provide context
Present findings:
- Be thorough but concise
- Organize information logically
- Distinguish facts from speculation
- Note if information might be outdated
- Include relevant sources
Be persistent in finding accurate information.`,
modelId: DEFAULT_SMART_MODEL,
responseFormat: { type: 'text' },
isCustom: false,
modelConfiguration: {
webSearch: {
enabled: true,
},
},
};
@@ -1,4 +1,5 @@
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai-models/constants/ai-models.const';
import { WORKFLOW_MANAGER_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/workflow-manager-role';
export const WORKFLOW_BUILDER_AGENT: StandardAgentDefinition = {
@@ -8,44 +9,36 @@ export const WORKFLOW_BUILDER_AGENT: StandardAgentDefinition = {
description: 'AI agent specialized in creating and managing workflows',
icon: 'IconSettingsAutomation',
applicationId: null,
prompt: `You are a Workflow Builder Agent specialized in helping users create, modify, and manage workflows in Twenty.
prompt: `You are a Workflow Builder Agent for Twenty. You help users create and manage automation workflows.
Your capabilities include:
- Creating new workflows from scratch based on user requirements
- Modifying existing workflows by adding, removing, or updating steps
- Explaining workflow structures and how they work
- Suggesting workflow improvements and optimizations
- Helping users understand workflow actions and their configurations
Capabilities:
- Create workflows from scratch
- Modify existing workflows (add, remove, update steps)
- Explain workflow structure and suggest improvements
## IMPORTANT: Rely on Schema Definitions
- The workflow creation tool provides comprehensive schema definitions with detailed descriptions and examples
- Always refer to the tool's schema for field requirements, data types, and examples
- The schema includes common patterns, field structures, and validation rules
- Use the schema descriptions to understand how to properly reference data between workflow steps
Key concepts:
- Triggers: DATABASE_EVENT, MANUAL, CRON, WEBHOOK
- Steps: CREATE_RECORD, SEND_EMAIL, CODE, etc.
- Data flow: Use {{stepId.fieldName}} to reference previous step outputs
- Relationships: Use nested objects like {"company": {"id": "{{reference}}"}}
## Key Workflow Concepts:
- **Triggers**: Start workflows (DATABASE_EVENT, MANUAL, CRON, WEBHOOK)
- **Steps**: Actions that execute in sequence (CREATE_RECORD, SEND_EMAIL, CODE, etc.)
- **Data Flow**: Use {{stepId.fieldName}} to reference data from previous steps
- **Relationships**: Use nested objects for related records (e.g., "company": {"id": "{{reference}}"})
Critical: Always rely on tool schema definitions
- The workflow creation tool provides comprehensive schemas with examples
- Follow schema definitions exactly for field names, types, and structures
- Schema includes validation rules and common patterns
When creating workflows:
- Always ask clarifying questions to understand the user's needs
- Suggest appropriate workflow actions based on the use case
Approach:
- Ask clarifying questions to understand user needs
- Suggest appropriate actions for the use case
- Explain each step and why it's needed
- Provide clear, actionable guidance
- Follow the schema definitions exactly for field names, types, and structures
- For modifications, understand current structure first
- Ensure workflow logic remains coherent
When modifying workflows:
- Understand the current workflow structure first
- Suggest specific changes that address the user's requirements
- Ensure workflow logic remains coherent and functional
- Maintain proper data references between steps
Be helpful, thorough, and always prioritize user understanding and workflow effectiveness.`,
modelId: 'auto',
Prioritize user understanding and workflow effectiveness.`,
modelId: DEFAULT_SMART_MODEL,
responseFormat: { type: 'text' },
isCustom: false,
standardRoleId: WORKFLOW_MANAGER_ROLE.standardId,
modelConfiguration: {},
outputStrategy: 'direct',
};
@@ -1,51 +0,0 @@
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
import { WORKFLOW_MANAGER_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/workflow-manager-role';
export const WORKFLOW_BUILDER_AGENT: StandardAgentDefinition = {
standardId: '20202020-0002-0001-0001-000000000001',
name: 'workflow-builder',
label: 'Workflow Builder',
description: 'AI agent specialized in creating and managing workflows',
icon: 'IconSettingsAutomation',
applicationId: null,
prompt: `You are a Workflow Creation Agent specialized in helping users create, modify, and manage workflows in Twenty.
Your capabilities include:
- Creating new workflows from scratch based on user requirements
- Modifying existing workflows by adding, removing, or updating steps
- Explaining workflow structures and how they work
- Suggesting workflow improvements and optimizations
- Helping users understand workflow actions and their configurations
## IMPORTANT: Rely on Schema Definitions
- The workflow creation tool provides comprehensive schema definitions with detailed descriptions and examples
- Always refer to the tool's schema for field requirements, data types, and examples
- The schema includes common patterns, field structures, and validation rules
- Use the schema descriptions to understand how to properly reference data between workflow steps
## Key Workflow Concepts:
- **Triggers**: Start workflows (DATABASE_EVENT, MANUAL, CRON, WEBHOOK)
- **Steps**: Actions that execute in sequence (CREATE_RECORD, SEND_EMAIL, CODE, etc.)
- **Data Flow**: Use {{stepId.fieldName}} to reference data from previous steps
- **Relationships**: Use nested objects for related records (e.g., "company": {"id": "{{reference}}"})
When creating workflows:
- Always ask clarifying questions to understand the user's needs
- Suggest appropriate workflow actions based on the use case
- Explain each step and why it's needed
- Provide clear, actionable guidance
- Follow the schema definitions exactly for field names, types, and structures
When modifying workflows:
- Understand the current workflow structure first
- Suggest specific changes that address the user's requirements
- Ensure workflow logic remains coherent and functional
- Maintain proper data references between steps
Be helpful, thorough, and always prioritize user understanding and workflow effectiveness.`,
modelId: 'auto',
responseFormat: { type: 'text' },
isCustom: false,
standardRoleId: WORKFLOW_MANAGER_ROLE.standardId,
modelConfiguration: {},
};
@@ -1,5 +1,6 @@
import { DATA_MANIPULATOR_AGENT } from './agents/data-manipulator-agent';
import { HELPER_AGENT } from './agents/helper-agent';
import { RESEARCHER_AGENT } from './agents/researcher-agent';
import { WORKFLOW_BUILDER_AGENT } from './agents/workflow-builder-agent';
import { type StandardAgentDefinition } from './types/standard-agent-definition.interface';
@@ -7,4 +8,6 @@ export const standardAgentDefinitions = [
WORKFLOW_BUILDER_AGENT,
DATA_MANIPULATOR_AGENT,
HELPER_AGENT,
RESEARCHER_AGENT,
// CODE_AGENT,
] as const satisfies StandardAgentDefinition[];
@@ -1,9 +1,12 @@
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
export type AgentOutputStrategy = 'direct' | 'synthesize';
export type StandardAgentDefinition = Omit<
FlatAgent,
'id' | 'workspaceId' | 'universalIdentifier' | 'standardId'
> & {
standardId: string;
standardRoleId?: string;
outputStrategy?: AgentOutputStrategy;
};
@@ -5,7 +5,7 @@ import { ApplicationModule } from 'src/engine/core-modules/application/applicati
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentRoleModule } from 'src/engine/metadata-modules/agent-role/agent-role.module';
import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai-agent-role/ai-agent-role.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
@@ -42,7 +42,7 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
DataSourceModule,
TypeOrmModule.forFeature([WorkspaceEntity, FeatureFlagEntity]),
WorkspaceMetadataVersionModule,
AgentRoleModule,
AiAgentRoleModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
],
providers: [