The original design had three containers — the agent, the SIEM toolset, the internet toolset — talking to each other over HTTP. The shipped design is one process with internal modules. Collapsing them removed an attack surface rather than adding one, and the reason why is a useful correction to an instinct most of us have.
Why it was split in the first place
The reasoning was conventional and, in a different context, would have been right. Separate the components that touch customer telemetry from the components that reach the public internet. Give each one its own container, its own identity, its own network policy. Scale them independently. Segment the blast radius.
That is standard practice, and I did not question it until the deployment model changed.
What it actually cost
Three containers meant three images, three build paths, three deployment steps, and a config surface that had to stay consistent across all of them. Every tool call became an HTTP request to a service sitting on the same host, which means serialisation, a network hop, timeout handling, retry semantics, and a failure mode where a tool call fails for reasons that have nothing to do with the tool.
It also meant authentication between my own components. Which is either a real control or theatre, depending on your threat model — and that question turned out to be the whole argument.
| The split bought | The split cost |
|---|---|
| Segmentation between internal components | An auth surface between components that trust each other anyway |
| Independent scaling, in principle | Nothing ever needed to scale independently |
| Clean conceptual boundaries | Three build and deploy paths for one logical unit |
| — | Debugging across process boundaries for synchronous, co-located calls |
The correction: which boundary is actually doing the work
In a provider-hosted design — where the agent runs in my cloud and reaches into the customer's tenant — segmentation between my components is a meaningful control, because my infrastructure is the thing standing between the customer's data and the internet.
Once the deployment model moved to running entirely inside the customer's own tenant, that stopped being true. The security boundary that matters is the tenant boundary itself. All investigation data — telemetry, intermediate findings, state, audit records — stays inside the customer's own subscription. The only thing that exits is model inference. In that picture, a network hop between two of my modules on the same host is not a control. It is an additional listening socket, an additional credential, and additional code, all inside the perimeter that actually matters.
What collapsing bought
Tool calls became direct function calls: no serialisation, no network failure mode, no auth. One image, one deploy, one process to debug, one place where a stack trace tells the whole story. Startup got simpler, which matters more than it sounds once you are running a container per tenant across an unknown number of tenants.
It also made the dependency footprint smaller, because the HTTP client that had been carrying inter-component traffic went back to doing only what it should: talking to external APIs.
What stayed separate, and why
One component did not get absorbed, and the reason is the test for all of this: it sits in a genuinely different trust domain.
There is a small bootstrap service that runs on my infrastructure, not the customer's. It handles licence validation and delivers the prompt bundle to the agent at runtime, which is the mechanism that keeps the prompts from ever sitting at rest inside a customer-controlled container. That service and the agent do not trust each other by default, they have different lifecycles, they are operated by different parties, and the network between them is genuinely hostile.
So it is a separate service with real authentication, and that authentication is not theatre. The distinction is not "monolith good, services bad" — it is that a process boundary should exist where a trust boundary exists, and nowhere else.
The general version
Microservice instincts are trained on a context: independently scaling components, separate teams, separate deploy cadences, mutually distrusting domains. When none of those conditions hold, the pattern inverts and every boundary you add is pure cost — paid in build complexity, debugging time, and, in a security product, in surface you then have to justify to a buyer who is asking what your opaque container is doing with their data.