A silent failure at two agents
When we added a second production agent, copying the first agent's runtime looked inexpensive because each branch was small. The websocket handler nevertheless switched on an agent-type string in four places, and those paths already carried different argument dictionaries, auth mappings, cache keys, and context loaders. Shared concerns had begun to drift even though the surface area of each branch still looked manageable.
The defect appeared as a missing place rather than a crash. One path performed a location-name lookup; the other defaulted to an empty dictionary. Store-specific queries then failed to resolve a location, without raising an exception or writing a useful log. The request appeared to succeed, so ordinary monitoring did not catch it. The cost of that wiring is that six shared concerns can now change independently and fail in different ways.
Put in the manifest only what varies by agent
The conversion moved agent-specific choices into a manifest: graph strategy, model use case, tool groups, context slots, prompt source, capabilities, and access policy. Streaming, persistence, reconnection, auth enforcement, checkpointing, and observability stayed in the runtime.
A field belongs in the manifest when it varies by agent and can be validated before execution. A concern belongs in the runtime when every agent needs the same implementation. Applying that test took agent names out of the websocket path without pretending every agent had the same tools or topology. If a reviewer cannot tell which side a field belongs on, the field is probably doing two jobs and will leak into both.
Fail closed, because degraded context looks like success
The compiler checks whether the user may access the agent, collects requested tools, intersects them with user permissions, drops tools whose domains are not ready, loads declared context, creates the model, and builds the graph. An explicit override cannot restore a tool a gate removed. The compile fails instead.
Graceful fallback would have been the friendlier product choice. We had already seen what degraded context looks like: a request that appears to work while losing part of its meaning. Refusing to create the agent makes the missing contract visible before an answer reaches a user. A weaker agent that still answers is how a silent lookup failure gets reported as an unreliable model.
Audit the last copy before adding the next agent
Before adding another agent, search the runtime for every branch on agent identity. Compare auth, context, caching, tool binding, and streaming across those paths. Any unexplained difference is either a bug or an undeclared product decision, and it should be written down as one or the other before the wiring is copied again.
Early abstraction still has a cost. The first platform version supported too few graph strategies and later needed a typed composed-graph option. That correction was cheaper than reconciling another copied runtime path. The trigger is the first duplicated integration seam, not a particular agent count.