aegent Markdown
The file format for pages, generated from the specification, with the Org mode mapping.
Every page in org is an aegent Markdown file: ordinary Markdown with Org mode's features added in ways that still read well anywhere else. This reference is built from the specification each time the docs are built, so it always matches what org parses.
aegent Markdown is CommonMark 0.31 + GFM (tables, task-list checkboxes,
strikethrough, autolinks, footnotes) plus the extensions below. Files use the
.md extension and UTF-8 with LF line endings. It supports every Org-mode
feature. Where a convention already exists, it reuses it: Org timestamps,
Obsidian wikilinks, embeds, block IDs, callouts and comments, Dataview
key:: value fields, and Pandoc footnotes, citations, attributes and fenced
divs.
Frontmatter
YAML between --- lines at byte 0. The recognised keys are shown below.
Unknown keys are page properties.
---
title: Car # the page title (otherwise the first H1, otherwise the file name)
aliases: [Vehicle] # link aliases (Org radio targets)
tags: [admin] # page tags (Org FILETAGS), inherited by every entry
todo: # TODO sequences for this page (Org #+TODO). See §7.1
- ["TODO(t)", "NEXT(n)", "WAIT(w@/!)", "|", "DONE(d!)", "CANCELLED(c@)"]
priorities: {highest: A, lowest: C, default: B}
category: Car # the agenda category (Org CATEGORY)
tz: Europe/London # a fixed time zone for this page's timestamps (the default is floating)
startup: overview # overview | content | showall | show2levels … (Org STARTUP)
id: a78 # the page's short ID (written by aegent)
archive: "Archive/Car" # the archive target (Org ARCHIVE)
bibliography: refs.bib # citations
csl: harvard.csl
macros: {version: "1.2"} # {{version}}
export: {toc: true, numbered: false, author: true}
database: … # makes this page a database (§10)
views: … # saved views on a database page (§10)
---
Headings and entries
## TODO [#A] Renew car insurance #admin #money [1/3]
Grammar (after the ATX #{1,6} marker):
heading := [KEYWORD " "] [PRIORITY " "] TITLE [" " TAGS] [" " COOKIE]*
KEYWORD := one of the in-scope TODO keywords (§7.1), case-sensitive
PRIORITY := "[#" LETTER "]" (A–Z, within the configured range)
TAGS := ("#" TAG) (" #" TAG)* trailing tags only, where TAG := [\p{L}\p{N}_/-]+, not all digits
COOKIE := "[" n "/" m "]" | "[" n "%]" (statistics, which may sit anywhere in the title)
- A heading is a task if and only if it starts with a keyword.
- Deeper than 6 levels: write
######with the metadata linelevel:: N(N ≥ 7). The outline treats it as level N. Default. - The subtree of a heading runs to the next heading of the same or a higher level.
- An inline task is a list item whose text starts with a keyword:
- TODO Call Sam. Its metadata lines are indented to the item's content column. Inline tasks have no children. - Checkboxes (
- [ ],- [x],- [-]) are checklist items, not tasks. They're counted by cookies and are clickable.- [-]means partially done.
Metadata lines
These are contiguous lines immediately after a heading (or indented under an inline task), each matching:
^[a-z][a-z0-9_]*:: .*$
- They hold Org's planning line and property drawer.
- The first non-matching line ends the block.
- Keys are lowercase snake_case, and values are raw text, typed by the key.
- Duplicate keys are an error on write. On read, the last one wins and a diagnostic is raised.
- Canonical order on write:
scheduled,deadline,closed,id, then the keys in the order listed below, then custom keys in their existing order. - New keys are appended in canonical position.
Reserved keys:
| Key | Type | Meaning |
|---|---|---|
scheduled |
timestamp | when you plan to do it (Org SCHEDULED) |
deadline |
timestamp | when it must be done (Org DEADLINE) |
closed |
inactive timestamp | when it was completed (written automatically) |
id |
short ID | the entry's identity (written by aegent when it's first projected) |
custom_id |
text | a human anchor (Org CUSTOM_ID) |
owner |
handle | @user, @agent or @team |
depends |
short IDs | a78, b12: blocked by these |
ordered |
bool | children must be completed in order |
noblocking |
bool | children don't block this entry |
effort |
duration | 30m, 1h30m, 2d (Org EFFORT) |
category |
text | agenda category |
acceptance |
text | what "done" means (§7.6) |
review |
auto, required, none |
review policy (§7.6); the UI labels auto Review agent completions |
agent |
text | plain-language guidance for agents (§7.9) |
agent_allow |
list | a subset of research, draft, contact, spend, irreversible, code |
agent_ask |
list | the same vocabulary: ask the human first |
budget |
money | £450, $20, 100 EUR |
secrets |
short IDs | secrets usable while working this task (§14.4) |
style |
habit |
habit tracking (§8.6) |
habit_min / habit_max |
duration | through the repeater .+2d/4d instead |
last_repeat |
inactive timestamp | written when a repeater advances (Org LAST_REPEAT) |
archived |
bool | archived in place (Org ARCHIVE tag) |
archive_page, archive_path, archive_time, archive_category, archive_state |
text | origin, written when archiving to another page |
export |
bool | false excludes the entry from export (Org COMMENT) |
level |
int | the outline level when it's deeper than 6 |
created |
inactive timestamp | written when an entry is created through the API |
source / external_id |
text | import provenance (§16.6) |
reminders |
list | extra reminders: -15m, -1d, <2026-10-11 Sun 18:00> (§17.1) |
rows |
pages, headings |
database row source (§10) |
Any other key is a custom property, inherited if it's listed in
inherit_properties.
Timestamps
These are Org's syntax, verbatim.
active := "<" body ">"
inactive := "[" body "]"
body := DATE [" " DAYNAME] [" " TIME ["-" TIME]] (" " MODIFIER)*
DATE := YYYY "-" MM "-" DD
DAYNAME := [A-Za-z]{2,9} (ignored on read, and regenerated in English on write)
TIME := H?H ":" MM (24-hour)
MODIFIER := REPEATER | WARNING | TZ
REPEATER := ("+" | "++" | ".+") N UNIT ["/" N UNIT] (the /max form is for habits)
WARNING := ("-" | "--") N UNIT (on a deadline: warning period; on scheduled: delay)
TZ := "@" IANA_ZONE (a fixed zone; without it the time is floating)
UNIT := "h" | "d" | "w" | "m" | "y"
range := active "--" active | inactive "--" inactive
special := "<rrule:" RFC5545_RRULE [" " TIME ["-" TIME]] [" " TZ] ">"
| "<yearly:" DATE ">" (anniversary; the agenda shows the count of years)
| "<rrule:" RRULE ";DTSTART=" DATE ">"
- Active timestamps anywhere in an entry's title or body are appointments (§8.4). Inactive timestamps are just records.
- The
<…>form isn't HTML (it starts with a digit,roryplus a colon). Parsers treat it as literal text, so it renders literally in CommonMark. - The canonical writer emits
<2026-10-12 Mon>,<2026-10-12 Mon 09:00>, and modifiers in the order repeater, warning, TZ. - Diary sexps from Org are replaced by
rrule:andyearly:. Converters MUST mapdiary-floattoBYDAYwithBYSETPOS,diary-anniversarytoyearly:, anddiary-cyclictorrule:FREQ=DAILY;INTERVAL=n.
Special fenced blocks
The language tag decides the meaning. Each of these is folded by default in the editor.
| Fence info | Meaning |
|---|---|
logbook |
the entry's LOGBOOK (§5.7). It MUST come directly after the metadata lines, or after the first body paragraph |
drawer NAME |
a generic Org drawer |
results [NAME] |
Babel results for the preceding code block (or a named one) |
encrypted |
an age-armoured ciphertext of a Markdown fragment (§5.12) |
agenda |
a custom agenda command definition (YAML, §9.5) |
query |
a live query with results rendered inline (YAML, §9.4) |
clocktable |
a clock report, rendered live (YAML, §8.7) |
columns |
a column-view definition for the parent subtree (YAML, §10.3) |
capture |
capture template definitions (YAML, §11.5) |
example, verse, export FORMAT |
Org example and verse blocks, and raw export blocks |
Pandoc fenced divs ::: verse, ::: center and ::: {.class} are supported
for Org's #+BEGIN_VERSE, #+BEGIN_CENTER and special blocks. Callouts
> [!note] Title (Obsidian) cover #+BEGIN_NOTE, warnings and so on.
Logbook
A fenced logbook block is one entry per line, using Org's wording.
- State "DONE" from "TODO" [2026-10-09 Fri 18:02]
- Note taken on [2026-10-08 Thu 12:00] \\
Called broker, waiting on quote.
- Rescheduled from "[2026-10-05 Mon]" on [2026-10-04 Sun 10:00]
- New deadline from "[2026-10-10 Sat]" on [2026-10-04 Sun 10:00]
- Claimed by @planner [2026-10-08 Thu 09:00]
- Released by @planner [2026-10-08 Thu 09:40] \\
Handoff: quotes collected in [[Car#^quotes]]; need a human to choose.
- Submitted by @planner [2026-10-08 Thu 11:00] \\
Evidence: paid, policy PDF attached ![[policy-2026.pdf]]
- Approved by @daniel [2026-10-08 Thu 12:00] \\
Checked the policy number.
- Rejected by @daniel [2026-10-08 Thu 12:00] \\
Wrong start date.
- Cancelled by @daniel [2026-10-08 Thu 12:00]
- Reopened by @daniel [2026-10-08 Thu 12:00] \\
Requirements changed.
- Decision by @daniel via claude (verified) [2026-10-08 Thu 10:00] \\
Q: Switch provider to Admiral at £398? A: Yes
CLOCK: [2026-10-08 Thu 09:00]--[2026-10-08 Thu 09:40] => 0:40
CLOCK: [2026-10-08 Thu 13:00]
\\at the end of a line starts an indented continuation note.- New lines go at the top (newest first), as Org does by default
(
org-log-states-order-reversed). A page setting can flip that. - An unclosed
CLOCK:line is the running clock. - Clock durations use Org's
H:MMform with two spaces after=>. - A
CLOCK:line may end with@handlenaming who clocked (CLOCK: [..]--[..] => 0:40 @planner, orCLOCK: [..] @plannerwhile running). aegent always writes it. Without one, the clock is the owner's. - Unknown lines are preserved.
Links, targets and embeds
| Syntax | Meaning |
|---|---|
[[Page]], [[Page|label]] |
wikilink by title or alias, within the space, then the org |
[[Page#Heading]], [[Page#^block]], [[#Heading]], [[#^block]] |
a heading or block in a page, or in the current one |
[[a78]], [[a78|label]] |
a short ID link to any entity (it resolves through aliases) |
[[acme/a78]] |
a cross-org short ID link (it resolves only if the viewer has access) |
[text](https://…), <https://…> |
external links |
[text](Page.md), [text](Page.md#heading) |
relative file links (the folder-sync form) |
![[Page]], ![[Page#Heading]], ![[a78]] |
transclusion (Org #+INCLUDE) |
![[file.pdf]], ![[image.png|300]],  |
attachments and images |
^block-id at the end of a paragraph or list item |
a block target (Org <<target>>) |
Unresolved wikilinks render as "create page" links. Renaming a page updates the wikilinks that point at it (if the user opts in, which is the default).
Tables and formulas
GFM tables. A formula line is a tblfm:: line directly after the
table's last row:
| Item | Qty | Price | Total |
|-------|-----|-------|-------|
| Tyres | 4 | 80 | 320 |
| MOT | 1 | 55 | 55 |
| Total | | | 375 |
tblfm:: $4=$2*$3::@>$4=vsum(@2..@-1)
- The language is Org's table formula language without Emacs Lisp or Calc
internals:
- references:
$N,@N,@N$M,$name(a named column through an optional| ! | name |row),@>(last row),@<(first),@-1,@I/@II(horizontal-rule groups) - ranges
a..b - the operators
+ - * / ^ %and comparisons - the functions
vsum vmean vmin vmax vcount vmedian round floor ceil abs if concat - format suffixes
;%.2f,;N,;T
- references:
- Recalculation happens on demand (the editor's "Recalculate", the API, or the
CLI
ae org tblfm). It's never automatic on read. - The editor aligns columns when you press Tab.
Code blocks (Babel)
```python {#fetch-rates tangle=scripts/rates.py exports=both results=output session=rates}
print(sum([1, 2, 3]))
```
```results fetch-rates
6
```
- Attributes use Pandoc syntax after the language:
#name,.class, andkey=value(Org header arguments:tangle,exports,results,var,session,dir,noweb,cache,eval). - Noweb:
<<name>>inside a block expands to the named block when tangling or executing (ifnoweb=yes). - Execution happens only in the CLI and desktop app, on the user's
machine, after a per-block confirmation (unless
eval=yeshas been approved for the page on that device). The server and web app never execute code. Results replace the followingresultsblock. - Tangling:
ae org tangle <page>writes blocks to theirtanglepaths, relative to the current directory or--dir.
Other syntax
| Feature | Syntax |
|---|---|
| Footnotes | [^1] with [^1]: text, and inline ^[text] |
| Maths | $x^2$, $$ … $$ (KaTeX) |
| Comments | %% not rendered or exported %%, inline or as a block |
| Macros | {{name}} and {{name(arg1, arg2)}}, defined in frontmatter macros:. Built-in: {{date}}, {{time}}, {{title}}, {{author}}, {{modification-time}} |
| Citations | [@key], [@key, p. 4], [-@key], @key, with bibliography: and csl: in frontmatter. Bare @key, and bracketed citations with a prefix ([see @key]), are citations only on pages with a bibliography: (otherwise they are handles or text) |
| Horizontal rule | --- (not at byte 0) |
| Callouts | > [!note] Title, with the types note, tip, warning, danger, info, quote and todo. [!note]- makes it folded |
Encrypted blocks
```encrypted
-----BEGIN AGE ENCRYPTED FILE-----
…
-----END AGE ENCRYPTED FILE-----
```
- The plaintext is an aegent Markdown fragment. It's encrypted to a
passphrase (age scrypt recipient) on the client: web (
age-encryptionJavaScript), desktop or CLI (agecrate). - The server stores ciphertext only, and never indexes it or shows it to agents.
- The editor shows a locked placeholder with "Unlock", which decrypts in memory only.
Org to aegent Markdown mapping (normative)
| Org | aegent Markdown |
|---|---|
* Heading … ****** Heading |
# … ###### (deeper levels through level::) |
#+TITLE, #+FILETAGS, #+TODO, #+SEQ_TODO, #+TYP_TODO, #+PRIORITIES, #+CATEGORY, #+STARTUP, #+ARCHIVE, #+BIBLIOGRAPHY, #+MACRO, #+OPTIONS |
frontmatter title, tags, todo, todo, todo, priorities, category, startup, archive, bibliography, macros, export |
TODO/DONE keyword |
the same, after the hashes |
[#A] |
[#A] |
:tag1:tag2: |
#tag1 #tag2 |
SCHEDULED:, DEADLINE:, CLOSED: |
scheduled::, deadline::, closed:: |
:PROPERTIES: drawer |
key:: value lines (lowercased keys) |
:ID: |
id:: (the short ID). A UUID from Org is kept in org_id:: for back-references |
:LOGBOOK: drawer |
a ```logbook block |
| other drawers | ```drawer NAME |
<2026-10-12 Mon +1w -2d> |
the same |
%%(diary-…) |
<rrule:…> or <yearly:…> |
- [ ], - [X], - [-] |
- [ ], - [x], - [-] |
[1/3], [33%] |
the same |
[[id:…][desc]] |
[[a78|desc]] |
[[file:x.org::*Heading][d]] |
[[x#Heading|d]] |
[[https://…][desc]] |
[desc](https://…) |
<<target>> |
^target |
<<<radio>>> |
frontmatter aliases |
#+BEGIN_SRC lang :tangle x |
```lang {tangle=x} |
#+RESULTS: |
```results |
#+BEGIN_QUOTE |
> |
#+BEGIN_EXAMPLE |
```example |
#+BEGIN_VERSE, #+BEGIN_CENTER |
::: verse, ::: center |
#+BEGIN_NOTE (special) |
> [!note] |
# comment, #+BEGIN_COMMENT |
%% … %% |
COMMENT heading keyword |
export:: false |
#+TBLFM: |
tblfm:: |
#+INCLUDE: |
![[…]] |
{{{macro(a)}}} |
{{macro(a)}} |
[cite:@key] |
[@key] |
[fn:1] |
[^1] |
\( … \), \[ … \] |
$ … $, $$ … $$ |
:crypt: + PGP body |
```encrypted (age, re-encrypted with the user's passphrase during conversion) |
:ARCHIVE: tag |
archived:: true |
STYLE: habit |
style:: habit |
Clock tables #+BEGIN: clocktable |
```clocktable |
Column view #+COLUMNS: |
```columns |
#+BEGIN: dynamic block |
```query |
ae org convert --from org <file|dir> implements this table.
Conversion is lossless for every row, and anything unmapped is kept in a
```drawer ORG block with a diagnostic.