Daily task management with org-agenda and org-roam-dailies

For a long time I’ve been wanting to apply ideas found in this interview to decide what to work on at the beginning of a day. The core idea is that even when pruning through scheduling and tags, my org-agenda view still contains too many tasks, and regularly going back to it during the day is overwhelming. So as in the interview above, at the beginning of the day I decide on a few tasks that I want to do today, and a few others that I may do today. These get stored in the org-roam-daily note for today, and I only look at them during the day.

Concretely, my daily note looks like this:

#+title: 2020-11-22 (Sunday)
* [0/2] Do Today
- [ ] [[file:~/Documents/Org/todo-orgx.org::*Relever compteurs][Relever compteurs]]
- [ ] [[file:~/Documents/Org/todo-orgx.org::*Faire les comptes][Faire les comptes]]
* [0/1] Maybe Do Today
- [ ] [[file:~/Documents/Org/todo-orgx.org::*write an org-roam-discourse post about daily task management][write an org-roam-discourse post about daily task management]]
* Journal
** 9:58 Ran for an hour

This post is how I generate it.

There are two technical issues I first need to address: one cannot capture tasks directly from the agenda, and the %a annotation is not supported by org-roam-dailies-capture-templates (bug report about the later). Here is the code I use to go from the agenda view to the corresponding task, to run org-capture, and to store the result in some variable as/agenda-captured-link link. (Big thanks to the org-mode mailing list that helped me with this.)

  (defun as/org-roam-today-mk-agenda-link ()
    (interactive)
    (let* ((marker (or (org-get-at-bol 'org-marker)
                       (org-agenda-error)))
           (buffer (marker-buffer marker))
           (pos (marker-position marker)))
      (with-current-buffer buffer
        (save-excursion
          (goto-char pos)
          (let ((link (org-store-link nil)))
            (when (stringp link)
              (remove-text-properties 0 (length link)
                                      '(read-only t) link))
            (setq as/agenda-captured-link link))
          (org-roam-dailies-capture-today)))))

The last step calls org-roam-dailies-capture-today, which uses the variable to write the link. Here is my template definition:

(setq org-roam-dailies-capture-templates
        '(("j" "journal" entry
           #'org-roam-capture--get-point
           "* %<%H:%M> %?"
           :file-name "daily/%<%Y-%m-%d>"
           :head "#+title: %<%Y-%m-%d (%A)>\n* [/] Do Today\n* [/] Maybe Do Today\n* Journal\n"
           :olp ("Journal"))
          ("t" "do today" item
           #'org-roam-capture--get-point
           "[ ] %(princ as/agenda-captured-link)"
           :file-name "daily/%<%Y-%m-%d>"
           :head "#+title: %<%Y-%m-%d (%A)>\n* [/] Do Today\n* [/] Maybe Do Today\n* Journal\n"
           :olp ("Do Today")
           :immediate-finish t)
          ("m" "maybe do today" item
           #'org-roam-capture--get-point
           "[ ] %(princ as/agenda-captured-link)"
           :file-name "daily/%<%Y-%m-%d>"
           :head "#+title: %<%Y-%m-%d (%A)>\n* [/] Do Today\n* [/] Maybe Do Today\n* Journal\n"
           :olp ("Maybe Do Today")
           :immediate-finish t)))

(I know recent versions of org-roam do not need the full :head as it’s created on demand when there is an olp property, but I want to create them with the [/] marker to count tasks.)

This is about it. I’ve bound as/org-roam-today-mk-agenda-link to a convenient shortcut (SPC m l), and I can now easily choose tasks from my agenda, dispatching them (using t or m) as tasks that must or may be done, respectively.

5 Likes

The core idea is that even when pruning through scheduling and tags,
my org-agenda view still contains too many tasks, and regularly
going back to it during the day is overwhelming.

…at the beginning of the day I decide on a few tasks that I want to do
today, and a few others that I may do today.

I share the similar feeling as yours.

Before the time of using Org-mode extensively, I used a small piece
paper to write down what I want to achieve every day. Org-roam-dailies
is a digitalized version of my previous habit, so now I just stick to
it.

I like your setup. Unfortunately I am getting the following error message when i tried to use your code. Could you provide the function as/agenda-captured-link?

- [ ] %![Error: (void-variable as/agenda-captured-link)]

For my part, I still find it more convenient to keep all the tasks in the same file (belonging to org-agenda-files) but integrating a link to the original file of the capture. When the capture is an org-roam-dailies file, I have a link that allows you to quickly see the date (2021-03-21 for example) and jump.

1 Like

You actually do not need it with recent org-roam, as %a is now supported (it is not a function, by the way, but it was set in as/org-roam-today-mk-agenda-link). Here is the new code:

(defun as/org-roam-today-mk-agenda-link ()
  (interactive)
  (let* ((marker (or (org-get-at-bol 'org-marker)
                     (org-agenda-error)))
         (buffer (marker-buffer marker))
         (pos (marker-position marker)))
    (with-current-buffer buffer
      (save-excursion
        (goto-char pos)
        (org-roam-dailies-capture-today)))))

and the new templates (which also include extra code to insert the day’s appointments)

    (setq org-roam-dailies-capture-templates
          (let ((head
                 (concat "#+title: %<%Y-%m-%d (%A)>\n#+startup: showall\n* Daily Overview\n"
                         "#+begin_src emacs-lisp :results value raw\n"
                         "(as/get-daily-agenda \"%<%Y-%m-%d>\")\n"
                         "#+end_src\n"
                         "* [/] Do Today\n* [/] Maybe Do Today\n* Journal\n")))
            `(("j" "journal" entry
               #'org-roam-capture--get-point
               "* %<%H:%M> %?"
               :file-name "daily/%<%Y-%m-%d>"
               :head ,head
               :olp ("Journal"))
              ("t" "do today" item
               #'org-roam-capture--get-point
               "[ ] %a"
               :file-name "daily/%<%Y-%m-%d>"
               :head ,head
               :olp ("Do Today")
               :immediate-finish t)
              ("m" "maybe do today" item
               #'org-roam-capture--get-point
               "[ ] %a"
               :file-name "daily/%<%Y-%m-%d>"
               :head ,head
               :olp ("Maybe Do Today")
               :immediate-finish t))))

thank you

Great post. Please can you share the as/get-daily-agenda function.

Here it is:

(defun as/get-daily-agenda (&optional date)
  "Return the agenda for the day as a string."
  (interactive)
  (let ((file (make-temp-file "daily-agenda" nil ".txt")))
    (org-agenda nil "d" nil)
    (when date (org-agenda-goto-date date))
    (org-agenda-write file nil nil "*Org Agenda(d)*")
    (kill-buffer)
    (with-temp-buffer
      (insert-file-contents file)
      (goto-char (point-min))
      (kill-line 2)
      (while (re-search-forward "^  " nil t)
        (replace-match "- " nil nil))
      (buffer-string))))

I use it with this agenda view:

("d" "Daily schedule"
 ((agenda ""
          ((org-agenda-span 'day)
           (org-agenda-use-time-grid nil)
           (org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled 'deadline))))))
2 Likes

Thanks for the function. Unfortunately, it does not work as I expected or maybe my expectations were wrong. When I create the daily, it just shows the code block but does not execute it and does not show the agenda. When I C-c C-c on the code block it opens a separate buffer with the agenda only. Looking at your code, it looks like you are expecting a buffer named “Org Agenda(d)”. The buffer which is created in my setup does not have the “(d)” part. I am running plain emacs with my own customizations. Any ideas?

You can modify the name of the expected buffer in the function. The reason the key is shown in the buffer name is because I have org-agenda-sticky set to t.

Hey there, I am trying to follow your way to see if it fits what I need. I would like to ask for some clarifications, could you please explain the above part of the code and how do you use it? And could you please share an example of how does your workflow is?

That part of the configuration is to get a clean agenda view, with only the appointments for the day.

When I start a day, I create the daily file calling org-roam-dailies-find-today, which creates something like this:

#+title: 2021-04-26 (Monday)
#+startup: showall
* Daily Overview
#+begin_src emacs-lisp :results value raw
(as/get-daily-agenda "2021-04-26")
#+end_src
* [/] Do Today
* [/] Maybe Do Today
* Journal

I can then execute the code block to get an overview of the day’s appointment.

Then I populate the “Do Today” and “Maybe Do Today” using org-roam-dailies capture.

Ok, I see. And where should this part of code be located in the init.el? Just past it somewhere or it is part of something else?

This should be part of you definition of org-agenda-custom-commands.

Fine, thank you for your help. I will try it.

This is a great workflow! I’m trying to set it up, but it looks like it may have changed with the roam V2 release - does anyone have an equivalent of the daily templates that works with V2?

It should still works, I only had to adapt the capture templates. Here they are:

    (setq org-roam-dailies-capture-templates
          (let ((head
                 (concat "#+title: %<%Y-%m-%d (%A)>\n#+startup: showall\n* Daily Overview\n"
                         "#+begin_src emacs-lisp :results value raw\n"
                         "(as/get-daily-agenda \"%<%Y-%m-%d>\")\n"
                         "#+end_src\n"
                         "* [/] Do Today\n* [/] Maybe Do Today\n* Journal\n")))
            `(("j" "journal" entry
               "* %<%H:%M> %?"
               :if-new (file+head+olp "%<%Y-%m-%d>.org" ,head ("Journal")))
              ("t" "do today" item
               "[ ] %a"
               :if-new (file+head+olp "%<%Y-%m-%d>.org" ,head ("Do Today"))
               :immediate-finish t)
              ("m" "maybe do today" item
               "[ ] %a"
               :if-new (file+head+olp "%<%Y-%m-%d>.org" ,head ("Maybe Do Today"))
               :immediate-finish t))))

Old topic, but I find your method really effective. On the other hand, and I’m not sure if it’s done on purpose, but when you check a box, you don’t directly put this task with the status “DONE”.
Have you modified your method since then ?
Thanks again for sharing!

I no longer use this, but I had written a function to this end.

(defun as/toggle-and-mark-as-done ()
  "Toggle the current checkbox, follow the link under point, and mark the task as done"
  (interactive)
  (org-toggle-checkbox)
  (org-open-at-point)
  (org-todo 'done))

(after! evil-org
  (map! :map org-mode-map
        :localleader
        "m x" 'as/toggle-and-mark-as-done))

thanks you