During the last three years I have managed a team of engineers doing ink-jet printer design at HP’s R&D lab in Barcelona. The excellent org-mode emacs package has helped me keep up-to-date with the work of the 12 engineers in my team, working in four different projects, without an obvious impact in my sanity.
During this time some work-flows have evolved, and I have written helper functions for org-mode to support them. This is an account of the steady state at which I have arrived. Please let me know if any of what follows could be done or written in a simpler or more idiomatic way.
After this article was published some people suggested that it might make sense to make it a contrib in org-mode, so I packaged it as org-secretary.el
.
Status as of 2025-07-28
Still shipped in the org-contrib collection. It shows up on the Worg index and installs from the NonGNU ELPA archive as org-secretary.el
.
Maintenance — The whole org-contrib repo is marked “little if no maintenance; no guarantee of compatibility” (GitHub). Last change: a trivial homepage-URL tweak on 29 Oct 2024 (commit 5e809267). Version string is still 0.02
. No active maintainer is listed.
Installation — M-x package-install RET org-contrib RET
(with the NonGNU ELPA feed enabled) pulls it in; then (require 'org-secretary)
.
I have re-structured this page so that it shows first how to configure and use it. I’ve left the code description at the end.
Todo keywords
I keep todo items for my team members and for me. I encode mine with TODO, the team’s with TASK:
(setq org-todo-keywords
'((sequence "TODO(t)" "|" "DONE(d)" "CANCELLED(c)")
(sequence "TASK(f)" "|" "DONE(d)")
(sequence "MAYBE(m)" "|" "CANCELLED(c)")))
Using different colors for TODO and TASK makes it easier to pick up at a glance which items are mine:
(setq org-todo-keyword-faces
'(("TODO" . (:foreground "DarkOrange1" :weight bold))
("MAYBE" . (:foreground "sea green"))
("DONE" . (:foreground "light sea green"))
("CANCELLED" . (:foreground "forest green"))
("TASK" . (:foreground "blue"))))
Projects
Complex activities are projects, and are marked with the prj
tag. They are supposed to contain todo items, and are considered stuck unless they do. The prj
tag should not be inheritable, because otherwise its tasks will appear as projects:
(setq org-tags-exclude-from-inheritance '("prj")
org-stuck-projects '("+prj/-MAYBE-DONE"
("TODO" "TASK") ()))
Task and project ownership
Ownership is managed with tags, one for each team member. When somebody owns a project his or her name will tag the project definition line, together with prj
, and it will be inherited by all the TASK todo items belonging to the project. When a TASK todo item does not belong to a project it has at least a person’s name tag.
Agenda custom commands
With all this we are ready to define the custom commands. The cjr
tag marks personal todo items in my case; change to whatever suits you.
(setq org-agenda-custom-commands
'(("h" "Work todos" tags-todo
"-personal-doat={.+}-dowith={.+}/!-TASK"
((org-agenda-todo-ignore-scheduled t)))
("H" "All work todos" tags-todo "-personal/!-TASK-MAYBE"
((org-agenda-todo-ignore-scheduled nil)))
("A" "Work todos with doat or dowith" tags-todo
"-personal+doat={.+}|dowith={.+}/!-TASK"
((org-agenda-todo-ignore-scheduled nil)))
("j" "TODO dowith and TASK with"
((org-sec-with-view "TODO dowith")
(org-sec-where-view "TODO doat")
(org-sec-assigned-with-view "TASK with")
(org-sec-stuck-with-view "STUCK with")))
("J" "Interactive TODO dowith and TASK with"
((org-sec-who-view "TODO dowith")))))
Usage
I always start a meeting telling emacs who I am with, using C-c w
. If I am with more than one person I enter all the names separated by spaces. I sometimes also say where I am, using C-c W
. Then I do C-c a j
and I see:
- Todo items defined with TODO (ie, mine) in which the
dowith
property matches any of the people with me. - Todo items defined with TODO in which the
doat
property matches my current location. - Todo items defined with TASK that are tagged with the name of any of the people with me (this is, assigned to them).
- Stuck projects tagged with the name of the people with me.
As the meeting advances I will use C-c j
to add meta-data with the people with me, the location and the time to some entries.
Example
In this file I have defined a TASK for John, a couple for Sue, and a TODO for me. There’s also a stuck project associated to Sue (for some reason, the export does not honor the colors I defined for TODO and TASK).
* Open areas :program:
*** TASK Win-win scenario for tremendous success :john:
:PROPERTIES:
:on: <2009-12-04 Fri 11:49>
:with: john
:END:
Leveraging on our existing assets.
*** Capitalize on previous challenges :prj:sue:
***** TASK Figure out what the previous challenges were
:PROPERTIES:
:on: <2009-12-04 Fri 12:04>
:with: alex
:END:
And who was promoted.
***** TASK Look up "capitalize" in the dictionary
*** Success strategy looking forward :prj:sue:
The strategy looking backwards already in place.
*** TODO And why are we doing this, again?
:PROPERTIES:
:dowith: sue
:END:
Shouldn't we build a win-win scenario for tremendous
success leveraging on our success strategy looking
forward, while capitalizing on our existing assets as we
move forward into the next quarter?
If I start a meeting with Sue I’ll do C-c w sue
, and then C-c a j
to get the following agenda view:
How it works
Context: with whom and where
Most of the management activity happens at meetings with team members. There are two main variables that define the context: who is with me, and where I am when we are attending a regular meeting (like the section’s staff). I want to be able to:
- Quickly add meta-data to entries storing the context, including with whom, where and the time.
- Easily retrieve todo items and stuck projects associated to the people I am meeting with.
- Easily retrieve todo items that I want to do when meeting with the people currently with me.
I define two variables and their histories:
(defvar org-sec-with "nobody"
"Value of the :with: property when doing an
org-sec-tag-entry. Change it with org-sec-set-with,
set to C-c w")
(defvar org-sec-where ""
"Value of the :at: property when doing an
org-sec-tag-entry. Change it with org-sec-set-with,
set to C-c W")
(defvar org-sec-with-history '()
"History list of :with: properties")
(defvar org-sec-where-history '()
"History list of :where: properties")
with the functions to set them up and keyboard shortcuts:
(defun org-sec-set-with ()
"Changes the value of the org-sec-with variable for use
in the next call of org-sec-tag-entry."
(interactive)
(setq org-sec-with (read-string "With: " nil
'org-sec-with-history "")))
(global-set-key "\C-cw" 'org-sec-set-with)
(defun org-sec-set-where ()
"Changes the value of the org-sec-where variable for use
in the next call of org-sec-tag-entry."
(interactive)
(setq org-sec-where
(read-string "Where: " nil
'org-sec-where-history "")))
(global-set-key "\C-cW" 'org-sec-set-where)
These make it easy to let emacs know who is with me and where I am, which we shall later use to tag entries with the current context and to retrieve agenda information based on it.
Tagging entries
There are three kinds of meta-data I want to be able to store quickly:
- A
dowith
property that marks a TODO entry as intended to do when meeting with somebody. - A
doat
property that marks a TODO entry as intended to do when at a given place. - And the
on
,at
andwith
properties that store current context information in any kind of entry.
(defun org-sec-set-dowith ()
"Sets the value of the dowith property."
(interactive)
(let ((do-with
(read-string "Do with: "
nil 'org-sec-dowith-history "")))
(unless (string= do-with "")
(org-entry-put nil "dowith" do-with))))
(global-set-key "\C-cd" 'org-sec-set-dowith)
(defun org-sec-set-doat ()
"Sets the value of the doat property."
(interactive)
(let ((do-at (read-string "Do at: "
nil 'org-sec-doat-history "")))
(unless (string= do-at "")
(org-entry-put nil "doat" do-at))))
(global-set-key "\C-cD" 'org-sec-set-doat)
(defun org-sec-tag-entry ()
"Adds a :with: property with the value of org-sec-with if
defined, an :at: property with the value of org-sec-where
if defined, and an :on: property with the current time."
(interactive)
(save-excursion
(org-entry-put nil "on" (format-time-string
(org-time-stamp-format 'long)
(current-time)))
(unless (string= org-sec-where "")
(org-entry-put nil "at" org-sec-where))
(unless (string= org-sec-with "nobody")
(org-entry-put nil "with" org-sec-with))))
(global-set-key "\C-cj" 'org-sec-tag-entry)
Agenda views
The next step is to define agenda functions that make use of the stored state and the taggings. First we need a helper function (there has to be an easier way to do this):
(defun join (lst sep &optional pre post)
(mapconcat (function (lambda (x)
(concat pre x post)))
lst sep))
And then we define several views for use with the agenda:
(defun org-sec-with-view (par &optional who)
"Select tasks marked as dowith=who, where who
defaults to the value of org-sec-with."
(org-tags-view '(4) (join (split-string (if who
who
org-sec-with))
"|" "dowith=\"" "\"")))
(defun org-sec-where-view (par)
"Select tasks marked as doat=org-sec-where."
(org-tags-view '(4) (concat "doat={" org-sec-where "}")))
(defun org-sec-assigned-with-view (par &optional who)
"Select tasks assigned to who, by default org-sec-with."
(org-tags-view '(4)
(concat (join (split-string (if who
who
org-sec-with))
"|")
"/TASK")))
(defun org-sec-stuck-with-view (par &optional who)
"Select stuck projects assigned to who, by default
org-sec-with."
(let ((org-stuck-projects
`(,(concat "+prj+"
(join (split-string (if who
who
org-sec-with)) "|")
"/-MAYBE-DONE")
("TODO" "TASK") ())))
(org-agenda-list-stuck-projects)))
It is also convenient to be able to build an agenda view for somebody that is not currently in the org-sec-with
state variable,
(defun org-sec-who-view (par)
"Builds agenda for a given user. Queried. "
(let ((who (read-string "Build todo for user/tag: "
"" "" "")))
(org-sec-with-view "TODO dowith" who)
(org-sec-assigned-with-view "TASK with" who)
(org-sec-stuck-with-view "STUCK with" who)))