Skip to main content

A simple GTD approach using Org mode and Org Edna

I think, I found the final version of my personal workflow

In his blog post A Draft Workflow for Advanced Project Management Using Org Mode and Org Edna Karl Voit wrote about his dream of a workflow that would allow him to do complex project management within Org mode. If you havn't read it, please do so. As always it's a good piece of some well thought processes, and I won't most of them repeat here.

Most of the work flow is already covered by Org mode, and another good part of handling dependencies between the steps is managed by the package org-edna . To get to his goal he needed just two patches to some Emacs packeges he uses.

And so my journey began

I have never written some sophisticated code in Emacs lisp, but I thought 'hold my tea' and started. To be fair, at the end the code did it's job, but what was more important, I put some serious thoughts into my own workflow, and I realised, that I do a project a little bit other than Karl.

I realised that, when I worked on his garage example (repainting a garage). After the paint is bought, you want to remove the car, the bike, and the shelves. To do the latter, you have to empty them first. Now the following thoughts developed:

  1. While digging in the code of org-edna, to find the place where I could put a dependency handling that allows grouped dependencies, I saw that the example workflow was all flat, and that those dependencies could be handled by Org mode if I only use hierarchy in the tree
  2. After that it became clear, that I would never plan more than at most three tasks to be the next task. With GTD the pure doctrine would be to have only one active next task per context, but in reality I loose track when they become too many.
  3. And the order does matter to me. what if I need 5 minutes to bring the car to the next public parking space, and 3 days to empty and remove the shelves. I would have to go to the parking space all days while I am clearing the garage. So I would plan those teps in the best order anyway.
  4. When it is now a flat list again, but with an distinctive order, why not use the property ORDERED?
** proj [1/4] Brille richten                    :Brille:project:
       :PROPERTIES:
       :COOKIE_DATA: todo recursive
       :ORDERED: t
       :TRIGGER: next-sibling todo!(NEXT)
       :END:
*** done Zum Optiker fahren
        CLOSED: [2020-08-22 Sa 15:22]
*** NEXT Warten ob es ein neues Mittelteil gibt
*** todo Bestellen 
*** todo Gläser einsetzen

By using this property, and setting it to 't' it is enforced, that every task can only be completed, when all tasks before are completed. if that comes in my way, sai I want to do the overnext task first I am still free to just move it a step forward, and then work on it firstly. At least I do not have to reassign manually assigned dependencies. I think without a graph that has to end in dependency hell.

But what NEXT?

After it is ensured, that I work as close to the plan as possible. How about the NEXT action. When I finish a task, the next sibling should become the NEXT action to work on. For this I use the help of org-edna. It's TRIGGER property in the example above sets the todo state of the next-sibling to NEXT whenever a task is set to a finished state. This has to be set as a property in every task where it is needed, or in case of a simple flat task list could be done just in the header by setting the variable org-edna-use-inheritance to 't'.

So there is always a new NEXT actionable task when I finish the one before. But what when I decide to wait with that task, and stall the project? For that I have set up an agenda view that shows me all projects (they are defined by an TODO-state of 'proj') that have no next actionable task. Those projects are shown in the stuck projects agenda that is build into Org mode.

What would a project be without a DEADLINE?

So far, all problems with my workflow could be solved with Org mode or org-edna alone, and without tampering with their code. But one thing remained. How should I set SCHEDULED or DEADLINE dates, depending on the finishing date of the task before. Karls idea was to tie it to the finishing of the task, or of a group of tasks before, but as I abandoned the concept of tasks that are explicitely linked to each othe by static links, this is also not possible anyme.

I had to tackle it from the receiving end. Whenever a task is set to NEXT for the first time I calculate the SCHEDULED and DEADLINE date. I have written a function that ist tied to the hook org-after-todo-state-change-hook which looks for a property named PLANNED and uses its value to calculate a date.

(add-hook 'org-after-todo-state-change-hook 'gtd/planning-trigger)

Lets say I want to do the task 'Empty dishwasher' 3 hours after finishing the 'Load dishwasher' task. So I give it a property value of +3h for PLANNED like this

** proj [0/2] Wash dishes         :dishes:project:
               :PROPERTIES:
               :COOKIE_DATA: todo recursive
               :ORDERED: t
               :TRIGGER: next-sibling todo!(NEXT)
               :END:
*** NEXT Load dishwasher
*** todo Empty dishwasher
               :PROPERTIES:
               :PLANNED:: +3h
               :END:

After setting the first task to done, the second task is set to NEXT by the :TRIGGER: next-sibling todo!(NEXT). When succesfully changed to NEXT the hook pulls my function, and voila:

** proj [1/2] Wash dishes                       :dishes:project:
               :PROPERTIES:
               :COOKIE_DATA: todo recursive
               :ORDERED: t
               :TRIGGER: next-sibling todo!(NEXT)
               :END:
*** done Load dishwasher
        CLOSED: [2020-08-24 Mo 17:18]
*** NEXT Empty dishwasher
        SCHEDULED: <2020-08-24 Mo 20:18>

The code for this is pretty simple, and I still have to expand it to DEADLINE and secure it against unruly situations, but it works so far.

(defun gtd/planning-trigger ()
  "Automatically insert chain-find-next trigger when entry becomes NEXT"
  (when (equal org-state "NEXT")
    (message "das war next")
    (setq planned (car (org-map-entries (lambda () (
      org-entry-get nil  "PLANNED")) "PLANNED<>\"\"" 'tree)))
    (if planned (
      (message "Geplant ist %s" planned)
      (org-entry-put nil "SCHEDULED" planned)
      (org-entry-delete nil "PLANNED")
  ) nil) ))

Conclusion

During the last tenday I learned a lot about my own workflow, just by trying to follow another persons thoughts about his own workflow. Thats exactly what the Internet was made for, and it made me happy. And I learned a lot more.

  • got a grip on elisp
  • learned how to debug in Emacs
  • and foremost I learned how much I trust my workflow, when it generates a comprehensive agenda.

But that will be another story…

2020-08-24