My implementation of pipes follows the pattern of the calculator on slide 221, using a do/1 predicate to implement commands. Here's one of the two rules I've got for do(echo):
do(echo) :- echo, retract(echo), prompt(_, '\nCommand? '), writeln('Echo turned off; prompt turned on'), !.
That rule first sees if there's an echo/0 fact in the database. If there is, that indicates we're in echo mode—we're currently not prompting with Command? but we are echoing (printing) each command. We then:
See if you can write the second rule for do(echo). If echo can't be proven, it should do the following:
As mentioned in the sketchy hint in the write-up, to allow that manipulation of echo/0 with assert and retract you'll need to declare echo as dynamic. Have the following line as the first line in your pipes.pl.
:-dynamic(echo/0).
As help(dynamic) shows, that call "Informs the interpreter that the definition of the predicate(s) may change during execution (using assert/1 and/or retract/1)."
Here's a swipl session that shows how if you use a predicate out of the blue it's considered to be an error but if you declare the predicate to be dynamic, the absence of a procedure (i.e., having no clauses) is simply treated as failure.
% swipl ... ?- echo. ERROR: toplevel: Undefined procedure: echo/0 ?- dynamic(echo/0). true. ?- echo. false. ?- assert(echo). true. ?- echo. true. ?- retract(echo). true. ?- echo. false.
Another angle would be echo/1, with echo(on) and echo(off) facts.