Tasks is org-mode based productivity workflow useful for capturing
organizing, and tracking task information.
Emacs/Tasks
Requirements
- Git: distributed version control system
- GNU/GUIX: a functional package management system
- LitDoc: literate programming utility
Introduction
This workflow uses Emacs org-mode features for capturing, managing, and
tracking tasks. Once a task is captured it is added to a default
inbox org file. Using org-mode todo states and calendar scheduler the
task start date, deadline can be added. To track time spent working on a task,
org Pomodoro timer is used. Tasks captured in different files are also
integrated into a single view using org-super-agenda.
Usage
CLOCK: [2026-06-24 Wed 21:52]–[2026-06-24 Wed 21:52] => 0:00
Clone repository
git clone https://codeberg.org/distroconfig/tasks
Switch to directory and build it,
cd tasks && guix ld build tasks
Run Emacs and launch tasks using M-x workflow::select.
When prompted for input, select tasks. Follow the on
screen instruction.
Customizations
The default inbox file
(defcustom tasks:default-inbox "${HOME}/Documents/planner/inbox.org"
"specifies the default inbox file for saving captured information"
:type '(file)
:group 'tasks)capture template
(setq org-capture-templates
`(("t"
"Task"
entry
(file+headline tasks::inbox-location "Tasks")
"* TODO %?\nDEADLINE:%^t\n%i\n/see/: %a")))Task search paths
(defcustom agenda::search-paths
`( ,(expand-file-name "~/Documents/KB")
,(expand-file-name "~/Documents/planner")
,(expand-file-name "~/Documents/research/notes"))
"List of static directory paths where org-agenda files are stored."
:type '(file))Key Bindings
Capturing information
(kb::def :states 'normal
"o" '(:ignore t :which-key "Org" )
"oc" '(org-capture :which-key "Capture")
"oi" '(tasks::find-inbox :which-key "Inbox"))Reviewing information
(kb::def :states 'normal
"o" '(:ignore t "Org")
"oa" '(tasks::org-agenda-open :which-key "agenda"))Org pomodoro time tracking,
(kb::def :states 'normal
"o" '(:ignore t "Org")
"op" '(org-pomodoro :which-key "🍅 pomodoro"))Commands
Capture commands
org-captureAgenda commands
tasks::org-agenda-openFunctions
Inbox Location
(defun tasks::inbox-location ()
"Returns the location for storing captured information."
(require 'fs)
(let ((inbox-file (substitute-in-file-name tasks:default-inbox)))
;;setup capture file path
(fs::dir-ensure (file-name-directory inbox-file))
inbox-file))Open Inbox
(defun tasks::find-inbox ()
"Opens the current project's inbox.org file"
(interactive)
(require 'fs)
(let ((inbox-file (tasks::inbox-location)))
(find-file inbox-file)))Agenda
(defun tasks::org-agenda-load ()
"scan and initialize `org-agenda-files'"
(require 's)
(let ((search-path-list agenda::search-paths)
(cmd nil)
(r nil)
(org-agenda-files nil)
(org-agenda-file nil))
(setq cmd (with-temp-buffer
(cl-loop
for p in search-path-list
do (insert "ag -l -U --hidden --org 'TODO|WORKING|BLOCKED|DEADLINE|SCHEDULED' " "\""p"\"" " | grep -v 'forks'" "\n" ))
(buffer-string)))
(setq r (shell-command-to-string cmd))
(setq r (s-lines r))
(while (setq org-agenda-file (car r))
(setq r (cdr r))
(when (and (stringp org-agenda-file)
(not (string-empty-p org-agenda-file))
(file-exists-p org-agenda-file))
(cl-pushnew (expand-file-name org-agenda-file) org-agenda-files)))
org-agenda-files))
(defun tasks::org-agenda-open ( )
"Launches current project agenda browser and the journal directory."
(interactive)
(require 'log)
(require 'org-super-agenda)
(setq org-agenda-files (tasks::org-agenda-load))
(org-agenda nil "c" ))
Task States: task state is tracked using org todo states.
Time spent on a task can also be tracked using org-pomodoro package.
(defvar tasks:pomodoro-ignore-state-changed nil)
(defun tasks::start-or-resume-clock ( )
"Start or resume org pomodoro clock"
(require 'org-pomodoro)
(unless tasks:pomodoro-ignore-state-changed
(cond (;; no pomodoro started yet?
(eq org-pomodoro-state :none)
(org-pomodoro))
(;; is pomodoro on a short break?
(or
(eq org-pomodoro-state :break)
(eq org-pomodoro-state :long-break))
(let ((org-pomodoro-keep-killed-pomodoro-time t))
(org-pomodoro-killed)
(org-pomodoro)))
(;; nothing to do
t))))
(defun tasks::stop-clock ()
"Stop current org pomodoro clock"
(require 'org-pomodoro)
(when (or (eq org-pomodoro-state :pomodoro)
(eq org-pomodoro-state :break)
(eq org-pomodoro-state :long-break))
(let ((org-pomodoro-keep-killed-pomodoro-time t))
(org-pomodoro-killed))))
(defun tasks::pomodoro-on-break-finished ( )
"Update task state to WORKING and resume pomodoro."
(let ((tasks:pomodoro-ignore-state-changed t)
(m (car org-clock-history)))
(save-window-excursion
(pop-to-buffer-same-window (marker-buffer m))
(if (or (< m (point-min)) (> m (point-max))) (widen))
(goto-char m)
(cond ((yes-or-no-p "Ready for another pomodoro session?")
(org-todo "WORKING")
(org-show-entry)
(org-pomodoro))
(t
(org-todo "TODO")
(org-show-entry))))))
(defun tasks::pomodoro-on-finished ( )
(save-window-excursion
(let ((m (car org-clock-history))
(tasks:pomodoro-ignore-state-changed t))
(pop-to-buffer-same-window (marker-buffer m))
(when (or
(< m (point-min))
(> m (point-max)))
(widen))
(goto-char m)
(org-todo "TODO")
(org-show-entry))))
(defun tasks::pomodoro-on-started ( )
(save-window-excursion
(let ((m org-clock-marker)
(tasks:pomodoro-ignore-state-changed t))
(pop-to-buffer-same-window (marker-buffer m))
(when (or
(< m (point-min))
(> m (point-max)))
(widen))
(goto-char m)
(org-todo "WORKING")
(org-show-entry))))
(defun tasks::on-todo-state-changed ()
"Tasks TODO state changed"
(require 'org-pomodoro)
(unless (or tasks:pomodoro-ignore-state-changed
(eq org-pomodoro-state :none)
(eq org-state "WORKING")
(eq org-state "WORKING"))
(cond
(;; * -> WORKING: start or resume clock?
(equal org-state "WORKING")
(tasks::start-or-resume-clock))
(;; WORKING -> *
(equal org-last-state "WORKING")
(tasks::stop-clock))
(;; ignore anything else
t))))Packages
Package: org-capture
(use-package org-capture
:demand t
:commands
<<org-capture-commands>>
:general
<<org-capture-kb>>
:config
<<org-capture-config>>)
Package: org-agenda
(use-package org-agenda
:demand t
:commands
<<org-agenda-commands>>
:init
(defalias 'tasks::org-agenda-open-super 'org-super-agenda)
<<org-agenda-init>>
:general
<<org-agenda-kb>>
:config
(setq org-agenda-todo-list-sublevels nil)
(require 'org-super-agenda))
Package: org-super-agenda
(use-package ts
:guix (:name emacs-ts))
(use-package org-super-agenda
:guix (:name emacs-org-super-agenda)
:demand t
:after org-agenda
:init
(setq org-agenda-skip-scheduled-if-done t
org-agenda-skip-deadline-if-done t
org-agenda-include-deadlines nil
org-agenda-block-separator nil
org-agenda-compact-blocks t
org-agenda-start-day nil ;; i.e. today
org-agenda-span 1
org-agenda-start-on-weekday nil)
(setq org-agenda-custom-commands
'(("c" "Super view"
((alltodo "" ((org-agenda-overriding-header "")
(org-super-agenda-groups
'((:log t)
(:name "Overdue\n" :deadline past :order 1)
(:name "Important\n" :priority "A" :order 2)
(:name "Due Today\n" :deadline today :order 3)
(:name "Tasks\n" :tag "@incoming" :order 15)
(:name "Weekly\n" :deadline today :order 6)
(:name "Monthly\n" :deadline today :order 7)
(:name "Scheduled Soon\n" :scheduled future :order 4)
(:discard (:anything t))
))))))))
:config
(org-super-agenda-mode))
Package: silver searcher
(use-package the-silver-searcher
:guix (:name the-silver-searcher)
:disabled)
Package: pomodoro
(use-package org-pomodoro
:guix (:name emacs-org-pomodoro)
:demand t
:commands
org-pomodoro
:hook
(org-after-todo-state-change . tasks::on-todo-state-changed)
(org-pomodoro-finished . tasks::pomodoro-on-finished)
(org-pomodoro-break-finished . tasks::pomodoro-on-break-finished)
(org-pomodoro-started . tasks::pomodoro-on-started)
:custom
(org-pomodoro-length 24)
(org-pomodoro-ask-upon-killing nil)
(org-pomodoro-format "%s")
(org-pomodoro-short-break-format "%s")
(org-pomodoro-long-break-format "%s")
(org-pomodoro-play-sounds t)
:custom-face
(org-pomodoro-mode-line ((t (:foreground "#E9967a"))))
(org-pomodoro-mode-line-break ((t (:foreground "#90ee90")))))
Sox package
(use-package sox
:guix (:name sox)
:disabled)