That's one of the fun issues with service oriented architecture.
You either choose high-chatter and low complexity, or low-chatter and high complexity. As high-chatter is also a decrease in performance (mostly due to JSON serialisation/deserialisation and HTTP requests), we opted for low-chatter.
Resources that were a composite of other resources had their own knowledge of how they were composed, and generated their own IDs to save that knowledge. Each new composition resource got it's own Accept header identifier as a result.
Within that composition resource, there wasn't just knowledge of "I'm composed of x:17 and y:3", but also knowledge of how to search across the underlying services. This could be to call search on the underlying service if one side of the composition was dominant in applying the filter to the dataset.
Where it was trivial, a search index belonging to the composed service was used. And because this duplicates data from the underlying resources, messaging was put into the underlying service to notify the composed service of changes that affect searches.
For simplicity you can just imagine that we had some kind of table in the composed service, with ID on each row that because the ID of the composed resource, and that we could search that table, and that messaging magic kept that table in sync.
We could do all of this because we owned all of the services. If we didn't, then I imagine we would have followed a similar path to http://ql.io/