In this section: Learning Path
- UMA Learning Path
- The Book. Pre-order
- End-to-End UMA Example: Feature Flag Service
- Ch.1: Introduction to UMA
- Ch.2: Device Independence
- Ch.3: What Is UMA?
- Ch.4: From SOA to Metadata
- Ch.5: Building UMA Services
- Ch.6: The UMA Runtime Layer
- Ch.7: WebAssembly and WASM Runtimes
- Ch.8: Contracts and Orchestration
- Ch.9: From Services to Systems
- Ch.10: Security and Trust in UMA
- Ch.11: UMA Patterns and Tradeoffs
- Ch.12: Evolving UMA Systems
- Ch.13: Agents, MCP, and Reasoning
- Ch.14: The Reference Experience
Chapter 12: Evolving and Adapting UMA Systems
How do you change a UMA system over time without introducing behavioral drift or breaking compatibility?
The question this chapter answers
Every distributed system eventually needs to change. The failure mode is not that change happens: it is that change happens silently. A service updates its behavior, nothing breaks immediately, and six months later two consumers are running against different assumptions about what the service does. UMA addresses this at the model level: evolution is contract-driven, which means incompatible changes become explicit events rather than silent incompatibilities. Chapter 12 establishes what that means operationally.
The core concept
A service can change its implementation freely as long as its declared contract is honored. When the contract must change (because the input shape, output shape, or behavioral guarantee changes) UMA provides a migration pattern: version the descriptor, run both versions in parallel, validate behavioral equivalence between them, then retire the old version only after all consumers have migrated.
This pattern transforms breaking changes from accidents into decisions. The descriptor version becomes the migration signal. Behavioral equivalence validation catches divergence before retirement. The parallel-run period makes rollback cheap. None of this requires a new infrastructure platform: it requires discipline about what counts as a contract and what counts as an implementation detail.
Chapter 12 applies this to a real service graph where three services need to evolve simultaneously. One service changes its output schema. A second service depends on that output. A third service uses both. The chapter walks through the sequencing, the validation steps, and the failure modes that the contract-driven approach prevents.
Why contract-driven evolution is different from backward compatibility
Backward compatibility is a promise not to break existing callers. It is a useful constraint, but it addresses only one dimension of evolution. It says nothing about whether new callers can use the service correctly: a service can be backward compatible while having a contract that no longer accurately describes its behavior. It says nothing about semantic drift: a service whose output field values have changed meaning while the field names remain the same is backward compatible in schema but not in semantics. And it says nothing about whether the contract reflects what the service actually does, or whether it reflects what the service did when the contract was first written.
Contract-driven evolution is a stronger requirement. Every change to a service's observable behavior must be reflected in a change to its declared contract. This means the runtime can detect incompatibilities before execution. not by comparing schemas, but by comparing behavioral declarations. A service that has added a side effect, changed its output semantics, or narrowed its valid input range must declare those changes explicitly. Callers that have not been updated to account for the new declaration are identified before they are broken.
This also changes the relationship between evolution and incidents. In a system without contract-driven evolution, a behavioral change that breaks a downstream caller produces an incident: a runtime failure that must be diagnosed, traced back to the change, and remediated under pressure. In a contract-driven system, the same change produces a contract incompatibility that the runtime reports before execution. The failure mode shifts from an incident to a planning event. Teams learn about incompatibilities when they make changes, not when their changes break something downstream.
What evolution looks like in a UMA system
When a service needs to change its contract, the UMA migration pattern requires declaring a new contract version alongside the existing one. The runtime manages both versions simultaneously: callers that have declared a dependency on the previous version continue to be served by it. callers that have updated their declarations are routed to the new version. No big-bang migration window, no coordination across teams to synchronize deployments, no rollback risk associated with updating all callers at once.
Behavioral equivalence validation is the mechanism that makes the parallel-run period safe. Before the old version is retired, the runtime validates that the new version produces equivalent outputs for the inputs that existing callers send. This catches cases where the contract change was declared correctly but the implementation drifted from the declaration: a new version that says it is behaviorally equivalent to the old one but isn't. The validation is automated and runs continuously during the parallel-run period, not as a one-time check at migration time.
Chapter 12 applies this to a worked example: a capability adds a new output field that some callers need and others do not. The chapter traces how the runtime handles callers that declare a dependency on the new field, callers that do not, callers that declared a dependency on the old schema and need to migrate, and the retirement of the old version after all callers have either migrated or been explicitly excluded. The example is chosen because this specific evolution pattern (additive change with heterogeneous callers) is the most common case in production systems and the one most often handled incorrectly.
How it connects
Chapter 11 established the decision framework for structuring UMA systems. Chapter 12 applies that structure to the dimension of time, showing how deliberate contract management prevents the drift that accumulates in systems that treat evolution as an implementation concern. Chapter 13 extends the model to AI-native execution environments where evolution pressure is higher and behavioral guarantees matter more.