agent plumbing: convert outbox to subscribers and an iterator

WaitForMessage() could only work for one thread, because it was using a
singular channel for outboxes. This was fine when we only had one user,
but WaitForMessageCount() was kinda similar, and had its own thing, and
I want to rework how polling works and need another user.

Anyway, this one is hand-coded, because Sketch really struggled
with getting the iterator convincingly safe. In a follow-up commit,
I'll try to get Sketch to write some tests.
diff --git a/loop/agent_test.go b/loop/agent_test.go
index c62fd21..9663e26 100644
--- a/loop/agent_test.go
+++ b/loop/agent_test.go
@@ -94,26 +94,16 @@
 
 	// Collect responses with a timeout
 	var responses []AgentMessage
-	timeout := time.After(10 * time.Second)
+	ctx2, _ := context.WithDeadline(ctx, time.Now().Add(10*time.Second))
 	done := false
+	it := agent.NewIterator(ctx2, 0)
 
 	for !done {
-		select {
-		case <-timeout:
-			t.Log("Timeout reached while waiting for agent responses")
+		msg := it.Next()
+		t.Logf("Received message: Type=%s, EndOfTurn=%v, Content=%q", msg.Type, msg.EndOfTurn, msg.Content)
+		responses = append(responses, *msg)
+		if msg.EndOfTurn {
 			done = true
-		default:
-			select {
-			case msg := <-agent.outbox:
-				t.Logf("Received message: Type=%s, EndOfTurn=%v, Content=%q", msg.Type, msg.EndOfTurn, msg.Content)
-				responses = append(responses, msg)
-				if msg.EndOfTurn {
-					done = true
-				}
-			default:
-				// No more messages available right now
-				time.Sleep(100 * time.Millisecond)
-			}
 		}
 	}