Agents drift when interfaces are loose
Build a multi-agent workflow and you will likely watch it fail in a subtle way. The pipeline completes, agents take actions, yet something is off: one agent closes an issue another just opened, or a change ships that trips a check the agent never knew about. The root cause is almost always implicit assumptions about state, ordering, or validation that the agents made because nobody made the contracts explicit.
At GitHub, where agentic experiences span Copilot, internal automation, and multi-agent orchestration, the lesson is clear: these systems behave like distributed systems, not chat interfaces. That means reliability comes from the same discipline you would apply to any service boundary — explicit schemas, constrained actions, and enforced protocols.
Typed schemas catch failures at the boundary
Multi-agent workflows commonly fail early because agents exchange messy JSON or natural language with drifting field names and mismatched types. Nothing enforces consistency, so downstream steps guess at the meaning of payloads. The fix is to define machine-checkable contracts at every agent boundary, just as you would between services in a distributed system.
Start by declaring the data shape each agent must return:
type UserProfile = {
id: number;
email: string;
plan: "free" | "pro" | "enterprise";
};
With a typed schema, debugging shifts from reading logs and guessing to a precise statement: this payload violated schema X. Treat a violation like a contract failure — retry, repair, or escalate before the bad state propagates further into the workflow.
The bottom line: typed schemas are table stakes. Without them, no amount of prompt engineering or orchestration logic will make a multi-agent system reliable.
Constraining what agents can do
Typed data alone does not stop multi-agent systems from failing. LLMs follow explicit instructions, not implied intent. A prompt like “analyze this issue and help the team take action” leaves every agent free to do something reasonable — close, assign, escalate, or do nothing — none of which is automatable.
Action schemas narrow that space by defining the exact set of allowed actions and their structure. Not every intermediate step needs such structure, but the final outcome of the workflow should always resolve to one item in a small, explicit set:
const ActionSchema = z.discriminatedUnion("type", [
{ type: "request-more-info", missing: string[] },
{ type: "assign", assignee: string },
{ type: "close-as-duplicate", duplicateOf: number },
{ type: "no-action" }
]);
With this in place, an agent must return exactly one valid action. Anything else fails validation and is retried or escalated, so no ambiguous outcome can silently reach the production system.
The bottom line: most agent failures are action failures. If the set of results is not small and explicit, the system is not reliable no matter how well the prompts are written.
Enforcing contracts with MCP
Schemas and constrained actions only work if they are actually enforced at runtime. Without enforcement, they are conventions — followed until one agent drifts and nothing stops it. Model Context Protocol (MCP) provides that enforcement layer, turning patterns into hard contracts.
MCP defines an input and output schema for every tool and resource, and it validates calls before execution:
{
"name": "create_issue",
"input_schema": { ... },
"output_schema": { ... }
}
The effect is that agents cannot invent fields, omit required inputs, or silently drift across interface versions. Validation runs before any side effect, so bad state never reaches external systems in the first place. It also gives you a clean place to hook in retries or escalation when a call fails validation.
The bottom line: schemas define structure, action schemas define intent, and MCP enforces both. Together they turn an assembly of LLM calls into a coherent component of a larger system.
Treat agents like system components
Multi-agent systems become reliable when structure is explicit at every layer: typed data, constrained actions, and enforced interfaces. The shift is simple — treat agents like code rather than chat. When each boundary is machine-checkable and each outcome is a defined action in a small set, agents start to behave like dependable parts of the stack.



