Skip to main content

Crate agd_core

Crate agd_core 

Source
Expand description

agd-core reads and safely changes org-mode files. It is the library behind the agd command line, terminal UI and stdio MCP server, and it works on local files only.

Every caller follows the same path:

  1. load_settings reads config.toml into Settings: the roots agd may touch, the agenda files, TODO keywords and the other Org options.
  2. WorkspaceIndex::load parses every agenda file under the roots into an IndexedDocument and gives each heading an Item with a stable ref: its :ID: when unique, else file:line#sha256:digest.
  3. AgendaEngine reads the index: agenda days, TODO lists, tags matches, org-ql queries and saved views, and exports them as Emacs prints them.
  4. WriteEngine changes files: edits, captures, refiles and undo, each journaled so it can be reverted.

§Conventions

These hold in every module:

  • Org files are user data. A write replaces only the bytes it changes and leaves every other byte exactly as it was, or it is refused and nothing is written. A file’s own syntax (#+TODO, #+PRIORITIES, #+STARTUP) wins over configuration.
  • An error is an AgdError whose code is one of ErrorCode, the one list of agd.error/v1 codes, named by one rule (invalid_<thing>, unknown_<thing>, <thing>_not_found, <thing>_conflict, <thing>_failed; see ErrorCode); its message says what to do.
  • Every serialized record names its schema, agd.<name>/v1, with a *_SCHEMA constant such as ITEM_SCHEMA or MUTATION_SCHEMA.
  • Config keys are snake_case. Values are lowercase, kebab-case when they name an Org option (ids = "add-on-write", sorting_strategy = ["time-up"], kind = "tags-todo").
  • Output is deterministic: lists keep a defined order, and dates are computed from the today or clock the caller passes.
  • Nothing touches the network.

§Example

use agd_core::{
    AgendaEngine, Change, EditIntent, WorkspaceIndex, WriteContext, WriteEngine, load_settings,
};
use chrono::NaiveDate;

let dir = tempfile::tempdir()?;
let tasks = dir.path().join("tasks.org");
std::fs::write(&tasks, "* TODO Renew passport\n")?;
let config = dir.path().join("config.toml");
std::fs::write(&config, "deadline_warning_days = 7\n")?;

// `load_settings(None, None)` reads the user's own config.toml instead.
let settings = load_settings(Some(&config), Some(dir.path()))?;
let index = WorkspaceIndex::load(settings.clone())?;
let today = NaiveDate::from_ymd_opt(2026, 10, 16).unwrap();
let todo = AgendaEngine::new(&index).todo(None, today)?;
assert_eq!(todo[0].title, "Renew passport");

let engine = WriteEngine::new(settings, dir.path().join("journal.jsonl"))?;
let context = WriteContext {
    actor: "example".to_string(),
    project: "docs".to_string(),
    now: None,
};
let done = EditIntent {
    state: Change::Set("DONE".to_string()),
    ..EditIntent::default()
};
let mutation = engine.edit(&todo[0].item_ref, &done, false, &context)?;
assert!(mutation.diff.contains("+* DONE Renew passport"));

engine.undo(&mutation.journal_id, &context)?;
assert_eq!(std::fs::read_to_string(&tasks)?, "* TODO Renew passport\n");

Structs§

AgdError
AgendaEngine
AgendaEntry
AgendaQuery
AgendaSummary
What the status band counts, each item once.
AgentProfile
A named, filtered agenda an agent may read at session start.
BatchMutation
What WriteEngine::edit_batch wrote: one mutation per edit, in the order given, all under one journal transaction; undoing any of its journal ids, or the transaction id, reverts them all. A dry run’s transaction and journal ids are dry-run.
ByteSpan
CaptureDraft
CapturePrompt
One %^{...} field a template asks for. A date prompt (%^{name}t, %^t, …) takes the same input as due: and may be left empty. planning names the keyword (DEADLINE, SCHEDULED) when the prompt is that keyword’s date on the template’s planning line: due: or on: then answers it, so the prompt need not be asked.
CaptureTemplate
CheckFileReport
CheckProblem
CheckReport
CheckSummary
ChecklistItem
One checklist line.
ConfigSources
The editor config files an import reads, in load order, and what was passed over.
EditIntent
ErrorResponse
HeadlineSpan
ImportDiagnostic
ImportResult
The editor’s settings applied over base, and what could not be.
ImportedCapture
One capture template as the editor reports it; disabled templates are reported, not imported.
ImportedCommand
One custom agenda command as the editor reports it. Enabled commands become views; diagnostics name what was left out.
IndexedDocument
Item
ItemEdit
One item and the edit to apply to it, for WriteEngine::edit_batch.
JournalRecord
LogSettings
Effective logging for one file: configuration overridden by #+STARTUP.
Mutation
Occurrence
OrgFiles
Org files discovered under the roots, plus the paths discovery left out.
Patch
Planning
PlanningTimestamp
An Org timestamp: date is YYYY-MM-DD, and time and end_time are always HH:MM (<… 9:30> reads 09:30); raw is the text as the file writes it, which a write that leaves the timestamp alone keeps.
PriorityRange
A file’s priority cookies, highest to lowest, and the priority of a heading without one: #+PRIORITIES: A C B (org-priority-highest, -lowest and -default), else the configured range.
Progress
A checklist’s progress: done of its total boxes are checked.
PropertySpan
RefileResult
Repeat
A repeating task marked done (org-auto-repeat-maybe): the done state that was recorded, and the next occurrence the item now has.
ResolvedEditorConfig
RootSet
Settings
Everything agd reads from config.toml, one flat table: every setting is a top-level key, and only capture templates, views and agent profiles are lists of records ([[capture]], [[view]], [[agent_profile]]). Missing keys take the defaults below; unknown keys are errors.
StoredPatch
A journaled replacement: start..end is the span of after in the post-image; context_before/context_after are the post-image bytes around it (the rest of its lines plus one line each way), used to find the span again if the file changed elsewhere before undo.
TimestampSpan
TodoKeyword
One TODO keyword with the logging markers from KEYWORD(key!/@).
UndoResult
ViewBlock
ViewGroup
One group of a view block, as an org-super-agenda group: each row goes to the first group, in list order, whose match it passes; discard drops those rows; by makes one group per category, parent heading, TODO keyword or priority cookie. Groups are shown by order, then list order, and rows no group takes come last as “Other items”.
ViewSection
One block of a saved view: agenda rows for an agenda block, otherwise a list of items. from/to are set for agenda blocks only. A block with groups has its rows in groups and none in entries or items.
ViewSectionGroup
A grouped block’s rows under one group name, in display order.
ViewSettings
A saved agenda view, the declarative part of an org-agenda custom command: one block, or several shown one after another like a block agenda.
WorkspaceIndex
WriteContext
WriteEngine

Enums§

AgendaSpan
How many days an agenda shows, as org-agenda-span. config.toml writes it as “day”, “week”, “month” or a TOML integer; command-line and MCP input is the same names or a number of days (AgendaSpan::from_str).
Change
ConfigEditor
ErrorCode
Every agd.error/v1 code, in one place, for agd-core and the agd binary alike: each variant’s as_str is the snake_case code consumers match on, and an error is raised as AgdError::new(ErrorCode::Variant, message). agd setup check reports its problems with the same codes.
IdPolicy
ImportStatus
JournalOperation
What a journaled write did, so every surface names a change the same way without reading its diff. A review, undo or resolve keeps the operation of the change it settles.
JournalStatus
LogKind
How a state change or planning change is recorded, as in Org’s ! (time) and @ (note) markers and the org-log-* variables.
LogMode
What a state or planning change records, as an org-log-* variable: no log, a timestamp, or a timestamp and a note.
Notation
How tags and a priority are written in a capture line, and how agd draws tags: agd is #work and !A; org is Org’s own :work: (or :a:b:) and #A. Org’s files, --csv and the agenda text are Org’s either way.
OccurrenceType
SkipReason
Why discovery left a path out instead of failing.
WeekStart
The day a week agenda starts on.

Constants§

AGENDA_ENTRY_SCHEMA
CHECK_SCHEMA
An agd setup check report (CheckReport).
COLOR_NAMES
Colors a keyword or view section may name; the TUI maps them onto its light or dark palette.
DESCRIBE_SCHEMA
The MCP describe tool’s answer.
ERROR_SCHEMA
ITEM_SCHEMA
JOURNAL_SCHEMA
One line of the write journal (JournalRecord).
MUTATION_SCHEMA
REFILE_SCHEMA
What a refile wrote (RefileResult).
SORTING_KEYS
Agenda sorting keys agd implements; habit-* and category-keep keep collection order.
STATUS_SCHEMA
The payload of agd agenda --status --json: the status band’s counts (AgendaSummary) and the overdue rows (overdue_entries).
UNDO_SCHEMA
What an undo wrote (UndoResult).
VIEWS_SCHEMA
The payload of agd agenda --views --json.
VIEW_BLOCK_KINDS
VIEW_GROUP_BY
VIEW_SECTION_SCHEMA
One block of agd agenda --view KEY --json.

Functions§

agenda_csv
Agenda rows in Emacs’s org-batch-agenda-csv layout: all eleven columns (category, head, type, todo, tags, date, time, extra, priority-l, priority-n, agenda-day) with Emacs’s values. As in Emacs, a field’s commas become semicolons and nothing is quoted. today decides which scheduled rows Emacs calls past-scheduled.
agenda_row
One agenda row as org-agenda prints it: category:, the time, the leader (Scheduled:, In 3 d.:), TODO, priority, headline and tags.
agenda_text
The agenda from from to to as org-batch-agenda prints it with the time grid off: header, or Org’s span line such as Week-agenda (W42):, then every day with its rows (org-agenda-show-all-dates).
agenda_title
Headline text as org-agenda prints it: active timestamps in the title, such as Birthday <1993-10-16 Sat +1y>, are left out.
agent_actor
The actor an MCP client’s writes are journaled under: mcp:<client>, without the -mcp-client suffix Codex sends (codex-mcp-client is mcp:codex); other names are kept as sent.
agent_name
The agent an actor names, codex for mcp:codex; None for a person.
apply_import
Applies resolved editor settings over base (the current config). The editor’s values replace the keys it reports; agd-only keys (editor, theme, agents, triage) keep their base values. On a first import (derive_roots) the roots become org-directory, or the directories the agenda entries live in; otherwise base.roots stay and entries outside them are left out with a diagnostic.
apply_patches
capture_prompts
The %^{prompt} fields a template asks for, in order and without duplicates; %^g asks for “tags” and a bare %^t for “date”.
check_settings
checklist
The checklist in headline’s body, in order; empty when it has none. Lines in drawers and blocks are not list items.
default_config_path
find_config_sources
Finds the files to read under paths. A file is taken as given; a directory is walked (respecting .gitignore, at most MAX_DEPTH levels, MAX_FILES files and MAX_BYTES each, skipping SKIPPED_DIRECTORIES), keeping Emacs Lisp files that mention org-, Lua files that mention orgmode and Org files with emacs-lisp source blocks. Both editors’ files under one path is an error: import one editor at a time.
format_active_timestamp
import_root
The root an import writes for agd -r DIR setup import: DIR made absolute (symlinks kept, as the editor’s paths keep them) with ~ for the home directory. DIR must exist, since agenda entries and capture files are placed under it.
item_row
One row of a TODO list or tags match: category:, TODO, priority, headline and tags.
items_csv
A TODO-list or tags block’s rows in the same layout: type tagsmatch for a tags or tags-todo block and todo for the others, with no date, time, extra or agenda day.
journal_problems
One problem per interrupted write whose file changed since (see WriteEngine::journal_conflicts), naming the command that settles it.
keyword_color
A TODO keyword’s color name (COLOR_NAMES) wherever agd draws it: the configured todo_keyword_colors entry, else green when done, orange for the first keyword of its sequence (TODO), and blue for the rest and for headings without a keyword.
keyword_colors
Every TODO keyword the agenda files use, with its keyword_color. A keyword two files order differently takes the first file’s color.
load_settings
next_repeat
The date a repeating timestamp moves on to when its task is marked done on today, as org-auto-repeat-maybe moves it: +1w a week from its date, ++1w by weeks until after today, .+1w a week from today.
overdue_entries
The overdue rows among entries (a day’s agenda), one per item: missed deadlines first, most days late first, then past-scheduled reminders, oldest first. Rows of equal lateness keep the agenda’s order. Done items are never overdue.
parse_agenda_day
A day as org-agenda-start-day and agd agenda --from take it: a date, an offset from today such as -1d or +2w, or what parse_date_input reads, such as “fri”.
parse_capture_line
Reads one capture line: due:DATE and on:DATE (a date as read_date reads it, which may continue into the next words: due:fri 9am, on:oct 30, due:2026-10-30 14:00 +1w -2d), tags and a priority in notation (tag_token, priority_token). Every other token stays in the title.
parse_config_file
Parses a config file without validating roots, so agd setup import can read one whose roots do not exist yet.
parse_date_change
A date answer for a field that may be cleared: none clears it, as in MCP plan and the TUI’s date prompts; anything else is a date (parse_date_input).
parse_date_input
Reads a whole date answer, relative to today: one date as read_date reads it, with nothing left over.
parse_timestamp
read_date
The date at the start of words and how many words it takes, as Org’s date prompt reads it: a day (an ISO date, today, tomorrow, ., a weekday, +2d, -wed, 14, oct 30) or a time alone (today at that time), then a time, a repeater (+1w) and a warning (-3d), each optional and taken when the next word is one. None when the first word starts no date. A word after the day that reads as a time but is not one (25:00, 13pm) is an error, not the end of the date.
render_capture_template
render_config
Writes settings as a config file: grouped, commented top-level keys, then [[capture]], [[view]] and [[agent_profile]] records. Values equal to the defaults are left out, so the file shows only what differs.