@memberjunction/ai-agent-manager 2.112.0 → 2.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { BaseAgent } from '@memberjunction/ai-agents';
2
2
  import { ExecuteAgentParams, AgentConfiguration, BaseAgentNextStep, AgentSpec } from '@memberjunction/ai-core-plus';
3
3
  import { RegisterClass } from '@memberjunction/global';
4
- import { Metadata } from '@memberjunction/global';
4
+ import { Metadata } from '@memberjunction/core';
5
5
  import { AIAgentRunStepEntityExtended } from '@memberjunction/core-entities';
6
6
  import { AgentSpecSync } from '../agent-spec-sync';
7
7
  import { TemplateEngineServer } from '@memberjunction/templates';
@@ -26,128 +26,137 @@ import { AIEngineBase } from '@memberjunction/ai-engine-base';
26
26
  */
27
27
  @RegisterClass(BaseAgent, 'AgentBuilderAgent')
28
28
  export class AgentBuilderAgent extends BaseAgent {
29
- /**
30
- * Override executeAgentInternal to run code instead of chat loop.
31
- *
32
- * Directly creates the AI Agent Run Step with mutations from AgentSpecSync.
33
- */
34
- protected override async executeAgentInternal<P = any>(
35
- params: ExecuteAgentParams<P>,
36
- _config: AgentConfiguration
37
- ): Promise<{ finalStep: BaseAgentNextStep<P>; stepCount: number }> {
38
- console.log('🔨 Builder Agent: Starting agent persistence...');
39
-
40
- try {
41
- // Validate payload
42
- const agentSpec = params.payload as AgentSpec;
43
- if (!agentSpec) {
44
- throw new Error('No AgentSpec found in payload - ensure Architect Agent provided valid AgentSpec');
45
- }
46
- if (!agentSpec.Name) {
47
- throw new Error('AgentSpec is missing required Name field');
48
- }
49
-
50
- console.log(`🔨 Builder Agent: Creating agent "${agentSpec.Name}"...`);
51
-
52
- // Create AgentSpecSync and save to database
53
- const specSync = new AgentSpecSync(agentSpec, params.contextUser);
54
- specSync.markDirty();
55
-
56
- // If updating existing agent (ID exists), mark as loaded so delete logic runs
57
- if (agentSpec.ID && agentSpec.ID !== '') {
58
- specSync.markLoaded();
59
- }
60
-
61
- const result = await specSync.SaveToDatabase();
62
-
63
- if (!result.success) {
64
- throw new Error('AgentSpecSync.SaveToDatabase() returned success=false');
65
- }
66
-
67
- console.log(`✅ Builder Agent: Successfully created agent with ID: ${result.agentId}`);
68
-
69
- // Refresh metadata and template caches
70
- console.log('🔄 Builder Agent: Refreshing metadata and template caches...');
71
- const md = new Metadata();
72
- await md.Refresh();
73
-
74
- const templateEngine = new TemplateEngineServer();
75
- await templateEngine.Config(true, params.contextUser);
76
- console.log('✅ Builder Agent: Metadata and template caches refreshed');
77
-
78
- // Refresh AIEngine cache so newly created agents are immediately available
79
- const aiEngine = AIEngineBase.Instance;
80
- await aiEngine.Config(true, params.contextUser);
81
- console.log('✅ Builder Agent: AIEngine cache refreshed');
82
-
83
- // Create AI Agent Run Step record directly
84
- const agentRunId = params.parentRun?.ID || 'unknown';
85
- const stepEntity = await md.GetEntityObject<AIAgentRunStepEntityExtended>('MJ: AI Agent Run Steps', params.contextUser);
86
-
87
- stepEntity.AgentRunID = agentRunId;
88
- stepEntity.StepNumber = 2; // Validation is step 1, this is step 2
89
- stepEntity.StepType = 'Decision';
90
- stepEntity.StepName = 'Sync Agent Spec';
91
- stepEntity.Status = 'Completed';
92
- stepEntity.Success = true;
93
- stepEntity.StartedAt = new Date();
94
- stepEntity.CompletedAt = new Date();
95
-
96
- // InputData = Full AgentSpec
97
- stepEntity.InputData = JSON.stringify(agentSpec, null, 2);
98
- stepEntity.PayloadAtStart = JSON.stringify(params.payload);
99
-
100
- // OutputData = Mutations array
101
- stepEntity.OutputData = JSON.stringify(result.mutations, null, 2);
102
- stepEntity.PayloadAtEnd = stepEntity.PayloadAtStart;
103
-
104
- await stepEntity.Save();
105
- console.log(`✅ Builder Agent: Created AI Agent Run Step with ID: ${stepEntity.ID}`);
106
-
107
- // Return success
108
- const updatedSpec = { ...agentSpec, ID: result.agentId };
109
- return {
110
- finalStep: {
111
- terminate: true,
112
- step: 'Success',
113
- reasoning: `Successfully created agent "${agentSpec.Name}" with ID: ${result.agentId}`,
114
- newPayload: updatedSpec as P,
115
- },
116
- stepCount: 2,
117
- };
118
- } catch (error: any) {
119
- console.error('❌ Builder Agent: Failed to create agent:', error);
120
-
121
- // Create failed AI Agent Run Step
122
- const agentRunId = params.parentRun?.ID || 'unknown';
123
- const md = new Metadata();
124
- const stepEntity = await md.GetEntityObject<AIAgentRunStepEntityExtended>('MJ: AI Agent Run Steps', params.contextUser);
125
-
126
- stepEntity.AgentRunID = agentRunId;
127
- stepEntity.StepNumber = 2;
128
- stepEntity.StepType = 'Decision';
129
- stepEntity.StepName = 'Sync Agent Spec';
130
- stepEntity.Status = 'Failed';
131
- stepEntity.Success = false;
132
- stepEntity.StartedAt = new Date();
133
- stepEntity.CompletedAt = new Date();
134
- stepEntity.ErrorMessage = error?.message || String(error);
135
-
136
- stepEntity.InputData = JSON.stringify(params.payload, null, 2);
137
- stepEntity.PayloadAtStart = JSON.stringify(params.payload);
138
- stepEntity.OutputData = JSON.stringify({ error: error?.message || String(error) });
139
- stepEntity.PayloadAtEnd = stepEntity.PayloadAtStart;
140
-
141
- await stepEntity.Save();
142
-
143
- return {
144
- finalStep: {
145
- terminate: true,
146
- step: 'Failed',
147
- reasoning: `Failed to create agent: ${error?.message || String(error)}`,
148
- },
149
- stepCount: 2,
150
- };
29
+
30
+ /**
31
+ * Override executeAgentInternal to run code instead of chat loop.
32
+ *
33
+ * Directly creates the AI Agent Run Step with mutations from AgentSpecSync.
34
+ */
35
+ protected override async executeAgentInternal<P = any>(
36
+ params: ExecuteAgentParams<P>,
37
+ _config: AgentConfiguration
38
+ ): Promise<{ finalStep: BaseAgentNextStep<P>; stepCount: number }> {
39
+
40
+ console.log('🔨 Builder Agent: Starting agent persistence...');
41
+
42
+ try {
43
+ // Validate payload
44
+ const agentSpec = params.payload as AgentSpec;
45
+ if (!agentSpec) {
46
+ throw new Error('No AgentSpec found in payload - ensure Architect Agent provided valid AgentSpec');
47
+ }
48
+ if (!agentSpec.Name) {
49
+ throw new Error('AgentSpec is missing required Name field');
50
+ }
51
+
52
+ console.log(`🔨 Builder Agent: Creating agent "${agentSpec.Name}"...`);
53
+
54
+ // Create AgentSpecSync and save to database
55
+ const specSync = new AgentSpecSync(agentSpec, params.contextUser);
56
+ specSync.markDirty();
57
+
58
+ // If updating existing agent (ID exists), mark as loaded so delete logic runs
59
+ if (agentSpec.ID && agentSpec.ID !== '') {
60
+ specSync.markLoaded();
61
+ }
62
+
63
+ const result = await specSync.SaveToDatabase();
64
+
65
+ if (!result.success) {
66
+ throw new Error('AgentSpecSync.SaveToDatabase() returned success=false');
67
+ }
68
+
69
+ console.log(`✅ Builder Agent: Successfully created agent with ID: ${result.agentId}`);
70
+
71
+ // Refresh metadata and template caches
72
+ console.log('🔄 Builder Agent: Refreshing metadata and template caches...');
73
+ const md = new Metadata();
74
+ await md.Refresh();
75
+
76
+ const templateEngine = new TemplateEngineServer();
77
+ await templateEngine.Config(true, params.contextUser);
78
+ console.log('✅ Builder Agent: Metadata and template caches refreshed');
79
+
80
+ // Refresh AIEngine cache so newly created agents are immediately available
81
+ const aiEngine = AIEngineBase.Instance;
82
+ await aiEngine.Config(true, params.contextUser);
83
+ console.log('✅ Builder Agent: AIEngine cache refreshed');
84
+
85
+ // Create AI Agent Run Step record directly
86
+ const agentRunId = params.parentRun?.ID || 'unknown';
87
+ const stepEntity = await md.GetEntityObject<AIAgentRunStepEntityExtended>(
88
+ 'MJ: AI Agent Run Steps',
89
+ params.contextUser
90
+ );
91
+
92
+ stepEntity.AgentRunID = agentRunId;
93
+ stepEntity.StepNumber = 2; // Validation is step 1, this is step 2
94
+ stepEntity.StepType = 'Decision';
95
+ stepEntity.StepName = 'Sync Agent Spec';
96
+ stepEntity.Status = 'Completed';
97
+ stepEntity.Success = true;
98
+ stepEntity.StartedAt = new Date();
99
+ stepEntity.CompletedAt = new Date();
100
+
101
+ // InputData = Full AgentSpec
102
+ stepEntity.InputData = JSON.stringify(agentSpec, null, 2);
103
+ stepEntity.PayloadAtStart = JSON.stringify(params.payload);
104
+
105
+ // OutputData = Mutations array
106
+ stepEntity.OutputData = JSON.stringify(result.mutations, null, 2);
107
+ stepEntity.PayloadAtEnd = stepEntity.PayloadAtStart;
108
+
109
+ await stepEntity.Save();
110
+ console.log(`✅ Builder Agent: Created AI Agent Run Step with ID: ${stepEntity.ID}`);
111
+
112
+ // Return success
113
+ const updatedSpec = { ...agentSpec, ID: result.agentId };
114
+ return {
115
+ finalStep: {
116
+ terminate: true,
117
+ step: 'Success',
118
+ reasoning: `Successfully created agent "${agentSpec.Name}" with ID: ${result.agentId}`,
119
+ newPayload: updatedSpec as P
120
+ },
121
+ stepCount: 2
122
+ };
123
+
124
+ } catch (error: any) {
125
+ console.error('❌ Builder Agent: Failed to create agent:', error);
126
+
127
+ // Create failed AI Agent Run Step
128
+ const agentRunId = params.parentRun?.ID || 'unknown';
129
+ const md = new Metadata();
130
+ const stepEntity = await md.GetEntityObject<AIAgentRunStepEntityExtended>(
131
+ 'MJ: AI Agent Run Steps',
132
+ params.contextUser
133
+ );
134
+
135
+ stepEntity.AgentRunID = agentRunId;
136
+ stepEntity.StepNumber = 2;
137
+ stepEntity.StepType = 'Decision';
138
+ stepEntity.StepName = 'Sync Agent Spec';
139
+ stepEntity.Status = 'Failed';
140
+ stepEntity.Success = false;
141
+ stepEntity.StartedAt = new Date();
142
+ stepEntity.CompletedAt = new Date();
143
+ stepEntity.ErrorMessage = error?.message || String(error);
144
+
145
+ stepEntity.InputData = JSON.stringify(params.payload, null, 2);
146
+ stepEntity.PayloadAtStart = JSON.stringify(params.payload);
147
+ stepEntity.OutputData = JSON.stringify({ error: error?.message || String(error) });
148
+ stepEntity.PayloadAtEnd = stepEntity.PayloadAtStart;
149
+
150
+ await stepEntity.Save();
151
+
152
+ return {
153
+ finalStep: {
154
+ terminate: true,
155
+ step: 'Failed',
156
+ reasoning: `Failed to create agent: ${error?.message || String(error)}`
157
+ },
158
+ stepCount: 2
159
+ };
160
+ }
151
161
  }
152
- }
153
162
  }
@@ -0,0 +1,179 @@
1
+ import { BaseAgent } from '@memberjunction/ai-agents';
2
+ import { ExecuteAgentParams, BaseAgentNextStep } from '@memberjunction/ai-core-plus';
3
+ import { AIAgentRunEntityExtended, AIAgentRunStepEntityExtended, AIAgentEntityExtended } from '@memberjunction/core-entities';
4
+ import { RegisterClass } from '@memberjunction/global';
5
+ import { AIEngine } from '@memberjunction/aiengine';
6
+
7
+ /**
8
+ * Planning Designer Agent - Designs agent architectures by researching existing capabilities
9
+ *
10
+ * This agent creates TechnicalDesign or modificationPlan by researching available
11
+ * actions, agents, and database entities. It overrides validateSuccessNextStep to
12
+ * enforce that required research actions are called before finalizing designs.
13
+ *
14
+ * Key responsibilities:
15
+ * - Force discovery of existing agents before selecting actions
16
+ * - Force discovery of available actions before designing
17
+ * - Force database schema research for CRUD operations
18
+ * - Prevent hallucination of non-existent entities/actions
19
+ * - Ensure designs are research-backed, not assumption-based
20
+ */
21
+ @RegisterClass(BaseAgent, 'AgentPlanningDesigner')
22
+ export class PlanningDesignerAgent extends BaseAgent {
23
+
24
+ protected override async validateSuccessNextStep<P>(
25
+ params: ExecuteAgentParams,
26
+ nextStep: BaseAgentNextStep<P>,
27
+ currentPayload: P,
28
+ agentRun: AIAgentRunEntityExtended,
29
+ currentStep: AIAgentRunStepEntityExtended
30
+ ): Promise<BaseAgentNextStep<P>> {
31
+ // 1. Call base validation first (checks MinExecutionsPerRun, FinalPayloadValidation)
32
+ const baseValidation = await super.validateSuccessNextStep(params, nextStep, currentPayload, agentRun, currentStep);
33
+ if (baseValidation.step === 'Retry') {
34
+ return baseValidation;
35
+ }
36
+
37
+ // 2. Check if required research actions were called
38
+ const researchViolations = await this.checkResearchRequirements(params.agent, agentRun, currentPayload);
39
+
40
+ if (researchViolations.length > 0) {
41
+ console.log('🚫 Planning Designer: Research requirements not met:', researchViolations);
42
+ return {
43
+ ...nextStep,
44
+ step: 'Retry',
45
+ retryInstructions: this.formatResearchViolations(researchViolations)
46
+ };
47
+ }
48
+
49
+ console.log('✅ Planning Designer: All research requirements met');
50
+ return nextStep;
51
+ }
52
+
53
+ /**
54
+ * Checks if Planning Designer called required research actions/agents
55
+ */
56
+ protected async checkResearchRequirements(
57
+ agent: AIAgentEntityExtended,
58
+ agentRun: AIAgentRunEntityExtended,
59
+ currentPayload: any
60
+ ): Promise<string[]> {
61
+ const violations: string[] = [];
62
+
63
+ // Hardcoded IDs for required research capabilities (from metadata)
64
+ const FIND_CANDIDATE_AGENTS_ACTION_ID = 'EF610103-E48D-4D9C-8DA2-1CF5B05F8AB6';
65
+ const FIND_CANDIDATE_ACTIONS_ACTION_ID = 'FD932133-CB1D-4C96-8EFC-14F0C8CE045B';
66
+ const DATABASE_RESEARCH_AGENT_ID = '746CD1E8-CB8D-49A4-BE69-D0F208A0B462';
67
+
68
+ // Get execution counts using hardcoded IDs
69
+ const findAgentsCount = await this.getActionExecutionCount(agentRun.ID, FIND_CANDIDATE_AGENTS_ACTION_ID);
70
+ const findActionsCount = await this.getActionExecutionCount(agentRun.ID, FIND_CANDIDATE_ACTIONS_ACTION_ID);
71
+ const dbResearchCount = await this.getSubAgentExecutionCount(agentRun.ID, DATABASE_RESEARCH_AGENT_ID);
72
+
73
+ // Find Candidate Agents is MANDATORY (at least once)
74
+ if (findAgentsCount === 0) {
75
+ violations.push(`❌ MISSING: "Find Candidate Agents" action has NOT been called
76
+
77
+ **Purpose**: Discovers existing agents that can handle your subtasks as related subagents
78
+ **What it returns**: Agent IDs, names, descriptions, existing actions, and existing subAgents
79
+ **Why critical**: Existing specialized agents eliminate the need for multiple actions - one capable agent can replace several actions. You must search BEFORE selecting actions to avoid duplication.
80
+ **What to do**: Call "Find Candidate Agents" multiple times with different TaskDescriptions for each major subtask. Set ExcludeSubAgents=false to see ALL available agents. Review their actions and subAgents arrays to understand full capabilities.`);
81
+ }
82
+
83
+ // Find Candidate Actions is MANDATORY (at least once)
84
+ if (findActionsCount === 0) {
85
+ violations.push(`❌ MISSING: "Find Candidate Actions" action has NOT been called
86
+
87
+ **Purpose**: Discovers existing actions that can handle specific tasks (only use for tasks NOT covered by existing agents)
88
+ **What it returns**: Action IDs, names, parameters (inputs/outputs), and descriptions
89
+ **Why critical**: You cannot guess or make up action IDs - they must come from actual search results. Actions have specific parameter structures you need to understand.
90
+ **What to do**: Call "Find Candidate Actions" for each task that existing agents cannot handle. Use the exact action IDs and names from results in your design.`);
91
+ }
92
+
93
+ // Database Research Agent is REQUIRED if design mentions CRUD/database operations
94
+ const designText = this.getDesignText(currentPayload);
95
+ if (this.mentionsDatabaseOperations(designText)) {
96
+ if (dbResearchCount === 0) {
97
+ violations.push(`❌ MISSING: "Database Research Agent" sub-agent has NOT been called (but your design mentions database/CRUD operations)
98
+
99
+ **Purpose**: Provides actual entity names, field names, primary keys, data types, and relationships from the database schema
100
+ **What it returns**: Complete entity schema information written to payload.TechnicalDesign.databaseSchema
101
+ **Why critical**: NEVER guess entity or field names - they must come from Database Research Agent. CRUD actions require exact entity names and field names to work correctly.
102
+ **What to do**: Call Database Research Agent with specific questions like "Is there an entity called [NAME] or related to [CONCEPT]? Give me all fields." Review the results and use ONLY those exact entity/field names in your CRUD action designs.`);
103
+ }
104
+ }
105
+
106
+ return violations;
107
+ }
108
+
109
+ /**
110
+ * Extracts design text from payload (FunctionalRequirements, TechnicalDesign, or modificationPlan)
111
+ */
112
+ protected getDesignText(payload: any): string {
113
+ if (!payload) {
114
+ return '';
115
+ }
116
+
117
+ // Check all possible fields that might mention database operations:
118
+ // - FunctionalRequirements (from Requirements Analyst - may mention data storage needs)
119
+ // - TechnicalDesign (creation mode - the design document)
120
+ // - modificationPlan (modification mode - the change plan)
121
+ const functionalRequirements = payload.FunctionalRequirements || '';
122
+ const technicalDesign = payload.TechnicalDesign || '';
123
+ const modificationPlan = payload.modificationPlan || '';
124
+
125
+ return (functionalRequirements + ' ' + technicalDesign + ' ' + modificationPlan).toLowerCase();
126
+ }
127
+
128
+ /**
129
+ * Checks if text mentions database operations that require entity knowledge
130
+ */
131
+ protected mentionsDatabaseOperations(text: string): boolean {
132
+ const keywords = [
133
+ 'create record',
134
+ 'update record',
135
+ 'delete record',
136
+ 'crud',
137
+ 'database',
138
+ 'entity',
139
+ 'save to',
140
+ 'store in',
141
+ 'persist',
142
+ 'write to database',
143
+ 'update priority',
144
+ 'create new record',
145
+ 'insert into',
146
+ 'entityname'
147
+ ];
148
+
149
+ return keywords.some(keyword => text.includes(keyword));
150
+ }
151
+
152
+ /**
153
+ * Formats research violations into clear, actionable retry instructions
154
+ */
155
+ protected formatResearchViolations(violations: string[]): string {
156
+ const header = '## Research Requirements Not Met\n\nYou must conduct thorough research before finalizing your design:\n\n';
157
+ const violationList = violations.map((v, idx) => `${idx + 1}. ${v}`).join('\n\n');
158
+ const footer = `\n\n---
159
+
160
+ **Next Steps - Complete Research Process**:
161
+
162
+ 1. **Call each missing research action/agent listed above** (in order shown)
163
+
164
+ 2. **Review research results carefully**:
165
+ - "Find Candidate Agents": Check each agent's actions and subAgents arrays - if an agent can handle multiple subtasks, use it instead of individual actions
166
+ - "Find Candidate Actions": Note the exact action IDs and parameter structures - you'll need these exact IDs in your design
167
+ - "Database Research Agent": Copy the exact entity names and field names - use ONLY these in your CRUD action instructions
168
+
169
+ 3. **Update your design based on actual research findings**:
170
+ - Replace any guessed/assumed names with actual IDs and names from research
171
+ - Remove redundant actions if subagents already provide those capabilities
172
+ - Maximize reuse of existing agents to simplify your design
173
+ - For CRUD actions: Include exact entity names and field names in the agent prompt
174
+
175
+ 4. **Try again** - Your next response must include research-backed design (no assumptions!)`;
176
+
177
+ return header + violationList + footer;
178
+ }
179
+ }
package/src/index.ts CHANGED
@@ -12,13 +12,15 @@ export * from './agent-spec-sync';
12
12
  // Export agent implementations
13
13
  export * from './agents/architect-agent';
14
14
  export * from './agents/builder-agent';
15
+ export * from './agents/planning-designer-agent';
15
16
 
16
17
  /**
17
18
  * Loads the Agent Manager Core module and ensures all agent classes are registered.
18
19
  * This function prevents tree shaking from removing the agent class registrations.
19
20
  *
20
21
  * The function itself doesn't need to do anything - importing the module is enough
21
- * to trigger the @RegisterClass decorators on AgentArchitectAgent and AgentBuilderAgent.
22
+ * to trigger the @RegisterClass decorators on AgentArchitectAgent, AgentBuilderAgent,
23
+ * and PlanningDesignerAgent.
22
24
  */
23
25
  export function LoadAgentManagerCore() {
24
26
  // Forces module to load and decorators to execute