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:

  1. retract(echo) to remove that echo/0 fact from the database.
  2. Set the prompt to '\nCommand? '.
  3. Inform the user about the change in mode.

See if you can write the second rule for do(echo). If echo can't be proven, it should do the following:

  1. assert(echo)
  2. Use prompt(_,'') to turn off the prompt.
  3. Inform the user about the change in mode.

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.