I've been working on an AI-powered trip planning assistant that sits on top of an existing set of booking microservices. The AI layer is genuinely interesting work — natural language input, iterative trip refinement, the whole thing. But the most valuable engineering work had nothing to do with the AI itself.
It was about types. Specifically, who owns them and what happens when they drift.
The problem with local schemas
The AI assistant service had its own validation schema for incoming flight search requests. It wasn't wrong, exactly — it reflected the shape of requests the service expected at the time it was written. But the canonical definition of what a valid flight search request looks like lives in the fare search service, and over time, subtle differences had crept in.
This is a fairly standard distributed systems problem. When two services independently define what they think the same thing looks like, they'll stay in sync right up until they don't. A field gets added somewhere. A constraint gets tightened. Someone updates one schema and not the other. Nothing breaks immediately — the tests pass, the service starts — but you've created a time bomb.
The fix was straightforward: pull the shared type out of the fare search package and use that directly in the AI assistant, removing the local definition entirely. One source of truth. When the API evolves, every consumer stays aligned automatically.
Simple to describe. Surprisingly easy to defer.
Why AI features make this urgent
Here's the thing about building an AI layer on top of existing services: it doesn't introduce new complexity so much as it surfaces the existing ambiguity.
A traditional integration between two services fails fast. Service A sends a request to Service B, B returns an error, A logs it, someone gets paged. The feedback loop is tight enough that schema drift tends to get caught reasonably early.
An AI assistant is different. The user is expressing intent in natural language. The assistant is interpreting that intent, deciding what to query, constructing requests, handling the responses. There are more layers of abstraction between the user's words and the actual API call. When something goes wrong, it might manifest as a confusing or unhelpful response rather than a clear error — which means it can go unnoticed for longer.
More importantly, the AI layer is making decisions about how to construct requests. If its understanding of what a valid request looks like is subtly out of sync with reality, those decisions will be subtly wrong. Not catastrophically wrong — just wrong enough to be annoying and hard to diagnose.
This is why schema consolidation that might have felt like a nice-to-have became genuinely urgent once an AI layer was involved. The model compounds every ambiguity downstream.
Session state is harder than it looks
The other significant change this week was enforcing a required session-tracking header throughout the conversation service. This one is less about types and more about correctness guarantees.
The header was already being passed in some code paths. The problem was "some" — in a service where every request needs to carry session context to maintain coherent conversation state, optional isn't good enough. A user iterating on a trip in natural language needs the system to remember where they are in the conversation. If that header gets dropped mid-flow, the session context is gone and the experience breaks in a way that's confusing rather than obvious.
Making it a validated requirement — something the service explicitly checks for and rejects requests without — is the kind of change that feels bureaucratic until the alternative happens in production.
The lesson isn't "validate everything". It's more specific: identify the invariants your system actually depends on and make them impossible to violate rather than relying on every caller to get it right.
If you're planning to add an AI layer on top of an existing set of services, the time to audit your type ownership is before you do it, not after.
It's not that the AI makes the type problems worse, exactly. It's that it makes them more consequential and harder to spot. A messy schema definition that was fine when the integration was service-to-service becomes a genuine liability when a model is making decisions based on it.
The boring foundational work — shared types, enforced invariants, single sources of truth — isn't glamorous. But it's what determines whether the interesting work on top of it actually holds up.