Event-driven architecture has become a common pattern for modern .NET applications.
Instead of sending every operation through synchronous APIs, services publish events such as:
OrderCreated
PaymentCompleted
CustomerUpdated
InventoryChanged
DocumentProcessed
Other services consume those events asynchronously.
This works well when event producers and consumers evolve independently. But introducing AI agents into the architecture creates a new challenge.
An AI system may consume events to:
At that point, an event is no longer just an integration message.
It can become input to an AI decision-making system.
That makes the quality, stability, semantics, and governance of the event contract much more important.
The solution is not to create a special "AI event format" for everything.
Instead, design event contracts so that they are explicit, versioned, backward-compatible, semantically clear, and safe for machine consumption.
What Is a Data Contract?
A data contract defines what information a producer guarantees to provide and what consumers can expect.
For an event such as:
{
"eventType": "OrderCreated",
"orderId": "ORD-1001",
"customerId": "CUS-204",
"totalAmount": 249.99
}
the contract should answer questions such as:
What does orderId represent?
Is it globally unique?
What format does totalAmount use?
Which currency applies?
Is customerId optional?
Can fields be removed?
How are new fields introduced?
What does an event timestamp mean?
Without these rules, consumers end up interpreting the payload differently.
For traditional applications, this creates integration bugs.
For AI systems, it can create incorrect decisions.
Why AI Changes the Contract Problem
Consider this event:
{
"customer": "Acme",
"value": 1000,
"status": "active"
}
A human developer might understand what these fields mean from application context.
An AI system cannot safely assume that:
value = revenue
or:
status = customer account status
The field could represent something completely different.
A better contract is explicit:
{
"eventType": "CustomerRevenueUpdated",
"schemaVersion": "1.0",
"customerId": "CUS-204",
"revenue": {
"amount": 1000.00,
"currency": "USD"
},
"effectiveAt": "2026-08-12T09:30:00Z"
}
Now the semantics are much clearer.
This matters when an agent uses event data to make decisions.
Event-Driven AI Architecture
A typical architecture can look like this:
Business Services
|
v
Event Broker
|
+------------+------------+
| | |
v v v
.NET Worker AI Pipeline Analytics
| |
| v
| AI Agent
| |
| +----+----+
| | |
v v v
Database Tools Knowledge
The event broker could be backed by services such as Kafka, Amazon EventBridge, Azure Event Hubs, or another messaging platform.
The important architectural principle is:
Producer
|
v
Stable Contract
|
v
Event Infrastructure
|
+--> Traditional Consumer
|
+--> AI Consumer
|
+--> Analytics Consumer
The producer should not need to know which consumers are AI-powered.
Separate Business Events From AI Prompts
One common mistake is designing an event specifically for an LLM:
{
"prompt": "Analyze this order and decide whether it is suspicious..."
}
This creates unnecessary coupling.
The business event should describe the business fact:
{
"eventType": "OrderCreated",
"orderId": "ORD-1001",
"customerId": "CUS-204",
"totalAmount": 249.99
}
An AI consumer can then construct the appropriate reasoning context.
Business Event
|
v
AI Consumer
|
+--> Enrich Context
|
+--> Apply Policy
|
+--> Build Prompt
|
v
AI Model
This separation allows the same event to support multiple consumers.
Design Events Around Business Facts
A useful event should describe something that happened.
Prefer:
OrderCreated
PaymentCaptured
CustomerAddressChanged
InventoryReserved
DocumentUploaded
over:
ProcessOrder
AnalyzeCustomer
RunAI
GenerateRecommendation
The first group describes facts.
The second group describes actions or implementation details.
This distinction makes event contracts more reusable.
Example .NET Event Contract
A strongly typed C# contract might look like:
public sealed record OrderCreatedEvent
{
public required string EventId { get; init; }
public required string EventType { get; init; }
public required int SchemaVersion { get; init; }
public required string OrderId { get; init; }
public required string CustomerId { get; init; }
public required decimal TotalAmount { get; init; }
public required string Currency { get; init; }
public required DateTimeOffset OccurredAt { get; init; }
}
The contract explicitly identifies:
Event identity
Event type
Schema version
Business identifiers
Monetary value
Currency
Event timestamp
This is preferable to passing an anonymous dictionary through the system.
Add a Contract Envelope
A useful event structure separates metadata from business data.
For example:
{
"id": "evt-12345",
"type": "OrderCreated",
"version": 1,
"source": "orders-service",
"occurredAt": "2026-08-12T09:30:00Z",
"correlationId": "corr-98765",
"data": {
"orderId": "ORD-1001",
"customerId": "CUS-204",
"totalAmount": 249.99,
"currency": "USD"
}
}
This gives infrastructure and consumers consistent metadata.
The data section contains business information.
The envelope contains information about the event itself.
Event ID and Idempotency
AI consumers can process events asynchronously and may retry failed messages.
Therefore, every event should have a stable identifier.
For example:
eventId = evt-12345
The consumer can use it to detect duplicates.
A simplified .NET consumer might use:
public async Task HandleAsync(
OrderCreatedEvent message,
CancellationToken cancellationToken)
{
if (await _store.ExistsAsync(
message.EventId,
cancellationToken))
{
return;
}
await ProcessAsync(message, cancellationToken);
await _store.MarkProcessedAsync(
message.EventId,
cancellationToken);
}
This is particularly important for AI workflows.
If the same event triggers an expensive model invocation twice, the application may incur unnecessary processing or produce duplicate actions.
At-Least-Once Delivery Changes the Design
Many event systems provide at-least-once delivery semantics.
That means consumers should be prepared to see the same event more than once.
The correct design is generally:
Receive Event
|
v
Check Idempotency
|
+---- Already processed ---> Stop
|
v
Process
|
v
Persist Result
|
v
Mark Event Complete
Do not assume:
One event = exactly one processing attempt
unless your infrastructure and application explicitly guarantee that behavior.
Schema Evolution
Data contracts will change.
Suppose version 1 contains:
{
"orderId": "ORD-1",
"totalAmount": 100
}
Later, the producer adds:
{
"orderId": "ORD-1",
"totalAmount": 100,
"currency": "USD"
}
Adding an optional field is generally easier to handle than removing or changing the meaning of an existing field.
A consumer should tolerate fields it does not understand.
For example:
public sealed record OrderCreatedEvent
{
public required string OrderId { get; init; }
public decimal TotalAmount { get; init; }
public string? Currency { get; init; }
}
Consumers can continue operating when Currency is absent if the contract defines an appropriate default or fallback behavior.
Never Change Field Meaning Silently
This is dangerous:
Version 1:
status = payment status
Version 2:
status = fulfillment status
The JSON shape has not changed.
But the semantic contract has.
An AI consumer could continue interpreting status according to the old meaning.
This is one reason semantic changes should be treated as contract changes even when the JSON schema remains valid.
Version Contracts Explicitly
There are several strategies for versioning.
Version in the Event Type
OrderCreated.v1
OrderCreated.v2
Version in Metadata
{
"type": "OrderCreated",
"version": 2
}
Schema Registry
A schema registry can maintain versions independently of the event name.
The choice depends on the messaging infrastructure.
The important rule is consistency.
Consumers should know exactly which contract they are processing.
Avoid Breaking AI Consumers
Suppose an AI pipeline expects:
customerId
orderId
totalAmount
currency
The producer removes:
currency
The traditional application may fail quickly.
An AI system might still generate a plausible response using incomplete context.
That can be more dangerous.
For example:
Order value: 1,000
Currency: unknown
The model could incorrectly infer the currency from unrelated context.
Therefore, AI consumers should validate contract completeness before reasoning.
if (string.IsNullOrWhiteSpace(message.Currency))
{
throw new InvalidOperationException(
"Currency is required for AI analysis.");
}
A failed workflow is preferable to a confident decision based on incomplete data.
Make Units Explicit
Avoid ambiguous fields:
{
"temperature": 30
}
Is that Celsius or Fahrenheit?
Prefer:
{
"temperature": {
"value": 30,
"unit": "C"
}
}
The same principle applies to:
Currency
Weight
Distance
Duration
Percentage
Tax
Exchange rates
AI systems are especially sensitive to ambiguous numerical context.
Dates and Time Zones
Use explicit timestamps.
Prefer:
{
"occurredAt": "2026-08-12T09:30:00Z"
}
over:
{
"date": "08/12/2026"
}
Also distinguish between:
occurredAt
createdAt
processedAt
effectiveAt
expiresAt
These timestamps represent different concepts.
An AI agent analyzing trends can easily produce an incorrect conclusion if the event timestamp semantics are unclear.
Do Not Put Large Documents Directly in Events
An event should generally identify a document rather than contain an unnecessarily large document payload.
Instead of:
{
"document": "very-large-base64-content..."
}
prefer:
{
"documentId": "DOC-1001",
"storageUri": "..."
}
Then the consumer can retrieve the document through an authorized storage interface.
For AI systems, this also enables:
Event
|
v
Document Reference
|
v
Authorized Retrieval
|
v
Chunking
|
v
Embedding / Analysis
This keeps the event contract smaller and makes access control explicit.
Separate Event Data From Retrieved Context
An AI workflow often needs more information than the event contains.
For example:
OrderCreated
|
v
Customer ID
|
+--> CRM
|
+--> Order History
|
+--> Inventory
|
+--> Payment History
The event should not attempt to contain the entire enterprise context.
Instead:
Event = Trigger + Stable Facts
and:
Retrieval = Additional Context
This makes the architecture more flexible.
Use Correlation and Causation IDs
When an event triggers an AI workflow, tracing becomes important.
For example:
{
"id": "evt-200",
"correlationId": "order-1001",
"causationId": "evt-199",
"type": "PaymentCompleted"
}
This can help reconstruct:
OrderCreated
|
v
PaymentInitiated
|
v
PaymentCompleted
|
v
AI Risk Analysis
|
v
RiskAlertCreated
Without correlation identifiers, debugging asynchronous AI workflows becomes significantly harder.
Design for Dead-Letter Handling
AI consumers can fail for reasons that traditional consumers may not:
Therefore, failed events should not disappear.
A typical architecture is:
Event Broker
|
v
AI Consumer
|
+---- Success ---> Processed
|
+---- Retry -----> Retry Queue
|
+---- Permanent -> Dead Letter
The dead-letter record should preserve enough metadata to diagnose the failure.
Validate Before Sending Data to the Model
Not every event field needs to reach an LLM.
Suppose the event contains:
CustomerId
Email
Phone
InternalNotes
CreditLimit
PaymentReference
OrderValue
The AI task may require only:
CustomerId
OrderValue
OrderHistory
Create a controlled projection:
var aiContext = new OrderAnalysisContext
{
OrderId = message.OrderId,
CustomerId = message.CustomerId,
TotalAmount = message.TotalAmount,
Currency = message.Currency
};
This follows a simple principle:
Minimize the data supplied to the model.
It reduces privacy exposure and makes the AI workflow easier to reason about.
Data Contracts and PII
Events can contain personally identifiable information.
Before creating an AI consumer, classify fields:
| Field | Sensitivity | AI Required? |
|---|
| Order ID | Low | Yes |
| Customer ID | Medium | Yes |
| Email | High | Sometimes |
| Phone | High | Usually No |
| Internal Notes | High | Depends |
| Payment Reference | High | Usually No |
Do not automatically forward every event field to the model.
Use explicit projections.
Contract Testing
Consumer-driven contract testing is useful for event-driven systems.
The basic workflow is:
Producer Contract
|
v
Schema Validation
|
v
Consumer Tests
|
v
Compatibility Check
For a .NET consumer, you might deserialize the contract during CI:
var message =
JsonSerializer.Deserialize<OrderCreatedEvent>(
json,
options);
Assert.NotNull(message);
Assert.False(
string.IsNullOrWhiteSpace(message!.OrderId));
For more sophisticated systems, use JSON Schema, AsyncAPI, or a schema registry appropriate to your messaging platform.
The goal is to detect incompatible contract changes before deployment.
Test AI-Specific Contract Failures
Traditional contract tests should be extended for AI consumers.
For example, test:
Missing currency
Unknown status
Invalid timestamp
Missing customer ID
Unexpected null
Unknown event version
Duplicate event
Out-of-order event
Oversized payload
Sensitive field present
Then verify that the AI workflow behaves safely.
For example:
Missing currency
|
v
Validation failure
|
v
No model call
|
v
Dead-letter / remediation
Do not allow invalid business context to reach the reasoning layer.
Handle Out-of-Order Events
Distributed systems can receive events in an order different from their creation order.
For example:
CustomerUpdated
PaymentCompleted
OrderCreated
may arrive as:
OrderCreated
CustomerUpdated
PaymentCompleted
An AI system that interprets events sequentially may form an incorrect state.
If ordering matters, include:
sequenceNumber
aggregateVersion
occurredAt
and define how consumers should handle out-of-order events.
Use Aggregate Versions
For entity-oriented events, a version can help consumers detect stale updates.
Example:
{
"customerId": "CUS-1001",
"version": 17,
"status": "Active"
}
If version 16 arrives after version 17, the consumer can identify the stale message.
This is particularly useful when events update state used by an AI agent.
Event Contract Example
A more complete event might look like:
{
"id": "evt-8f21",
"type": "OrderCreated",
"version": 2,
"source": "orders-service",
"occurredAt": "2026-08-12T09:30:00Z",
"correlationId": "corr-901",
"data": {
"orderId": "ORD-1001",
"customerId": "CUS-204",
"total": {
"amount": 249.99,
"currency": "USD"
},
"items": [
{
"productId": "PROD-10",
"quantity": 2
}
]
}
}
This contract is explicit enough for both traditional and AI consumers.
AI Consumer Architecture
A .NET AI consumer can follow:
Event
|
v
Schema Validation
|
v
Authorization
|
v
Data Projection
|
v
Context Enrichment
|
v
AI Agent
|
v
Policy Validation
|
v
Business Action
The model should not receive raw broker messages automatically.
There should be a controlled transformation between the event and the AI context.
Example Consumer
public async Task HandleAsync(
OrderCreatedEvent message,
CancellationToken cancellationToken)
{
ValidateContract(message);
var context =
await _contextBuilder.BuildAsync(
message,
cancellationToken);
var result =
await _agent.AnalyzeAsync(
context,
cancellationToken);
await _resultHandler.HandleAsync(
result,
cancellationToken);
}
Each stage has a separate responsibility.
ValidateContract
Checks that the event is structurally and semantically valid.
BuildAsync
Retrieves additional business context.
AnalyzeAsync
Invokes the AI workflow.
HandleAsync
Applies business rules to the result.
This separation is much easier to test than putting everything inside one event handler.
Do Not Let AI Directly Publish Arbitrary Events
Suppose an agent decides:
Customer is high risk.
It should not automatically be allowed to publish:
DeleteCustomer
SuspendAccount
CancelOrder
Instead:
AI Decision
|
v
Policy Validation
|
v
Business Service
|
v
Authorized Event
For high-impact actions, human approval may also be appropriate.
AI Output Should Also Have a Contract
The input event needs a contract.
The AI result needs one too.
For example:
{
"decision": "review",
"confidence": 0.87,
"reasonCodes": [
"HIGH_ORDER_VALUE",
"UNUSUAL_PATTERN"
]
}
The consuming application should validate this output before acting on it.
Avoid treating free-form model text as a business command.
Prefer:
Structured AI Output
|
v
Schema Validation
|
v
Business Rules
|
v
Action
Common Mistakes
Designing Events Around One Consumer
Events should represent business facts rather than a single application's internal needs.
Sending Raw Database Rows
Database schemas change independently of business contracts.
Letting AI Interpret Ambiguous Fields
Explicit semantics are better than assumptions.
Embedding Prompts in Business Events
This couples the event to a specific AI implementation.
Ignoring Versioning
Event contracts inevitably evolve.
Treating Duplicate Delivery as Impossible
Design consumers to be idempotent.
Sending Every Field to the Model
Use data minimization.
Ignoring Event Ordering
Define ordering requirements explicitly.
Allowing AI Outputs to Become Commands Automatically
Validate and authorize AI-generated actions.
Troubleshooting
Consumer Fails After a Producer Deployment
Check:
Schema version
Removed fields
Changed field types
Changed semantics
Required fields
Serialization settings
AI Produces Different Results After a Schema Change
Check whether field meaning changed even though the JSON schema remained compatible.
Duplicate AI Actions Occur
Implement idempotency using the event ID or an appropriate business idempotency key.
AI Uses Stale Information
Check event ordering, aggregate versions, caching, and enrichment timing.
Sensitive Data Reaches the Model
Review the event-to-AI projection layer and remove fields that are not required.
Events Trigger Expensive AI Calls Repeatedly
Check duplicate delivery, retries, idempotency, and whether every event actually needs an AI workflow.
Comparison: Traditional Events vs AI-Ready Events
| Area | Basic Event Contract | AI-Ready Contract |
|---|
| Schema | Defined | Defined and versioned |
| Business semantics | May be implicit | Explicit |
| Units | May be ambiguous | Explicit |
| Idempotency | Consumer-dependent | Explicitly designed |
| Tenant context | May be external | Explicitly governed |
| Data minimization | Often overlooked | Required before model use |
| Provenance | Basic | Correlation and source metadata |
| AI validation | Not applicable | Required |
| Output contract | N/A | Structured and validated |
| Security | Consumer-specific | Data and tool boundaries |
| Evolution | Ad hoc | Compatibility strategy |
Conclusion
Event-driven architecture provides a strong foundation for AI-enabled .NET systems, but AI consumers raise the standard for event quality.
An event consumed by an AI agent is not simply another message.
It can become context for a decision, trigger a workflow, influence a recommendation, or initiate a business action.
That makes ambiguity dangerous.
A robust architecture should look like:
Business Service
|
v
Versioned Event Contract
|
v
Event Broker
|
v
Validation
|
v
Authorized Context Enrichment
|
v
AI Agent
|
v
Structured AI Result
|
v
Policy Validation
|
v
Business Action
The most important principle is to keep the business contract independent from the AI implementation.
Your OrderCreated event should describe an order being created.
It should not contain a prompt telling an AI system what to do with that order.
That separation gives traditional services, analytics pipelines, AI agents, and future consumers a stable foundation.
For .NET teams, this means treating event contracts as first-class architecture assets: strongly typed, versioned, validated, observable, secure, and designed around business semantics.
When those contracts are reliable, AI becomes another consumer of the event-driven platform rather than a special integration that has to redefine the architecture.