Hi Nathan,
Well, this is an unorthodox solution using org-ql, but it seems to work.
So, for what it's worth:
#+BEGIN_SRC elisp
(let* ((sub-query (lambda ()
(save-excursion
(save-restriction
(cons (org-ql--add-markers (org-element-headline-parser (line-end-position)))
(-take 3 (progn
(org-narrow-to-subtree)
(org-ql-select nil
'(todo)
:narrow t
:action 'element-with-markers))))))))
(entries (-flatten-n 1 (org-ql-select buffer
'(and (tags "PROJECT")
(not (todo)))
:action sub-query))))
(org-ql-agenda--agenda nil nil :entries entries))
#+END_SRC
This produces an agenda-like view showing (I changed "todo" to "TODO" in
the test file):
Project 1 :PROJECT:
TODO task 1.1 :PROJECT:
TODO task 1.2 :PROJECT:
TODO task 1.3 :PROJECT:
Project 2 :PROJECT:
TODO task 2.1 :PROJECT:
TODO task 2.2 :PROJECT:
TODO task 2.3 :PROJECT:
This is a bit awkward, but it's given me an idea about running nested
queries, so I'll see if I can make that easier.