Org-mode as Team Knowledge Base: Architecture Decisions for Adoption
Architecture note on adopting Emacs Org-mode as a team knowledge base: requirements, minimal viable structure, trust boundaries, operational checks, failure modes, and triggers for redesign. Includes concrete .dir-locals.el configuration and CI verification steps.
02 Nov 2025, 01:51 UTC

Requirements Driving the Decision
Teams adopting Org-mode typically need three things: a single source of truth for project documentation, executable specifications that stay current with code, and task tracking that does not require a separate SaaS tool. The constraint is that everything must live in plain text under version control so that diffs, reviews, and history work the same way as application code.
Org-mode satisfies these because it is a major mode built into Emacs 29+, meaning no external runtime is required to read or edit files. The format is a superset of Markdown with hierarchical outlines, TODO keywords, tags, property drawers, and source blocks that can be evaluated in place via Babel.
Smallest Suitable Design
Start with one repository directory docs/ containing:
index.org— top-level table of contents using#+INCLUDEdirectivesprojects/— one file per project, each with its own TODO treetemplates/— capture templates for meeting notes, decisions, and tasksarchive.org— completed items moved here viaorg-archive-subtree
Configure org-agenda-files to point at projects/ only. This keeps agenda generation fast while still allowing index.org to reference everything for export.
Trust and Data Boundaries
Org files are plain text; anyone with repository read access sees all content. There is no encryption, no access control lists, and no server-side validation. Treat the repository as the trust boundary:
- Secrets (API keys, credentials) must never be committed. Use
org-babelheader arguments like:var password=(getenv \"API_TOKEN\")so values come from the environment at evaluation time. - Code blocks execute with the user's full shell permissions. Restrict
org-babel-load-languagesto only the languages the team actually uses (e.g.,python,shell,sql). Disableemacs-lispunless explicitly required. - Export pipelines (LaTeX for PDF, HTML for publishing) run arbitrary commands. Run exports in CI with a locked-down container image, not on developer laptops.
Operational Checks
Agenda Performance Baseline
Run this in *scratch* after opening the repository:
(require 'org-agenda)\n(setq org-agenda-files '(\"docs/projects/\"))\n(org-agenda-list)\nMeasure the time reported in the minibuffer. With ~1,000 TODO entries across 20 files, expect 1–2 seconds on a modern laptop. If it exceeds 5 seconds, consider splitting projects/ into subdirectories and setting org-agenda-files to a generated list that excludes archive files.
Babel Execution Smoke Test
Add a temporary block to any project file:
#+BEGIN_SRC python :results output\nprint(\"hello from \" + __import__('platform').python_version())\n#+END_SRCPress C-c C-c on the block. The result should appear in a #+RESULTS: drawer. If it fails, check org-babel-python-command and ensure the Python interpreter is on exec-path.
Export Verification in CI
Add a GitHub Actions step:
- name: Build Org documentation\n run: |\n emacs --batch -l org \\\n --eval \"(require 'ox-html)\" \\\n --eval \"(setq org-confirm-babel-evaluate nil)\" \\\n --eval \"(org-html-export-to-html)\" docs/index.org\n env:\n API_TOKEN: ${{ secrets.API_TOKEN }}This catches broken links, missing images, and Babel errors before they reach the published site.
Failure Modes
| Symptom | Root Cause | Mitigation |
|---|---|---|
| Agenda takes >10 s | Too many files or deeply nested TODO trees | Set org-agenda-max-entries, move done items to archive.org, use org-agenda-files as a function that filters |
| Babel block hangs | Infinite loop or blocking I/O in code | Add :timeout 30 header argument; run long jobs externally and include results as static blocks |
| PDF export fails | Missing LaTeX packages or font issues | Pin a Docker image (e.g., texlive/texlive:2024) and install only required packages |
Merge conflicts in index.org | Multiple people adding #+INCLUDE lines simultaneously | Generate index.org from a script in CI instead of editing by hand |
Conditions That Would Change the Design
- Team grows beyond 15 contributors — configuration drift becomes a problem. Move shared setup to a
.dir-locals.elfile committed in the repo, and enforce it via a pre-commit hook that runsemacs --batch -l .dir-locals.el. - Non-Emacs users need read/write access — adopt
org-roamwith a static site export (Ox-Hugo or custom Ox-HTML) so the published HTML is the canonical view. Keep Org source as the authoring format but accept Markdown PRs via a conversion script. - Regulatory audit trail required — Org's plain-text history is insufficient. Add a signed commit policy (
git commit -S) and a CI job that verifies signatures on every push tomain. - Real-time collaboration needed — Org-mode is single-user, single-threaded. Pair it with a CRDT-based editor (e.g., Yjs-backed web UI) for live editing, then commit the merged Org file.
Concrete Configuration Example
Place this in .dir-locals.el at the repository root so every developer gets the same baseline without personal init.el pollution:
((org-mode . (\n (org-agenda-files . \"docs/projects/\")\n (org-todo-keywords\n . ((sequence \"TODO(t)\" \"NEXT(n)\" \"WAITING(w@/!)\" \"|\" \"DONE(d!)\" \"CANCELLED(c@)\")))\n (org-todo-keyword-faces\n . ((\"TODO\" . \"#cc0000\")\n (\"NEXT\" . \"#ff8800\")\n (\"WAITING\" . \"#0088cc\")\n (\"DONE\" . \"#00aa00\")\n (\"CANCELLED\" . \"#888888\")))\n (org-babel-load-languages\n . '((python . t) (shell . t) (sql . t) (emacs-lisp . nil)))\n (org-confirm-babel-evaluate . nil)\n (org-archive-location . \"docs/archive.org::\")\n (org-capture-templates\n . '((\"t\" \"Task\" entry (file+headline \"docs/projects/inbox.org\" \"Inbox\")\n \"* TODO %?\\n %i\\n %a\")\n (\"d\" \"Decision\" entry (file+headline \"docs/decisions.org\" \"Decisions\")\n \"* %? :decision:\\n %U\\n %i\"))))\n (nil . (\n (tab-width . 2)\n (indent-tabs-mode . nil))))This sets consistent TODO keywords, faces, allowed Babel languages, archive location, and two capture templates. The nil section ensures basic indentation hygiene for all file types.
Verification Checklist
emacs --batch -l .dir-locals.el --eval \"(message \\\"agenda files: %s\\\" org-agenda-files)\"returns the expected directory.- Open a project file, create a TODO item, run C-c a a — it appears in the agenda.
- Run the CI export step locally with
actordocker runto confirm PDF/HTML output. - Attempt to evaluate a shell block with
:timeout 1andsleep 5— it should abort cleanly.
If all four pass, the baseline is operational. Revisit the failure-mode table quarterly or when the TODO count crosses the next order of magnitude.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.