Getting Things Done: Projects List and Next Actions

Lately I’ve been practicing David Allen’s “Getting Things Done” framework, which consists of components for getting tasks out of your head and into a system to improve productivity and reduce stress. I wrote about the overall system here. In this post, I want to talk about my Projects list and my Next Actions agenda.

I use an org-mode file called projects.org to document projects and associated tasks. (I store it in Dropbox so I can sync it across devices and even onto my iOS device using beorg!) Here’s what it looks like:

* Project 1                          :@home:
** TODO Task 1
*** TODO Subtask 1a
*** TODO Subtask 1b
** TODO Task 2
*** TODO Subtask 2a
*** TODO Subtask 2b
* Project 2                          :@work:
** TODO Task 3
*** TODO Subtask 3a
*** TODO Subtask 3b
** TODO Task 4
*** TODO Subtask 4a
*** TODO Subtask 4b

The “projects” are really just a somewhat arbitrary grouping of tasks. Tasks often have subtasks and even sub-subtasks. My projects file consists of about a dozen projects. Most projects have a tag like “@home” or “@work” representing context.

This projects file is designed primarily to power various agenda views in org-mode. The most common agenda view is a collection of next actions for each project, segregated by context. Specifically, each project has at most one next action. (A project with zero next actions is called a “stuck project” – more on that in a second.)

To restrict the agenda view to next actions, I had to write some elisp (my first nontrivial elisp!):

(defun my-org-agenda-skip-all-siblings-but-first ()
  "Skip all but the first non-done entry."
  (let (should-skip-entry)
    (unless (org-current-is-todo)
      (setq should-skip-entry t))
    (save-excursion
      ;; If previous sibling exists and is TODO,
      ;; skip this entry
      (while (and (not should-skip-entry) (org-goto-sibling t))
        (when (org-current-is-todo)
          (setq should-skip-entry t))))
    (let ((num-ancestors (org-current-level))
          (ancestor-level 1))
      (while (and (not should-skip-entry) (<= ancestor-level num-ancestors))
        (save-excursion
          ;; When ancestor (parent, grandparent, etc) exists
          (when (ignore-errors (outline-up-heading ancestor-level t))
            ;; If ancestor is WAITING, skip entry
            (if (string= "WAITING" (org-get-todo-state))
                (setq should-skip-entry t)
              ;; Else if ancestor is TODO, check previous siblings of
              ;; ancestor ("uncles"); if any of them are TODO, skip
              (when (org-current-is-todo)
                (while (and (not should-skip-entry) (org-goto-sibling t))
                  (when (org-current-is-todo)
                    (setq should-skip-entry t)))))))
        (setq ancestor-level (1+ ancestor-level))
        ))
    (when should-skip-entry
      (or (outline-next-heading)
          (goto-char (point-max))))))

(defun org-current-is-todo ()
  (string= "TODO" (org-get-todo-state)))

This implementation started off from Nicolas Petton’s post. His original implementation was very good, but defined the “next action” as the first TODO under any subheading. By that definition, Tasks 1 and 3, and Subtasks 1a, 2a, 3a, and 4a are all next actions. That was too many next actions cluttering up my agenda view! My implementation works in terms of siblings, ancestors, and siblings of ancestors (I call these “uncles”). If a task does not have state TODO, it is not a next action. If a task has a previous sibling with state TODO, it is not a next action (but the previous sibling might be). If the parent has state WAITING or if it has state TODO and the parent has a previous sibling with state TODO (meaning, the task has an “uncle” that has state TODO), then it is not a next action. This logic repeats up until it reaches an ancestor that is neither WAITING nor TODO.

Having defined this routine, I use it in my Emacs init file as part of my org configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(use-package org
  :ensure t
  :bind (
         ("C-c l" . org-store-link)
         ("C-c a" . org-agenda)
         ("C-c c" . org-capture)
         ("C-c b" . org-switchb)
         )
  :custom
  (org-todo-keywords '((sequence "TODO(t)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)")))
  (org-agenda-custom-commands
   '(
     ("N" "Next Actions"
      ((agenda)
       (tags-todo "@work"
                  ((org-agenda-overriding-header "Work")
                   (org-agenda-skip-function #'my-org-agenda-skip-all-siblings-but-first)))
       (tags-todo "@home"
                  ((org-agenda-overriding-header "Home")
                   (org-agenda-skip-function #'my-org-agenda-skip-all-siblings-but-first)))
       ))
     ("h" "At home" tags-todo "@home"
      ((org-agenda-overriding-header "Home")
       (org-agenda-skip-function #'my-org-agenda-skip-all-siblings-but-first)))
     ("w" "Work" tags-todo "@work"
      ((org-agenda-overriding-header "Work")
       (org-agenda-skip-function #'my-org-agenda-skip-all-siblings-but-first)))
     ("F" "First Action"
      ((tags-todo "@first"
                  ((org-agenda-overriding-header "First Action")
                   (org-agenda-skip-function #'my-org-agenda-skip-all-siblings-but-first)))))
     ("W" "Waiting" todo "WAITING")
     ))
  (org-stuck-projects '("+LEVEL>=2+LEVEL<=3-@notstuck/-CANCELLED-DONE"
                        ("TODO" "WAITING")
                        nil
                        ""))
  )
(actually, my org configuration is a little more complicated than this, but I stripped out the irrelevant bits).

Lines 11 through 33 define multiple agenda views. The Next Actions agenda is the one I use the most frequently. It shows my daily agenda, followed by work tasks, then home (personal) tasks that leverage the next actions function defined above. If I want, I can get a view with just home tasks, or just work tasks, but I don’t do that very often.

The First Action agenda is special. As part of my shutdown routine (described by Cal Newport in Deep Work), the last thing I do at the end of my workday is tag exactly one next action “@first”. That way when I get started the next morning, I know exactly the first thing I need to do. That avoids me having to refresh my memory and I can hit the ground running. I usually like to choose something easy so I can start the morning with that sweet dopamine hit that comes from checking off a task.

As part of my weekly review, I use the Waiting agenda to review any blocked tasks. Simple enough. I also review any stuck projects, as defined on line 34. These are any projects without any children having state TODO or WAITING. Sometimes projects really aren’t stuck even though they don’t have such children, in which I’ll tag them “@notstuck”. I’ll talk more about my weekly review and the other components of my GTD system in future posts.

Subscribe to Adventures in Why

* indicates required
Bob Wilson
Bob Wilson
Data Scientist

The views expressed on this blog are Bob’s alone and do not necessarily reflect the positions of current or previous employers.

Related