π«π· Version franΓ§aise
See also: Methodology Β· Back to index
Porting plan example β Order processing pipeline
This example applies the methodology from the Porting methodology file to a fictional e-commerce order processing application.
The source application
The "OrderFlow" application is a .NET service that processes orders for an e-commerce site. It is composed of five main services:
| Service | Responsibility |
|---|---|
OrderIngestionService |
Reads new orders from a RabbitMQ queue and stores them in a PostgreSQL database |
InventoryCheckService |
Checks product availability in the catalog (internal REST API) |
FraudDetectionService |
Analyzes fraud risk by cross-referencing customer history and order amount |
PricingService |
Applies promotions, computes discounts and VAT (TVA) |
NotificationService |
Generates and sends a confirmation email with the order summary |
The sequential flow is: Ingestion β Stock check β Fraud detection β Price computation β Notification.
Step 1 β Responsibility analysis
Component inventory
| Component | Category | Inputs | Outputs | External dependencies |
|---|---|---|---|---|
OrderIngestionService |
Collection | RabbitMQ messages | Order in DB | PostgreSQL, RabbitMQ |
InventoryCheckService |
Decision | Product IDs | Availability status | Internal Catalog API |
FraudDetectionService |
Analysis | Order + customer history | Risk score (0-100) | PostgreSQL (history) |
PricingService |
Analysis | Order + promo rules | Final amounts (HT, TVA, TTC) | Promotions CSV file |
NotificationService |
Production | Validated order + price | Confirmation HTML email | SMTP server |
Identified human interactions
The FraudDetectionService flags orders with a score > 80 for manual review. This point will become a task with HumanInput = true.
Step 2 β Mapping to agents
Agent 1: Order Collector
Source responsibility : OrderIngestionService
β Agent role : "Order Data Collector"
β Agent goal : "Retrieve and structure new order data from the database"
β Backstory : "Experienced data engineer specialized in order management systems"
β Required tools : RelationalDatabaseTool (PostgreSQL)
β Constraints : MaxIterations=5, MaxRpm=10
Key decision: We do not port the RabbitMQ reading. The agent reads pending orders directly from the PostgreSQL database. Queue consumption stays outside the agent perimeter β it is a purely mechanical infrastructure operation (no LLM reasoning needed).
Agent 2: Inventory Checker
Source responsibility : InventoryCheckService
β Agent role : "Inventory Verification Specialist"
β Agent goal : "Verify product availability and flag out-of-stock items"
β Backstory : "Supply chain analyst with deep knowledge of inventory systems"
β Required tools : HttpApiTool (Catalog API)
β Constraints : MaxIterations=10, MaxRpm=20
Agent 3: Fraud Analyst
Source responsibility : FraudDetectionService
β Agent role : "Fraud Detection Analyst"
β Agent goal : "Analyze order risk based on customer history and transaction patterns"
β Backstory : "Senior fraud prevention specialist with expertise in e-commerce transaction patterns"
β Required tools : RelationalDatabaseTool (customer history), JsonTool (result structuring)
β Constraints : MaxIterations=10, MaxRpm=10
Key decision: The LLM brings real value here β it can reason about complex fraud patterns beyond the static rules of the original service. The risk score is produced with a textual justification the original service did not provide.
Agent 4: Pricing Specialist
Source responsibility : PricingService
β Agent role : "Pricing Calculation Specialist"
β Agent goal : "Apply promotions and compute final pricing (HT, TVA, TTC)"
β Backstory : "Pricing analyst expert in French tax regulations and promotional strategies"
β Required tools : CsvReaderTool (promotions file), SecureCodeInterpreterTool (calculations)
β Constraints : MaxIterations=5, MaxRpm=10
Key decision: Use SecureCodeInterpreterTool for the VAT (TVA) calculations rather than a custom tool. The LLM generates the C# calculation code which is executed in the sandbox β this gives the flexibility to handle complex promotion rules without hardcoding the logic.
Agent 5: Notification Writer
Source responsibility : NotificationService
β Agent role : "Customer Communication Specialist"
β Agent goal : "Generate personalized order confirmation content"
β Backstory : "Customer experience writer skilled in e-commerce communication"
β Required tools : FileWriteTool (content generation)
β Constraints : MaxIterations=3, MaxRpm=5
Key decision: SMTP sending stays outside the agent perimeter β only the generation of the email content is ported. The actual sending is a mechanical operation that will be triggered by the calling system after receiving the Crew's result. A custom SmtpSendTool could be created if the sending must be integrated.
Step 3 β Tool identification
| Agent | Required tool | Exists? | Action |
|---|---|---|---|
| Order Collector | RelationalDatabaseTool (PostgreSQL) |
Yes β Orkeon.Tools.Data |
Reuse |
| Inventory Checker | HttpApiTool |
Yes β Orkeon.Tools.Web |
Reuse |
| Fraud Analyst | RelationalDatabaseTool |
Yes β Orkeon.Tools.Data |
Reuse |
| Fraud Analyst | JsonTool |
Yes β Orkeon.Tools.Data |
Reuse |
| Pricing Specialist | CsvReaderTool |
Yes β Orkeon.Tools.Data |
Reuse |
| Pricing Specialist | SecureCodeInterpreterTool |
Yes β Orkeon.Infrastructure |
Reuse |
| Notification Writer | FileWriteTool |
Yes β Orkeon.Tools.FileSystem |
Reuse |
All the required tools already exist. No custom tool is needed for this port.
Step 4 β Tasks and orchestration
Chosen ProcessType: Sequential
Justification: The order processing flow is intrinsically sequential β each step depends on the result of the previous one (you cannot compute the price without checking the stock, nor notify without knowing the price).
Task definition (Fluent Builder approach)
// Task 1 β Order data collection
var collectTask = new CrewTaskBuilder()
.Description("Query the orders database for pending orders with status 'NEW'. " +
"For each order, retrieve: order_id, customer_id, product_ids, " +
"quantities, and order_date. Return structured JSON.")
.ExpectedOutput("JSON array of pending orders with all fields populated")
.Priority(TaskPriority.High)
.AssignTo(orderCollector)
.Build();
// Task 2 β Stock verification
var stockTask = new CrewTaskBuilder()
.Description("For each order from the previous task, call the inventory API " +
"at https://api.internal/catalog/v1/stock to verify product availability. " +
"Flag any out-of-stock items. Return the order list with availability status.")
.ExpectedOutput("JSON array of orders enriched with stock availability per product")
.Priority(TaskPriority.High)
.DependsOn(collectTask) // Accepts CrewTask or TaskId
.AssignTo(inventoryChecker)
.Build();
// Task 3 β Fraud analysis
var fraudTask = new CrewTaskBuilder()
.Description("For each order with available stock, analyze fraud risk. " +
"Query customer purchase history from the database. " +
"Evaluate: order amount vs. average, shipping address changes, " +
"payment method risk. Produce a risk score (0-100) with justification.")
.ExpectedOutput("JSON array of orders with risk_score (0-100) and risk_justification")
.Priority(TaskPriority.High)
.DependsOn(stockTask)
.HumanInput(true) // Manual review if score > 80
.AssignTo(fraudAnalyst)
.Build();
// Task 4 β Price calculation
var pricingTask = new CrewTaskBuilder()
.Description("For each approved order (risk_score <= 80 or manually approved), " +
"read the promotions CSV file at /data/promotions.csv. " +
"Apply applicable promotions, compute HT, TVA (20%), and TTC. " +
"Use the code interpreter for precise calculations.")
.ExpectedOutput("JSON array of orders with price_ht, tva_amount, price_ttc, applied_promotions")
.Priority(TaskPriority.Normal)
.DependsOn(fraudTask)
.AssignTo(pricingSpecialist)
.Build();
// Task 5 β Notification generation
var notifTask = new CrewTaskBuilder()
.Description("For each priced order, generate a personalized confirmation email " +
"in French. Include: order summary, items with prices, total TTC, " +
"estimated delivery date. Write output to /output/confirmations/.")
.ExpectedOutput("One confirmation file per order in /output/confirmations/{order_id}.html")
.Priority(TaskPriority.Normal)
.DependsOn(pricingTask)
.OutputFile("/output/confirmations/")
.AssignTo(notificationWriter)
.Build();
Crew construction (Fluent Builder approach)
var orderProcessingCrew = new CrewBuilder()
.Goal("Process pending e-commerce orders: verify stock, detect fraud, " +
"calculate pricing, and generate confirmation emails")
.Sequential()
.WithAgent(orderCollector)
.WithAgent(inventoryChecker)
.WithAgent(fraudAnalyst)
.WithAgent(pricingSpecialist)
.WithAgent(notificationWriter)
.WithTask(collectTask)
.WithTask(stockTask)
.WithTask(fraudTask)
.WithTask(pricingTask)
.WithTask(notifTask)
.Verbose(true)
.EnableMemory(true)
.Planning(true)
.Language("fr")
.Build();
Step 4b β Full execution: from config to result
Complete YAML version
The crew can also be defined entirely in YAML and loaded dynamically. This eases maintenance and evolution without recompiling the code.
File: config.yaml
name: "order-processing-crew"
goal: "Process pending e-commerce orders: verify stock, detect fraud, calculate pricing, and generate confirmation emails"
process: "sequential"
verbose: true
memory: true
planning: true
agents:
order_collector:
role: "Order Data Collector"
goal: "Retrieve and structure new order data from the database"
backstory: |
Experienced data engineer specialized in order management systems.
Expert at writing efficient SQL queries and structuring data for downstream processing.
You ensure all order data is complete, correctly formatted, and ready for analysis.
tools:
- "relational_database_query"
maxIter: 5
maxRpm: 10
allowDelegation: false
inventory_checker:
role: "Inventory Verification Specialist"
goal: "Verify product availability and flag out-of-stock items"
backstory: |
Supply chain analyst with deep knowledge of inventory systems.
Methodical approach to stock verification across multiple warehouses.
You provide accurate, real-time inventory status to prevent overselling.
tools:
- "http_api"
maxIter: 10
maxRpm: 20
allowDelegation: false
fraud_analyst:
role: "Fraud Detection Analyst"
goal: "Analyze order risk based on customer history and transaction patterns"
backstory: |
Senior fraud prevention specialist with expertise in e-commerce transaction patterns.
Combines statistical analysis with behavioral indicators to produce accurate risk assessments.
You flag suspicious orders for manual review while allowing legitimate transactions to proceed.
tools:
- "relational_database_query"
- "json_tool"
maxIter: 10
maxRpm: 10
allowDelegation: false
pricing_specialist:
role: "Pricing Calculation Specialist"
goal: "Apply promotions and compute final pricing (HT, TVA, TTC)"
backstory: |
Pricing analyst expert in French tax regulations and promotional strategies.
Ensures precise calculations with full audit trail for compliance.
You deliver accurate pricing that respects all promotional rules and tax obligations.
tools:
- "csv_reader"
- "code_interpreter"
maxIter: 5
maxRpm: 10
allowDelegation: false
notification_writer:
role: "Customer Communication Specialist"
goal: "Generate personalized order confirmation content"
backstory: |
Customer experience writer skilled in e-commerce communication.
Creates warm, professional emails that reinforce brand trust.
You ensure every customer receives clear, detailed, and reassuring order confirmation.
tools:
- "file_write"
maxIter: 3
maxRpm: 5
allowDelegation: false
tasks:
collect_orders:
description: |
Query the orders database for pending orders with status 'NEW'.
For each order, retrieve: order_id, customer_id, product_ids,
quantities, and order_date. Return structured JSON.
expectedOutput: "JSON array of pending orders with all fields populated"
agent: "order_collector"
check_inventory:
description: |
For each order from the previous task, call the inventory API
at https://api.internal/catalog/v1/stock to verify product availability.
Flag any out-of-stock items. Return the order list with availability status.
expectedOutput: "JSON array of orders enriched with stock availability per product"
agent: "inventory_checker"
dependencies:
- "collect_orders"
analyze_fraud:
description: |
For each order with available stock, analyze fraud risk.
Query customer purchase history from the database.
Evaluate: order amount vs. average, shipping address changes,
payment method risk. Produce a risk score (0-100) with justification.
expectedOutput: "JSON array of orders with risk_score (0-100) and risk_justification"
agent: "fraud_analyst"
dependencies:
- "check_inventory"
humanInput: true
calculate_pricing:
description: |
For each approved order (risk_score <= 80 or manually approved),
read the promotions CSV file at /data/promotions.csv.
Apply applicable promotions, compute HT, TVA (20%), and TTC.
Use the code interpreter for precise calculations.
expectedOutput: "JSON array of orders with price_ht, tva_amount, price_ttc, applied_promotions"
agent: "pricing_specialist"
dependencies:
- "analyze_fraud"
generate_notifications:
description: |
For each priced order, generate a personalized confirmation email
in French. Include: order summary, items with prices, total TTC,
estimated delivery date. Write output to /output/confirmations/.
expectedOutput: "One confirmation file per order in /output/confirmations/{order_id}.html"
agent: "notification_writer"
dependencies:
- "calculate_pricing"
Advantages of the YAML approach
- Separation of responsibilities: the business configuration is separated from the C# code
- Simplified maintenance: modifying a backstory or a description does not require recompilation
- Flexible deployment: several crew variants (e.g. production, test, debug) without duplicating code
- Onboarding: non-developers (PMs, Product Owners) can read and understand the crew
Step 4c β Application bootstrap
Configuration
File: Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Orkeon.Application.DependencyInjection;
using Orkeon.Infrastructure.DependencyInjection;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
// Register the Orkeon services.
// For a configured LLM provider (API key, model), register an ILlmProvider
// BEFORE AddOrkeonInfrastructure() β its TryAdd* calls honor the existing registration.
services.AddOrkeonApplication();
// The IConfiguration overload also activates the modules tied to the
// "Orkeon:*" sections (ChromaDb, Pinecone, Telemetry, MCP, RAG, ...)
services.AddOrkeonInfrastructure(context.Configuration);
services.AddOrkeonFileSystemTools();
services.AddOrkeonDataTools();
services.AddOrkeonWebTools();
services.AddOrkeonCodeTools();
})
.Build();
// Wait for the host to be built
await host.StartAsync();
Loading and running the crew
File: OrderProcessingService.cs
using Microsoft.Extensions.Logging;
using Orkeon.Application.Interfaces;
using Orkeon.Application.Interfaces.Services;
public class OrderProcessingService
{
private readonly ICrewFactory _crewFactory;
private readonly ICrewOrchestrationService _orchestrator;
private readonly ILogger<OrderProcessingService> _logger;
public OrderProcessingService(
ICrewFactory crewFactory,
ICrewOrchestrationService orchestrator,
ILogger<OrderProcessingService> logger)
{
_crewFactory = crewFactory;
_orchestrator = orchestrator;
_logger = logger;
}
public async Task ProcessOrdersAsync(CancellationToken cancellationToken = default)
{
try
{
// 1. Load the crew from the YAML file
_logger.LogInformation("Loading order processing crew from config.yaml");
var crew = await _crewFactory.CreateFromFileAsync(
"config/order-processing/config.yaml",
cancellationToken);
// 2. Prepare the input
var input = CrewInput.Empty(
"Process all pending orders from today with fraud analysis and pricing");
// 3. Run the crew
_logger.LogInformation("Starting crew execution: {CrewId}", crew.Id);
var output = await _orchestrator.KickoffAsync(crew.Id, input, cancellationToken);
// 4. Use the results
_logger.LogInformation("Crew execution completed in {Duration}ms",
output.ExecutionTime.TotalMilliseconds);
_logger.LogInformation("Final output:\n{Output}", output.FinalOutput);
// 5. Process each task's outputs
foreach (var taskOutput in output.TaskOutputs)
{
_logger.LogInformation(
"Task {TaskId} - Success: {Success}, Duration: {ExecutionTime}ms",
taskOutput.TaskId,
taskOutput.Success,
taskOutput.ExecutionTime.TotalMilliseconds);
if (!taskOutput.Success)
{
_logger.LogError("Task failed: {Output}", taskOutput.RawOutput);
}
}
// 6. Post-processing: send the generated notifications
await SendConfirmationEmailsAsync(output, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during order processing");
throw;
}
}
private async Task SendConfirmationEmailsAsync(CrewOutput output, CancellationToken cancellationToken)
{
// Read the confirmation files generated by notification_writer
var confirmationDir = new DirectoryInfo("/output/confirmations/");
if (!confirmationDir.Exists)
{
_logger.LogWarning("No confirmation directory found");
return;
}
var htmlFiles = confirmationDir.GetFiles("*.html");
_logger.LogInformation("Sending {Count} confirmation emails", htmlFiles.Length);
foreach (var file in htmlFiles)
{
try
{
var orderId = Path.GetFileNameWithoutExtension(file.Name);
var htmlContent = await File.ReadAllTextAsync(file.FullName, cancellationToken);
// Call your SMTP service (outside Orkeon)
// await _emailService.SendAsync(
// recipient: customer.Email,
// subject: $"Order confirmation {orderId}",
// htmlBody: htmlContent,
// cancellationToken: cancellationToken);
_logger.LogInformation("Confirmation email sent for order {OrderId}", orderId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send confirmation email for {File}", file.Name);
}
}
}
}
Integration into an existing application
Example: triggered by a RabbitMQ event
public class OrderProcessingMessageConsumer : IMessageHandler
{
private readonly OrderProcessingService _orderProcessing;
private readonly ILogger<OrderProcessingMessageConsumer> _logger;
public async Task HandleAsync(IMessage message, CancellationToken cancellationToken)
{
_logger.LogInformation("Received order processing event");
// RabbitMQ messages simply trigger the crew call
await _orderProcessing.ProcessOrdersAsync(cancellationToken);
}
}
Runtime configuration (appsettings.json)
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"Llm": {
"Provider": "openai",
"Model": "gpt-4o-mini",
"Temperature": 0.3
// The API key is NEVER stored here β it is read from the environment
// (OPENAI_API_KEY / ORKEON_Llm__ApiKey).
},
"Memory": {
"Provider": "Redis",
"ConnectionString": "localhost:6379"
// Redis memory also needs the opt-in AddOrkeonRedisMemory(...) call;
// without it the config-driven factory falls back to in-memory.
}
}
Expected execution result
During execution, you will see in the logs:
[INFO] Loading order processing crew from config.yaml
[INFO] Starting crew execution: crew-order-processing-2025-04-05-143025
[INFO] Agent order_collector executing task collect_orders
[INFO] Agent inventory_checker executing task check_inventory
[INFO] Agent fraud_analyst executing task analyze_fraud
[WARN] Task analyze_fraud flagged 3 orders for manual review (risk > 80)
[INFO] Waiting for human input on flagged orders...
[INFO] Human input received: 2 orders approved, 1 order rejected
[INFO] Agent pricing_specialist executing task calculate_pricing
[INFO] Agent notification_writer executing task generate_notifications
[INFO] Crew execution completed in 45230ms
[INFO] Sending 47 confirmation emails
[INFO] Confirmation email sent for order ORD-2025-0001
[INFO] Confirmation email sent for order ORD-2025-0002
...
Step 5 β Final porting plan
| Source component | Responsibility | Target agent | Required tool(s) | Status | Effort |
|---|---|---|---|---|---|
OrderIngestionService |
DB order reading | Order Collector | RelationalDatabaseTool |
Ready | S |
InventoryCheckService |
API stock check | Inventory Checker | HttpApiTool |
Ready | S |
FraudDetectionService |
Fraud risk analysis | Fraud Analyst | RelationalDatabaseTool, JsonTool |
Ready | S |
PricingService |
Price + promo computation | Pricing Specialist | CsvReaderTool, SecureCodeInterpreterTool |
Ready | S |
NotificationService (content) |
Email generation | Notification Writer | FileWriteTool |
Ready | S |
NotificationService (sending) |
SMTP sending | β | Not ported (outside agent perimeter) | β | β |
| RabbitMQ consumption | Pipeline triggering | β | Not ported (infrastructure) | β | β |
Total estimated effort: S-M (1-2 days)
The port is mainly configuration work (agents, tasks, prompts) since all the required tools exist. The main effort lies in the prompt engineering of the backstories and task descriptions to obtain quality results.
Elements remaining outside the agent perimeter
- RabbitMQ queue consumption (external trigger β calls
KickoffAsync) - SMTP sending (post-crew action β triggered by the calling system)
- Operational monitoring and alerting (use the
IStepCallback/ITaskCallbackcallbacks to integrate)
Expected gains from the port
- Flexibility: fraud and pricing rules can evolve without modifying the code, via the prompts
- Traceability: each agent decision is documented with a textual justification (vs. a raw score)
- Extensibility: adding a step (e.g. address verification) = adding an agent + a task, without modifying the existing code
- Human intervention: the manual fraud review is natively integrated via
HumanInput = true