@memberjunction/ai-agents 4.0.0 → 4.2.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.
package/README.md CHANGED
@@ -1,76 +1,68 @@
1
1
  # @memberjunction/ai-agents
2
2
 
3
- This npm package provides a complete framework for building AI agents using the MemberJunction platform. Agents can execute prompts, invoke actions, and orchestrate complex workflows with comprehensive execution tracking.
4
-
5
- ## Overview
6
-
7
- The `@memberjunction/ai-agents` package enables developers to create sophisticated AI agents that can:
8
- - Execute AI prompts in a hierarchical structure (system + agent prompts)
9
- - Perform actions based on prompt results using the MJ Actions framework
10
- - Make decisions about next steps through configurable agent types
11
- - Orchestrate sub-agents for complex, multi-step tasks
12
- - Track all execution steps in database for analysis and debugging
13
- - Manage conversation context with automatic compression
14
-
15
- ## Key Components
16
-
17
- ### BaseAgent
18
- The core execution engine that all agents use. Provides functionality for:
19
- - Hierarchical prompt execution (system prompt as parent, agent prompts as children)
20
- - Managing conversation context and placeholders
21
- - Invoking MemberJunction actions
22
- - Running sub-agents recursively
23
- - Comprehensive execution tracking via AIAgentRun and AIAgentRunStep entities
24
- - Automatic context compression for long conversations
25
- - **Effort level management** with hierarchical precedence and inheritance
26
-
27
- ### BaseAgentType
28
- Abstract class that defines reusable agent behavior patterns:
29
- - Determines next steps based on prompt results
30
- - Encapsulates decision-making logic
31
- - Enables different execution patterns (loops, decision trees, etc.)
32
-
33
- ### LoopAgentType
34
- Concrete implementation of BaseAgentType that:
35
- - Executes in a loop until task completion
36
- - Parses structured JSON responses from prompts
37
- - Supports actions, sub-agents, and conditional termination
38
- - ForEach and While iteration operations (v2.112+)
39
- - LLM can request batch processing over collections
40
- - Conditional loops with While operations
41
- - 90% token reduction for iterative tasks
42
-
43
- ### FlowAgentType
44
- Deterministic workflow agent type that:
45
- - Executes predefined workflows using directed graphs
46
- - Evaluates boolean conditions to determine paths
47
- - Supports parallel starting steps (Sequence=0)
48
- - Provides action output mapping to payload
49
- - Enables hybrid AI/deterministic workflows
50
- - ForEach and While loop step types (v2.112+)
51
- - Iterate over collections with ForEach steps
52
- - Conditional loops with While steps
53
- - Self-contained loop configuration
54
- - Support for Action, Sub-Agent, and Prompt loop bodies
55
- - **Execution customization** (v2.127+) - Start at specific steps or skip steps
56
-
57
- ### AgentRunner
58
- Orchestrator that provides multiple execution modes:
59
- - Loads agent metadata from database
60
- - Instantiates correct agent class using ClassFactory
61
- - Executes agents with provided context
62
- - **RunAgent**: Core execution method for direct agent invocation
63
- - **RunAgentInConversation**: Integrated execution with conversation and artifact management
64
-
65
- ### PayloadManager
66
- Advanced payload access control for hierarchical agent execution:
67
- - Controls which payload paths sub-agents can read (downstream)
68
- - Controls which payload paths sub-agents can write (upstream)
69
- - Supports JSON path patterns with wildcards
70
- - Detects suspicious changes with configurable rules
71
- - Generates human-readable diffs for audit trails
72
- - PayloadScope support for narrowing sub-agent data access
73
- - Transformation for scoped payload merging
3
+ Complete framework for building and executing AI agents in MemberJunction. Provides the `BaseAgent` execution engine, pluggable agent type system (Loop and Flow agents), hierarchical sub-agent orchestration, action execution, memory management with notes and examples, payload management, conversation context with message lifecycle management, and reranker integration.
4
+
5
+ ## Architecture
6
+
7
+ ```mermaid
8
+ graph TD
9
+ subgraph "@memberjunction/ai-agents"
10
+ BA["BaseAgent<br/>Core Execution Engine"]
11
+ style BA fill:#2d8659,stroke:#1a5c3a,color:#fff
12
+
13
+ AR["AgentRunner<br/>Orchestration Entry Point"]
14
+ style AR fill:#2d8659,stroke:#1a5c3a,color:#fff
15
+
16
+ subgraph "Agent Types"
17
+ BAT["BaseAgentType"]
18
+ style BAT fill:#7c5295,stroke:#563a6b,color:#fff
19
+ LAT["LoopAgentType"]
20
+ style LAT fill:#7c5295,stroke:#563a6b,color:#fff
21
+ FAT["FlowAgentType"]
22
+ style FAT fill:#7c5295,stroke:#563a6b,color:#fff
23
+ end
24
+
25
+ subgraph "Support Systems"
26
+ PM["PayloadManager<br/>Data Flow Between Steps"]
27
+ style PM fill:#b8762f,stroke:#8a5722,color:#fff
28
+ PCA["PayloadChangeAnalyzer"]
29
+ style PCA fill:#b8762f,stroke:#8a5722,color:#fff
30
+ PFM["PayloadFeedbackManager"]
31
+ style PFM fill:#b8762f,stroke:#8a5722,color:#fff
32
+ ACI["AgentContextInjector<br/>Notes, Examples, Data Sources"]
33
+ style ACI fill:#b8762f,stroke:#8a5722,color:#fff
34
+ ADP["AgentDataPreloader<br/>Batch Metadata Loading"]
35
+ style ADP fill:#b8762f,stroke:#8a5722,color:#fff
36
+ MMA["MemoryManagerAgent<br/>Note/Example Management"]
37
+ style MMA fill:#b8762f,stroke:#8a5722,color:#fff
38
+ end
39
+ end
40
+
41
+ BA --> BAT
42
+ BA --> PM
43
+ BA --> ACI
44
+ BA --> ADP
45
+ AR --> BA
46
+
47
+ subgraph Dependencies
48
+ AIP["@memberjunction/ai-prompts<br/>AIPromptRunner"]
49
+ style AIP fill:#2d6a9f,stroke:#1a4971,color:#fff
50
+
51
+ AIE["@memberjunction/aiengine<br/>AIEngine"]
52
+ style AIE fill:#2d6a9f,stroke:#1a4971,color:#fff
53
+
54
+ ACT["@memberjunction/actions<br/>ActionEngineServer"]
55
+ style ACT fill:#2d6a9f,stroke:#1a4971,color:#fff
56
+
57
+ RR["@memberjunction/ai-reranker<br/>RerankerService"]
58
+ style RR fill:#2d6a9f,stroke:#1a4971,color:#fff
59
+ end
60
+
61
+ AIP --> BA
62
+ AIE --> BA
63
+ ACT --> BA
64
+ RR --> BA
65
+ ```
74
66
 
75
67
  ## Installation
76
68
 
@@ -78,2598 +70,254 @@ Advanced payload access control for hierarchical agent execution:
78
70
  npm install @memberjunction/ai-agents
79
71
  ```
80
72
 
81
- ## Basic Usage
73
+ ## Key Components
82
74
 
83
- ```typescript
84
- import { AgentRunner } from '@memberjunction/ai-agents';
85
- import { UserInfo } from '@memberjunction/core';
75
+ ### BaseAgent
86
76
 
87
- // Using AgentRunner (recommended) which uses `ClassFactory` to pick the highest priority sub-class of BaseAgent that matches your Agent (and falls back to just using `BaseAgent` if there's no custom sub-class)
88
- const runner = new AgentRunner();
89
- const result = await runner.RunAgent({
90
- agent: agentEntity, // AIAgentEntity from database
91
- conversationMessages: messages,
92
- contextUser: user
93
- });
77
+ The core execution engine that all agents use. Handles:
94
78
 
95
- // Direct instantiation when you want to pick the exact class that gets run
96
- const agent = new YourAgentClass();
97
- const result = await agent.Execute({
98
- agent: agentEntity,
99
- conversationMessages: messages,
100
- contextUser: user
101
- });
79
+ - Hierarchical prompt execution (agent type's system prompt as parent, agent's prompts as children)
80
+ - Action execution through the MJ Actions framework
81
+ - Sub-agent orchestration with full context propagation
82
+ - Conversation context management with automatic message compaction
83
+ - Memory retrieval (notes and examples) with optional reranking
84
+ - Payload data management across execution steps
85
+ - ForEach and While loop operations
86
+ - Comprehensive execution tracking (AIAgentRun, AIAgentRunStep records)
102
87
 
103
- // Using run chaining to maintain context across multiple runs
104
- const followUpResult = await runner.RunAgent({
105
- agent: agentEntity,
106
- conversationMessages: newMessages,
107
- contextUser: user,
108
- lastRunId: result.agentRun.ID,
109
- autoPopulateLastRunPayload: true // Automatically use previous run's final payload
110
- });
111
- ```
88
+ ### AgentRunner
112
89
 
113
- ### RunAgentInConversation - Integrated Conversation & Artifact Management
90
+ High-level entry point for agent execution. Provides:
114
91
 
115
- The `RunAgentInConversation` method provides a complete workflow for executing agents within a conversation context, automatically handling conversation creation, artifact generation, and linking:
92
+ - Agent resolution by ID or entity reference
93
+ - Permission checking before execution
94
+ - Data preloading for performance
95
+ - Simplified execution interface
116
96
 
117
97
  ```typescript
118
98
  import { AgentRunner } from '@memberjunction/ai-agents';
119
- import { UserInfo } from '@memberjunction/core';
120
99
 
121
100
  const runner = new AgentRunner();
122
-
123
- // Execute agent with automatic conversation and artifact management
124
- const result = await runner.RunAgentInConversation({
125
- agent: agentEntity,
126
- conversationMessages: messages,
127
- contextUser: user
128
- }, {
129
- // Optional: Use existing conversation
130
- conversationId: 'existing-conversation-id',
131
-
132
- // Optional: Use existing conversation detail (skips creation)
133
- conversationDetailId: 'existing-detail-id',
134
-
135
- // Required if conversationDetailId not provided
136
- userMessage: 'Analyze the sales data for Q4',
137
-
138
- // Optional: Control artifact creation (default: true)
139
- createArtifacts: true,
140
-
141
- // Optional: Source artifact for versioning (continuity/refinement)
142
- sourceArtifactId: 'base-artifact-id',
143
-
144
- // Optional: Custom conversation name
145
- conversationName: 'Q4 Sales Analysis'
101
+ const result = await runner.ExecuteAgent({
102
+ agentId: 'agent-uuid',
103
+ conversationMessages: [{ role: 'user', content: 'Analyze Q3 sales trends' }],
104
+ contextUser: currentUser,
105
+ onProgress: (step) => console.log(`${step.step}: ${step.message}`)
146
106
  });
147
-
148
- // Result includes everything you need
149
- console.log('Agent result:', result.agentResult);
150
- console.log('Conversation ID:', result.conversationId);
151
- console.log('Detail ID:', result.conversationDetailId);
152
- if (result.artifactInfo) {
153
- console.log('Artifact created:', result.artifactInfo.artifactId);
154
- console.log('Version:', result.artifactInfo.versionNumber);
155
- }
156
- ```
157
-
158
- #### What RunAgentInConversation Does
159
-
160
- The method provides a complete workflow:
161
-
162
- 1. **Conversation Management**
163
- - Creates new conversation if not provided
164
- - Uses existing conversation if `conversationId` provided
165
- - Skips creation entirely if `conversationDetailId` provided
166
-
167
- 2. **Conversation Detail Creation**
168
- - Creates conversation detail record for user message
169
- - Automatically handles message ordering via `__mj_CreatedAt`
170
- - Skipped if `conversationDetailId` already provided
171
-
172
- 3. **Agent Execution**
173
- - Runs agent with conversation context
174
- - Links agent run to conversation detail
175
- - Passes through all execution parameters (callbacks, data, context, etc.)
176
-
177
- 4. **Artifact Processing** (if `createArtifacts !== false`)
178
- - Creates artifacts from agent payload
179
- - Handles intelligent versioning:
180
- - Uses `sourceArtifactId` if provided (explicit continuity)
181
- - Otherwise checks for previous artifacts on this conversation detail
182
- - Creates new artifact version or entirely new artifact as appropriate
183
- - Respects agent's `ArtifactCreationMode` configuration
184
- - Links artifacts to conversation details via junction table
185
- - Extracts artifact names from payload attributes
186
-
187
- #### Artifact Versioning Logic
188
-
189
- The method implements smart artifact versioning:
190
-
191
- ```typescript
192
- // Priority 1: Explicit source artifact (agent continuity/refinement)
193
- {
194
- sourceArtifactId: 'artifact-to-refine'
195
- // Creates version 2, 3, 4, etc. of the specified artifact
196
- }
197
-
198
- // Priority 2: Previous artifact on this conversation detail (fallback)
199
- // Automatically finds last artifact linked to the conversation detail
200
- // Creates next version of that artifact
201
-
202
- // Priority 3: No previous artifact
203
- // Creates entirely new artifact with version 1
204
107
  ```
205
108
 
206
- #### Respecting Agent Configuration
207
-
208
- The method honors agent-level settings:
109
+ ### Agent Type System
209
110
 
210
- - **`ArtifactCreationMode === 'Never'`**: Skips artifact creation entirely
211
- - **`ArtifactCreationMode === 'System Only'`**: Creates artifact with `Visibility='System Only'`
212
- - **`DefaultArtifactTypeID`**: Uses agent's preferred artifact type (defaults to JSON type)
111
+ Agents execute using a pluggable type system. The type determines how the agent decides its next action after each LLM call.
213
112
 
214
- #### Use Cases
113
+ #### BaseAgentType
215
114
 
216
- **GraphQL Resolvers** - Simplify agent execution endpoints:
217
- ```typescript
218
- // Before: Manually manage conversations, artifacts, notifications
219
- // After: One method call handles everything
220
- const result = await runner.RunAgentInConversation({...}, {
221
- conversationDetailId: args.conversationDetailId,
222
- createArtifacts: args.createArtifacts,
223
- sourceArtifactId: args.sourceArtifactId
224
- });
225
- ```
115
+ Abstract base that all agent types extend. Defines the `DetermineNextStep()` interface that produces a `BaseAgentNextStep` decision:
226
116
 
227
- **Interactive Chat Interfaces** - Maintain conversation context:
228
- ```typescript
229
- // First message - creates conversation
230
- const firstResult = await runner.RunAgentInConversation({...}, {
231
- userMessage: 'Analyze sales data',
232
- createArtifacts: true
233
- });
117
+ | Step | Description |
118
+ |---|---|
119
+ | `Chat` | Send a message back to the user |
120
+ | `Actions` | Execute one or more actions |
121
+ | `SubAgents` | Delegate to sub-agents |
122
+ | `MoreInfo` | Ask the user for additional information |
123
+ | `Retry` | Retry the current step (e.g., after validation failure) |
124
+ | `End` | Complete execution |
125
+ | `ForEach` | Iterate over a collection |
126
+ | `While` | Loop while a condition is true |
234
127
 
235
- // Follow-up message - uses existing conversation
236
- const followUp = await runner.RunAgentInConversation({...}, {
237
- conversationId: firstResult.conversationId,
238
- userMessage: 'Show me the trends',
239
- createArtifacts: true
240
- });
241
- ```
128
+ #### LoopAgentType
242
129
 
243
- **Agent Refinement Workflows** - Iterate on artifacts:
244
- ```typescript
245
- // Initial generation
246
- const initial = await runner.RunAgentInConversation({...}, {
247
- userMessage: 'Create a report',
248
- createArtifacts: true
249
- });
130
+ Conversational agent that runs in a loop: prompt -> decide -> act -> repeat. Best for interactive, chat-based agents. The LLM decides the next step at each iteration by producing a structured JSON response.
250
131
 
251
- // Refinement - creates version 2 of same artifact
252
- const refined = await runner.RunAgentInConversation({...}, {
253
- userMessage: 'Make it more concise',
254
- createArtifacts: true,
255
- sourceArtifactId: initial.artifactInfo.artifactId
256
- });
257
- ```
132
+ #### FlowAgentType
258
133
 
259
- #### Benefits
134
+ Step-based agent that follows a predefined flow graph. Each step has explicit paths to the next step based on conditions. Best for deterministic workflows where the execution path is known in advance.
260
135
 
261
- - **Single Responsibility**: One method handles entire workflow
262
- - **Flexible**: Works with new or existing conversations
263
- - **Intelligent**: Smart artifact versioning without manual tracking
264
- - **Clean Code**: Moves business logic out of transport layers (GraphQL, REST)
265
- - **Type Safe**: Full TypeScript typing for results
266
- - **Auditable**: All artifacts linked to conversation details
136
+ ```mermaid
137
+ graph LR
138
+ subgraph "Loop Agent"
139
+ L1["Prompt LLM"] --> L2["Parse Response"]
140
+ L2 --> L3{Decision}
141
+ L3 -->|"Actions"| L4["Execute Actions"]
142
+ L4 --> L1
143
+ L3 -->|"Chat"| L5["Reply to User"]
144
+ L5 --> L1
145
+ L3 -->|"End"| L6["Complete"]
146
+ end
267
147
 
268
- #### Helper Methods
148
+ subgraph "Flow Agent"
149
+ F1["Step 1"] -->|"Path A"| F2["Step 2a"]
150
+ F1 -->|"Path B"| F3["Step 2b"]
151
+ F2 --> F4["Step 3"]
152
+ F3 --> F4
153
+ F4 --> F5["End"]
154
+ end
269
155
 
270
- `RunAgentInConversation` uses these public helper methods (available for custom workflows):
156
+ style L1 fill:#2d6a9f,stroke:#1a4971,color:#fff
157
+ style L2 fill:#7c5295,stroke:#563a6b,color:#fff
158
+ style L3 fill:#b8762f,stroke:#8a5722,color:#fff
159
+ style L4 fill:#2d8659,stroke:#1a5c3a,color:#fff
160
+ style L5 fill:#2d8659,stroke:#1a5c3a,color:#fff
161
+ style L6 fill:#2d8659,stroke:#1a5c3a,color:#fff
271
162
 
272
- ```typescript
273
- // Get maximum version number for an artifact
274
- const maxVersion = await runner.GetMaxVersionForArtifact(artifactId, user);
275
-
276
- // Find previous artifact for a conversation detail
277
- const previousArtifact = await runner.FindPreviousArtifactForMessage(detailId, user);
278
-
279
- // Process agent artifacts manually
280
- const artifactInfo = await runner.ProcessAgentArtifacts(
281
- agentResult,
282
- conversationDetailId,
283
- sourceArtifactId,
284
- user
285
- );
163
+ style F1 fill:#2d6a9f,stroke:#1a4971,color:#fff
164
+ style F2 fill:#7c5295,stroke:#563a6b,color:#fff
165
+ style F3 fill:#7c5295,stroke:#563a6b,color:#fff
166
+ style F4 fill:#b8762f,stroke:#8a5722,color:#fff
167
+ style F5 fill:#2d8659,stroke:#1a5c3a,color:#fff
286
168
  ```
287
169
 
288
- ## Iterative Operations (v2.112+)
289
-
290
- Both Flow and Loop agents support native ForEach and While iterations for efficient batch processing and retry logic.
170
+ ### PayloadManager
291
171
 
292
- **📘 Complete Guide:** [Guide to Iterative Operations in Agents](./guide-to-iterative-operations-in-agents.md)
172
+ Manages data flow through agent execution:
293
173
 
294
- ### Quick Start
174
+ - Stores key-value data accessible across all steps and sub-agents
175
+ - Supports typed payload changes requested by the LLM
176
+ - Validates and applies changes through PayloadChangeAnalyzer
177
+ - Provides feedback to the LLM about successful/failed changes via PayloadFeedbackManager
295
178
 
296
- **Flow Agent - ForEach Example:**
297
179
  ```typescript
298
- // Create a ForEach step that sends email to each customer
299
- const forEachStep = await md.GetEntityObject<AIAgentStepEntity>('MJ: AI Agent Steps');
300
- forEachStep.StepType = 'ForEach';
301
- forEachStep.LoopBodyType = 'Action';
302
- forEachStep.ActionID = sendEmailActionId;
303
- forEachStep.Configuration = JSON.stringify({
304
- type: 'ForEach',
305
- collectionPath: 'payload.customers',
306
- itemVariable: 'customer',
307
- maxIterations: 500
308
- });
309
- forEachStep.ActionInputMapping = JSON.stringify({
310
- to: 'customer.email',
311
- subject: 'Welcome!'
312
- });
180
+ const manager = new PayloadManager();
181
+ manager.Set('customerData', { name: 'Acme', revenue: 1000000 });
182
+ const data = manager.Get('customerData');
313
183
  ```
314
184
 
315
- **Loop Agent - ForEach Example:**
316
- ```json
317
- {
318
- "taskComplete": false,
319
- "message": "Processing all documents",
320
- "nextStep": {
321
- "type": "ForEach",
322
- "forEach": {
323
- "collectionPath": "payload.documents",
324
- "action": {
325
- "name": "Analyze Document",
326
- "params": { "path": "item.path" }
327
- }
328
- }
329
- }
330
- }
331
- ```
332
-
333
- **Benefits:**
334
- - 90% token reduction (Loop agents)
335
- - Deterministic iteration (Flow agents)
336
- - Type-safe loop variables
337
- - Built-in error handling
185
+ ### AgentContextInjector
338
186
 
339
- See the [complete guide](./guide-to-iterative-operations-in-agents.md) for While loops, nested iterations, and advanced patterns.
187
+ Injects contextual information into agent prompts:
340
188
 
341
- ## Agent Configuration
189
+ - Retrieves relevant notes via vector similarity search
190
+ - Retrieves relevant examples for few-shot learning
191
+ - Injects data source content
192
+ - Applies reranking when configured
342
193
 
343
- Agents are configured through MemberJunction entities:
194
+ ### AgentDataPreloader
344
195
 
345
- 1. **AIAgentType**: Defines the behavior pattern and system prompt
346
- - `DriverClass`: The TypeScript class implementing the behavior
347
- - `SystemPromptID`: Base prompt providing foundational behavior
196
+ Optimizes agent startup by batch-loading all required metadata in parallel:
348
197
 
349
- 2. **AIAgent**: Specific agent instance
350
- - `AgentTypeID`: Links to the agent type
351
- - Configuration for specific use cases
198
+ - Agent entity with all relationships
199
+ - Actions and their parameters
200
+ - Sub-agent data
201
+ - Prompt configurations
352
202
 
353
- 3. **AIPrompt**: Reusable prompt templates
354
- - Support for placeholders and dynamic content
355
- - Can be chained hierarchically
203
+ ### MemoryManagerAgent
356
204
 
357
- 4. **AIAgentPrompt**: Associates prompts with agents
358
- - `ExecutionOrder`: Determines prompt execution sequence
359
- - Links agents to their specific prompts
205
+ Handles persistent memory operations for agents:
360
206
 
361
- 5. **AIAgentStep**: Defines workflow steps for Flow agents
362
- - `StepType`: Action, Sub-Agent, or Prompt
363
- - `StartingStep`: Boolean flag for initial steps
364
- - `TimeoutSeconds`: Step execution timeout
365
- - `ActionOutputMapping`: JSON mapping for action results
366
-
367
- 6. **AIAgentStepPath**: Connects workflow steps
368
- - `Condition`: Boolean expression for path evaluation
369
- - `Priority`: Determines path selection order
370
-
371
- ## Creating Custom Agent Types
372
-
373
- ```typescript
374
- import { BaseAgentType, RegisterClass, BaseAgentNextStep } from '@memberjunction/ai-agents';
375
- import { AIPromptRunResult } from '@memberjunction/ai-prompts';
376
-
377
- @RegisterClass(BaseAgentType, "MyCustomAgentType")
378
- export class MyCustomAgentType extends BaseAgentType {
379
- async DetermineNextStep(promptResult: AIPromptRunResult): Promise<BaseAgentNextStep> {
380
- // Parse the prompt result
381
- const response = JSON.parse(promptResult.FullResult);
382
-
383
- // Determine next action based on response
384
- if (response.taskComplete) {
385
- return { type: 'stop', reason: 'Task completed successfully' };
386
- } else if (response.action) {
387
- return {
388
- type: 'action',
389
- actionName: response.action.name,
390
- actionParams: response.action.params
391
- };
392
- } else {
393
- return { type: 'continue' };
394
- }
395
- }
396
- }
397
- ```
207
+ - Creating and updating agent notes
208
+ - Managing agent examples
209
+ - Scoped memory for multi-tenant deployments (UserScope support)
398
210
 
399
- ### Handling JSON Validation Syntax in Agent Responses
211
+ ## Usage
400
212
 
401
- When AI prompts use validation syntax (like `?`, `*`, `:type`, etc.), the AI might inadvertently include these in its JSON response keys. Agent types that parse embedded JSON need to handle this cleaning:
213
+ ### Basic Agent Execution
402
214
 
403
215
  ```typescript
404
- import { JSONValidator } from '@memberjunction/global';
405
-
406
- @RegisterClass(BaseAgentType, "StructuredResponseAgent")
407
- export class StructuredResponseAgent extends BaseAgentType {
408
- async DetermineNextStep(promptResult: AIPromptRunResult): Promise<BaseAgentNextStep> {
409
- // For responses with embedded JSON strings
410
- const outerResponse = promptResult.result as any;
411
-
412
- // If the response contains an embedded JSON string
413
- if (outerResponse.response && typeof outerResponse.response === 'string') {
414
- // Parse the embedded JSON
415
- const innerData = JSON.parse(outerResponse.response);
416
-
417
- // Clean validation syntax that AI might have included
418
- const validator = new JSONValidator();
419
- const cleanedData = validator.cleanValidationSyntax<any>(innerData);
420
-
421
- // Now work with cleaned data
422
- if (cleanedData.analysisComplete) {
423
- return { type: 'stop', reason: 'Analysis completed' };
424
- }
425
- }
426
-
427
- return { type: 'continue' };
428
- }
429
- }
430
- ```
431
-
432
- **Important Notes:**
433
- - The AIPromptRunner automatically cleans validation syntax for top-level JSON objects when an OutputExample is defined
434
- - However, agent types must handle cleaning for **embedded JSON strings** within the response
435
- - This is common when the prompt response structure contains a JSON string as a field value
436
- - The `cleanValidationSyntax` method preserves values while removing validation syntax from keys
216
+ import { AgentRunner } from '@memberjunction/ai-agents';
217
+ import { ExecuteAgentParams } from '@memberjunction/ai-core-plus';
437
218
 
438
- Example scenario:
439
- ```typescript
440
- // AI Prompt response (top-level is cleaned automatically)
441
- {
442
- "status": "success",
443
- "response": "{\"analysisComplete?\":true,\"recommendations:[3+]\":[\"A\",\"B\",\"C\"]}"
444
- }
219
+ const runner = new AgentRunner();
220
+ const result = await runner.ExecuteAgent({
221
+ agentId: 'my-agent-id',
222
+ conversationMessages: [
223
+ { role: 'user', content: 'What are the top 5 customers by revenue?' }
224
+ ],
225
+ contextUser: currentUser
226
+ });
445
227
 
446
- // After agent type cleans the embedded JSON
447
- {
448
- "analysisComplete": true,
449
- "recommendations": ["A", "B", "C"]
228
+ if (result.success) {
229
+ console.log(result.outputMessages);
450
230
  }
451
231
  ```
452
232
 
453
- ## Execution Flow
454
-
455
- 1. **Initialization**:
456
- - Creates AIAgentRun entity for tracking
457
- - Loads agent configuration and type
458
- - Initializes AI and Action engines
233
+ ### With Sub-Agent Orchestration
459
234
 
460
- 2. **Configuration Loading**:
461
- - Loads AIAgentType with system prompt
462
- - Loads agent's prompts ordered by ExecutionOrder
463
- - Validates placeholders and dependencies
464
-
465
- 3. **Execution Loop**:
466
- - Executes prompts hierarchically (system as parent)
467
- - Agent type analyzes results via DetermineNextStep()
468
- - Executes actions or sub-agents as determined
469
- - Creates AIAgentRunStep for each operation
470
- - Continues until stop condition met
471
-
472
- 4. **Result Tracking**:
473
- - All steps recorded with full context
474
- - Execution tree available for analysis
475
- - Errors and outputs captured
476
-
477
- ### Early Run ID Callback
478
-
479
- Get the AgentRun ID immediately after creation for real-time monitoring:
235
+ Sub-agents are automatically discovered from the agent's relationships and invoked when the LLM requests delegation:
480
236
 
481
237
  ```typescript
482
- const params: ExecuteAgentParams = {
483
- agent: myAgent,
238
+ const result = await runner.ExecuteAgent({
239
+ agentId: 'orchestrator-agent-id',
484
240
  conversationMessages: messages,
485
241
  contextUser: currentUser,
486
-
487
- // Callback fired immediately after AgentRun record is saved
488
- onAgentRunCreated: async (agentRunId) => {
489
- console.log(`Agent run started: ${agentRunId}`);
490
-
491
- // Use cases:
492
- // - Link to parent records (e.g., AIAgentRunStep.TargetLogID for sub-agents)
493
- // - Send to monitoring systems
494
- // - Update UI with tracking info
495
- // - Start real-time log streaming
496
- }
497
- };
498
-
499
- const result = await runner.RunAgent(params);
500
- ```
501
-
502
- The callback is invoked:
503
- - **When**: Right after the AIAgentRun record is created and saved
504
- - **Before**: The actual agent execution begins
505
- - **Error Handling**: Callback errors are logged but don't fail the execution
506
- - **Async Support**: Can be synchronous or asynchronous
507
- - **Sub-Agent Tracking**: BaseAgent automatically uses this callback to link sub-agent runs to their parent step's TargetLogID
508
-
509
- ## Advanced Features
510
-
511
- ### Agent Data Preloading
512
-
513
- Agents can declaratively preload reference data without requiring custom application code or action calls. Data sources are configured through the `AIAgentDataSource` entity and automatically loaded before agent execution.
514
-
515
- #### Overview
516
-
517
- Data preloading solves the common problem of agents needing access to reference data (like entity lists, configuration values, or initial state) that doesn't change during execution. Instead of:
518
- - Writing custom application code to load data
519
- - Having agents call actions to fetch data (which bloats conversation context)
520
- - Manually passing the same data to every agent invocation
521
-
522
- Agents can now specify data sources that are automatically loaded and injected into the appropriate destination (`data`, `context`, or `payload`).
523
-
524
- #### Three Destination Types
525
-
526
- **1. Data Destination** - For Nunjucks templates in prompts (visible to LLMs)
527
- ```typescript
528
- // Configuration
529
- {
530
- "Name": "ALL_ENTITIES",
531
- "SourceType": "RunView",
532
- "EntityName": "Entities",
533
- "OrderBy": "Name ASC",
534
- "DestinationType": "Data",
535
- "DestinationPath": null // Uses "ALL_ENTITIES" at root level
536
- }
537
-
538
- // Result in agent prompt:
539
- // params.data.ALL_ENTITIES = [{ Name: "Users", ... }, { Name: "Entities", ... }]
540
-
541
- // Prompt can use Nunjucks:
542
- // You have access to {{ALL_ENTITIES.length}} entities:
543
- // {% for entity in ALL_ENTITIES %}
544
- // - {{entity.Name}}: {{entity.Description}}
545
- // {% endfor %}
546
- ```
547
-
548
- **2. Context Destination** - For actions only (NOT visible to LLMs)
549
- ```typescript
550
- // Configuration
551
- {
552
- "Name": "ORG_SETTINGS",
553
- "SourceType": "RunView",
554
- "EntityName": "Organization Settings",
555
- "ExtraFilter": "OrgID='${context.organizationId}'",
556
- "DestinationType": "Context",
557
- "DestinationPath": "organization.settings"
558
- }
559
-
560
- // Result:
561
- // params.context.organization.settings = { apiEndpoint: "...", features: [...] }
562
-
563
- // Actions can access context, but prompts/LLMs cannot
564
- // This keeps API keys and sensitive configuration away from LLMs
565
- ```
566
-
567
- **3. Payload Destination** - For agent state initialization
568
- ```typescript
569
- // Configuration
570
- {
571
- "Name": "CustomerOrders",
572
- "SourceType": "RunQuery",
573
- "QueryName": "Recent Orders by Customer",
574
- "Parameters": JSON.stringify({ customerId: "{{context.customerId}}" }),
575
- "DestinationType": "Payload",
576
- "DestinationPath": "analysis.orders.recent"
577
- }
578
-
579
- // Result:
580
- // params.payload.analysis.orders.recent = [{ OrderID: "123", ... }]
581
-
582
- // Agent starts with rich initial state without caller manually loading it
583
- ```
584
-
585
- #### Data Source Types
586
-
587
- **RunView Data Sources** - Query entities with filters
588
- ```typescript
589
- {
590
- "Name": "ACTIVE_MODELS",
591
- "SourceType": "RunView",
592
- "EntityName": "AI Models",
593
- "ExtraFilter": "IsActive=1 AND Vendor='OpenAI'",
594
- "OrderBy": "Priority DESC",
595
- "FieldsToRetrieve": JSON.stringify(["ID", "Name", "Vendor", "MaxInputTokens"]),
596
- "ResultType": "simple", // or "entity_object"
597
- "MaxRows": 100,
598
- "DestinationType": "Data"
599
- }
600
- ```
601
-
602
- **RunQuery Data Sources** - Execute stored queries
603
- ```typescript
604
- {
605
- "Name": "MONTHLY_STATS",
606
- "SourceType": "RunQuery",
607
- "QueryName": "Monthly Analytics",
608
- "CategoryPath": "/Reports/Analytics",
609
- "Parameters": JSON.stringify({
610
- month: "{{context.currentMonth}}",
611
- year: "{{context.currentYear}}"
612
- }),
613
- "DestinationType": "Payload",
614
- "DestinationPath": "stats.monthly"
615
- }
616
- ```
617
-
618
- #### Path Support
619
-
620
- The `DestinationPath` field supports nested paths using dot notation:
621
-
622
- ```typescript
623
- // Simple root-level
624
- {
625
- "Name": "ENTITIES",
626
- "DestinationPath": null // Uses "ENTITIES" at root
627
- }
628
- // Result: data.ENTITIES
629
-
630
- // Nested paths
631
- {
632
- "Name": "ModelList",
633
- "DestinationPath": "config.ai.models"
634
- }
635
- // Result: data.config.ai.models
636
-
637
- // Deep nesting
638
- {
639
- "Name": "CustomerData",
640
- "DestinationPath": "analysis.customer.profile.orders"
641
- }
642
- // Result: payload.analysis.customer.profile.orders
643
- ```
644
-
645
- #### Caching Policies
646
-
647
- Data sources support three caching strategies:
648
-
649
- **1. None** - No caching (default)
650
- ```typescript
651
- {
652
- "CachePolicy": "None"
653
- // Data is loaded fresh every time
654
- }
655
- ```
656
-
657
- **2. PerRun** - Cache for duration of a single agent run
658
- ```typescript
659
- {
660
- "CachePolicy": "PerRun"
661
- // Multiple data sources with same AgentID+Name share cached data within one run
662
- // Cache is cleared when agent run completes
663
- }
664
- ```
665
-
666
- **3. PerAgent** - Global cache with TTL
667
- ```typescript
668
- {
669
- "CachePolicy": "PerAgent",
670
- "CacheTimeoutSeconds": 3600 // 1 hour
671
- // Cached across all runs for this agent until TTL expires
672
- // Good for rarely-changing reference data like entity lists
673
- }
674
- ```
675
-
676
- #### Execution Control
677
-
678
- **Disable data preloading** for specific executions:
679
- ```typescript
680
- const result = await runner.RunAgent({
681
- agent: myAgent,
682
- conversationMessages: messages,
683
- contextUser: user,
684
- disableDataPreloading: true // Skip automatic data preloading
685
- });
686
- ```
687
-
688
- **Caller precedence**: Caller-provided data always takes precedence over preloaded data:
689
- ```typescript
690
- const result = await runner.RunAgent({
691
- agent: myAgent,
692
- conversationMessages: messages,
693
- contextUser: user,
694
- data: {
695
- CUSTOM_ENTITIES: myEntities // Overrides preloaded CUSTOM_ENTITIES
242
+ onProgress: (step) => {
243
+ // Track execution across agent hierarchy
244
+ console.log(`[${step.agentName}] ${step.message}`);
696
245
  }
697
246
  });
698
247
  ```
699
248
 
700
- #### Configuration Examples
701
-
702
- **Database Research Agent** - Preload entity metadata
703
- ```typescript
704
- // Data source 1: All entities for reference
705
- {
706
- "AgentID": "database-research-agent-id",
707
- "Name": "ALL_ENTITIES",
708
- "SourceType": "RunView",
709
- "EntityName": "Entities",
710
- "OrderBy": "Name ASC",
711
- "FieldsToRetrieve": JSON.stringify(["ID", "Name", "SchemaName", "Description", "BaseView"]),
712
- "DestinationType": "Data",
713
- "ExecutionOrder": 1,
714
- "Status": "Active",
715
- "CachePolicy": "PerAgent",
716
- "CacheTimeoutSeconds": 3600
717
- }
718
-
719
- // Data source 2: Schema information
720
- {
721
- "AgentID": "database-research-agent-id",
722
- "Name": "SCHEMA_INFO",
723
- "SourceType": "RunView",
724
- "EntityName": "Entity Fields",
725
- "DestinationType": "Data",
726
- "DestinationPath": "schema.fields",
727
- "ExecutionOrder": 2,
728
- "Status": "Active",
729
- "CachePolicy": "PerAgent",
730
- "CacheTimeoutSeconds": 3600
731
- }
732
- ```
733
-
734
- **Customer Service Agent** - Preload customer context
735
- ```typescript
736
- // Preload customer data into payload
737
- {
738
- "AgentID": "customer-service-agent-id",
739
- "Name": "CUSTOMER_PROFILE",
740
- "SourceType": "RunView",
741
- "EntityName": "Customers",
742
- "ExtraFilter": "ID='{{context.customerId}}'",
743
- "DestinationType": "Payload",
744
- "DestinationPath": "customer.profile",
745
- "Status": "Active",
746
- "CachePolicy": "PerRun"
747
- }
748
-
749
- // Preload recent orders
750
- {
751
- "AgentID": "customer-service-agent-id",
752
- "Name": "RECENT_ORDERS",
753
- "SourceType": "RunQuery",
754
- "QueryName": "Recent Orders by Customer",
755
- "Parameters": JSON.stringify({ customerId: "{{context.customerId}}", days: 30 }),
756
- "DestinationType": "Payload",
757
- "DestinationPath": "customer.orders",
758
- "Status": "Active",
759
- "CachePolicy": "PerRun"
760
- }
761
-
762
- // Preload organization settings (for actions)
763
- {
764
- "AgentID": "customer-service-agent-id",
765
- "Name": "ORG_CONFIG",
766
- "SourceType": "RunView",
767
- "EntityName": "Organization Settings",
768
- "ExtraFilter": "OrgID='{{context.organizationId}}'",
769
- "DestinationType": "Context",
770
- "DestinationPath": "organization.config",
771
- "Status": "Active",
772
- "CachePolicy": "PerAgent",
773
- "CacheTimeoutSeconds": 1800
774
- }
775
- ```
776
-
777
- #### Benefits
778
-
779
- - **Declarative**: Configure data preloading through metadata, not code
780
- - **Reusable**: Same agent works across different environments
781
- - **Efficient**: Caching reduces redundant database queries
782
- - **Clean Separation**: Keeps data in appropriate destinations (data/context/payload)
783
- - **Flexible**: Supports both RunView and RunQuery with full parameter control
784
- - **Secure**: Context destination keeps sensitive data away from LLMs
785
- - **Performance**: Multiple caching strategies for different use cases
786
-
787
- #### Database Schema
788
-
789
- The `AIAgentDataSource` table includes:
790
- - **AgentID**: The agent using this data source
791
- - **Name**: Variable name (used as fallback if DestinationPath is null)
792
- - **SourceType**: RunView or RunQuery
793
- - **EntityName**, **ExtraFilter**, **OrderBy**, **FieldsToRetrieve**, **ResultType**: RunView parameters
794
- - **QueryName**, **CategoryPath**, **Parameters**: RunQuery parameters
795
- - **MaxRows**: Limit results (applies to both source types)
796
- - **DestinationType**: Data, Context, or Payload
797
- - **DestinationPath**: Nested path using dot notation (optional)
798
- - **ExecutionOrder**: Order to execute when multiple sources exist
799
- - **Status**: Active or Disabled
800
- - **CachePolicy**: None, PerRun, or PerAgent
801
- - **CacheTimeoutSeconds**: TTL for PerAgent cache
802
-
803
- **Unique Constraint**: `AgentID + Name + DestinationType + DestinationPath`
804
- - Allows same Name across different destinations/paths
805
- - Example: "ENTITIES" can exist in both Data and Payload destinations
806
-
807
- ### Runtime Action Changes (v2.123.0)
808
-
809
- The framework supports dynamic customization of which actions are available to agents at runtime, without modifying database configuration. This is particularly useful for:
810
-
811
- - **Multi-tenant scenarios** where different executions need different integrations
812
- - **Security restrictions** where sub-agents should have limited action access
813
- - **Testing scenarios** with controlled action availability
814
-
815
- #### ActionChange Interface
816
-
817
- ```typescript
818
- interface ActionChange {
819
- scope: ActionChangeScope; // Which agents to apply to
820
- mode: ActionChangeMode; // 'add' or 'remove'
821
- actionIds: string[]; // Action entity IDs to add/remove
822
- agentIds?: string[]; // Required when scope is 'specific'
823
- }
824
-
825
- type ActionChangeScope = 'global' | 'root' | 'all-subagents' | 'specific';
826
- type ActionChangeMode = 'add' | 'remove';
827
- ```
828
-
829
- #### Scope Options
830
-
831
- - **`global`**: Applies to all agents in the hierarchy (root + all sub-agents)
832
- - **`root`**: Applies only to the root agent
833
- - **`all-subagents`**: Applies to all sub-agents but NOT the root agent
834
- - **`specific`**: Applies only to agents listed in `agentIds`
835
-
836
- #### Usage Examples
249
+ ### With Runtime Action Changes
837
250
 
838
251
  ```typescript
839
- // Example 1: Tenant A context - add LMS and CRM integrations to all agents
840
- const result = await runner.RunAgent({
841
- agent: myAgent,
842
- conversationMessages: messages,
843
- contextUser: user,
844
- actionChanges: [
845
- {
846
- scope: 'global',
847
- mode: 'add',
848
- actionIds: ['lms-query-action-id', 'crm-search-action-id']
849
- }
850
- ]
851
- });
852
-
853
- // Example 2: Tenant B context - different integrations
854
- const result = await runner.RunAgent({
855
- agent: myAgent,
856
- conversationMessages: messages,
857
- contextUser: user,
858
- actionChanges: [
859
- {
860
- scope: 'global',
861
- mode: 'add',
862
- actionIds: ['membership-action-id', 'events-action-id']
863
- }
864
- ]
865
- });
866
-
867
- // Example 3: Remove dangerous actions from sub-agents only
868
- const result = await runner.RunAgent({
869
- agent: myAgent,
252
+ const result = await runner.ExecuteAgent({
253
+ agentId: 'my-agent-id',
870
254
  conversationMessages: messages,
871
- contextUser: user,
255
+ contextUser: currentUser,
872
256
  actionChanges: [
873
- {
874
- scope: 'all-subagents',
875
- mode: 'remove',
876
- actionIds: ['delete-record-action-id', 'execute-sql-action-id']
877
- }
257
+ { scope: 'global', mode: 'add', actionIds: ['crm-search-id'] },
258
+ { scope: 'all-subagents', mode: 'remove', actionIds: ['delete-record-id'] }
878
259
  ]
879
260
  });
261
+ ```
880
262
 
881
- // Example 4: Add special actions to a specific sub-agent
882
- const result = await runner.RunAgent({
883
- agent: myAgent,
884
- conversationMessages: messages,
885
- contextUser: user,
886
- actionChanges: [
887
- { scope: 'global', mode: 'add', actionIds: ['common-action-id'] },
888
- {
889
- scope: 'specific',
890
- mode: 'add',
891
- actionIds: ['special-data-action-id'],
892
- agentIds: ['data-gatherer-sub-agent-id']
893
- }
894
- ]
895
- });
263
+ ### With User Scope (Multi-Tenant)
896
264
 
897
- // Example 5: Replace actions (remove then add)
898
- const result = await runner.RunAgent({
899
- agent: myAgent,
265
+ ```typescript
266
+ const result = await runner.ExecuteAgent({
267
+ agentId: 'my-agent-id',
900
268
  conversationMessages: messages,
901
- contextUser: user,
902
- actionChanges: [
903
- // First remove the default integrations
904
- {
905
- scope: 'global',
906
- mode: 'remove',
907
- actionIds: ['default-crm-action-id']
908
- },
909
- // Then add the tenant-specific ones
910
- {
911
- scope: 'global',
912
- mode: 'add',
913
- actionIds: ['tenant-specific-crm-action-id']
914
- }
915
- ]
269
+ contextUser: currentUser,
270
+ userScope: {
271
+ primaryEntityName: 'Organizations',
272
+ primaryRecordId: orgId,
273
+ secondary: { TeamID: teamId }
274
+ }
916
275
  });
917
276
  ```
918
277
 
919
- #### Propagation Rules
920
-
921
- When sub-agents are executed, action changes are propagated based on scope:
922
-
923
- | Original Scope | Propagated As | Behavior |
924
- |---------------|---------------|----------|
925
- | `global` | `global` | Propagated as-is to all sub-agents |
926
- | `root` | (not propagated) | Only applied to root agent |
927
- | `all-subagents` | `global` | Becomes global for sub-agent's perspective |
928
- | `specific` | `specific` | Propagated as-is; each agent checks if it's in agentIds |
929
-
930
- #### How It Works
931
-
932
- 1. **During prompt preparation** (`gatherPromptTemplateData`):
933
- - Base actions are loaded from database configuration (`AIAgentAction` table)
934
- - Runtime action changes are applied based on scope
935
- - The modified action list is injected into the prompt template
936
- - LLM sees only the effective actions
937
-
938
- 2. **During action execution** (`executeActionsStep`):
939
- - When LLM requests an action, it's validated against the effective action list
940
- - Actions not in the effective list are rejected with a clear error message
941
-
942
- 3. **During sub-agent execution** (`ExecuteSubAgent`):
943
- - Action changes are filtered and transformed for propagation
944
- - Sub-agents receive only applicable changes
945
-
946
- #### Key Benefits
947
-
948
- - **No database changes required** - Actions are modified at runtime
949
- - **Tenant isolation** - Same agent, different action sets per tenant
950
- - **Security** - Restrict sub-agents from dangerous operations
951
- - **Flexibility** - Combine add/remove operations for complex scenarios
952
- - **Type-safe** - Full TypeScript typing with ActionChange interface
953
-
954
- **See:** [@memberjunction/ai-core-plus README](../CorePlus/README.md) for type definitions.
955
-
956
- ### Payload Scoping for Sub-Agents
957
-
958
- The framework now supports narrowing the payload that sub-agents work with through the `PayloadScope` field:
278
+ ### With Message Lifecycle Management
959
279
 
960
280
  ```typescript
961
- // Configure an agent to work with a specific part of the payload
962
- const subAgent = {
963
- Name: 'RequirementsAnalyzer',
964
- PayloadScope: '/functionalRequirements', // Only sees this part of parent payload
965
- PayloadSelfWritePaths: ['analysis', 'recommendations'] // Paths within the scope
966
- };
967
-
968
- // When parent payload is:
969
- {
970
- "functionalRequirements": {
971
- "features": ["A", "B", "C"],
972
- "constraints": {...}
281
+ const result = await runner.ExecuteAgent({
282
+ agentId: 'my-agent-id',
283
+ conversationMessages: messages,
284
+ contextUser: currentUser,
285
+ messageExpirationOverride: {
286
+ expirationTurns: 3,
287
+ expirationMode: 'Compact',
288
+ compactMode: 'First N Chars',
289
+ compactLength: 500
973
290
  },
974
- "technicalSpecs": {...},
975
- "timeline": {...}
976
- }
977
-
978
- // Sub-agent only sees:
979
- {
980
- "features": ["A", "B", "C"],
981
- "constraints": {...}
982
- }
983
-
984
- // Sub-agent changes are merged back under the scope path
985
- ```
986
-
987
- Benefits:
988
- - **Reduced token usage**: Sub-agents only see relevant data
989
- - **Improved focus**: Agents work with their specific domain
990
- - **Automatic merging**: Changes are properly placed back in parent payload
991
- - **Error handling**: Critical failures if scope path doesn't exist
992
-
993
- ### Input Payload Validation
994
-
995
- Agents can validate their input payload before execution begins to ensure data quality and prevent errors:
996
-
997
- ```typescript
998
- // Configure validation in AIAgent entity
999
- {
1000
- StartingPayloadValidation: JSON.stringify({
1001
- "customerId": "string:!empty",
1002
- "orderItems": "array:[1+]",
1003
- "shippingAddress": {
1004
- "street": "string:!empty",
1005
- "city": "string:!empty",
1006
- "zipCode": "string:[5]"
1007
- },
1008
- "priority": "string?:enum:normal,high,urgent" // Optional with enum values
1009
- }),
1010
- StartingPayloadValidationMode: "Fail" // or "Warn" (default: "Fail")
1011
- }
1012
- ```
1013
-
1014
- Input validation features:
1015
- - **Early failure detection**: Validates before any processing begins
1016
- - **Two modes**:
1017
- - `Fail`: Reject invalid input immediately (default)
1018
- - `Warn`: Log warning but proceed with execution
1019
- - **Deterministic guardrails**: Ensures agents receive valid data
1020
- - **Cost savings**: Prevents expensive operations with invalid input
1021
- - **Parent responsibility**: Parent agents provide properly scoped payloads to children
1022
-
1023
- ### Final Payload Validation
1024
-
1025
- Agents can validate their final output before marking execution as successful:
1026
-
1027
- ```typescript
1028
- // Configure validation in AIAgent entity
1029
- {
1030
- FinalPayloadValidation: JSON.stringify({
1031
- "analysis": {
1032
- "summary": "string:!empty",
1033
- "score": "number:[0-100]",
1034
- "recommendations": "array:[3+]"
1035
- },
1036
- "metadata": {
1037
- "processedAt": "string",
1038
- "version": "string?" // Optional field
1039
- }
1040
- }),
1041
- FinalPayloadValidationMode: "Retry", // or "Fail" or "Warn"
1042
- FinalPayloadValidationMaxRetries: 3
1043
- }
291
+ onMessageLifecycle: (event) => {
292
+ console.log(`${event.type}: ${event.reason} (saved ${event.tokensSaved} tokens)`);
293
+ }
294
+ });
1044
295
  ```
1045
296
 
1046
- Validation features:
1047
- - **JSON schema validation**: Using JSONValidator from @memberjunction/global
1048
- - **Multiple modes**:
1049
- - `Retry`: Re-execute with validation feedback (up to max retries)
1050
- - `Fail`: Immediately fail the run
1051
- - `Warn`: Log warning but allow success
1052
- - **Retry tracking**: Prevents infinite validation loops
1053
- - **Step-level logging**: Validation results stored in AIAgentRunStep
1054
-
1055
- ### Execution Guardrails
1056
-
1057
- New fields provide comprehensive limits to prevent runaway agent execution:
1058
-
1059
- ```typescript
1060
- // Configure guardrails in AIAgent entity
1061
- {
1062
- MaxCostPerRun: 10.00, // $10 maximum
1063
- MaxTokensPerRun: 100000, // 100k tokens total
1064
- MaxIterationsPerRun: 50, // 50 prompt iterations
1065
- MaxTimePerRun: 300 // 5 minutes
1066
- }
1067
-
1068
- // The framework monitors these in real-time and terminates if exceeded
1069
- // Termination reason is logged in AIAgentRun.ErrorMessage
1070
- ```
1071
-
1072
- Guardrail features:
1073
- - **Cost tracking**: Monitors cumulative API costs
1074
- - **Token counting**: Tracks input + output tokens
1075
- - **Iteration limits**: Counts each prompt execution
1076
- - **Time limits**: Enforces maximum execution duration
1077
- - **Graceful termination**: Saves state before stopping
1078
-
1079
- ### Run Chaining
1080
-
1081
- The framework supports linking multiple agent runs together to maintain context across interactions:
1082
-
1083
- ```typescript
1084
- // Execute an agent with run chaining
1085
- const result = await agent.Execute({
1086
- agent: agentEntity,
1087
- conversationMessages: messages,
1088
- contextUser: user,
1089
- lastRunId: previousRunId, // Links to previous run
1090
- autoPopulateLastRunPayload: true // Auto-loads previous payload
1091
- });
1092
-
1093
- // The framework will:
1094
- // 1. Load the FinalPayload from the previous run
1095
- // 2. Set it as StartingPayload for the new run
1096
- // 3. Use it as the initial payload if none provided
1097
- // 4. Validate against circular references in the chain
1098
- ```
1099
-
1100
- Key features:
1101
- - **LastRunID**: Links runs in a chain (different from ParentRunID for sub-agents)
1102
- - **StartingPayload**: Captures the initial state of each run
1103
- - **Auto-population**: Reduces bandwidth by avoiding payload round-trips
1104
- - **Circular reference detection**: Prevents infinite loops in run chains
1105
-
1106
- ### Payload Management and Change Detection
1107
-
1108
- The framework includes sophisticated payload management with automatic change detection:
1109
-
1110
- ```typescript
1111
- // Payload changes are automatically analyzed
1112
- const changeResult = payloadManager.applyAgentChangeRequest(
1113
- originalPayload,
1114
- changeRequest,
1115
- {
1116
- analyzeChanges: true, // Detect suspicious changes
1117
- generateDiff: true, // Create audit trail
1118
- agentName: 'MyAgent'
1119
- }
1120
- );
1121
-
1122
- // Suspicious changes are flagged:
1123
- // - Content truncation (>70% reduction)
1124
- // - Non-empty key removal
1125
- // - Type changes (object→primitive)
1126
- // - Pattern anomalies (placeholder replacement)
1127
- ```
1128
-
1129
- **Sub-agent Payload Access Control**:
1130
- ```typescript
1131
- // In AIAgent entity configuration:
1132
- {
1133
- PayloadDownstreamPaths: ["customer.id", "order.*"], // What sub-agent can read
1134
- PayloadUpstreamPaths: ["analysis.*", "recommendations"] // What sub-agent can write
1135
- }
1136
- ```
1137
-
1138
- **Operation-Level Payload Control**:
1139
- The framework supports fine-grained control over which operations (add, update, delete) are allowed on specific payload paths:
1140
-
1141
- ```typescript
1142
- // Basic syntax - all operations allowed (backward compatible)
1143
- PayloadUpstreamPaths: ["analysis.*", "recommendations"]
1144
-
1145
- // Operation-specific syntax using colon notation
1146
- PayloadUpstreamPaths: [
1147
- "analysis.*:add,update", // Can add or update, but not delete
1148
- "recommendations:add", // Can only add new recommendations
1149
- "summary:update", // Can only update existing summary
1150
- "temp.*:delete", // Can only delete temporary data
1151
- "metadata.tags:add,delete" // Can add/remove tags but not modify existing
1152
- ]
1153
-
1154
- // For agent's own payload access (PayloadSelfWritePaths)
1155
- PayloadSelfWritePaths: [
1156
- "workspace.*", // Full access to workspace
1157
- "results:add", // Can only add results, not modify
1158
- "status:update" // Can only update status field
1159
- ]
1160
- ```
1161
-
1162
- Operation types:
1163
- - `add` - Create new properties or array elements
1164
- - `update` - Modify existing values
1165
- - `delete` - Remove properties or array elements
1166
-
1167
- When operations are restricted, the framework will:
1168
- - Log warnings when unauthorized operations are attempted
1169
- - Block the disallowed changes while preserving allowed ones
1170
- - Include operation details in the audit trail
1171
-
1172
- ### Hierarchical Prompt Execution
1173
- ```typescript
1174
- // System prompt provides base behavior
1175
- // Agent prompts execute as children with shared context
1176
- const result = await agent.ExecutePrompt({
1177
- systemPrompt: agentType.SystemPrompt,
1178
- agentPrompt: currentPrompt,
1179
- messages: conversationContext
1180
- });
1181
- ```
1182
-
1183
- ### Context Management
1184
- Agents automatically manage conversation context:
1185
- - Maintains message history across steps
1186
- - **Intelligent message expiration** - Automatically compacts or removes old action results
1187
- - Compresses context when approaching token limits
1188
- - Handles placeholder replacement in prompts
1189
- - Preserves important context during compression
1190
-
1191
- ### Message Expiration and Compaction
1192
-
1193
- The framework provides sophisticated message lifecycle management to prevent context bloat from large action results:
1194
-
1195
- **Per-Action Configuration** (in `AIAgentAction` table):
1196
- - `ResultExpirationTurns`: Number of turns before message expires (e.g., 2)
1197
- - `ResultExpirationMode`: 'None' | 'Remove' | 'Compact'
1198
- - `CompactMode`: 'First N Chars' | 'AI Summary'
1199
- - `CompactLength`: Character limit for 'First N Chars' mode
1200
- - `CompactPromptID`: Custom AI prompt for 'AI Summary' mode
1201
-
1202
- **How It Works**:
1203
- ```typescript
1204
- // Configure a Google Search action to compact results after 2 turns
1205
- await agentAction.Save({
1206
- ResultExpirationTurns: 2,
1207
- ResultExpirationMode: 'Compact',
1208
- CompactMode: 'First N Chars',
1209
- CompactLength: 500
1210
- });
1211
-
1212
- // Turn 1: Action returns 10,000 char search results
1213
- // Turn 2: Results still in conversation (turn 1, limit 2)
1214
- // Turn 3: Results still in conversation (turn 2, limit 2)
1215
- // Turn 4: Results compacted to 500 chars (turn 3 > limit 2)
1216
- // Original content preserved in metadata for expansion
1217
- ```
1218
-
1219
- **Compaction Modes**:
1220
- 1. **First N Chars**: Fast truncation with annotation
1221
- ```
1222
- First 500 chars of result...
1223
-
1224
- [Compacted: showing first 500 of 10000 characters. Agent can request expansion if needed.]
1225
- ```
1226
-
1227
- 2. **AI Summary**: Intelligent LLM-based summarization
1228
- ```
1229
- [AI Summary of 10000 chars. Agent can request full expansion if needed.]
1230
-
1231
- Search found 47 results for "MemberJunction". Top results include...
1232
- ```
1233
-
1234
- **Message Expansion**:
1235
- Agents can restore compacted messages when needed:
1236
- ```typescript
1237
- // In agent's JSON response
1238
- {
1239
- "taskComplete": false,
1240
- "nextStep": {
1241
- "type": "Retry",
1242
- "messageIndex": 5, // Index of compacted message
1243
- "reason": "Need full search results to answer user's question about item #47"
1244
- }
1245
- }
1246
- ```
1247
-
1248
- **Runtime Override**:
1249
- Test different expiration strategies without modifying database:
1250
- ```typescript
1251
- const result = await runner.RunAgent({
1252
- agent: myAgent,
1253
- conversationMessages: messages,
1254
- contextUser: user,
1255
- messageExpirationOverride: {
1256
- expirationTurns: 1,
1257
- expirationMode: 'Compact',
1258
- compactMode: 'First N Chars',
1259
- compactLength: 200,
1260
- preserveOriginalContent: true
1261
- }
1262
- });
1263
- ```
1264
-
1265
- **Lifecycle Monitoring**:
1266
- Track message compaction for debugging and token savings analysis:
1267
- ```typescript
1268
- const result = await runner.RunAgent({
1269
- agent: myAgent,
1270
- conversationMessages: messages,
1271
- contextUser: user,
1272
- onMessageLifecycle: (event) => {
1273
- console.log(`[Turn ${event.turn}] ${event.type}: ${event.reason}`);
1274
- if (event.tokensSaved) {
1275
- console.log(` Tokens saved: ${event.tokensSaved}`);
1276
- }
1277
- }
1278
- });
1279
- // Output:
1280
- // [Turn 3] message-compacted: Compacted using First N Chars (saved 2375 tokens)
1281
- // [Turn 5] message-removed: Removed due to expiration
1282
- ```
1283
-
1284
- **Prompt Lookup Hierarchy**:
1285
- For AI Summary mode, prompts are resolved in this order:
1286
- 1. Runtime override (`messageExpirationOverride.compactPromptId`)
1287
- 2. Agent action configuration (`AIAgentAction.CompactPromptID`)
1288
- 3. Action default (`Action.DefaultCompactPromptID`)
1289
- 4. System default ("Compact Agent Message" prompt)
1290
-
1291
- **Benefits**:
1292
- - **Addresses Large Action Results**: Automatically handles the most common cause of context bloat
1293
- - **Configurable Per-Action**: Different expiration strategies for different action types
1294
- - **Non-Destructive**: Original content preserved in metadata for on-demand expansion
1295
- - **Token Savings**: Reduces context window usage by 70-95% for large results
1296
- - **Agent-Aware**: Agents can detect compacted messages and request full expansion when needed
1297
-
1298
- ### Context Length Recovery
1299
-
1300
- When a prompt execution fails due to context length overflow (even after model failover), BaseAgent provides **one-time automatic recovery** instead of immediately terminating. This gives the agent an opportunity to adapt its approach.
1301
-
1302
- **How It Works**:
1303
- 1. **Prompt fails with ContextLengthExceeded** → Detected as fatal error
1304
- 2. **First occurrence**: Recovery is attempted automatically (once per run)
1305
- 3. **Last user message is trimmed** using smart strategies:
1306
- - JSON arrays: Keeps first 10 items with truncation notice
1307
- - CSV data: Keeps header + first 10 rows
1308
- - Plain text: Keeps first 1000 characters
1309
- 4. **Agent receives clear guidance** explaining what happened and recommended actions
1310
- 5. **Agent gets Retry step** to choose alternative approach (e.g., more specific filters, batch requests)
1311
- 6. **If recovery fails again**: Normal fatal error handling (agent terminates)
1312
-
1313
- **Example Recovery Message**:
1314
- ```
1315
- ⚠️ CONTEXT OVERFLOW RECOVERY ⚠️
1316
-
1317
- The previous step returned a result that exceeded the context window (147,532 characters truncated).
1318
-
1319
- Here is a PARTIAL result from the previous action:
1320
- ---
1321
- [First 10 items from JSON array...]
1322
- ... (487 more items truncated due to context length)
1323
- ---
1324
-
1325
- ❗ THE ABOVE IS INCOMPLETE - the full result was too large for the context window.
1326
-
1327
- RECOMMENDED ACTIONS:
1328
- 1. Use a different action with more specific filters to get smaller result sets
1329
- 2. Request data in batches or pages instead of all at once
1330
- 3. Ask the user to clarify scope to narrow the query
1331
- 4. If you need the full data, acknowledge the limitation and ask the user how to proceed
1332
-
1333
- Please choose an alternative approach to complete your task.
1334
- ```
1335
-
1336
- **Benefits**:
1337
- - **Resilient**: Agents can adapt instead of failing immediately
1338
- - **Informative**: Clear explanation of what went wrong and how to recover
1339
- - **Safe**: ONE-TIME recovery prevents infinite loops
1340
- - **Smart**: Preserves data structure when possible (JSON, CSV)
1341
-
1342
- This feature is particularly useful when agents call actions that can return very large datasets (e.g., "Get Entity List" without filters).
1343
-
1344
- ### Action Integration
1345
- ```typescript
1346
- // In agent type's DetermineNextStep
1347
- return {
1348
- type: 'action',
1349
- actionName: 'SendEmail',
1350
- actionParams: {
1351
- to: 'user@example.com',
1352
- subject: 'Analysis Complete',
1353
- body: analysisResult
1354
- }
1355
- };
1356
- ```
1357
-
1358
- ### Sub-agent Orchestration
1359
- ```typescript
1360
- // Agents can invoke other agents recursively
1361
- return {
1362
- type: 'sub_agent',
1363
- agentName: 'DataValidationAgent',
1364
- messages: [
1365
- { role: 'user', content: `Validate this data: ${JSON.stringify(data)}` }
1366
- ]
1367
- };
1368
- ```
1369
-
1370
- ### Conversation Message Mapping for Actions and Sub-Agents
1371
-
1372
- The framework includes a **ConversationMessageResolver** utility that enables flexible conversation message referencing in action input mappings and sub-agent configurations. This is particularly useful for passing conversation context to knowledge base assistants, chatbots, or analysis agents.
1373
-
1374
- #### Basic Usage with Actions
1375
-
1376
- **In Flow Agent Step Configuration** (`ActionInputMapping`):
1377
- ```typescript
1378
- // Pass full conversation history to an action
1379
- {
1380
- "ActionInputMapping": {
1381
- "ConversationMessages": "conversation.all"
1382
- }
1383
- }
1384
- ```
1385
-
1386
- **In Loop Agent Response**:
1387
- ```typescript
1388
- {
1389
- "taskComplete": false,
1390
- "nextStep": {
1391
- "type": "Actions",
1392
- "actions": [{
1393
- "name": "Betty", // Knowledge base assistant
1394
- "params": {
1395
- "ConversationMessages": "conversation.all"
1396
- }
1397
- }]
1398
- }
1399
- }
1400
- ```
1401
-
1402
- #### Supported Conversation Patterns
1403
-
1404
- The `ConversationMessageResolver` supports powerful pattern-based message selection:
1405
-
1406
- **1. All Messages**:
1407
- ```typescript
1408
- "ConversationMessages": "conversation.all"
1409
- // Returns entire conversation history
1410
- ```
1411
-
1412
- **2. Role-Based Selection**:
1413
- ```typescript
1414
- // Last N user messages
1415
- "ConversationMessages": "conversation.user.last[5]"
1416
-
1417
- // Last N assistant messages
1418
- "ConversationMessages": "conversation.assistant.last[3]"
1419
-
1420
- // Last N system messages
1421
- "ConversationMessages": "conversation.system.last[1]"
1422
- ```
1423
-
1424
- **3. All Messages of a Role**:
1425
- ```typescript
1426
- // All user messages
1427
- "ConversationMessages": "conversation.user.all"
1428
-
1429
- // All assistant messages
1430
- "ConversationMessages": "conversation.assistant.all"
1431
- ```
1432
-
1433
- **4. Single Last Message by Role**:
1434
- ```typescript
1435
- // Just the last user message
1436
- "ConversationMessages": "conversation.user.last"
1437
-
1438
- // Just the last assistant message
1439
- "ConversationMessages": "conversation.assistant.last"
1440
- ```
1441
-
1442
- #### Use Cases
1443
-
1444
- **Knowledge Base Assistants** - Pass full conversation history for context-aware responses:
1445
- ```typescript
1446
- // In Knowledge Base Research Agent step
1447
- {
1448
- "StepType": "Action",
1449
- "ActionID": "betty-action-id",
1450
- "ActionInputMapping": {
1451
- "ConversationMessages": "conversation.all" // Betty sees full context
1452
- }
1453
- }
1454
- ```
1455
-
1456
- **Sentiment Analysis** - Analyze just user messages:
1457
- ```typescript
1458
- {
1459
- "ActionInputMapping": {
1460
- "messagesToAnalyze": "conversation.user.last[10]",
1461
- "includeSentiment": true
1462
- }
1463
- }
1464
- ```
1465
-
1466
- **Context Summarization** - Summarize recent conversation:
1467
- ```typescript
1468
- {
1469
- "ActionInputMapping": {
1470
- "recentMessages": "conversation.all", // or "conversation.last[20]" if supported
1471
- "summarizeAs": "bullet_points"
1472
- }
1473
- }
1474
- ```
1475
-
1476
- **Follow-up Question Generation** - Based on assistant responses:
1477
- ```typescript
1478
- {
1479
- "ActionInputMapping": {
1480
- "previousResponses": "conversation.assistant.last[3]",
1481
- "generateFollowUps": true
1482
- }
1483
- }
1484
- ```
1485
-
1486
- #### Sub-Agent Usage
1487
-
1488
- Sub-agents automatically receive the parent's conversation context, but you can control which messages are passed:
1489
-
1490
- **In Loop Agent** (via agent prompt instructions):
1491
- ```typescript
1492
- // The agent's system prompt can instruct:
1493
- "When invoking sub-agents, you can specify which conversation messages to pass using the ConversationMessages parameter in your action input mappings."
1494
- ```
1495
-
1496
- **In Flow Agent** (via SubAgentConfiguration):
1497
- ```typescript
1498
- // Configure sub-agent relationships with conversation context
1499
- {
1500
- "AgentID": "parent-agent-id",
1501
- "SubAgentID": "knowledge-base-research-agent-id",
1502
- "SubAgentOutputMapping": { "*": "knowledgeBaseResearch" },
1503
- "SubAgentContextPaths": ["*"] // Full context by default
1504
- }
1505
- ```
1506
-
1507
- #### How It Works
1508
-
1509
- The resolver operates during the parameter mapping phase:
1510
-
1511
- 1. **Detection**: Identifies `conversation.` prefixed strings in action/sub-agent parameters
1512
- 2. **Pattern Matching**: Parses the pattern (role, selector, count)
1513
- 3. **Message Filtering**: Extracts matching messages from conversation history
1514
- 4. **Type Validation**: Ensures the target parameter expects an array of messages
1515
- 5. **Injection**: Replaces the pattern with actual message objects
1516
-
1517
- **Example Resolution**:
1518
- ```typescript
1519
- // Input mapping configuration
1520
- {
1521
- "ConversationMessages": "conversation.user.last[3]"
1522
- }
1523
-
1524
- // Conversation history
1525
- [
1526
- { role: 'system', content: 'You are a helpful assistant' },
1527
- { role: 'user', content: 'What is MemberJunction?' },
1528
- { role: 'assistant', content: 'MemberJunction is...' },
1529
- { role: 'user', content: 'How do agents work?' },
1530
- { role: 'assistant', content: 'Agents work by...' },
1531
- { role: 'user', content: 'Can you give an example?' }
1532
- ]
1533
-
1534
- // Resolved parameter value (last 3 user messages)
1535
- [
1536
- { role: 'user', content: 'What is MemberJunction?' },
1537
- { role: 'user', content: 'How do agents work?' },
1538
- { role: 'user', content: 'Can you give an example?' }
1539
- ]
1540
- ```
1541
-
1542
- #### Benefits
1543
-
1544
- - **Context-Aware Actions**: Actions receive relevant conversation history
1545
- - **Flexible Filtering**: Select exactly which messages are needed
1546
- - **Declarative Configuration**: No custom code needed in action implementations
1547
- - **Type Safety**: Resolver validates parameter types at runtime
1548
- - **Performance**: Only selected messages are passed, reducing token usage
1549
- - **Composability**: Works seamlessly with Flow and Loop agents
1550
-
1551
- #### Integration with Betty Knowledge Base Action
1552
-
1553
- A prime example of this feature is the Betty action for knowledge base queries:
1554
-
1555
- ```typescript
1556
- // Betty action accepts ConversationMessages parameter
1557
- {
1558
- "name": "Betty",
1559
- "params": {
1560
- "ConversationMessages": "conversation.all" // Full conversation context
1561
- }
1562
- }
1563
-
1564
- // Betty uses the conversation history to provide context-aware responses
1565
- // and can reference earlier questions/answers in its knowledge base queries
1566
- ```
1567
-
1568
- This enables knowledge base agents to maintain conversation context across multiple queries, improving response relevance and follow-up question handling.
1569
-
1570
- ## Agent Permissions System
1571
-
1572
- The agent framework includes a comprehensive ACL-based permissions system that controls who can view, run, edit, and delete agents.
1573
-
1574
- ### Permission Model
1575
-
1576
- The permissions system uses **hierarchical permissions** with the following levels:
1577
-
1578
- 1. **View** - See agent configuration and details
1579
- 2. **Run** - Execute the agent (implies View)
1580
- 3. **Edit** - Modify agent configuration (implies Run and View)
1581
- 4. **Delete** - Remove the agent (implies Edit, Run, and View)
1582
-
1583
- ### Default Permission Behavior
1584
-
1585
- The system uses an **"open by default"** approach to minimize administrative overhead:
1586
-
1587
- - **No permission records exist**: Anyone can **View** and **Run** the agent
1588
- - **Owner**: Always has full permissions (View, Run, Edit, Delete)
1589
- - **Explicit permissions**: When permission records exist, only users/roles with matching permissions can access
1590
-
1591
- **Why this approach?**
1592
- - Minimizes setup overhead for most agents
1593
- - Allows broad access for running agents (common use case)
1594
- - Protects modification operations (Edit/Delete) through ownership
1595
- - Explicit permissions provide fine-grained control when needed
1596
-
1597
- ### Ownership
1598
-
1599
- Every agent has an `OwnerUserID` field:
1600
- - Owners always have full permissions regardless of ACL records
1601
- - Defaults to the user who created the agent
1602
- - Can be transferred by editing the agent
1603
-
1604
- ### Permission Records
1605
-
1606
- Permission records are stored in the `AIAgentPermission` table with these fields:
1607
-
1608
- - **AgentID** - The agent being controlled
1609
- - **UserID** - Direct user permission (mutually exclusive with RoleID)
1610
- - **RoleID** - Role-based permission (mutually exclusive with UserID)
1611
- - **CanView** - Boolean flag for view permission
1612
- - **CanRun** - Boolean flag for run permission
1613
- - **CanEdit** - Boolean flag for edit permission
1614
- - **CanDelete** - Boolean flag for delete permission
1615
- - **Comments** - Optional description of why permission was granted
1616
-
1617
- **Important**: Each record must have either `UserID` OR `RoleID` set, but not both.
1618
-
1619
- ### Permission Resolution
1620
-
1621
- When checking if a user can perform an operation:
1622
-
1623
- 1. **Check ownership** - If user is the owner, grant all permissions
1624
- 2. **Check if no permissions exist** - Grant View and Run by default
1625
- 3. **Find matching permissions** - Get all records for the user OR their roles
1626
- 4. **Apply OR logic** - If ANY permission grants access, allow the operation
1627
- 5. **Apply hierarchy** - Higher permissions automatically grant lower ones:
1628
- - Delete → Edit → Run → View
1629
- - If you have Run permission, you automatically get View
1630
- - If you have Edit permission, you automatically get Run and View
1631
-
1632
- ### Using Permissions in Code
1633
-
1634
- The framework provides helper methods for checking permissions:
1635
-
1636
- ```typescript
1637
- import { AIEngineBase } from '@memberjunction/ai-engine-base';
1638
- import { AIAgentPermissionHelper } from '@memberjunction/ai-engine-base';
1639
-
1640
- // Check specific permission
1641
- const canRun = await AIEngineBase.Instance.CanUserRunAgent(agentId, user);
1642
- const canEdit = await AIEngineBase.Instance.CanUserEditAgent(agentId, user);
1643
-
1644
- // Get all effective permissions
1645
- const permissions = await AIEngineBase.Instance.GetUserAgentPermissions(agentId, user);
1646
- console.log(permissions);
1647
- // {
1648
- // canView: true,
1649
- // canRun: true,
1650
- // canEdit: false,
1651
- // canDelete: false,
1652
- // isOwner: false
1653
- // }
1654
-
1655
- // Get all agents user can access with specific permission
1656
- const runnableAgents = await AIEngineBase.Instance.GetAccessibleAgents(user, 'run');
1657
-
1658
- // Using the helper directly
1659
- const hasPermission = await AIAgentPermissionHelper.HasPermission(agentId, user, 'run');
1660
- ```
1661
-
1662
- ### Runtime Permission Enforcement
1663
-
1664
- The BaseAgent class automatically enforces run permissions:
1665
-
1666
- ```typescript
1667
- // BaseAgent.Execute() checks permissions before running
1668
- const result = await agent.Execute({
1669
- agent: agentEntity,
1670
- conversationMessages: messages,
1671
- contextUser: user
1672
- });
1673
-
1674
- // If user lacks run permission, execution fails with:
1675
- // Error: "User {email} does not have permission to run agent '{name}'"
1676
- ```
1677
-
1678
- ### Managing Permissions in the UI
1679
-
1680
- The AI Agent form includes a "Permissions" button that opens a dialog for managing permissions:
1681
-
1682
- - View all existing permissions for the agent
1683
- - Add new user or role-based permissions
1684
- - Edit existing permissions with hierarchical checkboxes
1685
- - Delete permissions to return to default behavior
1686
- - See effective permissions after hierarchy is applied
1687
- - Display owner information with visual indicator
1688
-
1689
- ### Permission Caching
1690
-
1691
- Permissions are cached in the AIEngineBase metadata system for performance:
1692
-
1693
- ```typescript
1694
- // Clear cache after modifying permissions
1695
- AIEngineBase.Instance.ClearAgentPermissionsCache();
1696
-
1697
- // Refresh cache for specific agent
1698
- await AIEngineBase.Instance.RefreshAgentPermissionsCache(agentId, user);
1699
- ```
1700
-
1701
- ### Best Practices for Permissions
1702
-
1703
- 1. **Start Open**: Let anyone run new agents by default, add restrictions only when needed
1704
- 2. **Use Roles**: Grant permissions to roles instead of individual users when possible
1705
- 3. **Document Permissions**: Use the Comments field to explain why permissions were granted
1706
- 4. **Ownership Transfer**: Transfer ownership when primary maintainers change
1707
- 5. **Hierarchical Thinking**: Set the highest permission needed; lower ones are automatic
1708
- 6. **Test Access**: Verify permissions work as expected before deploying agents
1709
- 7. **Regular Audits**: Review permission records periodically to remove unnecessary entries
1710
-
1711
- ### Example Permission Scenarios
1712
-
1713
- **Scenario 1: Public Agent**
1714
- - No permission records
1715
- - Anyone can view and run
1716
- - Only owner can edit/delete
1717
-
1718
- **Scenario 2: Department Agent**
1719
- - Permission record: Role="Sales Team", CanRun=true
1720
- - Sales team members can view and run
1721
- - Owner can edit/delete
1722
- - Others cannot access
1723
-
1724
- **Scenario 3: Restricted Agent**
1725
- - Permission record: Role="Admins", CanEdit=true
1726
- - Admins can view, run, edit (and delete via hierarchy)
1727
- - Permission record: Role="Developers", CanRun=true
1728
- - Developers can view and run
1729
- - Owner has full access
1730
- - Others cannot access
1731
-
1732
- **Scenario 4: Shared Ownership**
1733
- - Permission record: User="alice@example.com", CanEdit=true
1734
- - Alice can view, run, and edit
1735
- - Permission record: User="bob@example.com", CanEdit=true
1736
- - Bob can view, run, and edit
1737
- - Owner can delete
1738
- - Creates "co-owner" scenario for collaborative development
1739
-
1740
- ## Database Schema
1741
-
1742
- Key entities used by the agent framework:
1743
-
1744
- - **AIAgentType**: Agent behavior patterns and system prompts
1745
- - **AIAgent**: Configured agent instances
1746
- - `OwnerUserID`: User who owns the agent (defaults to creator, grants full permissions)
1747
- - `PayloadDownstreamPaths`: JSON array of paths sub-agents can read
1748
- - `PayloadUpstreamPaths`: JSON array of paths sub-agents can write
1749
- - **NEW** `PayloadScope`: Path to narrow payload for sub-agents (e.g., "/functionalRequirements")
1750
- - **NEW** `StartingPayloadValidation`: JSON validation schema for input validation
1751
- - **NEW** `StartingPayloadValidationMode`: How to handle input validation failures (Fail/Warn)
1752
- - **NEW** `FinalPayloadValidation`: JSON validation schema for success validation
1753
- - **NEW** `FinalPayloadValidationMode`: How to handle validation failures (Retry/Fail/Warn)
1754
- - **NEW** `FinalPayloadValidationMaxRetries`: Maximum retry attempts for validation (default: 3)
1755
- - **NEW** `MaxCostPerRun`: Cost limit per agent run
1756
- - **NEW** `MaxTokensPerRun`: Token limit per agent run
1757
- - **NEW** `MaxIterationsPerRun`: Iteration limit per agent run
1758
- - **NEW** `MaxTimePerRun`: Time limit in seconds per agent run
1759
- - **AIAgentPermission**: Permission records for agent access control
1760
- - `AgentID`: The agent being controlled
1761
- - `UserID`: Direct user permission (exclusive with RoleID)
1762
- - `RoleID`: Role-based permission (exclusive with UserID)
1763
- - `CanView`: View agent configuration
1764
- - `CanRun`: Execute the agent
1765
- - `CanEdit`: Modify agent configuration
1766
- - `CanDelete`: Remove the agent
1767
- - `Comments`: Optional permission description
1768
- - **AIPrompt**: Reusable prompt templates with placeholders
1769
- - **AIAgentPrompt**: Links agents to prompts with execution order
1770
- - **AIAgentRun**: Tracks complete agent executions
1771
- - `LastRunID`: Links to previous run in a chain (for run chaining)
1772
- - `StartingPayload`: Initial payload for the run
1773
- - **NEW** `TotalPromptIterations`: Count of prompt executions in the run
1774
- - **AIAgentRunStep**: Records individual steps within runs
1775
- - `PayloadAtStart`: JSON snapshot of payload before step
1776
- - `PayloadAtEnd`: JSON snapshot of payload after step
1777
- - `OutputData`: Includes `payloadChangeResult` with analysis
1778
- - **NEW** `FinalPayloadValidationResult`: Validation outcome (Pass/Retry/Fail/Warn)
1779
- - **NEW** `FinalPayloadValidationMessages`: Validation error messages
1780
- - **AIAgentRunStepAction**: Details of actions executed
1781
- - **AIAgentRunStepPrompt**: Prompt execution details
1782
-
1783
- ## Best Practices
1784
-
1785
- 1. **Hierarchical Design**: Use system prompts for base behavior, agent prompts for specifics
1786
- 2. **Structured Responses**: Design prompts to return parseable JSON for agent types
1787
- 3. **Modular Prompts**: Break complex tasks into ordered, focused prompts
1788
- 4. **Proper Type Registration**: Register custom agent types with ClassFactory
1789
- 5. **Comprehensive Tracking**: Leverage built-in tracking for debugging and analysis
1790
- 6. **Context Efficiency**: Let the framework handle context compression automatically
1791
- 7. **Error Handling**: Implement robust error handling in custom agent types
1792
- 8. **Payload Security**: Use path-based access control for sub-agents
1793
- 9. **Change Monitoring**: Review payload change warnings in OutputData
1794
- 10. **Payload Scoping**: Use PayloadScope to reduce token usage for sub-agents
1795
- 11. **Input Validation**: Define StartingPayloadValidation to catch errors early
1796
- 12. **Output Validation**: Define FinalPayloadValidation for output quality control
1797
- 13. **Set Guardrails**: Configure cost/token/time limits to prevent runaway execution
1798
- 14. **Monitor Retries**: Track validation retry counts to avoid infinite loops
1799
- 15. **Fail Fast**: Use StartingPayloadValidation with 'Fail' mode for deterministic behavior
1800
- 16. **Permission Strategy**: Start with open access, add restrictions only when needed
1801
- 17. **Role-Based Permissions**: Use role-based permissions for easier management at scale
1802
- 18. **Document Access**: Use Comments field in permission records to explain grant rationale
1803
-
1804
- ## Examples
1805
-
1806
- ### Basic Loop Agent
1807
- ```typescript
1808
- // Agent type configured with LoopAgentType driver
1809
- // System prompt defines JSON response format
1810
- // Agent prompts execute tasks iteratively
1811
- const result = await runner.RunAgent({
1812
- agent: loopAgent,
1813
- conversationMessages: [
1814
- { role: 'user', content: 'Analyze these sales figures and create a report' }
1815
- ],
1816
- contextUser: user
1817
- });
1818
- ```
1819
-
1820
- ### Payload Operation Control Example
1821
- ```typescript
1822
- // Configure an agent with specific operation permissions
1823
- const analysisAgent = {
1824
- Name: 'DataAnalysisAgent',
1825
- PayloadSelfWritePaths: JSON.stringify([
1826
- "workspace.*", // Full control over workspace
1827
- "analysis.results:add", // Can only add new results
1828
- "analysis.status:update", // Can only update status
1829
- "temp.*:add,delete" // Can add/delete temp data, but not modify
1830
- ])
1831
- };
1832
-
1833
- // Configure a sub-agent with restricted write access
1834
- const validationAgent = {
1835
- Name: 'ValidationAgent',
1836
- PayloadDownstreamPaths: JSON.stringify([
1837
- "data.*", // Can read all data
1838
- "analysis.results" // Can read analysis results
1839
- ]),
1840
- PayloadUpstreamPaths: JSON.stringify([
1841
- "data.validated:update", // Can only update validation flag
1842
- "errors:add", // Can only add errors, not modify
1843
- "warnings:add,delete" // Can add/remove warnings
1844
- ])
1845
- };
1846
-
1847
- // When the sub-agent tries unauthorized operations:
1848
- // - Attempt to delete data.records → Blocked (no delete permission)
1849
- // - Attempt to update errors → Blocked (only add permission)
1850
- // - Add new warning → Allowed
1851
- // - Update data.validated → Allowed
1852
- ```
1853
-
1854
- ### Custom Decision Tree Agent
1855
- ```typescript
1856
- @RegisterClass(BaseAgentType, "DecisionTreeAgent")
1857
- export class DecisionTreeAgent extends BaseAgentType {
1858
- async DetermineNextStep(result: AIPromptRunResult): Promise<BaseAgentNextStep> {
1859
- const decision = JSON.parse(result.FullResult);
1860
-
1861
- switch(decision.branch) {
1862
- case 'needs_data':
1863
- return { type: 'action', actionName: 'FetchData', actionParams: decision.params };
1864
- case 'analyze':
1865
- return { type: 'sub_agent', agentName: 'AnalysisAgent', messages: decision.context };
1866
- case 'complete':
1867
- return { type: 'stop', reason: decision.summary };
1868
- default:
1869
- return { type: 'continue' };
1870
- }
1871
- }
1872
- }
1873
- ```
1874
-
1875
- ## Flow Agent Type - Deterministic Workflows
1876
-
1877
- Flow agents execute **deterministic, graph-based workflows** where the execution path is determined by boolean conditions evaluated against the payload and step results. Unlike Loop agents that rely on LLM decision-making at each step, Flow agents follow predefined paths through a directed graph.
1878
-
1879
- ### When to Use Flow Agents
1880
-
1881
- Flow agents are ideal for:
1882
- - **Predictable workflows** with well-defined decision points
1883
- - **Approval processes** with conditional routing
1884
- - **Data pipelines** with validation and transformation steps
1885
- - **Hybrid workflows** combining deterministic logic with AI prompts
1886
- - **Multi-step processes** where you need guaranteed execution order
1887
-
1888
- ### Flow Agent Execution Parameters
1889
-
1890
- Flow agents support specialized execution parameters via `FlowAgentExecuteParams` (v2.127+) that allow runtime customization of how the flow executes:
1891
-
1892
- ```typescript
1893
- import { FlowAgentExecuteParams } from '@memberjunction/ai-agents';
1894
- import { ExecuteAgentParams } from '@memberjunction/ai-core-plus';
1895
- import { AIEngine } from '@memberjunction/ai-engine-base';
1896
-
1897
- // Get the steps you want to work with
1898
- const agentSteps = AIEngine.Instance.GetAgentSteps(myFlowAgent.ID);
1899
- const approvalStep = agentSteps.find(s => s.Name === 'Approval Review');
1900
- const notificationStep = agentSteps.find(s => s.Name === 'Send Notification');
1901
-
1902
- // Execute with Flow Agent-specific parameters
1903
- const params: ExecuteAgentParams<unknown, unknown, FlowAgentExecuteParams> = {
1904
- agent: myFlowAgent,
1905
- conversationMessages: messages,
1906
- contextUser: user,
1907
- agentTypeParams: {
1908
- // Start at a specific step instead of the configured entry point
1909
- startAtStep: approvalStep,
1910
-
1911
- // Skip these steps during execution
1912
- skipSteps: [notificationStep]
1913
- }
1914
- };
1915
-
1916
- const result = await runner.RunAgent(params);
1917
- ```
1918
-
1919
- #### FlowAgentExecuteParams Interface
1920
-
1921
- ```typescript
1922
- interface FlowAgentExecuteParams {
1923
- /**
1924
- * Start execution at a specific step instead of the flow's entry point.
1925
- *
1926
- * When provided, the flow agent will begin execution at this step,
1927
- * skipping all steps that would normally precede it. Useful for:
1928
- * - Resuming a flow from a specific point
1929
- * - Testing specific branches of a flow
1930
- * - Re-running a portion of a flow after a failure
1931
- *
1932
- * The step must belong to the agent being executed.
1933
- */
1934
- startAtStep?: AIAgentStepEntity;
1935
-
1936
- /**
1937
- * Steps to skip during execution.
1938
- *
1939
- * When the flow would normally execute one of these steps, it will
1940
- * instead immediately evaluate the step's outgoing paths and continue
1941
- * to the next valid step. Useful for:
1942
- * - Bypassing steps that have already been completed externally
1943
- * - Testing flows without certain side effects
1944
- * - Conditional step execution based on runtime state
1945
- *
1946
- * Skipped steps are recorded in the execution path but marked as skipped.
1947
- * The step's output mapping is not applied when skipped.
1948
- */
1949
- skipSteps?: AIAgentStepEntity[];
1950
- }
1951
- ```
1952
-
1953
- #### Use Cases
1954
-
1955
- **Resume Flow After Failure:**
1956
- ```typescript
1957
- // User's approval was rejected, they fixed issues and want to resume
1958
- const reviewStep = agentSteps.find(s => s.Name === 'Manager Review');
1959
-
1960
- await runner.RunAgent({
1961
- agent: approvalFlowAgent,
1962
- agentTypeParams: {
1963
- startAtStep: reviewStep // Skip validation, go straight to review
1964
- },
1965
- payload: fixedPayload
1966
- });
1967
- ```
1968
-
1969
- **Skip Steps Based on External State:**
1970
- ```typescript
1971
- // Notification was already sent via another system
1972
- const notifyStep = agentSteps.find(s => s.Name === 'Send Notification');
1973
- const auditStep = agentSteps.find(s => s.Name === 'Create Audit Log');
1974
-
1975
- await runner.RunAgent({
1976
- agent: workflowAgent,
1977
- agentTypeParams: {
1978
- skipSteps: [notifyStep, auditStep] // Skip these, they're handled externally
1979
- }
1980
- });
1981
- ```
1982
-
1983
- **Testing Specific Flow Branches:**
1984
- ```typescript
1985
- // Test only the rejection path
1986
- const rejectionStep = agentSteps.find(s => s.Name === 'Handle Rejection');
1987
-
1988
- await runner.RunAgent({
1989
- agent: approvalFlowAgent,
1990
- agentTypeParams: {
1991
- startAtStep: rejectionStep
1992
- },
1993
- payload: { decision: { approved: false, reason: 'Budget exceeded' } }
1994
- });
1995
- ```
1996
-
1997
- #### Behavior Notes
1998
-
1999
- - **startAtStep validation**: The step must belong to the agent being executed. If invalid, execution fails with a descriptive error.
2000
- - **skipSteps behavior**: Skipped steps are marked as completed in the flow state with `{ skipped: true, stepName: '...' }` as the result.
2001
- - **Path evaluation**: When a step is skipped, its outgoing paths are still evaluated to determine the next step.
2002
- - **Recursive skipping**: If the next step after a skipped step is also in `skipSteps`, it will also be skipped until a non-skipped step is reached.
2003
- - **Output mapping**: Skipped steps do not apply their `ActionOutputMapping` since no action is executed.
2004
-
2005
- ### Core Concepts
2006
-
2007
- #### 1. Workflow Steps (AIAgentStep)
2008
-
2009
- Steps are the nodes in your workflow graph. Each step represents an action to perform:
2010
-
2011
- ```typescript
2012
- // Three types of steps:
2013
- {
2014
- Name: 'ValidateInput',
2015
- StepType: 'Action', // Execute a MJ Action
2016
- ActionID: 'validation-action-id',
2017
- StartingStep: true, // Marks this as an entry point
2018
- Sequence: 0, // For parallel starting steps
2019
- Status: 'Active', // Active, Disabled, or Pending
2020
- TimeoutSeconds: 30 // Optional timeout
2021
- }
2022
-
2023
- {
2024
- Name: 'AnalyzeData',
2025
- StepType: 'Prompt', // Execute an AI prompt
2026
- PromptID: 'analysis-prompt-id',
2027
- Description: 'Analyze data quality and completeness'
2028
- }
2029
-
2030
- {
2031
- Name: 'ProcessWithSubAgent',
2032
- StepType: 'Sub-Agent', // Invoke another agent
2033
- SubAgentID: 'processing-agent-id'
2034
- }
2035
- ```
2036
-
2037
- #### 2. Workflow Paths (AIAgentStepPath)
2038
-
2039
- Paths are the edges connecting your workflow nodes. They determine the flow:
2040
-
2041
- ```typescript
2042
- {
2043
- OriginStepID: 'step-a-id',
2044
- DestinationStepID: 'step-b-id',
2045
- Condition: 'payload.amount > 1000 && payload.approved === true',
2046
- Priority: 10 // Higher priority paths evaluated first
2047
- }
2048
-
2049
- // Path without condition (always valid)
2050
- {
2051
- OriginStepID: 'step-a-id',
2052
- DestinationStepID: 'default-step-id',
2053
- Condition: null, // No condition = always valid
2054
- Priority: 0 // Lower priority = fallback
2055
- }
2056
- ```
2057
-
2058
- #### 3. Output Mapping with Array Append Syntax
2059
-
2060
- **Array Append Syntax** - When mapping outputs that can occur multiple times, use the `[]` suffix to append values to an array instead of replacing them:
2061
-
2062
- ```typescript
2063
- // SubAgentOutputMapping for agents that can be called multiple times
2064
- {
2065
- "*": "codeAnalysis[]" // Append each sub-agent result to array
2066
- }
2067
-
2068
- // ActionOutputMapping for actions that run in loops
2069
- {
2070
- "result": "findings[]", // Append to array
2071
- "score": "scores[]", // Each iteration adds to array
2072
- "*": "rawResults.allData[]" // Wildcard append
2073
- }
2074
-
2075
- // Without [] suffix (default behavior - replace)
2076
- {
2077
- "*": "latestResult" // Each call REPLACES the value
2078
- }
2079
-
2080
- // Array append features:
2081
- // - Auto-initializes array if it doesn't exist
2082
- // - Validates target is an array before appending
2083
- // - Prevents data loss from multiple sub-agent/action calls
2084
- // - Works with both simple and nested payload paths
2085
- ```
2086
-
2087
- **When to Use Array Append**:
2088
- - Sub-agents that can be invoked multiple times (e.g., Codesmith for different analysis tasks)
2089
- - Actions in ForEach/While loops where each iteration produces a result
2090
- - Accumulating multiple responses over agent execution
2091
- - Building collections of findings, recommendations, or analysis results
2092
-
2093
- **Example Use Case**:
2094
- ```typescript
2095
- // Research Agent with Codesmith sub-agent for code-based analytics
2096
- // Each time Codesmith is called, its output is appended to codeAnalysis[]
2097
-
2098
- // First call to Codesmith
2099
- {
2100
- "name": "Sales Trend Analysis",
2101
- "code": "...",
2102
- "output": { trend: "increasing", rate: 0.15 }
2103
- }
2104
-
2105
- // Second call to Codesmith
2106
- {
2107
- "name": "Customer Segmentation",
2108
- "code": "...",
2109
- "output": { segments: [...] }
2110
- }
2111
-
2112
- // Final payload.codeAnalysis array contains BOTH results:
2113
- [
2114
- { name: "Sales Trend Analysis", output: {...} },
2115
- { name: "Customer Segmentation", output: {...} }
2116
- ]
2117
- ```
2118
-
2119
- #### 4. Action Input/Output Mapping
2120
-
2121
- **Action Input Mapping** (`ActionInputMapping`) - Maps payload values to action parameters:
2122
-
2123
- ```typescript
2124
- // In AIAgentStep.ActionInputMapping
2125
- {
2126
- "customerId": "payload.customer.id", // Map from payload
2127
- "orderDate": "static:2024-01-01", // Static value
2128
- "includeDetails": true, // Boolean literal
2129
- "maxResults": 100, // Numeric literal
2130
- "filters": { // Nested object
2131
- "status": "payload.filters.orderStatus",
2132
- "region": "static:US-WEST"
2133
- },
2134
- "itemIds": "payload.order.items" // Can map arrays
2135
- }
2136
-
2137
- // Supports nested resolution
2138
- {
2139
- "searchParams": {
2140
- "query": "payload.searchTerm",
2141
- "filters": {
2142
- "category": "payload.category",
2143
- "tags": "payload.selectedTags"
2144
- },
2145
- "options": {
2146
- "maxResults": 50,
2147
- "includeMetadata": true
2148
- }
2149
- }
2150
- }
2151
- ```
2152
-
2153
- **Action Output Mapping** (`ActionOutputMapping`) - Maps action results back to payload or special fields:
2154
-
2155
- ```typescript
2156
- // In AIAgentStep.ActionOutputMapping
2157
- {
2158
- "userId": "payload.customer.id", // Map specific output param
2159
- "orderTotal": "payload.order.total", // Nested path in payload
2160
- "metadata": "payload.action.lastResult", // Arbitrary nesting
2161
- "*": "payload.rawResults.fullData", // Wildcard = entire result
2162
- "responseText": "$message", // Special field - user message
2163
- "analysisDetails": "$reasoning", // Special field - reasoning
2164
- "confidenceScore": "$confidence" // Special field - confidence
2165
- }
2166
-
2167
- // Case-insensitive output parameter matching
2168
- // If action returns { UserId: "123" }, it matches "userId" in mapping
2169
- ```
2170
-
2171
- **Special Fields** (Flow Agents Only):
2172
-
2173
- Use the `$` prefix to map action outputs to special response fields instead of the payload:
2174
-
2175
- - **`$message`**: Maps to the user-facing message in the final Success step
2176
- - **`$reasoning`**: Optional reasoning/explanation shown with the response
2177
- - **`$confidence`**: Optional confidence score (number) for the response
2178
-
2179
- **Example - Betty Knowledge Base Agent:**
2180
- ```typescript
2181
- // Betty action returns: { BettyResponse: "The answer is...", BettyReferences: [...] }
2182
- {
2183
- "BettyResponse": "$message", // Shows directly to user
2184
- "BettyReferences": "references" // Stored in payload
2185
- }
2186
-
2187
- // When flow completes, user sees Betty's response as the message
2188
- // No LLM processing needed - deterministic, single-step flow
2189
- ```
2190
-
2191
- **Special Field Benefits:**
2192
- - ✅ Eliminates need for LLM to format final response
2193
- - ✅ Enables deterministic flows with dynamic user messages
2194
- - ✅ Clearly separates UI content from payload data
2195
- - ✅ No namespace pollution - can still have `message` in payload
2196
-
2197
- #### 5. Prompt Result Merging
2198
-
2199
- When a Prompt step executes, its JSON response is **deep merged** into the payload:
2200
-
2201
- ```typescript
2202
- // Before prompt execution
2203
- payload = {
2204
- decision: {
2205
- status: "pending",
2206
- reviewerId: "user-123"
2207
- },
2208
- metadata: { startTime: "..." }
2209
- };
2210
-
2211
- // Prompt returns
2212
- promptResponse = {
2213
- decision: {
2214
- approved: true,
2215
- confidence: 0.95
2216
- }
2217
- };
2218
-
2219
- // After deep merge (preserves existing keys!)
2220
- payload = {
2221
- decision: {
2222
- approved: true, // NEW from prompt
2223
- confidence: 0.95, // NEW from prompt
2224
- status: "pending", // PRESERVED from before
2225
- reviewerId: "user-123" // PRESERVED from before
2226
- },
2227
- metadata: { startTime: "..." } // PRESERVED
2228
- };
2229
- ```
2230
-
2231
- **Why Deep Merge?**
2232
- - **Preserves context** - Existing payload data isn't lost
2233
- - **Incremental updates** - Prompts can add fields without destroying structure
2234
- - **Composable decisions** - Multiple prompts can build up complex objects
2235
-
2236
- **Special Prompt Response Handling**:
2237
- ```typescript
2238
- // If prompt response contains Chat step request
2239
- {
2240
- "nextStep": { "type": "Chat" },
2241
- "message": "I need more information from the user",
2242
- "taskComplete": false
2243
- }
2244
- // OR
2245
- {
2246
- "taskComplete": true,
2247
- "message": "Here's the final result..."
2248
- }
2249
-
2250
- // Flow agent returns Chat step to bubble message to user
2251
- // This allows prompts within flows to communicate with users
2252
- ```
2253
-
2254
- ### Complete Flow Agent Example
2255
-
2256
- ```typescript
2257
- // Database configuration for a complete approval workflow
2258
- // 1. Define the workflow steps
2259
- const steps = [
2260
- {
2261
- Name: 'ValidateRequest',
2262
- StepType: 'Action',
2263
- ActionID: validateActionId,
2264
- StartingStep: true,
2265
- Sequence: 0,
2266
- ActionInputMapping: JSON.stringify({
2267
- "requestData": "payload.request",
2268
- "validationRules": "payload.rules"
2269
- }),
2270
- ActionOutputMapping: JSON.stringify({
2271
- "isValid": "payload.validation.isValid",
2272
- "errors": "payload.validation.errors"
2273
- })
2274
- },
2275
- {
2276
- Name: 'CheckAmount',
2277
- StepType: 'Prompt',
2278
- PromptID: amountCheckPromptId,
2279
- Description: 'AI analyzes amount and risk factors'
2280
- // Prompt returns: { risk: "low"|"medium"|"high", reasoning: "..." }
2281
- // Deep merged into payload.risk and payload.reasoning
2282
- },
2283
- {
2284
- Name: 'AutoApprove',
2285
- StepType: 'Action',
2286
- ActionID: approveActionId,
2287
- ActionInputMapping: JSON.stringify({
2288
- "requestId": "payload.request.id",
2289
- "approvedBy": "static:SYSTEM_AUTO"
2290
- }),
2291
- ActionOutputMapping: JSON.stringify({
2292
- "approvalId": "payload.approval.id",
2293
- "timestamp": "payload.approval.timestamp"
2294
- })
2295
- },
2296
- {
2297
- Name: 'ManagerReview',
2298
- StepType: 'Sub-Agent',
2299
- SubAgentID: managerReviewAgentId
2300
- // Sub-agent payload inherits and can modify parent payload
2301
- },
2302
- {
2303
- Name: 'NotifyUser',
2304
- StepType: 'Action',
2305
- ActionID: notificationActionId,
2306
- ActionInputMapping: JSON.stringify({
2307
- "userId": "payload.request.userId",
2308
- "message": "payload.approval.notificationMessage",
2309
- "channel": "static:email"
2310
- })
2311
- }
2312
- ];
2313
-
2314
- // 2. Define the workflow paths
2315
- const paths = [
2316
- // From validation
2317
- {
2318
- OriginStepID: validateStepId,
2319
- DestinationStepID: checkAmountStepId,
2320
- Condition: 'payload.validation.isValid === true',
2321
- Priority: 10
2322
- },
2323
- {
2324
- OriginStepID: validateStepId,
2325
- DestinationStepID: notifyUserStepId,
2326
- Condition: 'payload.validation.isValid === false',
2327
- Priority: 10
2328
- },
2329
-
2330
- // From AI risk assessment
2331
- {
2332
- OriginStepID: checkAmountStepId,
2333
- DestinationStepID: autoApproveStepId,
2334
- Condition: 'payload.risk === "low" && payload.request.amount <= 1000',
2335
- Priority: 10
2336
- },
2337
- {
2338
- OriginStepID: checkAmountStepId,
2339
- DestinationStepID: managerReviewStepId,
2340
- Condition: 'payload.risk === "medium" || payload.risk === "high"',
2341
- Priority: 10
2342
- },
2343
-
2344
- // From manager review
2345
- {
2346
- OriginStepID: managerReviewStepId,
2347
- DestinationStepID: autoApproveStepId,
2348
- Condition: 'payload.managerDecision.approved === true',
2349
- Priority: 10
2350
- },
2351
- {
2352
- OriginStepID: managerReviewStepId,
2353
- DestinationStepID: notifyUserStepId,
2354
- Condition: 'payload.managerDecision.approved === false',
2355
- Priority: 5
2356
- },
2357
-
2358
- // Final notification after approval
2359
- {
2360
- OriginStepID: autoApproveStepId,
2361
- DestinationStepID: notifyUserStepId,
2362
- Condition: null, // Always execute
2363
- Priority: 0
2364
- }
2365
- ];
2366
-
2367
- // 3. Execute the flow agent
2368
- const result = await runner.RunAgent({
2369
- agent: flowAgentEntity,
2370
- conversationMessages: messages,
2371
- contextUser: user,
2372
- payload: {
2373
- request: {
2374
- id: "req-123",
2375
- userId: "user-456",
2376
- amount: 5000,
2377
- description: "Equipment purchase"
2378
- },
2379
- rules: {
2380
- maxAutoApprove: 1000,
2381
- requiresManagerReview: true
2382
- }
2383
- }
2384
- });
2385
- ```
2386
-
2387
- ### Flow Agent Features
2388
-
2389
- #### Safe Expression Evaluation
2390
- Flow agents use the SafeExpressionEvaluator to securely evaluate path conditions without arbitrary code execution:
2391
-
2392
- ```typescript
2393
- // Supported operations in conditions:
2394
- // - Comparisons: ==, ===, !=, !==, <, >, <=, >=
2395
- // - Logical: &&, ||, !
2396
- // - Property access: payload.user.role, stepResult.score
2397
- // - Safe methods: .includes(), .length, .some(), .every()
2398
- // - Type checking: typeof
2399
-
2400
- // Example conditions:
2401
- "payload.status == 'approved' && payload.priority > 5"
2402
- "stepResult.items.some(item => item.price > 100)"
2403
- "payload.user.roles.includes('admin') || payload.override === true"
2404
- ```
2405
-
2406
- #### Action Output Mapping
2407
- Automatically map action results to the payload:
2408
-
2409
- ```typescript
2410
- // In AIAgentStep.ActionOutputMapping
2411
- {
2412
- "userId": "payload.customer.id", // Map specific output
2413
- "orderTotal": "payload.order.total", // Nested path mapping
2414
- "*": "payload.actionResults.lastResult" // Wildcard for entire result
2415
- }
2416
- ```
2417
-
2418
- #### Flow Context Tracking
2419
- The framework maintains flow execution state in `__flowContext`:
2420
-
2421
- ```typescript
2422
- // Automatically tracked in payload.__flowContext
2423
- {
2424
- agentId: "flow-agent-id",
2425
- currentStepId: "current-step-id",
2426
- completedStepIds: ["step1", "step2"],
2427
- stepResults: {
2428
- "step1": { success: true, data: {...} },
2429
- "step2": { approved: false }
2430
- },
2431
- executionPath: ["step1", "step2", "step3"]
2432
- }
2433
- ```
2434
-
2435
- #### Prompt Steps for AI Decisions
2436
- Flow agents can incorporate AI decision points:
2437
-
2438
- ```typescript
2439
- // Prompt step expects response format:
2440
- {
2441
- "nextStepName?": "StepToExecute",
2442
- "reasoning?": "Why this decision was made",
2443
- "confidence?": 0.95,
2444
- "terminate?": false,
2445
- "message?": "Decision explanation"
2446
- }
2447
- ```
2448
-
2449
- ## Architecture Documentation
2450
-
2451
- For detailed architecture information, see [agent-architecture.md](./agent-architecture.md).
2452
-
2453
- ## Contributing
2454
-
2455
- Contributions are welcome! Please see the main MemberJunction [contributing guide](../../../CONTRIBUTING.md).
2456
-
2457
- ## API Keys
2458
-
2459
- The AI Agents framework supports flexible API key management through integration with the AI Prompts system, including the new environment-based configuration features.
2460
-
2461
- ### Environment-Based Configuration for Agents
2462
-
2463
- AI Agents benefit from MemberJunction's environment-based configuration system, allowing different API keys and settings per environment:
2464
-
2465
- ```typescript
2466
- // Agents automatically use the correct configuration based on NODE_ENV
2467
- // Development -> AIConfigSet(Name='development') -> Different API keys
2468
- // Production -> AIConfigSet(Name='production') -> Production API keys
2469
-
2470
- // This is especially useful for:
2471
- // - Agent testing with development API keys
2472
- // - Production agents with higher rate limits
2473
- // - Environment-specific agent behaviors
2474
- ```
2475
-
2476
- ### Using Runtime API Keys with Agents
2477
-
2478
- You can provide API keys at agent execution time for multi-tenant scenarios:
2479
-
2480
- ```typescript
2481
- import { AgentRunner, ExecuteAgentParams } from '@memberjunction/ai-agents';
2482
- import { AIAPIKey } from '@memberjunction/ai';
2483
-
2484
- const runner = new AgentRunner();
2485
-
2486
- // Execute agent with specific API keys
2487
- const result = await runner.RunAgent({
2488
- agent: agentEntity,
2489
- conversationMessages: messages,
2490
- contextUser: user,
2491
- apiKeys: [
2492
- { driverClass: 'OpenAILLM', apiKey: 'sk-user-specific-key' },
2493
- { driverClass: 'AnthropicLLM', apiKey: 'sk-ant-department-key' }
2494
- ]
2495
- });
2496
-
2497
- // API keys are automatically propagated to:
2498
- // - All prompt executions by the agent
2499
- // - Sub-agent executions
2500
- // - Context compression operations
2501
- ```
2502
-
2503
- ### API Key Resolution for Agents
2504
-
2505
- When agents execute, API keys are resolved in this priority order:
2506
- 1. **Runtime API keys** passed to RunAgent (highest priority)
2507
- 2. **Configuration sets** from database based on environment
2508
- 3. **Environment variables** (traditional approach)
2509
- 4. **Custom implementations** via AIAPIKeys subclassing
2510
-
2511
- ### Multi-Environment Agent Setup
2512
-
2513
- ```typescript
2514
- // Example: Different agent configurations per environment
2515
-
2516
- // Development environment
2517
- const devAgentConfig = {
2518
- ConfigSet: { Name: 'development', Priority: 100 },
2519
- Configurations: [
2520
- { ConfigKey: 'OPENAI_LLM_APIKEY', ConfigValue: 'sk-dev-...', Encrypted: true },
2521
- { ConfigKey: 'MAX_AGENT_ITERATIONS', ConfigValue: '10', Encrypted: false },
2522
- { ConfigKey: 'AGENT_DEBUG_MODE', ConfigValue: 'true', Encrypted: false }
2523
- ]
2524
- };
2525
-
2526
- // Production environment
2527
- const prodAgentConfig = {
2528
- ConfigSet: { Name: 'production', Priority: 100 },
2529
- Configurations: [
2530
- { ConfigKey: 'OPENAI_LLM_APIKEY', ConfigValue: 'sk-prod-...', Encrypted: true },
2531
- { ConfigKey: 'MAX_AGENT_ITERATIONS', ConfigValue: '50', Encrypted: false },
2532
- { ConfigKey: 'AGENT_DEBUG_MODE', ConfigValue: 'false', Encrypted: false }
2533
- ]
2534
- };
2535
-
2536
- // Agents can access these configurations through the AI engine
2537
- ```
2538
-
2539
- ### Benefits for Agent Systems
2540
-
2541
- Runtime API keys and environment-based configuration are particularly useful for agent architectures:
2542
- - **Multi-tenant isolation**: Different customers use their own API keys
2543
- - **Cost attribution**: Track API usage per department or project
2544
- - **Security**: Limit exposure of production API keys
2545
- - **Testing**: Use test API keys for development agents
2546
- - **Environment-specific behavior**: Different limits and debugging per environment
2547
- - **Centralized management**: Update configurations without code changes
2548
-
2549
- ### Agent-Specific Configuration Example
2550
-
2551
- ```typescript
2552
- // Custom agent that uses environment-based configuration
2553
- @RegisterClass(BaseAgent, "ConfigAwareAgent")
2554
- export class ConfigAwareAgent extends BaseAgent {
2555
- protected async getConfiguration(key: string): Promise<string | null> {
2556
- // The framework automatically loads configurations based on NODE_ENV
2557
- const envName = process.env.NODE_ENV || 'production';
2558
-
2559
- // Query AIConfiguration for the current environment
2560
- const config = await this.loadConfigValue(envName, key);
2561
- return config;
2562
- }
2563
-
2564
- protected async setupExecution(): Promise<void> {
2565
- // Load agent-specific configurations
2566
- const maxIterations = await this.getConfiguration('MAX_AGENT_ITERATIONS');
2567
- const debugMode = await this.getConfiguration('AGENT_DEBUG_MODE');
2568
-
2569
- // Apply configurations to agent behavior
2570
- this.maxIterations = parseInt(maxIterations || '50');
2571
- this.debugMode = debugMode === 'true';
2572
- }
2573
- }
2574
- ```
2575
-
2576
- For detailed information about API key configuration and management, see the [AI Prompts API Keys documentation](../Prompts/README.md#api-keys).
2577
-
2578
- ## AI Configuration for Agents
2579
-
2580
- Agents fully support the AI Configuration system for environment-specific model selection. When you execute an agent with a `configurationId`, that configuration is automatically propagated to:
2581
-
2582
- - All prompts executed by the agent
2583
- - All sub-agents spawned by the agent
2584
- - All sub-sub-agents in the hierarchy
2585
-
2586
- ### Using Configurations with Agents
2587
-
2588
- ```typescript
2589
- const result = await runner.RunAgent({
2590
- agent: myAgent,
2591
- conversationMessages: messages,
2592
- contextUser: user,
2593
- configurationId: 'dev-config-id', // Optional - propagates to all prompts
2594
- });
2595
- ```
2596
-
2597
- ### Configuration Benefits for Agents
2598
-
2599
- - **Environment Isolation**: Test agents with development models without affecting production
2600
- - **Consistent Model Selection**: All prompts in the agent hierarchy use the same configuration
2601
- - **Easy Switching**: Change configurations without modifying agent code
2602
- - **Fallback Support**: Agents continue to work even if specific models aren't configured
2603
-
2604
- For comprehensive details about how AI Configurations work, including model selection logic and fallback behavior, see the [AI Configuration System documentation](../Prompts/README.md#ai-configuration-system).
2605
-
2606
- ## Effort Level Control in Agents
2607
-
2608
- Agents support sophisticated effort level management that controls how much reasoning effort AI models apply to each prompt execution. The effort level uses a 1-100 integer scale where higher values request more thorough analysis.
2609
-
2610
- ### Effort Level Hierarchy
2611
-
2612
- The effort level is resolved using hierarchical precedence:
2613
-
2614
- 1. **Runtime Override** (`ExecuteAgentParams.effortLevel`) - Highest priority
2615
- 2. **Agent Default** (`AIAgent.DefaultPromptEffortLevel`) - Medium priority
2616
- 3. **Prompt Setting** (`AIPrompt.EffortLevel`) - Lower priority
2617
- 4. **Provider Default** - Natural model behavior (lowest priority)
2618
-
2619
- ### Agent Execution with Effort Level
2620
-
2621
- ```typescript
2622
- // Execute agent with high effort level for all prompts
2623
- const result = await runner.RunAgent({
2624
- agent: myAnalysisAgent,
2625
- conversationMessages: messages,
2626
- contextUser: user,
2627
- effortLevel: 85 // High effort - applies to all prompts in execution
2628
- });
2629
-
2630
- // Execute with medium effort level
2631
- const result = await runner.RunAgent({
2632
- agent: myQuickAgent,
2633
- conversationMessages: messages,
2634
- contextUser: user,
2635
- effortLevel: 30 // Low effort - for quick responses
2636
- });
2637
- ```
2638
-
2639
- ### Sub-Agent Inheritance
2640
-
2641
- Sub-agents automatically inherit the effort level from their parent unless explicitly overridden:
2642
-
2643
- ```typescript
2644
- // Parent agent runs with effort level 70
2645
- const parentResult = await runner.RunAgent({
2646
- agent: parentAgent,
2647
- effortLevel: 70, // Inherited by all sub-agents
2648
- // ...
2649
- });
2650
-
2651
- // All sub-agents spawned during execution will use effort level 70
2652
- // unless the sub-agent has its own DefaultPromptEffortLevel setting
2653
- ```
2654
-
2655
- ### Agent Configuration
2656
-
2657
- You can configure default effort levels at the agent level:
2658
-
2659
- - **`AIAgent.DefaultPromptEffortLevel`**: Sets the default effort level for all prompts executed by this agent
2660
- - This takes precedence over individual prompt effort levels but can be overridden at runtime
2661
-
2662
- ### Provider-Specific Behavior
297
+ ## Re-exports
2663
298
 
2664
- Different AI providers handle effort levels differently:
299
+ For backward compatibility, this package re-exports the following from `@memberjunction/ai-reranker`:
2665
300
 
2666
- - **OpenAI**: Maps to reasoning_effort (low/medium/high)
2667
- - **Anthropic**: Controls thinking mode and token budgets
2668
- - **Groq**: Maps to experimental reasoning_effort parameter
2669
- - **Other providers**: May ignore effort levels gracefully
301
+ - `RerankerService`
302
+ - `RerankerConfiguration`
303
+ - `parseRerankerConfiguration`
304
+ - `RerankServiceResult`
305
+ - `RerankObservabilityOptions`
306
+ - `LLMReranker`
2670
307
 
2671
- For detailed effort level documentation, see the [AI Core Plus documentation](../CorePlus/README.md#effort-level-control).
308
+ New code should import these directly from `@memberjunction/ai-reranker`.
2672
309
 
2673
- ## License
310
+ ## Dependencies
2674
311
 
2675
- This package is part of the MemberJunction project. See the [LICENSE](../../../LICENSE) file for details.
312
+ - `@memberjunction/ai-prompts` -- AIPromptRunner for prompt execution
313
+ - `@memberjunction/aiengine` -- AIEngine for metadata and vector search
314
+ - `@memberjunction/ai-core-plus` -- Shared types (ExecuteAgentParams, ExecuteAgentResult)
315
+ - `@memberjunction/ai-engine-base` -- Base metadata cache and permissions
316
+ - `@memberjunction/ai` -- Core AI abstractions
317
+ - `@memberjunction/ai-reranker` -- Two-stage retrieval reranking
318
+ - `@memberjunction/actions` -- Server-side action execution
319
+ - `@memberjunction/actions-base` -- Action framework base types
320
+ - `@memberjunction/core` -- MJ framework core
321
+ - `@memberjunction/core-entities` -- Generated entity classes
322
+ - `@memberjunction/global` -- Class factory and utilities
323
+ - `lodash` -- Utility functions