The agent ecosystem has standardized capabilities, skills, tools, and agent cards. The unresolved question is what happens when you combine them. This page gives that question two operators, a zero, a suite of property-tested laws — and three counterexamples where our own laws still don't hold, which you can reproduce in this browser tab.
01 · The capability moment
A capability is a packaged unit of agent behavior that can be defined once and reused across
agents. In 2026 four major ecosystems converged on capability-shaped objects: Pydantic AI v2 bundles
instructions, tools, lifecycle hooks and model settings into a capability; Agent Skills
packages procedural knowledge as a folder with a SKILL.md; MCP exposes tools and
resources a client can invoke; A2A publishes an Agent Card so other agents can discover and delegate.
All four answer what an agent can do. None of them specify what it means to combine two.
The convergence is real and it is recent. It is worth being precise about who did what, because the argument on this page depends on the difference between having capabilities and composing them.
| Ecosystem | The unit | How you combine them | Composition semantics |
|---|---|---|---|
| Pydantic AI v2 23 June 2026 |
A capability — "a reusable, composable unit of agent behavior" bundling tools, lifecycle hooks, instructions, model settings and models. |
Agent(model, capabilities=[a, b, …]) |
The docs say capabilities "all compose, with each other and with your own." In the published material we found no stated ordering rule, no conflict-resolution or precedence mechanism, and no algebraic properties. a list |
| Agent Skills | A skill — an open-format folder centred on SKILL.md, with progressive disclosure. |
Put more folders on disk; the agent loads what looks relevant. | Relevance-triggered. Two skills that disagree are resolved by the model, at inference time. a directory |
| MCP spec 2026-07-28 |
Tools, resources, prompts on a server. The newest spec moves to a stateless request/response core with header-based routing and cacheable, deterministically ordered list results. | Connect another server. | Vertical: model to tool. Names share one flat space per client; nothing in the protocol decides what two servers offering the same tool mean together. a namespace |
| A2A joined AAIF 17 Aug 2026 |
An Agent Card. "Other agents read that card, discover capabilities, and delegate tasks without a human brokering the hand-off." | Delegate to another agent. | Horizontal: agent to agent. Discovery and delegation are specified; what a delegated result composes with on return is not. a hand-off |
The protocols are converging institutionally. Their composition semantics are not. — A2A joined the Agentic AI Foundation on 17 August 2026, alongside MCP, AGENTS.md, goose and agentgateway. This page was written the same day.
02 · Composition is not aggregation
Capability composition is the process of combining independently defined agent abilities into a larger executable behavior while preserving — or explicitly resolving — their interfaces, authority, state, confidence, cost, provenance, and failure semantics. It is distinct from aggregation, which only asserts that several capabilities are available. Aggregation is a set membership question; composition is an algebraic one, because the combined behavior has properties that none of the parts had individually.
Here is the concrete test. Given capabilities=[A, B, C], answer these six questions using only
the list. You cannot answer any of them.
[A, B] and [B, A] are different programs. A list is unordered by intent and ordered by accident — whichever the runtime happens to iterate first.Every one of those six is a question about what the combination means, and each has a standard mathematical answer. The rest of this page is those answers, in the order you would need them.
03 · Two operators
Parallel composition (written A & B) merges two capabilities into a joint capability
set that a single agent or coalition holds at once. Order does not matter and adding the same capability
twice changes nothing. Sequential composition (written A |> B) feeds the output of one
into the input of the next. Order matters absolutely, the hand-off types must line up, and quantities
like confidence and cost accumulate along the chain. They are different algebras, and conflating them is
where most composition bugs live.
[&] has had exactly these two operators since draft v0.1.0, before either had laws attached. Look at the shapes before the terminology — the terminology is only a name for what you can already see.
A merge. The result is a capability set containing everything either side offered, tagged with who provides it so provenance survives the merge.
&noneA hand-off. The result is a pipeline whose external interface is A's input and B's output, and whose internals must agree at every joint.
id& an algebra — a bounded join-semilattice, which is what lets independent agents merge to the
same set — and left |> as "data flows through," with no laws at all. Everything hard on this
page comes from closing that gap, and §12 is where closing it is still incomplete.
04 · What laws buy you
A composition law is a property that must hold for every possible combination of capabilities, stated precisely enough to be tested. Laws are not decoration on top of a runtime — they are the part of the runtime you can rely on without reading its source. Each of the five classical laws answers a question a systems engineer already asks; naming them just makes the answer checkable.
reduce(compose, capabilities, empty) and have the empty case be correct rather than special-cased. No identity means every fold needs a branch.Two things are worth noting about that last row. First, it is the only law of the five that is about refusal rather than rearrangement. Second, it is the one most composition systems don't have — which is the subject of the next section.
05 · Zero is an answer
In [&], an incompatible composition produces 0̲ — a distinguished zero value that
absorbs everything it touches. A type-incompatible pipeline does not "fail validation" and it does not
throw; it evaluates to the zero of the composition, exactly as multiplying by zero evaluates rather
than erroring. Anything composed with 0̲ is 0̲. The practical consequence is that a
broken step cannot be compensated for by good steps around it, and a high utility score cannot resurrect
it — the floor is checked before ranking, never after.
Four things collapse a composite to 0̲, and they are checked in this order, before any scoring happens at all.
feeds_into does not intersect the consumer's accepts_from. There is no partial credit for a pipeline whose joints don't meet.0̲, always — never a low score.Because refusal is a value rather than an exception, it can carry an explanation. Every composite — surviving or annihilated — ships a certificate saying which gate decided and why.
// (retrieve |> act) |> consolidate — with a backward step introduced { "subject": { "kind": "weave-composite", "parts": ["a1c4…", "7f20…"] }, "analyzer": { "name": "compose", "version": "0.1.0" }, "verdict": { "certified": false, "costClass": "unknown" }, "policy": { "resourceDecision": "annihilate", "reason": "π-violation: cannot chain 'act' after 'consolidate'" } } // result: 0̲ — and no amount of utility on the surviving steps changes it.
06 · Composition creates new failures
Because safety is non-compositional: a property proven of each part separately need not hold of the whole. Two agents that individually cannot reach a forbidden capability can collectively reach it when one supplies a precondition the other was missing. This is not a hypothetical — as of 2026 it has been formally proven for capability-based AI systems, and separately measured in deployed agent protocols, by two independent groups.
Formal Security Analysis of Agent Protocol Composition (AgentThread) analysed five protocols — MCP, A2A, ANP, ACP-Cap and ACP-Client. It reports 35 specification-level findings, supports them with 80 implementation tests against production SDKs and MCP reference servers, and then finds 30 additional failures that emerge only under protocol composition — failures invisible to any analysis of a single protocol.
No protocol assigns enforcement for cross-protocol behavior. — AgentThread. The paper's conclusion is that insecurity here is "not only a specification or implementation problem, but also a responsibility gap across protocols, SDKs, and deployments."
Safety is Non-Compositional: A Formal Framework for Capability-Based AI Systems (Spera) contains what it describes as the first formal proof that safety is non-compositional in the presence of conjunctive capability dependencies — cases where a capability requires several others simultaneously. The paper argues these need hyperedges with multi-element tails, because a pairwise graph can only encode single preconditions and so cannot express "A and B together unlock C" without inventing intermediate nodes that do not exist.
| Measurement | Value | What it is |
|---|---|---|
| Trajectories analysed | 900 | Real multi-tool agent runs, across two independent benchmarks. |
| Conjunctive dependencies | 42.6% | Share of trajectories containing a conjunctive dependency. 95% CI [39.4%, 45.8%]. |
| ToolBench G3 | 47.4% | Prevalence in the first benchmark. |
| TaskBench DAG | 36.5% | Prevalence in the second. |
Roughly two in five real agent trajectories already contain the structure that makes safety non-compositional. That is the empirical case for treating composition as its own layer with its own refusal semantics, rather than as a property you can infer from well-behaved parts.
07 · Hand-offs are compositions
An audit of 824 agent-to-agent execution logs published at ACL 2026 found four dominant error types at the message hand-off — the edge, not the node. In order of prevalence: Signal Corruption (36.8%), Data Gap (29.1%), Referential Drift (27.3%) and Capability Gap (6.8%). The significant finding is where the errors live: not inside agents, but between them. A hand-off is a sequential composition, and these are its failure modes.
Each of those four is refused by a different part of the composition machinery — and each is refused at compose time, before the pipeline runs, rather than diagnosed afterwards from a transcript.
| Failure class | Share | What goes wrong | What refuses it |
|---|---|---|---|
| Signal Corruption | 36.8% |
The message degrades in transit — meaning is altered, truncated, or re-encoded as it crosses the boundary. | The |> provenance chain: every stage's contribution is recorded, so a corrupted step is attributable rather than absorbed. running |
| Data Gap | 29.1% |
The receiver needs something the sender never provided. The classic silent failure: the agent proceeds with a hole. | The contract check. feeds_into must intersect accepts_from or the stage is 0̲. A gap is not a degraded run; it is no run. running |
| Referential Drift | 27.3% |
Both agents use the same term for different things. Nobody errors; they simply stop talking about the same object. | The epistemic requirement that coordination facts be common knowledge, not merely known — a joint plan resting on a fact one member does not commonly know is not executable. specified |
| Capability Gap | 6.8% |
The receiver was handed work it fundamentally cannot do. | The strategic check — ought implies can. A coalition may not be bound to a goal it cannot force; if it cannot, the composition is inadmissible and escalates instead of running. specified |
08 · Reliability composes too
Different quantities compose by different rules. Along a sequential pipeline, confidence multiplies, cost sums, and worst-case latency takes the maximum. Because confidence is a product of numbers below one, it falls geometrically with chain length — which is why practitioners keep rediscovering that long agent relays are unreliable. It is not a property of agents. It is a property of products.
Move the sliders. The curve is cn, and the threshold line is whatever the consuming
step demands before it will accept the input.
09 · Communication is not governance
Largely not. A June 2026 gap analysis by Kang and Diponegoro applied a six-dimension governance taxonomy — membership, deliberation, voting, dissent preservation, human escalation, and audit/replay — to MCP, A2A, ACP, ANP and ERC-8004. It found voting and dissent preservation universally absent, deliberation absent or partial, and concluded that agent community governance is a missing architectural layer rather than a set of missing protocol features.
That conclusion — governance belongs in a layer above interoperability, not inside it — is independent confirmation of a design decision [&] made for its own reasons: it does not replace MCP or A2A, it compiles into them, and it keeps the verdict layer separate from the transport. The State column below is ours, and it is deliberately unkind.
| Governance dimension | In the surveyed protocols | The [&] concept | State |
|---|---|---|---|
| Membership | Partial — identity and discovery exist; belonging to a decision-making body does not. | The coalition's agents set, with each capability holder-tagged through the merge. | specified |
| Deliberation | Absent or partial. | Cyclicity routing — a topology with a cycle routes to deliberation instead of acting. | running |
| Voting | Universally absent. | — | not implemented |
| Dissent preservation | Universally absent. | Unresolved conflict tags survive a merge and floor the composite rather than being averaged away. | running |
| Human escalation | Partial. | Contrary-to-duty escalation — a failed obligation routes to a human rather than degrading silently. | specified |
| Audit / replay | Partial. | The provenance hash-chain carried by every composite, plus the decision certificate naming the gate that decided. | running |
not implemented is more useful to you than a row that says
"roadmap." Note also that we score no better than the protocols on the two dimensions the paper found
universally missing — we match on dissent and lose on voting.
10 · Capability → coalition
A coalition lifts composition from one agent to a group. The question stops being "do I hold these capabilities?" and becomes: can this group, by pooling typed capabilities and coordinating, ensure the goal — with each task owned, the shared facts common knowledge, the joint run supervised, and a safety floor none of them can weaken? Every clause in that sentence is a separate admissibility check, and a coalition that fails any of them is refused before it runs.
canEnsure exists at strategic.mjs:70, common knowledge at
epistemic.mjs:68. The compose runtime calls neither. Its exports are
composeAnd, composePipe and composeTree, and
grep -n "strategic\|canEnsure\|common" compose.mjs returns nothing. So the pieces exist and
the wiring does not — the coalition object below is a draft schema over primitives that have never been
asked to admit one. The playground in §11 runs; this does not. We are marking it rather than blurring it,
because a page about verification that overclaims its own maturity has refuted itself in advance.
{
"coalition": "ship_it",
"agents": ["dev", "qa", "ops"],
"ensure": "deployed AND tests_pass", // can the group FORCE this?
"common_knowledge": ["release_plan", // not just known — commonly known
"rollback_ready"],
"owns": { "dev": "build", // every task has exactly one owner
"qa": "verify",
"ops": "release" },
"shield": ["NEVER secrets_leaked"], // safety over the JOINT trajectory
"floor": ["entrench: no_prod_without_qa"], // un-weakenable, even by learning
"compose": "build |> verify |> release"
}
The interesting clause is ensure, because it is the one that can refuse a plan that is
otherwise perfectly well-typed. In the worked example, the full coalition can force the goal — but
dev alone cannot. So a dev-only coalition is inadmissible and escalates to
recruit QA and ops, rather than starting work it cannot finish. That is the Capability Gap from
§07, caught before the hand-off instead of after it.
And floor is the clause that survives learning. A coalition may revise anything it likes as it
gets better at its job — except the entrenched floor. If it later discovers a faster path that skips QA,
the floor refuses the improvement. That refusal is the feature.
11 · Compose it yourself
By running it. Below is the actual composition runtime — compose.mjs, value.mjs
and numerics.mjs from the box-and-box package, inlined into this page unmodified except for
their import statements. Nothing here is a mock or a re-implementation for the web. Pick an expression
shape, set the contract types and phases, and watch the verdict. When it says 0̲, that is the
same code path that would refuse the composition in production.
retrieve → route → act → learn → consolidate. A pipeline may stay
at a phase or move forward through it; it may never go back. Two experiments worth a minute: set
B.accepts_from to image and watch the type floor fire; then set A's phase to
learn while B stays at route and watch the backward-step floor fire instead.
Those are the Data Gap and backward step refusals from §07 and §05, in the real runtime.
12 · Break it
A page arguing that composition needs laws would be worthless if it hid the laws it fails. The compose suite runs 2000 trials per law and prints three lines in red on every single run, by design — the build breaks if one of them ever starts passing, because that is the signal the fix landed and the law should be promoted into the real suite.
Swap the two operands of a commutative merge. One side lives. One side annihilates.
It would be easier to report if it were. The precise situation is more specific, and worth stating exactly:
A & B == B & A holds on the commutative sub-carrier — the families that genuinely are commutative. Passes, 2000 trials.isZero(a&b) === isZero(b&a) — the merge's own floor is commutative. Passes. This anchor exists so the gap below cannot be mistaken for a broken merge.isZero((a&b)|>c) === isZero((b&a)|>c) — FALSIFIED. Operand order at & leaks into a downstream pipeline floor.isZero((a|>b)|>c) === isZero(a|>(b|>c)) — FALSIFIED. The pipeline floor is not association-invariant.|>, in either association — FALSIFIED. A right-leaning grouping can smuggle a backward step past the floor.
"The pipeline is non-associative" is a symptom, not the disease. The disease is that the phase carrier is a
single slot. The merge sets it with a first-non-null rule, which is order-dependent, and the
pipeline's floor reads it. So merge order writes a value that pipeline safety later depends on — and the
three failing laws are three routes to that same leak. A left-fold-only pipeline would close CP5 and CP6
and not CP7, because there is no re-grouping to outlaw in CP7. Only carrying an
[entry, exit] phase interval instead of one slot closes all three.
13 · The broader formal landscape
Yes — and increasingly, in 2026. A typed lambda calculus for agent composition, a hypergraph framework for capability safety, assume-guarantee contracts for autonomous agents, and a formal security analysis of protocol composition were all published within months of each other, by unrelated groups, in unrelated venues. That convergence is the strongest available evidence that composition semantics is a real gap rather than a framing we invented to sell an idea.
Bundles instructions, tools, hooks and model settings into one reusable unit. Establishes the noun the whole ecosystem now shares.
An open folder format so a capability can move between vendors and runtimes rather than living inside one framework.
Agent Cards let agents find each other and hand off work without a human broker. Solves finding; leaves combining open.
A typed lambda calculus extending STLC with oracle calls, bounded fixpoints, probabilistic choice and mutable environments. Theorem 5.7 proves pipeline composition is associative with an identity — independently deriving the two laws our |> claims.
Formal proof that per-part safety does not imply whole-system safety under conjunctive dependencies, plus the measurement that 42.6% of real trajectories contain them.
Five protocols, 35 specification findings, 80 implementation tests — and 30 failures that appear only when protocols are composed, with no protocol owning the joint.
Two operators with property-tested laws, an absorbing zero with fail-closed defaults, decision certificates on every verdict — and three published counterexamples where the laws still fail. Not first, and not alone. Just runnable, and honest about its holes.
Look at where these independent lines of work are pointing. That is the layer. — The claim is not that others missed it. It is that everyone is arriving at the same place, and the place needs a runtime.
14 · Compile outward
They are the transports; composition is the layer above. MCP standardizes how a model reaches a tool (vertical). A2A standardizes how an agent reaches another agent (horizontal). Neither says what the combination means, and — per the governance analysis in §09 — that is best answered in a separate architectural layer rather than by extending either protocol. A composition declaration is validated and governed on its own terms, then generated down into MCP server configuration and A2A agent cards.
Concretely, the reference implementation takes a declaration and emits both wire formats:
# the reference CLI lives in the Elixir implementation; example paths are relative to it $ cd AmpersandBoxDesign/reference/elixir/ampersand_core $ mix deps.get && mix escript.build $ ./ampersand validate ../../../examples/infra-operator.ampersand.json $ ./ampersand compose ../../../examples/infra-operator.ampersand.json $ ./ampersand generate mcp ../../../examples/infra-operator.ampersand.json $ ./ampersand generate a2a ../../../examples/infra-operator.ampersand.json # the law suites live in the kernel package — a DIFFERENT directory $ cd AmpersandBoxDesign/box-and-box $ node test/laws.mjs // 109 enforced kernel laws $ node test/compose-laws.mjs // 101 enforced + 3 declared-open
15 · The ending this page has to have
Every row names a claim, its honest status, the command that exercises it, what you should see, and the specific observation that would falsify it. If you produce something in the last column, the claim is dead and we want the counterexample.
| Claim | Status | Command | Expected | What kills it |
|---|---|---|---|---|
| The two operators' enforced laws hold | running | cd box-and-box && node test/compose-laws.mjs |
101 enforced pass (100 suite + 1 anchor); CP5/CP6/CP7 print FALSIFIED in red | Any of CA1–CA4, CP1–CP4, CX1–CX6 or AC-COMM failing. Or CP5/CP6/CP7 passing without a fix landing — that would mean the trial generator stopped reaching the counterexample. |
| 210 enforced laws, derived not typed | running | cd box-and-box && node test/laws.mjs && node test/compose-laws.mjs |
109 kernel + 101 compose, both totals printed by the suites themselves | A total on this page that no suite prints. The published and printed counts drifted apart once before; that is why nothing here is hand-typed. |
| An incompatible hand-off annihilates rather than degrading | running | The playground in §11 — set B's accepts_from to something A does not feed |
0̲, immediately, with the failing gate named |
A surviving composite with a degraded score instead of a zero. Or a thrown exception rather than a value. |
| Utility cannot resurrect a floored branch | running | Set a brick's utility arbitrarily high, then break its contract | Still 0̲ — the floor is checked before ranking |
Any ordering of gates where a high-utility uncertified branch survives to be selected. |
| CP7 is reproducible in this browser tab | declared open | §12 — press swap | One side LIVE, one side 0̲, from a commutative operator |
Both sides agreeing. If you cannot reproduce it here, this page is lying about its own defect and we want to know first. |
| The coalition rungs exist but composition does not call them | specified | cd box-and-box && grep -n "strategic\|canEnsure\|common" compose.mjs |
No output. canEnsure is real — strategic.mjs:70 — and so is common at epistemic.mjs:68. The compose runtime references neither; its exports are composeAnd, composePipe, composeTree. |
Any hit in that grep, which would mean coalition admissibility is wired into composition and §10 understates what ships. Equally: us describing §10 as shipped while this grep stays empty. |
| Every external number here traces to a primary source | external | Follow the links in the footer | Each figure appears in the paper itself, not in a summary of it | Any number here that only exists in a blog post. Two were removed from a draft of this page for exactly that reason — see §08. |
Absence of evidence must remain distinguishable from evidence of absence, and every consequential
transformation must expose why it occurred.
— the [&] doctrine. It is why 0̲ is a value that carries a reason rather than an exception that carries a stack trace.