Connecting Independent .NET Agents Without Losing Control
articleOctober 22, 2026Updated October 28, 2026By Ali Haghighi
Building Enterprise AI Systems, part 4
An incident investigation system can begin with one coordinator and a few read-only tools. Part 3 showed a small .NET version of that design.
Now assume a different team owns deployment analysis. Their service tracks rollouts across environments, knows how long a rollout took, and serves several consumers. Keeping that responsibility inside our incident application would create an ownership problem.
So we need a boundary between two independently deployed systems. We do not automatically need an agent mesh.
The contract comes before the protocol
The incident coordinator asks for deployment evidence within a time window. The deployment service returns observations and, optionally, interpretations. They are not interchangeable.
public sealed record DeploymentAnalysisRequest(
string IncidentId,
string Service,
DateTimeOffset From,
DateTimeOffset To);
public sealed record DeploymentObservation(
string DeploymentId,
DateTimeOffset Timestamp,
string SourceReference,
string Description);
public sealed record DeploymentAnalysisResponse(
string TaskId,
IReadOnlyList<DeploymentObservation> Observations,
IReadOnlyList<string> Hypotheses);This is an illustrative contract, not a claim that an implementation has been deployed. In a real system it needs validation, result limits, schema evolution rules, and an agreed definition of timestamps and service names.
A deployment at 14:02 is an observation. "The deployment caused the latency spike" is a hypothesis until further evidence supports it. If those are returned as a single free-text answer, the investigation coordinator cannot reliably tell which is which.
Start with a typed HttpClient
An ordinary authenticated HTTP API is a reasonable first step. The client below deliberately handles transport and deserialization, not retry policy or incident reasoning.
using System.Net.Http.Json;
using System.Text.Json;
public sealed class DeploymentAgentClient(HttpClient client)
{
public async Task<DeploymentAnalysisResponse> AnalyzeAsync(
DeploymentAnalysisRequest request,
CancellationToken cancellationToken)
{
using var response = await client.PostAsJsonAsync(
"v1/deployment-analysis", request, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content
.ReadFromJsonAsync<DeploymentAnalysisResponse>(
cancellationToken: cancellationToken)
?? throw new JsonException("Missing response body.");
}
}Register the client with a trusted, configured address:
builder.Services.AddHttpClient<DeploymentAgentClient>(client =>
{
client.BaseAddress = new Uri(
builder.Configuration["DeploymentAgent:BaseUrl"]
?? throw new InvalidOperationException(
"DeploymentAgent:BaseUrl is missing."));
client.Timeout = TimeSpan.FromSeconds(10);
});A client timeout alone is not a complete resilience strategy. The caller should also have an overall investigation deadline and should map dependency failures to a clear unavailable outcome. It must not translate a timeout into "no deployments found."
The base URL must come from trusted configuration, not user input. Otherwise a seemingly helpful agent endpoint can become an SSRF path into private infrastructure.
The security boundary is more than HTTPS
Transport encryption protects the connection; it does not grant permission to inspect production deployment data.
A service-to-service integration needs an accepted identity mechanism, such as a workload identity or OAuth 2.0 client-credentials flow. The receiving service must validate the issuer, audience, token validity, and permissions appropriate to the requested environment.
An ASP.NET Core endpoint can express its authorization requirement:
app.MapPost("/v1/deployment-analysis",
async (DeploymentAnalysisRequest request,
IDeploymentAnalysisService service,
CancellationToken ct) =>
{
if (request.To <= request.From)
return Results.BadRequest("Invalid time window.");
var result = await service.AnalyzeAsync(request, ct);
return Results.Ok(result);
})
.RequireAuthorization("DeploymentAnalysis.Read");This assumes authentication and the named policy have been configured correctly. RequireAuthorization does not configure token validation or the policy for you. The service must also check whether the caller can access the specific service and environment in the request.
Avoid forwarding a user's bearer token to another agent by default. Delegation needs an explicit trust and scope model.
Retries can multiply work
Imagine the deployment agent accepts a request, starts an expensive analysis, and the client times out just before receiving the response. A blind retry may start the same analysis again.
If the work is short and read-only, duplicate execution might be acceptable. For long-running work, I would prefer a task lifecycle:
POST /v1/analyses
-> 202 Accepted
-> taskId, statusUrl
GET /v1/analyses/{taskId}
-> queued | running | completed | failed
GET /v1/analyses/{taskId}/result
-> observations and hypothesesStatus and result endpoints require their own authorization checks. Tasks need retention limits. If the client uses an idempotency key, the server must persist and enforce it; merely sending a header does not provide deduplication.
Only retry failures that are plausibly transient, and only while the overall deadline permits it. Authorization failures, invalid contracts, and malformed responses should fail clearly.
Follow the request across services
Debugging becomes difficult when the incident coordinator and deployment agent report independent timelines.
Use distributed tracing across the outbound HttpClient request and incoming ASP.NET Core endpoint. Record the operation, duration, status, task identifier, and a non-sensitive investigation correlation value. Do not export secrets, full prompts, or private incident documents as trace attributes.
A trace answers which calls happened and where time went. It cannot establish whether an agent's conclusion was correct. For that, we need a separate evaluation: did the answer cite observations that actually support the claim?
This distinction is especially important when a model summarizes output from another model.
Does this mean we should adopt A2A?
Not necessarily.
The custom API above fits a narrow, agreed integration. The Agent2Agent protocol becomes more interesting when agents built by different teams or frameworks need common discovery, message exchange, and task semantics.
A bespoke JSON endpoint is not automatically A2A-compatible. Adopting A2A means implementing its actual protocol contracts and security requirements, preferably using a maintained SDK where suitable.
I'd test a protocol implementation against concrete requirements: capability discovery, authorized calls, task cancellation, deadline handling, and partial or invalid responses. Interoperability is valuable only when multiple independent consumers benefit from it.
The point where mesh infrastructure could help
With several independently operated agents, teams may repeatedly solve the same infrastructure problems: routing between environments, workload identity, policy distribution, and cross-service observability.
That is a reasonable moment to evaluate AI Mesh. But first compare it with existing API gateways, identity systems, discovery mechanisms, and service-mesh capabilities.
The evaluation should answer three practical questions:
- What repeated operational work does the mesh eliminate?
- What new dependencies and failure modes does it introduce?
- Can we measure a reduction in integration effort or operational risk?
A mesh won't repair an ambiguous evidence contract, and it won't make a model-generated guess trustworthy.
A compact integration test plan
These are the cases I'd insist on before allowing production incident data to cross the boundary:
| Case | Expected behavior | | --- | --- | | Authorized request | Source-linked observations returned | | Invalid or missing credentials | Request rejected | | Disallowed production scope | Denied without disclosing records | | Remote timeout | Explicit unavailable result | | Duplicate task submission | Deduplicated or bounded execution | | Unsupported contract version | Clear compatibility error | | Cross-service trace | Client and server operations correlated |
This is not a complete security review, but it gives engineers something concrete to build and verify.
The architectural decision
The sensible progression is less exciting than the diagram:
Start with a typed .NET integration. Add explicit identity, per-resource authorization, deadlines, tracing, and tests. Adopt A2A when independent agents genuinely benefit from protocol-level interoperability. Consider AI Mesh only when shared governance and connectivity problems have become measurable.
Make one boundary dependable before connecting ten more.
That is the point of this four-part series: AI Mesh may be useful, but sound engineering at the application and service boundaries comes first.
Companion code on GitHub
The agent communication sketch illustrates the caller-side boundary, deadlines, and explicit unavailable outcomes. It is intentionally not an A2A implementation or a production-ready security example.