Zebra puzzle in Prolog
This is quite nice, but there are still several ways that would improve this.

Here are a  few ideas:

Relations instead of side-effects

First, I recommend to avoid side-effects. Instead, think in terms of relations. By this I mean that you should not use write/1 to print a solution, but express what constitutes a solution, as in the following:


solution(Days) :-
        Days = [[tuesday,_,_],[wednesday,_,_],[thursday,_,_],[friday,_,_]],
        etc.


Here, I have made Days available for reasoning as a predicate argument. This way, you can explicitly reason about solutions, and for example write actual test cases by stating what should and should not hold about given solutions. You cannot do this if the solution only occurs in the form of output on the system terminal.

If you need to print a solution in any way, you can use the more general form above to still do that. For example:


?- solution(Ds),
   maplist(writeln, Ds).
[tuesday,ms_green,water_tower]
[wednesday,mr_niven,frisbee]
[thursday,mr_klien,clothesline]
[friday,ms_barnum,balloon]


Use higher-order predicates

Suppose I give you the following auxiliary predicate:


list_member(Ls, E) :- member(E, Ls).


Then you can use maplist/2 to express members/2 as follows:


members(Es, Ls) :- maplist(list_member(Ls), Es).


When you encounter predicates that reason over lists, check the available higher-order predicates (especially maplist/N and foldl/N) to see if they can simplify your code.

Use dif/2 instead of (\=)/2

You are currently using the impure predicate (\=)/2 in your code. This prevents you from using Prolog to its fullest potential.

Use dif/2 instead to benefit from the declarative nature of true relations: You can then pull these goals towards the beginning of the clause or at least earlier, and benefit from their pruning in addition to the added flexibility of placing the goal anywhere you like in principle.

Indentation

Never put (;)/2 at the end of a line. It looks too similar to (',')/2, which typically occurs at that position.

In total, a solution could look like (in addition to the points mentioned previously):


solution(Days) :-
        Days = [[tuesday,_,_],[wednesday,_,_],[thursday,_,_],[friday,_,_]],
        before([_,mr_klien,_],[_,_,balloon],Days),
        before([_,_,frisbee],[_,mr_klien,_],Days),
        (   member([friday,ms_barnum,_],Days)
        ;   member([friday,_,clothesline],Days)
        ;   member([friday,ms_barnum,clothesline],Days)
        ),
        members([[_,mr_klien,_],[_,ms_barnum,_],[_,ms_green,_],[_,mr_niven,_]], Days),
        members([[_,_,balloon],[_,_,frisbee],[_,_,clothesline],[_,_,water_tower]], Days),
        dif(NOT_ms_green, ms_green),
        member([_,NOT_ms_green,frisbee],Days),
        dif(NOT_mr_niven, mr_niven),
        member([tuesday,NOT_mr_niven,_],Days),
        dif(NOT_mr_klien, mr_klien),
        member([_,NOT_mr_klien,water_tower],Days).


Now we can query:


?- solution(Ds).
Ds = [[tuesday, ms_green, water_tower], [wednesday, mr_niven, frisbee], [thursday, mr_klien, clothesline], [friday, ms_barnum, balloon]] ;
false.


And also ask something different, for example:


?- Ds = [[monday, ms_green|_]|_],
   solution(Ds).
false.


Thus, the system tells us: No, that's not a solution!

This is now easily possible because we can reason explicitly about solutions, having made them available as a predicate argument.