How does one clearly structure dependencies between core.async channels?

This question is kind of hard to answer because your question lacks specifics about your use case. Libraries like Graph, Javelin and Onyx all have different use cases that go beyond just making computations depend on each other.

If you would just like to have a thread or go block depend on results generated in another part of your system, I would suggest just using the core.async primitives without any additional libraries.

The most basic solution to making execution wait for another thread of activity is using blocking takes when taking values from channels. This will halt the thread (or go block) when no values are available on that channel.

As you can see in the following example, making a computation depend upon an activity done in another thread is very easy.

(let [c (chan)]
  (thread (>!! c “hello”))
  (assert (= “hello” (<!! c)))
  (close! c)

There are also more elaborate mechanisms available. The Alts!! function provides the ability to wait on many channels at the same time. Several different flavours of the pipeline function allow you to model concurrency in a dataflow like manner.

Are there any specific problems you run into that can not be expressed clearly using the build-in functions?