Cuaderno
Cuaderno (cdno) is a command-line vault manager for the Research Logbook Method (RLM) — a
way of running long-horizon knowledge work out of a folder of plain Markdown files. It is built for
researchers and other deep-work practitioners, and is deliberately friendly to ADHD working styles:
it leads with what is there, not what is missing — no guilt counters, no angry overdue badges,
explicit permission to park or drop work.
You drive it from a terminal, and an AI assistant (Claude and other MCP clients) can drive it too, through the bundled MCP server.
What it gives you
- A dated, append-only log as the single source of truth for what you did.
- Projects with a current state, next actions, milestones, and waiting-on items — capped at five active at a time so you can’t overcommit.
- Evidence portfolios: per-question dossiers that accumulate papers, experiment results, and notes over months and years.
- Important questions kept front-and-centre and re-read often.
- Stewardships: small, bounded, long-haul responsibilities (health, finances) with optional habit tracking.
- A commitments register — promises with dates, aggregated from everywhere they live.
- Full-text search, frontmatter linting, and a JSON mode so every read and write verb is scriptable.
Two ways to use it
| Surface | What it is | Start here |
|---|---|---|
cdno CLI | The terminal tool for the daily loop | Quickstart |
cdno-mcp server | An MCP server so Claude can read and write your vault | Connect to Claude |
Both operate on the same Markdown vault — the files on disk are always the source of truth (the SQLite index is just a rebuildable cache).
How this guide is organised
- Getting started — install, create a vault, run the daily loop, and wire up Claude.
- Concepts — the mental model: the method, the twelve note types, the vault layout, and the rules the tool enforces.
- Tutorials — task-oriented walkthroughs of each workflow.
- CLI reference — every command, flag, and example.
- MCP server reference — every tool exposed to AI clients.
- Appendix — JSON shapes, frontmatter fields, the full config file, recurrence syntax, and troubleshooting.
This guide documents the shipped behaviour of Cuaderno. New to it? Read The Research Logbook Method for the why, then jump to the Quickstart for the how.
Installation
Cuaderno ships two binaries:
cdno— the command-line tool for the daily loop.cdno-mcp— the MCP server that lets Claude (and other MCP clients) read and write your vault.
Both are installed together.
Homebrew (macOS and Linux)
The recommended path. Pre-built bottles exist for macOS (Apple Silicon + Intel) and Linux (x86_64 + aarch64).
brew install agustinvalencia/tap/cuaderno
Verify:
cdno --version # -> cdno 0.1.24 (or newer)
cdno-mcp --version
To upgrade later:
brew upgrade cuaderno
From source
Use this on platforms without a bottle, or to track the latest main. You need a
Rust toolchain.
git clone https://github.com/agustinvalencia/cuaderno
cd cuaderno
cargo build --release --bins
# Binaries land at target/release/cdno and target/release/cdno-mcp
Put them on your PATH — for example:
ln -s "$PWD/target/release/cdno" /usr/local/bin/cdno
ln -s "$PWD/target/release/cdno-mcp" /usr/local/bin/cdno-mcp
Shell completions (optional)
cdno can print a completion script for your shell, with vault-aware suggestions (it completes
project, portfolio, and stewardship slugs by re-invoking the binary on TAB):
# zsh — add to your ~/.zshrc:
source <(cdno completions zsh)
Supported shells: bash, zsh, fish, elvish, powershell. See
completions for per-shell setup.
Uninstall
brew uninstall cuaderno # if installed via Homebrew
# or, for a source build, remove the symlinks you created:
rm -f /usr/local/bin/cdno /usr/local/bin/cdno-mcp
Uninstalling the binaries never touches your vault — it’s just Markdown files on disk. Delete the vault directory yourself if you want it gone.
Next step
Create your vault: Initialise a vault.
Initialise a vault
A vault is just a directory of Markdown files with a .cuaderno/ config folder at its root.
Create one with cdno init:
cdno init ~/notebook
cd ~/notebook
This scaffolds the full folder tree, writes a default .cuaderno/config.toml, and drops the
built-in note templates into .cuaderno/templates/ (so you can customise them later). What you get:
~/notebook/
├── journal/ # daily + weekly notes, partitioned by year
│ └── 2026/ # e.g. journal/2026/daily/2026-04-25.md, journal/2026/weekly/2026-W17.md
├── projects/ # project maps (max 5 active)
│ └── _parked/ # inactive projects
├── actions/ # manifest action notes (the heavy form)
│ └── _done/ # completed actions, partitioned by year
├── portfolios/ # evidence dossiers, one folder per question
├── stewardships/ # long-haul responsibilities (flat file or folder)
├── commitments/ # standalone promises with deadlines
│ └── _done/ # fulfilled commitments
├── questions/
│ ├── research/
│ └── life/
├── inbox/ # quick captures awaiting triage
└── .cuaderno/
├── config.toml # vault configuration
└── templates/ # note templates (override the built-ins here)
See Vault structure for what each folder holds.
Running cdno from anywhere
You rarely need to be at the vault root. cdno finds the vault by walking up from your current
directory until it sees a .cuaderno/ folder — so commands work from any subdirectory.
When you’re outside any vault, two fallbacks apply, in order:
- The
--vault <PATH>flag (highest priority — overrides everything). - The
CUADERNO_VAULT_PATHenvironment variable.
# From anywhere, target a specific vault:
cdno --vault ~/notebook log "spotted a bug in the sampler"
# Or set it once for the shell session:
export CUADERNO_VAULT_PATH=~/notebook
cdno log "spotted a bug in the sampler"
If you’re standing inside vault A while
CUADERNO_VAULT_PATHpoints at vault B, the directory you are in wins — writes land in A. The env var is only a fallback for when discovery finds nothing.
Back up your vault
A vault is just Markdown files (the SQLite index in .cuaderno/index.db is a rebuildable cache — see
Business rules). The simplest, most durable backup is version
control: git init the vault and commit as you go, or keep it in a synced folder.
cd ~/notebook
git init && git add . && git commit -m "Initial vault"
# The index is regenerated on demand, so it's safe to ignore:
echo ".cuaderno/index.db" >> .gitignore
Because the Markdown is the source of truth, your history is just your commits — nothing is locked inside a proprietary store.
Next step
Run your first daily loop: Quickstart.
Quickstart: the daily loop
This is Cuaderno in five commands. It assumes you’ve installed cdno and
created a vault. Run these from inside the vault (or with --vault).
# 1. Start a project (capped at 5 active).
cdno project create --title "Surrogate model" --context work
# 2. Give it a next action, tagged by the energy it needs.
cdno action add --project surrogate-model \
--title "Run feature set B on the full mesh" \
--energy deep
# 3. Record a promise with a deadline.
cdno commit create --title "Pay rent" --due 2026-06-01 --context personal
# 4. Each morning: see what's due, your active projects, and a suggested start.
cdno orient --energy deep
# 5. Mark the action done when it's finished (substring match on the bullet).
cdno action complete --project surrogate-model --query "feature set B"
Throughout the day, the two verbs you’ll reach for most:
cdno log "scaled the mesh to 2M cells; runtime 4x, still stable" # append to today's log
cdno capture "check whether Chen 2025 used the same preconditioner" # drop a thought into the inbox
Interactive vs. scripted
Every write command follows the same convention (see CLI overview):
- In a terminal, omit a required flag and
cdnoprompts you for it, then asks you to confirm before writing. - In a script, pipe, or with
--no-interactive, a missing required flag is an error instead — so automation never blocks on a prompt.
# Interactive: cdno asks for the title and context.
cdno project create
# Scripted: everything supplied, no prompts.
cdno project create --title "Surrogate model" --context work --no-interactive
Machine-readable output
Add --json to any read or write verb to get structured output instead of a formatted table.
Read verbs emit their listing/detail; write verbs emit a { "path": ..., "message": ... } result and
run non-interactively. Great for scripts and for piping into jq.
cdno project list --json | jq '.[].slug'
cdno project create --title "Surrogate model" --context work --json
# -> { "path": "projects/surrogate-model.md", "message": "Created projects/surrogate-model.md" }
See JSON output for the full shapes.
Where to go next
- Understand the model: The Research Logbook Method.
- Work through each workflow: Tutorials.
- Let Claude drive: Connect to Claude.
Connect to Claude (MCP)
Cuaderno ships an MCP server, cdno-mcp, that exposes your vault
to AI clients — Claude Desktop, Claude Code, Kiro, Gemini CLI, and anything else that speaks MCP.
The assistant can then read context (your orientation, a project, a portfolio) and make writes
(append to the log, file evidence, complete an action) on your behalf.
It’s the same engine as the CLI — the files on disk stay the source of truth.
Register the server
Add cdno-mcp to your client’s MCP configuration. For Claude Desktop, edit
~/.claude/claude_desktop_config.json (Claude Code: ~/.claude.json or per-project .mcp.json):
{
"mcpServers": {
"cuaderno": {
"command": "cdno-mcp",
"env": { "CUADERNO_VAULT_PATH": "/Users/you/notebook" }
}
}
}
commandmust resolve on the client’sPATH. If it doesn’t, use the absolute path (e.g./opt/homebrew/bin/cdno-mcp, ortarget/release/cdno-mcpfor a source build).CUADERNO_VAULT_PATHtells the server which vault to open. You can omit it; the server then opens whichever vault its working directory belongs to (the same discovery rule as the CLI).
Restart the client. Cuaderno’s tools then appear in the client’s tool list.
What the assistant can do
The server advertises 46 tools, grouped by purpose:
- Context-gathering reads —
get_orientation,get_project_context,get_portfolio_contents,get_weekly_context,search_notes,read_daily_note, and more. - Writes —
append_to_log,file_to_portfolio,update_project_state,add_action,complete_action,create_commitment,create_tracking_entry, the daily/weekly section writers, and others. - Creation and lifecycle —
create_project,create_portfolio,create_question,create_stewardship,park_project,activate_project,set_question_status, and so on.
The full catalogue, with each tool’s inputs and output shape, is in the MCP server reference.
Skills
You can wrap common multi-step flows as Claude skills that call these tools — e.g. a daily
orientation that reads get_orientation and writes your intention with upsert_daily_section. The
repo ships worked examples under
examples/skills/; see
Using with Claude skills.
Next step
Learn the model behind it all: The Research Logbook Method.
Desktop app
Cuaderno ships a macOS desktop app — a lens over the vault, not an editor. It answers “where am I, what’s next, what did I promise?” without a terminal, lets you tick, log, and capture, and hands deep editing off to your editor. The markdown files stay the source of truth; the app is a view that refreshes live when anything else (nvim, the CLI, Claude via MCP) touches the vault.
Apple Silicon only for now (an Intel build can join when there is a taker).
Install
Homebrew cask (recommended)
brew install --cask agustinvalencia/tap/cuaderno-app
xattr -dr com.apple.quarantine /Applications/cuaderno.app
The xattr step matters: the app is ad-hoc signed, not notarized, so Gatekeeper (recent
Homebrew removed the --no-quarantine install flag)
blocks the first launch.
Manual .dmg
Download cuaderno-app-<version>-aarch64-apple-darwin.dmg from the
releases page, copy the app to
/Applications, then strip the quarantine flag (right-click → Open no longer works on macOS 15+):
xattr -dr com.apple.quarantine /Applications/cuaderno.app
(Or use System Settings → Privacy & Security → “Open Anyway” after the first blocked attempt.)
Before first launch: two notes
-
Gatekeeper — covered above: strip the quarantine attribute from a manual install. There is no notarization and no auto-updater yet; upgrades go through
brew upgrade --cask cuaderno-appor a fresh.dmg. -
Vault discovery — on first launch the app asks for your vault folder with a native picker, validates that it is a cuaderno vault (a
.cuaderno/marker — pick the vault root, or runcdno initfirst if you have not created one), and remembers it for next time. If the folder you pick isn’t a vault it re-asks; if you cancel, it explains that it needs a vault and exits.The
CUADERNO_VAULT_PATHenvironment variable remains an override for terminals and dev, same as the CLI and the MCP server. A Finder-launched app inherits no shell environment, so if you want to pin the vault without the picker you can still set it for GUI apps once per login:launchctl setenv CUADERNO_VAULT_PATH "$HOME/Documents/notebook"or for a one-off run from a terminal:
CUADERNO_VAULT_PATH=~/Documents/notebook open -a cuadernoWhen set, the variable wins over the remembered folder (and an invalid override fails loudly, rather than falling back to the picker).
A quick tour
The sidebar is grouped the way the method is: a rhythm, and the two tracks it binds.
Rhythm is the cadence. Today is the day’s own note, with the morning orientation above it: a
Now band naming whatever you started and have not finished (read back from the day’s log, so a
cdno start from the terminal counts too), a one-line log composer, commitments due soon, and an
energy-filtered shortlist of one action per project to pick from. Any quietly lapsed habits sit at
the foot. Calendar is a month grid of your journal — days with a daily note are marked, today carries a
ring wherever you have paged to, and clicking a day opens it in a panel beside the grid that reads
read-only and jumps to the previous or next day, the day’s week, or its month. On a narrower window
the grid collapses behind a The month toggle instead of sitting alongside. Weekly is a guided, stop-anywhere five-step review with a labelled stepper and Back/Next; its
wins step hands you the week’s completions and log lines as cards you tick and reshuffle rather than a
blank box to compose in. Monthly is the review at the highest altitude — “am I still pointed at the right questions?” —
stepped like the weekly: questions, portfolio health, the five-slot project allocator, stewardship
trends, the six-week lookahead, and a focus step that writes wins, themes and next month’s focus to
the monthly note. It is the one review that leaves an artefact, which is what gives the monthly
cadence a reason to come back.
Operations is delivery. Projects heads the group and says how many of your slots are taken
(“3 of 5”), with each active project listed beneath it; every one has a full map
(/projects/<slug>) leading with its current state, then the next actions, blockers and milestones,
with backlinks and recent log mentions kept to a few and the rest a click away. Actions is the
cross-project list of next actions: a project rail with counts down the left, and a filter bar of
context and energy chips plus a text filter across the top. Every chip carries its own count, and
one of the energy chips is untagged — most bullets carry no (deep|medium|light) suffix, and
they are work like any other. Filtering says how much it is hiding rather than just showing less. Commitments is everything promised — what someone else is counting on, as
against what you merely decided to do (context-coloured, never red). It reads as a timeline banded
into This week / Next week / by month, or as a month grid with a dot per promise; the
horizon is yours to set, from a fortnight up to everything. Stewardships are the perpetual responsibilities. They never
complete, so the list shows status rather than progress: quietest first, with a count of how many
have gone quiet and a filter down to just those, and freshness read as ink emphasis rather than a
colour. Logging is one click from a row. A stewardship’s detail leads with Log entry in its
header and draws each tracked series as a calm trend, always in the context hue and never as a
target to hit. A metric that declares a plot in
[tracking.<activity>] is drawn the way it says; anything
undeclared falls back to reading the values — counts and volumes (reps, laps, sessions) as columns,
continuous measures (a weight, a pace) as lines.
What you see by default is deliberately bounded. One trend per activity is always on: the activity’s own number when it has one, rather than an arbitrary slice of a grouped metric. Every further series for that activity is denser material — a line per category, a grid of averaged ratings — and appears only when the metrics toggle is on. Nothing is hidden permanently; the dense view is opt-in rather than the default, and beyond six charts the rest sit behind an explicit show all that says how many it is holding back. That is a cap rather than a second filter: a grouped metric grows a chart per new category on its own, so without one the page would expand quietly as your data did. The activity chips narrow further and reset when you navigate away — looking is meant to be temporary, unlike a declaration.
The chart type is changeable in place. Picking one previews immediately and writes nothing; an
explicit Save chart type as default persists it into .cuaderno/config.toml, through the same
validated, conflict-checked save every other config edit uses (see
Editing the config in the app). It is worth knowing that a control on a chart
writes to your vault’s config — that is the point, since the declaration is what an agent reads
too, but it is a durable change rather than a view setting. Choosing None stops the chart being
drawn while the data stays collected and queryable; turning it back on is done from the config
editor, since there will be no chart left here to pick from.
Inquiry is investigation. Questions is the important-questions list that sits above any one project, grouped into research and life, each showing what is pointed at it and each movable between active, parked, answered and retired in place. Portfolios are the evidence dossiers those questions accumulate.
Everywhere: ⌘K opens search-and-jump, ⌘⇧C summons the global capture window from any app (Enter
files to the inbox, ⌘Enter appends to today’s log), and the menu-bar tray keeps Quick capture /
Open / Quit reachable even with every window closed. ⌘[ and ⌘] (or the mouse’s back and forward
side buttons) step backward and forward through your view history, just like a browser.
⌘, opens Settings, which holds everything that configures the app or the vault rather than
living in it: Appearance and Reading, a metrics toggle under General (which governs the denser half of a stewardship’s trend charts, described above), custom CSS under Advanced,
and two full editors — Vault config, which edits .cuaderno/config.toml in a Raw text view and
a structured Form for note types and schemas (every save validated before it touches disk, with a
live reload whenever the config changes underneath), and Templates, the per-note-type template
browser and editor. Both hold real drafts, so Settings will not close over unsaved changes without
asking. See Editing the config in the app for the full walkthrough.
Editing the config in the app
The desktop app’s Vault config editor edits .cuaderno/config.toml without
leaving the app. It reads the file, edits it two ways, validates every change before it touches disk, and
reloads the live vault the moment the config changes — whether the change came from the app or from
your editor.
This page covers using the editor. For what each key means, see Configuration and the Configuration reference.
Open it with ⌘, and pick Vault config from the settings rail. Configuration is not content,
so it lives in Settings rather than in the sidebar beside your notes.
Raw and Form
The editor opens on a Raw / Form toggle:
- Raw is the whole
config.tomlin a text editor. Everything is editable here, byte for byte — it is the escape hatch for anything the Form doesn’t cover. - Form is a structured view of the parts that have a fixed shape: note types, their
schema extensions, and the template variables block. You add, rename, and remove custom
note types and required frontmatter fields with inputs and toggles instead of hand-writing TOML.
Each schema field’s row also exposes its setter behaviour: Settable (whether
set_frontmattermay change the field) and Log changes to daily (auto-log each change to the daily note — available once the field is settable). The Template variables section edits the two variable maps: Static (values available in every template) and Prompted (asked for interactively when a template uses them).
Switch freely between them; both edit the same file. What the Form can’t represent stays in Raw (see What the Form doesn’t edit below).
The never-brick save
A vault whose config.toml fails to parse or validate won’t open — so the editor is built so you
cannot save it into that state from the app.
Every save, from either view, runs the exact validation the app runs when it opens a vault: the TOML is parsed, the ignore globs are compiled, and the type registry is validated. Only if all three pass does the save proceed. A failure is reported inline — with the line and column for a syntax slip — and nothing is written. The file on disk is never the broken version.
The Raw view also has a Check button that dry-runs the same validation without saving, so you can confirm a hand-edit before you commit to it.
Edits are surgical
Saving from the Form does not rewrite the whole file. It applies a targeted edit to just the table you changed, so your comments, key order, and formatting elsewhere survive untouched. Adding a required field rewrites only that one table; renaming a note type removes the old table and writes the new one — nothing else is touched.
Conflict detection
If the file changed on disk between the app reading it and your save — say you edited it in your editor in the meantime — the save is refused rather than silently overwriting the other change. The app tells you the file moved under it; reload to pick up the on-disk version, then reapply your edit.
Live reload
The app watches .cuaderno/config.toml. When it changes on disk — you edited it in nvim, ran a CLI
command, or another tool touched it — the app rebuilds the vault against the new config and
refreshes every view, no restart needed. The status line confirms the reload.
If an external edit leaves the config invalid, the app keeps the last good vault live and shows the validation error instead of the reload, so a bad hand-edit never takes the app down with it — fix the file and the next save reloads cleanly.
What the Form doesn’t edit
The Form covers note types, schema extensions, and template variables. A few things stay Raw-only by design:
max_active_projectsunder[vault], and the top-levelignoreglobs — edit these in Raw.
Everything Raw-only is still covered by the never-brick save and live reload; only the structured inputs are absent.
This is not the only thing that writes the config
A stewardship’s trend charts carry a chart-type picker that persists into
[tracking.<activity>.metrics.<name>], so a control that looks like a view setting makes a durable
change to .cuaderno/config.toml. It stages locally and writes nothing until you use its explicit
save, and that save runs through everything on this page — validation, the compare-and-swap against
what is on disk, and the live reload afterwards. See
the desktop app tour for what the picker does.
The reason it goes through the same gate rather than writing directly is the one this page is about: there is a single way config reaches disk, so an edit made from a chart cannot skip a check an edit made here would have to pass.
Next: the concepts behind these keys in Configuration, or the hands-on Customising templates and frontmatter tutorial.
The Research Logbook Method
The Research Logbook Method (RLM) is the practice Cuaderno implements. It distils habits common to prolific researchers — Faraday’s notebooks, Darwin’s dated entries, Hamming’s “important problems,” Knuth’s and Tao’s working logs — into six concrete pillars. Each pillar maps onto one or more note types the tool manages.
The six pillars
- A chronological log. A dated, append-only record of what you did and thought — the single source of truth. In Cuaderno this is the daily and weekly journal.
- Evidence portfolios. A dossier per important question that accumulates evidence — papers, experiment results, conversation notes — over months and years.
- Important questions. Hamming’s discipline, made first-class: name the questions that matter, keep them visible, re-read them often.
- Project maps. Lightweight overviews of active work — a current state, next actions, and milestones. Not a Gantt chart.
- Stewardships. Small, bounded, perpetual responsibilities (health, finances, a recurring service) — long-haul, low-drama, optionally with habit tracking.
- A commitments register. Promises to others (and dated promises to yourself), with deadlines — distinct from a to-do list.
Actions, not tasks
The RLM speaks of actions, not “tasks,” on purpose. The default form of an action is a single inline bullet on a project map — not a heavyweight per-item note. You only “promote” an action to its own note when it grows into an investigation spanning multiple days and evidence artefacts. This keeps the friction of capturing the next step near zero. See Actions.
Designed for ADHD working styles
The method is deliberately shaped to be sustainable when executive function is unreliable:
- Leads with what is there, not what is missing — no guilt engine, no red overdue counts.
- Permission to park or drop. Projects park, questions retire, commitments get fulfilled or dropped — all first-class, all reversible.
- Minimal maintenance. If keeping the system running costs more than a few minutes a day outside the weekly review, something is wrong.
- One obvious next step.
cdno orientanswers “what should I do now?” with a single suggestion, biased to your current energy.
The rhythm
The method runs on a few interlocking loops:
- Daily — orient in the morning, log and act through the day, a light close at the end. See The daily loop.
- Weekly — a short retrospective (wins, challenges, one improvement) and a single goal for next week. See Weekly review.
- Occasional — file evidence as you find it, triage the inbox, prune questions and projects.
How it maps to the tool
| Pillar | Note type(s) | Primary commands |
|---|---|---|
| Chronological log | daily, weekly | log, orient, review |
| Evidence portfolios | portfolio, evidence | portfolio, file |
| Important questions | question | question, questions |
| Project maps | project, action | project, action |
| Stewardships | stewardship, tracking | stewardship, track |
| Commitments register | commitment (+ computed) | commit, commitments |
Read on: Note types.
Note types
Every note in a vault has a type: in its frontmatter. Cuaderno parses that frontmatter into a
typed structure — if it parses, it’s valid. There are twelve note types.
| Type | Lives in | Mutability | Purpose |
|---|---|---|---|
daily | journal/<year>/daily/ | Append-only | One day’s chronological log |
weekly | journal/<year>/weekly/ | Append-only | Weekly review (Wins, Challenges, One Improvement, This Week’s Goal) |
monthly | journal/<year>/monthly/ | Append-only | Monthly review (Wins, Themes, Next Month’s Focus) + links to the month’s weeks |
project | projects/ (+ _parked/) | Mutable | Project map: state, next actions, milestones, waiting-on |
action | actions/ → actions/_done/<year>/ | Mutable while open, then archived | Manifest note for an action-as-investigation |
portfolio | portfolios/<slug>/_index.md | Occasionally edited | Index/summary of an evidence dossier |
evidence | portfolios/<slug>/ | Append-only | A single piece of evidence (paper, result, note) |
stewardship | stewardships/ (flat or folder) | Occasionally edited | Dashboard for a perpetual responsibility |
tracking | stewardships/<slug>/tracking/ | Append-only | One time-series entry (a gym session, a measurement) |
question | questions/research/ or questions/life/ | Status transitions | An important research or life question |
commitment | commitments/ → commitments/_done/ | Moves on completion | A standalone dated promise |
Plus the inbox: raw, untyped captures in inbox/ awaiting triage.
The journal: daily, weekly, and monthly
The chronological backbone. Daily notes collect timestamped log lines plus structured sections
(Intention, Agenda, Standup, Meeting). Weekly notes hold the review (Wins, Challenges, One
Improvement) and the week’s single goal. Monthly notes hold the higher-altitude review (Wins,
Themes, Next Month’s Focus) and a ## Weeks block that links the month’s weekly notes rather
than copying them, so the weeks stay the source of truth. All three are append-only — the
historical record only grows.
project — the one mutable map
Projects are the only freely-mutable note type. A project carries a Current State, a list of
next actions (inline bullets by default), milestones, and waiting-on items. When you
update the Current State, the previous state is auto-logged to today’s daily note first, so history
is never lost (see Business rules). At most five projects are active at
once; the rest live parked in projects/_parked/.
action — inline by default, a note when it grows
An action’s default form is a checkbox bullet on its project map. That’s usually all you need. When a
single action becomes an investigation spanning days and artefacts, you promote it to a manifest
action note (heavier: status, energy, criteria, links). Completing an action removes the bullet,
logs it, and — if it had a note — archives that note to actions/_done/<year>/. See
Actions.
portfolio + evidence — dossiers per question
A portfolio is a folder named for a question, with an _index.md (the portfolio note) and a set of
evidence notes filed into it over time. Each evidence note records a source and an origin
(a wikilink to whatever produced it). Evidence is append-only — a portfolio is a growing record. See
Research and evidence.
stewardship + tracking — long-haul responsibilities
A stewardship is a dashboard for something you tend indefinitely. It can be flat (a single
stewardships/<slug>.md) or expanded (a stewardships/<slug>/ folder with _index.md, a
tracking/ subfolder for time-series entries, and a routines/ subfolder for reference docs).
Expanded stewardships accept tracking notes via cdno track. See
Stewardships and tracking.
question — important questions, kept visible
A question note has a domain (research or life) and a status (active, parked, answered,
retired). Questions anchor portfolios and projects (via the core_question link). List the active
ones any time with cdno questions.
commitment — dated promises
A standalone promise with a hard due: date and a context. On fulfilment it moves to
commitments/_done/<year>/. Standalone commitments are one of four sources feeding the aggregated
commitments view.
These twelve are a closed set with built-in behaviour. For an entity they don’t cover (people,
books, clients), you can declare a schema-only custom note type
in config.toml — a folder, field rules, and a template, with no recompile (see the worked
Tracking people recipe).
For the exact frontmatter fields of each type, see Frontmatter fields. Next: Vault structure.
Vault structure
A vault is a directory tree of Markdown files plus a .cuaderno/ config folder. cdno init creates
the whole layout; here’s what each part holds.
vault/
├── journal/
│ └── 2026/ # partitioned by (ISO) year
│ ├── daily/
│ │ └── 2026-04-25.md # type: daily (append-only)
│ └── weekly/
│ └── 2026-W17.md # type: weekly (append-only)
│
├── projects/
│ ├── surrogate-model.md # type: project (mutable)
│ └── _parked/ # inactive projects (don't count toward the cap)
│ └── bayesian-opt.md
│
├── actions/
│ ├── characterise-sampler.md # type: action (manifest form)
│ └── _done/
│ └── 2026/ # completed actions, partitioned by year
│ └── run-ablation.md
│
├── portfolios/
│ └── sparse-vs-dense-ood/
│ ├── _index.md # type: portfolio
│ ├── 2026-03-15-chen-2025.md # type: evidence (append-only)
│ └── 2026-04-01-ablation-b.md
│
├── stewardships/
│ ├── finances.md # type: stewardship (flat)
│ └── health/ # expanded variant
│ ├── _index.md # type: stewardship
│ ├── tracking/ # type: tracking entries (append-only)
│ │ └── 2026-04-06-gym.md
│ └── routines/ # reference docs, not logs
│ └── upper-body-a.md
│
├── commitments/
│ ├── pay-rent.md # type: commitment
│ └── _done/
│ └── 2026/ # fulfilled commitments
│
├── questions/
│ ├── research/
│ │ └── surrogate-cost.md # type: question (domain: research)
│ └── life/
│ └── apartment-as-home.md # type: question (domain: life)
│
├── inbox/ # raw captures awaiting triage
│
└── .cuaderno/
├── config.toml # vault configuration
├── index.db # SQLite index cache (auto-created, rebuildable)
└── templates/ # note templates (override the built-ins)
Conventions worth knowing
_parked/(projects) and_done/(actions, commitments) prefix folders hold inactive or finished notes. The underscore keeps them sorted out of the way and signals “not the active set.”_done/is partitioned by year so the active folders stay scannable._index.mdis the identity note of a folder — theportfolionote inside a portfolio folder, thestewardshipnote inside an expanded stewardship folder.tracking/inside a stewardship holds time-series entries;routines/holds prescriptive reference documents (a workout plan, a checklist) — those are not logs.- Stewardships have two shapes: a flat
stewardships/<slug>.md, or an expandedstewardships/<slug>/folder. Only expanded ones can hold tracking entries.
.cuaderno/
config.toml— vault settings: the project cap, ignore globs, template behaviour, schema extensions, variables. See Configuration.index.db— a SQLite cache of the vault, used for fast search, linting, and link queries. It is rebuilt automatically when it’s stale (see Business rules); deleting it is safe. The Markdown files are always the source of truth.templates/— the note templatescdnofills when scaffolding a note. The built-ins are written here atinitso you can edit them; pure variable substitution, no logic.
Next: Business rules.
Business rules
Cuaderno enforces a small set of rules that keep the method honest. Knowing them explains why some commands behave the way they do.
Markdown is the source of truth
The files on disk are canonical. The SQLite index in .cuaderno/index.db is a cache — it makes
search, linting, and link lookups fast, but it can always be rebuilt from the Markdown. A stale
index is recoverable; a stale file would be data loss, so the tool never lets the cache override
the files. You can delete index.db at any time, or rebuild it explicitly with
cdno reindex.
Startup reconciliation
On every CLI invocation, MCP session, and app launch, Cuaderno reconciles the index against the filesystem — comparing modification times and content hashes — and quietly repairs anything stale before doing your command. This is why edits you make to notes by hand (in your editor, via sync) are picked up without a manual reindex.
The five-project cap
At most five projects are active at once. Try to create or activate a sixth and the command
stops you — you must park one first. Parked projects live in
projects/_parked/ and don’t count toward the cap. The limit is configurable via config.toml
(see Configuration); five is the default because it’s the point past which
“active” stops meaning anything.
Append-only notes
daily, weekly, evidence, and tracking notes are append-only — Cuaderno only ever grows
them, never overwrites. They are the historical record. (Projects are the deliberate exception; see
next.)
Project state history is preserved
A project’s Current State is the one piece of freely-mutable prose in the vault. To keep history
intact, every time you update it (via cdno project state or the MCP
update_project_state tool) the previous state is auto-logged to today’s daily note before the
new text overwrites it. You get a clean current view and a full audit trail in the journal.
Commitments are aggregated, not stored in one place
The commitments view is computed from four sources, so a promise is counted wherever it naturally lives:
- Project milestones marked with a hard deadline (
--hard). - Stewardship periodic commitments (the recurring lines on a stewardship dashboard).
- Standalone commitment notes in
commitments/. - Action notes carrying a self-imposed
due:that isn’t pinned to a milestone.
cdno commitments merges and sorts all four by date, with overdue
items flagged.
Atomicity and its limits
Each write is captured as a transaction (file writes + index updates) that commits as a batch and
rolls back on failure — while the process is alive. Startup reconciliation catches index staleness
afterward, but a crash midway through a rare multi-file operation can still leave partial state on
disk. In practice this is vanishingly rare; the takeaway is simply that the Markdown is authoritative
and reindex + lint will surface
anything odd.
Next: Contexts and energy.
Contexts and energy
Two small enumerations show up across many commands. Both are fixed vocabularies (not free text), so they stay consistent and filterable.
Contexts — the life domain
A context classifies which part of life a project, stewardship, or commitment belongs to. The set is fixed:
| Context | Typical use |
|---|---|
work | Your main job |
side-project | Personal projects outside work |
university | Studies, coursework, a degree |
family | Family responsibilities |
household | Running a home |
legal | Paperwork, contracts, official matters |
personal | Health, growth, anything else personal |
You set a context when you create a project (--context), stewardship (--context), or commitment
(--context). It groups and colours items in views and lets the system reason about balance across
your life, not just your work.
Contexts are a compile-time set, not configurable — keeping the vocabulary small and shared is the point. (Stewardships accept the same set.)
Energy — the effort a thing needs
An energy level tags how much focus an action demands, so the morning suggestion can match the work to how you actually feel:
| Energy | Meaning |
|---|---|
deep | Heavy, uninterrupted focus (real thinking, hard implementation) |
medium | Moderate focus (routine progress, review) |
light | Low focus (admin, quick wins, tidying) |
You tag an action with --energy when you add it. Then:
# "I have a clear morning" — bias the suggestion toward deep work:
cdno orient --energy deep
# "I'm fried" — surface something light instead:
cdno orient --energy light
cdno orient uses the energy bias to pick which next action to
suggest as your starting point. Matching the task to your state — rather than forcing the hardest
thing first — is part of what makes the daily loop sustainable.
Next: Configuration.
Configuration
Vault behaviour is configured in .cuaderno/config.toml, written for you by cdno init. The
defaults are sensible — you can run for a long time without touching it. This page explains what is
configurable and why; the Configuration reference lists every key.
What you can configure
- The active-project cap. Change the default of five via
[vault] max_active_projects. - Ignore globs. Patterns for files the index should skip —
CLAUDE.md,README.md, scratch notes — so they don’t appear in search, lint, or link checks. Patterns are additive (no negation), matched against vault-relative paths, and never delete anything on disk — they only scope what the index considers. - Templates. Override any built-in note template by adding a file under
.cuaderno/templates/. - Schema extensions. Add vault-specific required frontmatter fields per note type (e.g. require
collaboratorson every project), enforced bycdno lint.
The hands-on walkthrough is Customising templates and frontmatter.
Templates
When cdno scaffolds a note, it fills a template. Templates are pure variable substitution — no
conditionals, no logic. cdno init writes one starter template (.cuaderno/templates/daily.md);
every other type uses its built-in default until you add a file for it. cdno picks the most
specific template that exists:
- a custom variant template (for tracking, e.g.
tracking-gym.md), then - a custom type template (e.g.
project.md), then - the built-in variant default, then
- the built-in type default.
Because templates also define the canonical order of frontmatter keys,
cdno normalise uses them to reorder hand-authored or migrated
notes into a consistent shape.
Variables
Templates use {{placeholder}} markers that cdno fills from the values each note’s creation
command supplies — {{title}}, {{context}}, {{created}}, and so on. The exact set available per
note type, and how to use them in a custom template, is covered in
Customising templates and frontmatter. An unknown
placeholder is left verbatim, so use only the ones a type provides.
Custom templates can also reference static vault variables you set under [variables] in
config.toml (e.g. {{author}}); these resolve on every note type, with per-type values taking
precedence over a config variable of the same name.
For values that change per note, prompted variables under [variables.prompt] are gathered at
creation — from a --var name=value flag, an interactive prompt, or (failing both) a clear error.
The tutorial covers them in full.
Example
[vault]
name = "My Research Vault"
max_active_projects = 5 # the active-project cap
# Skip these from the index entirely (search/lint/links). Never deletes files.
ignore = ["CLAUDE.md", "README.md"]
# Require an extra field on every project note — enforced by `cdno lint`:
[schemas.project]
extra_required = ["collaborators"]
# Static template variables — resolve in any custom template (e.g. {{author}}):
[variables]
author = "A. Researcher"
# Prompted variables — gathered at note creation (--var, prompt, or error):
[variables.prompt]
collaborators = "Who are the collaborators?"
For the complete key-by-key reference, see Configuration reference.
That’s the concepts tour — next, put it to work in the Tutorials.
The daily loop
The core rhythm of Cuaderno is a daily loop: orient → act → log → close. None of it is mandatory or guilt-inducing; it’s a habit that keeps the vault current with almost no overhead.
Morning: orient
Start the day by asking the tool what deserves attention:
cdno orient --energy deep
orient shows commitments due soon, your active projects with their
current state and top next action, and a single suggested starting point — biased toward the
energy you tell it you have. Drop --energy to get a neutral
suggestion; pass --energy light on a low day.
Want just the project snapshot without commitments? Use cdno status.
Through the day: act and log
As you work, two verbs carry most of the weight.
Log what happens — append a timestamped line to today’s journal:
cdno log "scaled the mesh to 2M cells; 4x runtime, still stable"
Capture stray thoughts without breaking flow — they land in the inbox for later triage:
cdno capture "does Chen 2025 use the same preconditioner?"
Make progress on projects as you go:
# Add the next action you just thought of:
cdno action add --project surrogate-model --title "Profile the assembly step" --energy medium
# Record where a project now stands (auto-logs the previous state to today's journal):
cdno project state --slug surrogate-model --text "Mesh scaling works; assembly is the bottleneck"
# File a useful result into the right portfolio:
cdno file --portfolio sparse-vs-dense-ood --source "ablation run B" --origin projects/surrogate-model
# Tick off a finished action (substring match on the bullet):
cdno action complete --project surrogate-model --query "feature set B"
Evening: close
A light wind-down — note anything for your stewardships and reflect:
# Log a tracked activity under a stewardship:
cdno track gym --stewardship health --content "Upper body; good energy"
# A closing reflection is just another log line:
cdno log "good focus day; pick up assembly profiling tomorrow"
That’s it. The journal now holds a faithful record of the day, your projects reflect reality, and
tomorrow’s orient will pick up where you left off.
Letting Claude run the loop
Each step has an MCP equivalent (get_orientation, append_to_log, add_action,
update_project_state, …), so an assistant can drive the same loop conversationally. See
Connect to Claude and the example
skills.
Next: Managing projects.
Managing projects
A project is a lightweight map of a piece of active work: a current state, next actions,
milestones, and things you’re waiting on. You keep at most five active
at once. All the verbs live under cdno project (plus
cdno action for the next-action list).
Create one
cdno project create --title "Surrogate model" --context work
# -> projects/surrogate-model.md (slug derived from the title)
--context is the life domain. Optionally link the project’s
core question with --question questions/research/surrogate-cost. If you’re already at five active
projects, the new one is created parked — activate it once you free a slot.
See where things stand
cdno project list # active projects + a state snippet
cdno project show surrogate-model # one project in detail
cdno status # all active projects + their top action
Add --json to any of these for structured output (see JSON output).
Update the current state
The Current State is the project’s one mutable paragraph — “where is this right now?”. Updating it auto-logs the previous state to today’s journal first, so you never lose the trail:
cdno project state --slug surrogate-model \
--text "Mesh scaling works to 2M cells; assembly is now the bottleneck"
Next actions
Actions are the things to do next. By default they’re inline bullets on the project:
cdno action add --project surrogate-model --title "Profile the assembly step" --energy medium
cdno action list --project surrogate-model
cdno action complete --project surrogate-model --query "profile the assembly"
See Actions for the inline-vs-manifest distinction and promotion.
Milestones
Milestones are dated markers of progress. Mark one --hard to make it a real deadline that shows up
in the aggregated commitments view:
cdno project milestone add --slug surrogate-model --title "Submit to ICML" --date 2026-01-22 --hard
cdno project milestone done --slug surrogate-model --query "submit to icml"
Waiting-on
Track external blockers so they’re visible instead of forgotten:
cdno project waiting add --slug surrogate-model --description "Cluster quota increase from IT"
cdno project waiting resolve --slug surrogate-model --query "cluster quota"
Park and re-activate
Parking is first-class and reversible — it’s how you respect the five-project cap without deleting anything:
cdno project park --slug surrogate-model # -> projects/_parked/, frees a slot
cdno project activate --slug surrogate-model # bring it back (must be under the cap)
Next: Research and evidence.
Research and evidence
This is the knowledge-building half of the method: name the questions that matter, open a portfolio for each, and file evidence into it as you go. Over months a portfolio becomes a dossier you can actually reason from.
Name a question
cdno question create --domain research --text "Does sparse attention beat dense out-of-distribution?"
# -> questions/research/does-sparse-attention-beat-dense-out-of-distribution.md
--domain is research or life. List the active ones any time:
cdno questions # grouped by domain
cdno questions --json | jq .
As a question’s life changes, transition it (each transition is logged to the journal):
cdno question park --slug does-sparse-attention-beat-dense-out-of-distribution
cdno question answer --slug does-sparse-attention-beat-dense-out-of-distribution
cdno question retire --slug does-sparse-attention-beat-dense-out-of-distribution
cdno question activate --slug does-sparse-attention-beat-dense-out-of-distribution
Open a portfolio for it
A portfolio is a folder that accumulates evidence about one question:
cdno portfolio create --question "Sparse vs dense attention OOD"
# -> portfolios/sparse-vs-dense-attention-ood/_index.md
# Optionally tie it to a project when you create it:
cdno portfolio create --question "Sparse vs dense attention OOD" --project projects/surrogate-model
Already have a portfolio and want to link it after the fact? Use the retrofit verb (one of
--question/--project, not both):
cdno portfolio link --portfolio sparse-vs-dense-attention-ood --project projects/surrogate-model
File evidence
Every useful artefact — a paper, an experiment result, a conversation — goes in as an evidence
note. --source is the citation/reference; --origin is a wikilink to whatever produced it:
cdno file --portfolio sparse-vs-dense-attention-ood \
--source "Chen et al. 2025, NeurIPS" \
--origin projects/surrogate-model \
--content "They report a 4x speedup at 95% accuracy on the OOD split."
Attaching a real file
To file a non-Markdown artefact (PDF, image, video), point --attach at it. Cuaderno copies it into
the portfolio and scaffolds a linked evidence stub beside it (here --content is the abstract). Add
--move to move instead of copy:
cdno file --portfolio sparse-vs-dense-attention-ood \
--source "Chen et al. 2025" \
--origin projects/surrogate-model \
--attach ~/Downloads/chen2025.pdf \
--content "Key result: 4x speedup at 95% accuracy."
Review what’s accumulated
cdno portfolio list # all portfolios + evidence counts + staleness
cdno portfolio show --portfolio sparse-vs-dense-attention-ood
cdno search "preconditioner" --portfolio sparse-vs-dense-attention-ood
portfolio list flags staleness — dossiers you haven’t fed in a while — which is a useful prompt
during your monthly scan. Periodically synthesise the findings into the portfolio’s _index.md.
Next: Actions.
Actions
An action is the next concrete thing to do on a project. Cuaderno treats actions as cheap by
default: the normal form is a single bullet on the project map, not a note you have to create and
maintain. You only give an action its own note when it grows into real investigation. All verbs live
under cdno action.
Add a next action
cdno action add --project surrogate-model --title "Profile the assembly step" --energy medium
This appends a checkbox bullet to the project’s next-actions list, tagged with its energy. That’s usually all an action ever is.
List open actions
cdno action list --project surrogate-model
Bullets that have been promoted to notes show their status (active / blocked / completed) inline.
--json gives the structured list.
Promote an action to a manifest note
When an action becomes an investigation that spans days and produces evidence, promote it. This
rewrites the bullet as a wikilink and scaffolds an action note (with status, energy, criteria,
links). The match is a case-insensitive substring of the bullet text; energy is inherited:
cdno action promote --project surrogate-model --query "profile the assembly"
You can also create an action already promoted by passing --note to add:
cdno action add --project surrogate-model \
--title "Characterise sample efficiency across mesh sizes" \
--energy deep --note
Complete an action
Completing matches a bullet by substring, ticks it off, and logs it to today’s journal. If the
action had a manifest note, that note is archived to actions/_done/<year>/ and becomes append-only:
cdno action complete --project surrogate-model --query "feature set B"
Inline vs. manifest — when to promote
| Use an inline bullet (default) | Promote to a manifest note |
|---|---|
| A discrete next step | An investigation spanning multiple days |
| Done in one sitting | Produces evidence / artefacts to link |
| No supporting detail needed | Needs success criteria, status, or its own notes |
Keeping the default cheap is deliberate — it means capturing the next step never costs more than a sentence. See The Research Logbook Method for the rationale.
Next: Commitments and deadlines.
Commitments and deadlines
A commitment is a dated promise — to others, or a hard promise to yourself. Cuaderno keeps these distinct from your to-do list and gives you one aggregated view of everything with a deadline, wherever it lives.
The aggregated view
cdno commitments # everything due, sorted by date, overdue flagged
cdno commitments --weeks 6 # look six weeks ahead instead of the default two
cdno commitments is a computed view. It always includes a
30-day overdue look-back on top of the lookahead window, so nothing slips silently into the past. It
draws from four sources (see Business rules):
- Project milestones marked
--hard. - Stewardship periodic commitments (recurring dashboard lines).
- Standalone commitment notes (below).
- Action notes with a self-imposed
due:not tied to a milestone.
Standalone commitments
For a one-off promise that isn’t naturally a project milestone or a recurring stewardship line:
cdno commit create --title "Pay rent" --due 2026-06-01 --context personal
# -> commitments/pay-rent.md
# Optionally attribute it to a project or stewardship:
cdno commit create --title "Review Erik's draft" --due 2026-05-20 --context work --project projects/icml-paper
When it’s fulfilled, mark it done — it’s stamped and moved to commitments/_done/<year>/:
cdno commit done --slug pay-rent
Deadlines that live elsewhere
You often don’t need a standalone note — put the deadline where the work is:
# A hard project milestone shows up in `cdno commitments`:
cdno project milestone add --slug icml-paper --title "Camera-ready" --date 2026-02-01 --hard
# A recurring obligation on a stewardship dashboard:
cdno stewardship add-periodic --stewardship finances --title "File quarterly taxes" \
--every "every 3 months" --next 2026-07-15
Both surface in the same aggregated list, so cdno commitments is the single place to answer “what
have I promised, and when?”.
Next: Stewardships and tracking.
Stewardships and tracking
A stewardship is a small, bounded, perpetual responsibility — your health, your finances, a
service you maintain. Unlike a project it never “finishes”; you just tend it. Stewardships can carry
recurring commitments and, when expanded, time-series tracking. Verbs:
cdno stewardship and cdno track.
Two shapes: flat and expanded
# Flat — a single dashboard file, no tracking. Good for "finances".
cdno stewardship create --name "Finances" --context household
# Expanded — a folder with room for tracking/ and routines/. Use --tracking.
cdno stewardship create --name "Health" --context personal --tracking
A flat stewardship is stewardships/<slug>.md. An expanded one is stewardships/<slug>/ with an
_index.md, a tracking/ subfolder for entries, and a routines/ subfolder for reference docs
(workout plans, checklists — not logs). Only expanded stewardships accept tracking entries.
See what you’re tending
cdno stewardship list # each one's variant, tracking count, staleness badge
cdno stewardship show --slug health
Periodic commitments
Recurring obligations attached to a stewardship show up in the aggregated commitments view:
cdno stewardship add-periodic --stewardship health --title "Dental check-up" \
--every "every 6 months" --next 2026-09-01
--every takes a recurrence: daily, weekly, monthly, yearly,
or every N months.
Tracking entries
For habits and metrics on an expanded stewardship, file a tracking note. The activity is
positional and selects the template — a vault’s .cuaderno/templates/tracking-<activity>.md if you
have one, else a generic fallback. (Ready-made variants live in the repo’s
examples/templates/tracking/; see Customising templates.)
cdno track gym --stewardship health --content "Upper body A; RDL up to 25kg"
cdno track body --stewardship health --content "Weight 78.4kg, resting HR 54"
cdno track swim --stewardship health --content "1500m, 28min"
-
--stewardshipcan be omitted when there’s exactly one expanded stewardship — Cuaderno defaults to it. With more than one, it’s required. -
--routinelinks a reference doc from the stewardship’sroutines/folder into the entry — but only when the resolved template has aroutine:field (thegym.mdexample variant does; the generic default has none, so it no-ops there). -
--contentis optional; leave it empty and fill the entry’s tables in afterward. -
--atfiles the entry for a day that has already passed. Recording lags the event more often than not — a statement reconciled at the weekend, a reading taken this morning and typed up tonight — and without it the entry lands on the wrong day and the trend bends.cdno track body --stewardship health --at 2026-04-06Filing is always journalled to today’s daily log, naming the day the entry describes, so a backfill stays visible in the record rather than quietly appearing in the past. Dates more than 50 years back or a year ahead are refused — that far out is a typo, and a typo would reshape a trend without saying so.
Numbers belong in frontmatter
A number written into the body is prose; a number in frontmatter is data. An agent files them
through the MCP create_tracking_entry tool’s metrics parameter, and each key becomes a
frontmatter key:
---
type: tracking
stewardship: finances
activity: savings
date: 2026-07-25
balance: 12480.50
contributed: 400.00
---
When one entry holds several comparable items — three subjects practised, four categories spent against — write a sequence of flat records rather than one key per item, so the same subject can recur within the entry:
detail:
- { minutes: 25, subject: harmony }
- { minutes: 20, subject: harmony }
- { minutes: 15, subject: sight-reading }
A metric that reports a level rather than a total — a balance, a measurement, the last set of
the day — reduces to the last record in the entry, and “last” means the order the records appear in
the file. If you append them out of order, give every record an at field and they sort by it:
detail:
- { balance: 1200, at: "09:00" }
- { balance: 1240, at: "18:00" }
It is all-or-nothing: if any record lacks an at, or carries one that does not parse (09:00,
9:00, 9:00 AM and their with-seconds forms all do), the file’s own order stands and nothing is
reordered. The key is at rather than time precisely because time is a plausible metric —
a swim split, a lap time — and ordering a record set by one of its own measurements would report
a number that was never the last reading.
Declaring a metric under [schemas.tracking.fields] (see
Configuration) gets it type-checked on the way in — a float for
a measurement or an amount, an int for a count. Anything undeclared is written as given, except
the four keys that identify the note — type, stewardship, activity, date — which a metric
may not name. They are the note’s identity rather than data about it, and the engine owns them:
date is fixed by the filename, and activity is what every reader groups by.
Tracking entries are append-only — they’re your historical record.
Read them back over a window via the MCP get_stewardship_tracking tool, or with
cdno search.
From entries to series
Filing an entry is half the story. The other half — how those numbers turn into a chart, and why a
series sometimes shows a number that means nothing — comes down to two things: which aggregate
each metric declares, and which source, body table or frontmatter, the activity is read from.
Every [tracking.<activity>] key is documented in full in the
Tracking section of the configuration reference; this is
the plain-language version of why those keys exist.
The metric-kind taxonomy
Every number you track is one of four kinds, and the kind decides the aggregate it wants:
| Kind | Examples | Aggregate |
|---|---|---|
| Total | amount spent, pages read, minutes practised | sum |
| Level | account balance, a measurement, a top set | last, or max for a high-water mark |
| Rate or rating | a score out of ten, perceived difficulty | mean |
| Occurrence | a call, a visit | none — declare no metrics; the entry’s existence is the record |
Summing is right for exactly one of these — a total — and wrong for the rest. It’s wrong for a level because it adds successive readings of one quantity instead of reporting the quantity: three statements through a spending-free month, each reading a balance of roughly 12,480, would sum to over 37,000 — a number nothing in the account corresponds to. It’s wrong for a rating because the series grows with how often you record it, not with how the session actually went — log twice a day instead of once and the total doubles for no reason but the logging cadence. An occurrence doesn’t want a number at all: declaring the activity with no metrics is a complete, valid use — the record is “this happened”, not “how much”.
Two worked examples
A scalar activity, a level. The savings activity shown above has no repeated records — every
entry is one reading. balance is a level, so it wants last, not the default sum; contributed
is a total, so the default is already right:
[tracking.savings]
# no `records` — this activity's metrics are scalars read straight off the entry
[tracking.savings.metrics.balance]
aggregate = "last" # a LEVEL — the reading itself, not a running total of readings
unit = "EUR"
[tracking.savings.metrics.contributed]
aggregate = "sum" # a TOTAL — money added since the last entry
unit = "EUR"
Filed against the frontmatter shown earlier (balance: 12480.50, contributed: 400.00), this
produces two series: savings · balance, whose points are each entry’s balance verbatim, and
savings · contributed, whose points are the sum of every contribution recorded on that date.
A record-based activity, two aggregates over the same records. practice splits by subject
and tracks two different kinds of number per session:
[tracking.practice]
records = "detail" # frontmatter key holding the repeated records
group_by = "subject" # one series per distinct subject
[tracking.practice.metrics.minutes]
type = "int"
aggregate = "sum" # a TOTAL — minutes practised
unit = "min"
[tracking.practice.metrics.focus]
aggregate = "mean" # a RATING — sum would grow with how often you log, not with how focused you were
One entry covering two subjects in the same sitting:
---
type: tracking
stewardship: study
activity: practice
date: 2026-07-25
detail:
- { minutes: 25, focus: 8, subject: harmony }
- { minutes: 20, focus: 6, subject: harmony }
- { minutes: 15, focus: 9, subject: sight-reading }
---
Four series come out of it, named <activity> · <group> · <metric>:
practice · harmony · minutes= 45 (25 + 20, summed)practice · harmony · focus= 7 (the mean of 8 and 6)practice · sight-reading · minutes= 15practice · sight-reading · focus= 9
Same records, same date, two different reductions — because minutes and focus are different
kinds of number, not because one config option was chosen for the whole activity.
Gaps, not zeros
A subject you didn’t practise on a given day produces no point on that day, not a zero.
Zero-filling would draw a false line down to the axis — a session that didn’t happen is not the same
as a session scored zero. The same rule means a brand-new subject needs no configuration change: the
first time ear-training shows up in a detail record, its series starts there, on that date, with
every date before it correctly absent rather than zero.
Which source a series comes from
An undeclared activity — no [tracking.<activity>] for it in config.toml — is read from the
first table in the note’s body: one series per column, each column’s numeric cells summed. That’s
right for a rep count and wrong for almost everything else, which is why the declared path above
exists. If you’re still filing through a table, see
Wide, not long
in the example templates — a table column is a series, and a Metric / Value row shape sums
unrelated numbers into one meaningless total.
Declaring an activity moves it to frontmatter: once the declaration yields a series for that activity, its body tables are no longer read — every note of it, not just the ones carrying frontmatter metrics. So the same metric can never appear twice under two disagreeing numbers, one from a table and one from the declared reduction. It also means a half-migrated activity shows only what its frontmatter carries, so migrate an activity’s notes together rather than one at a time.
Next: Weekly review.
Weekly review
The weekly review is the one slightly-longer ritual in the method — a short retrospective plus a single goal for the week ahead. It’s where you celebrate progress, notice what’s stuck, and set direction without micromanaging.
The weekly note
Each ISO week has a weekly note with four sections:
- Wins — what moved (actions completed, evidence filed, projects advanced).
- Challenges — what got in the way.
- One Improvement — a single thing to change next week.
- This Week’s Goal — the week’s anchor: one goal everything else orbits.
View the current week’s note (or any week) any time:
cdno weekly # this ISO week
cdno weekly --date 2026-04-20 # the week containing that date
The guided ritual
cdno review weekly walks you through it. Interactively, it prompts for
each retrospective section and writes them into this week’s note, then asks for next week’s goal
and sets it as the This Week’s Goal of next week’s note — so when the new week starts, its
anchor is already there.
cdno review weekly
Run non-interactively (or with --no-interactive), it reads the current note rather than prompting —
handy for scripts or a quick scan.
A weekly cadence that works
A simple routine, ~15 minutes:
cdno status— skim active projects and their top actions.cdno commitments --weeks 2— what’s due in the next fortnight?cdno stewardship list— any habit looking stale?cdno review weekly— capture Wins / Challenges / One Improvement, set next week’s goal.
The point isn’t a perfect record; it’s a regular moment to look up from the work, acknowledge what you did, and choose one direction. Parking a project or retiring a question here is a good outcome, not a failure.
With Claude
The MCP tools get_weekly_context, read_weekly_note, and upsert_weekly_section let an assistant
run the same review conversationally — reading your week back to you and writing the sections as you
talk. See Connect to Claude.
Next: Inbox and triage.
Inbox and triage
The inbox is a pressure-release valve: capture a thought now, decide what to do with it later. This keeps the daily loop friction-free — you never have to stop and classify something mid-flow.
Capture
cdno capture "does Chen 2025 use the same preconditioner?"
cdno capture "ask IT about the cluster quota"
Each capture becomes a small slug-named note in inbox/. That’s the whole cost — no fields, no
decisions. Capture liberally.
Triage
When you have a moment (a natural fit for the weekly review), process the inbox:
cdno triage
Interactively, triage walks each pending capture and offers, for each one, to:
- keep it as a project action — turn it into a next action on a project, or
- discard it — it served its purpose, or
- skip it — leave it for next time.
Run non-interactively (or with --no-interactive), triage just lists what’s pending — a quick
way to see the backlog without acting on it.
A healthy inbox is empty-ish
The inbox is a transit point, not a filing cabinet. The goal isn’t zero at all times — it’s that nothing important lives only there. Anything worth keeping becomes an action, a piece of evidence, a question, or a log line; the rest gets discarded without ceremony.
With Claude
The MCP tools capture, triage_inbox (lists pending items), and discard_inbox_item let an
assistant capture on your behalf and help you clear the backlog conversationally.
Next: Searching your vault.
Searching your vault
Cuaderno keeps a full-text index of every note, so you can find anything fast — ranked best-first,
with filters by type, date, and portfolio. The command is cdno search.
Basic search
cdno search "preconditioner"
cdno search "sparse attention" # multiple words are ANDed
The query is matched case-insensitively and terms are combined with AND. Quotes and operators are treated as literal words, not search syntax. Results come back ranked, best match first.
Filter the results
# Only one note type:
cdno search "ablation" --type evidence
# Within a date window (inclusive):
cdno search "mesh" --from 2026-03-01 --to 2026-03-31
# Only inside one portfolio:
cdno search "speedup" --portfolio sparse-vs-dense-attention-ood
# Cap the number of hits (default 20):
cdno search "todo" --limit 5
Filters combine, so you can scope tightly:
cdno search "preconditioner" --type evidence --portfolio sparse-vs-dense-attention-ood --from 2026-01-01
Scripting with --json
--json returns the ranked hits as a JSON array — each with path, note_type, title, snippet,
and score — ready for jq or another tool:
cdno search "speedup" --json | jq -r '.[].path'
See JSON output for the exact shape.
When search feels stale
Search reads the SQLite index, which is reconciled automatically on every run. If results ever look out of date (e.g. after a bulk external edit), rebuild it explicitly:
cdno reindex
The Markdown files are always the source of truth; the index is just a rebuildable cache (see Business rules).
Next: Customising templates and frontmatter — tailor how notes are scaffolded and which frontmatter fields they require.
For exhaustive detail on any command, see the CLI reference.
Customising templates and frontmatter
When cdno scaffolds a note it fills a template. Every note type has a built-in template, and you
can override any of them per-vault — to change the structure, the default sections, or the frontmatter
fields. You can also require extra frontmatter fields so cdno lint
keeps your notes consistent. This tutorial walks through both, hands-on.
What’s covered here is the shipped behaviour. Both kinds of config variable now resolve in custom templates: static
[variables]and interactive[variables.prompt](gathered from a TTY prompt or a--var name=valueflag).
Where templates live
Templates live in .cuaderno/templates/, one Markdown file per note type (e.g. project.md,
evidence.md). cdno resolves the effective template at creation time:
- a custom variant file —
<type>-<variant>.md(trackinguses this: the variant is the activity slug, e.g.tracking-gym.md), then - a custom type file —
<type>.md, then - the built-in variant default, then
- the built-in type default.
So a custom file in .cuaderno/templates/ always wins over the built-in. No activity-specific
variants ship built-in (tier 3 is empty today) — tracking variants are entirely yours to add;
see Tracking variants below.
cdno initwrites just one starter template —.cuaderno/templates/daily.md. Every other type uses its built-in default until you add a file for it. The quickest way to get an editable copy of a built-in iscdno templates eject <type>— e.g.cdno templates eject projectwrites.cuaderno/templates/project.mdmatching the built-in, ready to edit. You can also just create the file yourself, as the next section shows.
Customise a template
Say you want every project to start with a ## Risks section. Eject the built-in as a starting point:
cdno templates eject project # writes .cuaderno/templates/project.md
That writes the full built-in template. Insert a ## Risks section (you can reference any of the
{{placeholders}} that cdno templates vars project lists) and leave the rest as-is, so
.cuaderno/templates/project.md reads:
---
type: project
context: {{context}}
status: {{status}}
created: {{created}}
core_question: {{core_question}}
---
# {{title}}
## Current State
New project. No work done yet.
## Risks
## Next Actions
- [ ] Define first concrete step (light)
## Waiting On
(nothing yet)
## Milestones
- [ ] First milestone — target: TBD
## Links
- Portfolio: (none yet)
Now create a project:
cdno project create --title "Surrogate model" --context work
The new projects/surrogate-model.md follows your template — including the ## Risks section:
---
type: project
context: work
status: active
created: 2026-06-30
core_question: null
---
# Surrogate model
## Current State
New project. No work done yet.
## Risks
## Next Actions
- [ ] Define first concrete step (light)
## Waiting On
(nothing yet)
## Milestones
- [ ] First milestone — target: TBD
## Links
- Portfolio: (none yet)
cdno templates eject <type> is the recommended way to get an editable base — it always matches the
current built-in. (You could instead hand-write the file, or shape it from a note cdno already
created, but eject saves the guesswork.)
Editing a template only affects notes created afterwards — existing notes are untouched. (And
cdno normaliseonly reorders frontmatter keys; it won’t add a new section like## Risksto old notes.)
Tracking variants
tracking is the one type whose template is chosen per activity. cdno track <activity>
slugifies the activity and looks for .cuaderno/templates/tracking-<activity>.md, falling back to
the generic tracking template when there’s none. So a custom .cuaderno/templates/tracking-gym.md
gives cdno track gym a bespoke layout without touching cdno track swim, and
.cuaderno/templates/tracking.md overrides the generic fallback for everything else.
Only the neutral generic template ships built-in — no activity-specific variants are baked into
the product. Ready-made gym, body, and swim variants (exercise table, body-metrics table,
swim-set table) live in the repo under
examples/templates/tracking/;
copy one to .cuaderno/templates/tracking-<activity>.md to use it, or start your own from it.
Template variables
Templates use {{placeholder}} markers. cdno substitutes the values the note’s creation command
supplies. Two rules to know:
- An omitted optional value renders as
null(e.g.core_question: nullabove when you don’t pass--question). - An unknown placeholder is left verbatim —
{{nope}}stays as the literal text{{nope}}in the note. So a template should only use the placeholders its note type actually provides.
Each type provides these:
| Note type | Available {{placeholders}} |
|---|---|
daily | date, heading, weekday, day_name, week |
weekly | week, week_num, year, date_start, date_end |
monthly | month, month_name, year, date_start, date_end, weeks |
project | title, context, status, created, core_question |
action | title, slug, project, energy, status, created, due, completed, milestone, criteria, blocker, tags |
portfolio | question, project, created |
evidence | source, origin, portfolio, content, created |
stewardship | name, context |
tracking | stewardship, activity, activity_title, routine, content, date, date_long |
question | question, domain, created, updated |
commitment | title, context, status, due, project, stewardship, created, completed |
inbox | body, created |
You can use any subset, in any order, and add as much static Markdown around them as you like.
Daily specifics.
weekdayandday_nameare aliases for the same value — the weekday name (e.g.Sunday) — so use whichever reads better.weekis the ISO-week labelYYYY-Www(e.g.2026-W27), matching the weekly note for that date, so[[{{week}}]]in a daily template links straight to it.
Discover them from the CLI.
cdno templates vars <type>lists exactly this table for a type — the complete set its create path supplies — and folds in any[variables]/[variables.prompt]names your config adds, classified by source. For examplecdno templates vars tracking. See thetemplatesreference.
Static config variables
Beyond the per-type placeholders above, a custom template can reference vault-wide static
variables you define under [variables] in .cuaderno/config.toml. These resolve on every note
type. For example:
# .cuaderno/config.toml
[variables]
author = "A. Researcher"
institution = "University of Examples"
A custom template can then use {{author}} / {{institution}} and they’ll be substituted at
creation. Precedence: a per-type (contextual) placeholder of the same name always wins over a config
variable, so config vars only fill names the note type doesn’t already supply.
Prompted variables
A static variable is the same on every note. When you want a value that changes per note — a ticket
id, a collaborator, a meeting code — declare it under [variables.prompt], where the value is the
prompt message:
# .cuaderno/config.toml
[variables.prompt]
ticket = "Ticket reference?"
Reference it in a custom template like any other placeholder (e.g. ticket: {{ticket}} in the
project frontmatter). When you create a note whose effective template uses a prompted variable, cdno
gets the value one of three ways:
--var name=valueon the command (repeatable), e.g.cdno project create --title T --context work --var ticket=ABC-123;- otherwise, in an interactive TTY,
cdnoasks (“Ticket reference?”) and shows the value in the confirm preview before writing; - otherwise (non-interactive, no
--var) it errors rather than writing a note with a literal{{ticket}}:
Error: missing value for template variable 'ticket' (pass `--var ticket=value`, set a default under
[variables] in .cuaderno/config.toml, or run interactively in a TTY)
--var is available on every note-creating command: project create, question create,
stewardship create, commit create, portfolio create, file, track, action add --note, and
action promote.
A few rules worth knowing:
- A prompted name that also has a static
[variables]default is satisfied by that default — you’re not asked, and it won’t error. (The static default wins by precedence, so--varcan’t override it; remove the default if you want to be prompted.) - A
[variables.prompt]entry whose{{name}}your template doesn’t actually use is ignored. - The same precedence applies: a per-type placeholder of the same name wins over a prompted variable.
--varonly applies to templated notes.cdno file --attach(the attachment stub) and a plainaction add(no--note) aren’t templated, so--varis ignored there.- The implicit-write paths — daily (
log), weekly, and inbox (capture) notes — don’t gather prompted values, and neither do MCP-driven creations (there’s no--varover MCP). A[variables.prompt]placeholder in one of those templates fails at creation (anUnresolvedPromptserror) instead of being asked for; give it a static[variables]default instead.
Frontmatter field order and normalise
Your template also defines the canonical order of frontmatter keys for that type. Notes cdno
creates are already in that order; for hand-authored or migrated notes,
cdno normalise reorders their frontmatter to match the template
(--check reports without writing). So if you reorder the keys in project.md, a later
cdno normalise brings older project notes into line.
Require extra frontmatter fields
Beyond the built-in required fields, you can demand vault-specific ones per type with a
[schemas.<type>] section. For example, to require every project to name an owner, add to
.cuaderno/config.toml:
[schemas.project]
extra_required = ["owner"]
This is enforced by cdno lint, which now errors on any project
missing the field (a missing key, or one whose value is null, fails):
cdno lint
# [error] projects/surrogate-model.md: missing required field `owner` for note type `project`
# Error: found 1 error(s), 0 warning(s)
lint exits non-zero on errors, so this is a good gate to run in a pre-commit hook or CI over a
git-tracked vault.
Satisfy the requirement going forward
Add the field to your template so new notes carry it. Give it a non-null default you can edit per
note (an empty key — owner: — is YAML null and still fails the lint; use a placeholder value):
---
type: project
context: {{context}}
status: {{status}}
created: {{created}}
core_question: {{core_question}}
owner: unassigned
---
New projects are now born with owner: unassigned (edit it as needed) and pass the lint.
Existing notes aren’t changed retroactively — fix them by adding the field, then re-run
cdno lint until it’s clean.
Required fields are about presence, not value: any non-null value satisfies the check. Combine
extra_requiredwith a template default and the occasionalcdno lintand your vault stays uniform without any per-note ceremony.
Give a field a type
extra_required only checks that a key is present. When you want cdno lint to also check the
value — that meds is a boolean, mood is one of a fixed set, since is a real date — declare a
typed field instead, under [schemas.<type>.fields.<name>]:
[schemas.daily.fields.meds]
type = "bool"
default = false
[schemas.daily.fields.mood]
type = "string"
values = ["low", "ok", "good"]
Now a daily note whose meds: isn’t a boolean, or whose mood: isn’t one of the three allowed
values, gets a cdno lint warning. Typed fields are also recognised by the desktop Templates editor,
so a custom template referencing {{meds}} no longer warns that it “renders literally”.
A typed field’s default is populated at create, too: add meds: {{meds}} to your custom
daily.md and every new daily note is scaffolded with meds: false — the declared default — rather
than a literal {{meds}}. A field with no default renders null. (As with any placeholder, the
field only appears in a note if the template references it; and if the note’s create path already
supplies that name, or a [variables] static var does, that value wins over the default.) See
Typed schema fields in the configuration
reference for the full grammar and its limits.
Edit templates in the desktop app
Everything above works from the CLI, but the desktop app also has a Templates editor that does
the same job without a terminal — press ⌘, and pick Templates from the settings rail.
You get a chip for every note type — the built-ins plus any custom types you declared
under [note_types.<name>]. Selecting one names its source in the editor’s header:
- Built-in — the type is using its built-in default (no override yet).
- Custom — a custom override exists in
.cuaderno/templates/. - No template — a custom type that has no template file yet.
The chips themselves flag only the last two, since most types sit on their built-in default.
Select a type to see its effective template in the editor. Edit the text and press Save: the
app writes .cuaderno/templates/<type>.md. For a type currently on the built-in default, that first
save creates the custom override — the same edit-and-save model as cdno templates eject followed
by an edit, but in one step. A custom type showing No template offers Create, which
scaffolds a starter from the type’s declared required fields.
The side panel lists the placeholders you can use, grouped by where their value comes from —
supplied keys the create path fills, a custom type’s own schema fields, and any config
variables or prompted variables. If you type a {{token}} that isn’t in that set, the editor
shows a calm inline notice so you can catch a typo before it renders literally — but it never blocks
you from saving. An edit made outside the app (in your editor, or by another tool) refreshes the
view automatically.
See also
- Configuration — the configurable surface.
- Configuration reference — every
config.tomlkey. - Frontmatter fields — the built-in fields per note type.
normalise,lint.
Tracking people
Cuaderno’s built-in types don’t include a “person” — deliberately, since a person decomposes into the notes you already keep (daily logs, meetings, actions, commitments). But if you want to answer questions like:
- What was my last interaction with Jane?
- When did I ask Jane to do something — and what did she ask me?
…a custom note type plus a linking convention gets you there,
with no bespoke CRM. This recipe is the worked example; the files live in
examples/note-types/person/.
1. Declare the type
Add to .cuaderno/config.toml:
[note_types.person]
folder = "people"
That’s the minimum — a person’s identity is the note title. (Add required/optional fields if you
want enforced frontmatter; see the reference.)
Optionally copy the example person.md
to .cuaderno/templates/person.md for a note with a ## Log section. Without a template, Cuaderno
synthesises a minimal note.
2. Create people
cdno note create person --title "Jane Smith"
This writes people/jane-smith.md. cdno note list person enumerates them; cdno lint keeps them
honest.
3. Link people from your notes
The key habit: whenever a person shows up in a daily log, a meeting section, or an action, reference
them with a [[people/<slug>]] wikilink:
## Logs
- **14:30**: standup — [[people/jane-smith]] asked me to review the sparse-attention draft
Body wikilinks are indexed, so every mention becomes a backlink on the person’s note, and the raw text is full-text searchable.
4. Answer the questions
“What was my last interaction with Jane?” — the reliable answer is the top line of her person
note’s ## Log, which you keep most-recent-first. To scan across the vault, search her slug:
cdno search "people/jane-smith" --type daily --from 2026-06-01
Search ranks by relevance, not date, so bound the window with --from / --to and read the
dates on the hits (daily notes carry their date in the filename) rather than trusting the order.
Drop --type to include meetings and action notes.
“What did Jane ask me — and what did I ask her?” — this is direction, which lives in your
prose, not in structure. Write the log line so the direction is explicit (“Jane asked me…”, “I asked
Jane…”), and either keep a running ## Log in her person note or let search surface the lines. A
Claude skill can read the dated results and summarise who-owes-what; the structure gives it the
material, the prose gives it the direction.
Why a “person” type and not a built-in
A custom person type is schema-only — it gives you a folder, linting, search, and backlinks,
but no bespoke behaviour. That’s exactly right here: people don’t need caps, state machines, or
aggregation; they need to be findable and linkable. If you later want richer per-person structure,
add fields to the config declaration — no code change. See
Custom note types for the full schema.
CLI reference
cdno is the command-line interface to a Cuaderno vault. This section documents every command. For
the why behind them, see Concepts and Tutorials.
cdno [OPTIONS] <COMMAND>
Run cdno --help for the command list, or cdno <command> --help for any command’s flags.
Global options
These apply to every command:
| Flag | Effect |
|---|---|
--vault <PATH> | Operate on the vault at PATH. Overrides both discovery and CUADERNO_VAULT_PATH. |
--json | Emit machine-readable JSON instead of a formatted table (see below). |
--no-interactive | Never prompt; a missing required argument is an error. Implicit when stdout isn’t a TTY. |
-h, --help | Print help. |
-V, --version | Print the version (top level only). |
Finding the vault
When --vault is not given, cdno discovers the vault by walking up from the current directory
until it finds a .cuaderno/ folder. If that finds nothing, it falls back to the
CUADERNO_VAULT_PATH environment variable. Standing inside a vault always wins over the env var.
See Initialise a vault.
Interactive vs. scripted
Write commands follow one convention (the “flags-and-prompts” pattern):
- Interactive terminal, missing a required flag →
cdnoprompts for it, then asks you to confirm before writing. - Non-interactive (piped, redirected, in CI, or
--no-interactive) → a missing required flag is an error. Supply every flag and the command runs unattended.
This means the same command serves a human at a prompt and a script with no changes.
JSON output
--json makes any supported verb emit structured output:
- Read verbs (
commitments,questions,status,orient,search, and thelist/showverbs ofproject/portfolio/stewardship, plusaction list) emit their listing or detail object. - Write verbs (
log,capture,file,track, and the create/update verbs ofproject,action,portfolio,stewardship,question,commit) emit a{ "path": ..., "message": ... }result, and run non-interactively (so prompts can’t corrupt the JSON on a terminal). - Maintenance / interactive / bootstrap commands (
init,lint,reindex,normalise,triage,review,weekly,monthly) ignore--json.
The CLI’s JSON shapes match the MCP server DTOs. See JSON output for every shape.
The commands
| Command | What it does |
|---|---|
init | Create a new vault |
log | Append a line to today’s daily note |
capture | Drop a quick note into the inbox |
triage | Process inbox captures |
orient | Morning orientation |
status | Active projects + top actions |
weekly | Show the weekly note |
monthly | Show the monthly note |
commitments | Aggregated deadlines |
questions | List active questions |
search | Full-text search |
review | Guided weekly/monthly review |
project | Manage project maps |
action | Manage next actions |
portfolio | Manage evidence portfolios |
file | File evidence into a portfolio |
question | Manage question notes |
stewardship | Manage stewardships |
track | File a tracking entry |
commit | Manage standalone commitments |
lint | Validate the vault |
reindex | Rebuild the index |
normalise | Reorder frontmatter |
completions | Shell-completion scripts |
cdno init
Create a new vault: the folder tree, a default .cuaderno/config.toml, and a starter daily.md template (every other type uses an in-binary default until you eject one).
cdno init [OPTIONS] [PATH]
Arguments
| Argument | Description |
|---|---|
[PATH] | Target directory. Defaults to the current working directory. |
Options
Only the global options apply. init ignores --json.
Examples
# Create a vault in a new directory:
cdno init ~/notebook
# Initialise the current directory as a vault:
cdno init
It fails if the target already contains a .cuaderno/ directory (it won’t clobber an existing
vault).
See also
- Initialise a vault — what the tree contains and how discovery works.
- Vault structure.
cdno log
Append a log entry to today’s daily note (creating the note if it doesn’t exist yet).
cdno log [OPTIONS] <MESSAGE>
Arguments
| Argument | Description |
|---|---|
<MESSAGE> | The log message. Quote it if it contains spaces. |
Options
| Flag | Description |
|---|---|
--at <TIMESTAMP> | Override the timestamp. Accepts YYYY-MM-DDTHH:MM:SS or YYYY-MM-DDTHH:MM. Defaults to now. |
Plus the global options. With --json, emits a {path, message}
result.
Examples
cdno log "scaled the mesh to 2M cells; 4x runtime, still stable"
# Backdate an entry:
cdno log "forgot to record: fixed the sampler seed" --at 2026-04-24T18:30
# Scripted:
cdno log "nightly run complete" --json
# -> { "path": "journal/2026/daily/2026-04-25.md", "message": "Logged to ..." }
Daily notes are append-only — log only ever adds.
Related MCP tool
append_to_log — the same operation for AI clients.
See also
cdno capture
Drop a quick note into inbox/ with a slug-based filename, to be processed later with
triage.
cdno capture [OPTIONS] <TEXT>
Arguments
| Argument | Description |
|---|---|
<TEXT> | The note text. Quote it if it contains spaces. |
Options
Only the global options. With --json, emits a {path, message}
result.
Examples
cdno capture "does Chen 2025 use the same preconditioner?"
cdno capture "ask IT about the cluster quota" --json
Capture is meant to be frictionless — no fields, no decisions. Classify later during triage.
Related MCP tool
See also
- Inbox and triage.
triage— process what you’ve captured.
cdno triage
Process uncategorised inbox/ captures. For each one, keep it as a project action, discard it, or
skip it.
cdno triage [OPTIONS]
Options
Only the global options. triage ignores --json.
Behaviour
- Interactive — walks each pending capture and prompts you to keep (turn into a project action), discard, or skip.
- Non-interactive (piped or
--no-interactive) — just lists what’s pending, without changing anything.
Examples
# Work through the inbox:
cdno triage
# Just see the backlog (no changes):
cdno triage --no-interactive
Related MCP tools
triage_inbox (lists pending items) and
discard_inbox_item (clears one).
See also
cdno orient
Daily orientation: the commitments due soon, your active projects, a single suggested starting point, and any stewardship habits that have lapsed.
A habit counts as lapsed when its line in a stewardship dashboard’s ## Active Habits section
declares it so — a status starting with “lapsed” after the em-dash, e.g.
- Swimming 1x/week — lapsed since March. The dashboard is the source of truth (updated
during reviews); orientation only surfaces what it says, without judgement.
cdno orient [OPTIONS]
Options
| Flag | Description |
|---|---|
--energy <ENERGY> | Bias the suggested starting point toward this energy level: deep, medium, or light. |
Plus the global options. With --json, emits the orientation as a
structured object.
Examples
cdno orient # neutral suggestion
cdno orient --energy deep # bias toward a heavy-focus action
cdno orient --energy light # bias toward something quick on a low day
cdno orient --json | jq '.suggested_start'
Related MCP tool
See also
- The daily loop.
status— projects only, without the commitments digest.
cdno status
A quick snapshot: your active projects and each one’s top next action. Lighter than
orient — no commitments digest, no suggestion.
cdno status [OPTIONS]
Options
Only the global options. With --json, emits the snapshot as
structured data.
Examples
cdno status
cdno status --json | jq '.[].slug'
Related MCP tool
list_projects — the projects view for AI clients.
See also
orient— the fuller morning view.- Managing projects.
cdno weekly
Show the weekly review/plan note: Wins, Challenges, One Improvement, and This Week’s Goal.
cdno weekly [OPTIONS]
Options
| Flag | Description |
|---|---|
--date <DATE> | Any day in the target ISO week (YYYY-MM-DD). Defaults to this week. |
Plus the global options. weekly ignores --json.
Examples
cdno weekly # this ISO week
cdno weekly --date 2026-04-20 # the week containing 20 Apr 2026
To write the weekly sections rather than just view them, use the guided
review weekly.
Related MCP tools
read_weekly_note and get_weekly_context.
See also
cdno monthly
Show the monthly review note: Wins, Themes, Next Month’s Focus, and the month’s linked weeks.
cdno monthly [OPTIONS]
Options
| Flag | Description |
|---|---|
--date <DATE> | Any day in the target calendar month (YYYY-MM-DD). Defaults to this month. |
Plus the global options. monthly ignores --json.
Examples
cdno monthly # this calendar month
cdno monthly --date 2026-04-20 # the month containing 20 Apr 2026
To write the monthly sections rather than just view them, use the guided
review monthly.
Related MCP tools
read_monthly_note and get_monthly_context.
See also
cdno commitments
List aggregated commitments across the vault — project hard milestones, standalone commitment notes, stewardship periodic commitments, and self-imposed action-note deadlines — sorted by date, with overdue items flagged.
cdno commitments [OPTIONS]
Options
| Flag | Description |
|---|---|
--weeks <WEEKS> | Lookahead window, in weeks. Default 2. A standing 30-day overdue look-back always applies on top. |
Plus the global options. With --json, emits the list as structured
data.
Examples
cdno commitments # next 2 weeks + anything overdue
cdno commitments --weeks 6 # next 6 weeks
cdno commitments --json | jq '.[] | select(.overdue)'
How it’s computed
This is a derived view, not a single file. It merges four sources (see
Business rules):
project milestones marked --hard, stewardship periodic commitments, standalone
commit notes, and action notes with a self-imposed due:.
Related MCP tool
See also
- Commitments and deadlines.
commit— create a standalone commitment.
cdno questions
List active questions grouped by domain (research, life) — the orientation surface against the
question system. For lifecycle changes (park / answer / retire / activate), use
cdno question.
cdno questions [OPTIONS]
Options
Only the global options. With --json, emits the list as structured
data.
Examples
cdno questions
cdno questions --json | jq '.[].slug'
Related MCP tool
get_active_questions — which additionally accepts a domain filter.
See also
- Research and evidence.
question— create questions and transition their status.
cdno search
Full-text search across all notes, ranked best-first, with optional filters by note type, date window, and portfolio.
cdno search [OPTIONS] <QUERY>
Arguments
| Argument | Description |
|---|---|
<QUERY> | Search text. Matched case-insensitively; terms are ANDed. Quotes and operators are treated as literal words. |
Options
| Flag | Description |
|---|---|
--type <TYPE> | Restrict to one note type (e.g. daily, project, evidence, or a config-defined custom type). A name that is neither built-in nor a registered custom type errors with the valid set; tab-completion offers your vault’s types. |
--from <FROM> | Inclusive earliest note date (YYYY-MM-DD). |
--to <TO> | Inclusive latest note date (YYYY-MM-DD). |
--portfolio <PORTFOLIO> | Restrict to notes in this portfolio. |
--limit <LIMIT> | Maximum results. Default 20. |
Plus the global options. With --json, emits an array of hits, each
with path, note_type, title, snippet, and score.
Examples
cdno search "preconditioner"
cdno search "sparse attention" --type evidence
cdno search "mesh" --from 2026-03-01 --to 2026-03-31 --limit 5
cdno search "speedup" --portfolio sparse-vs-dense-attention-ood --json | jq -r '.[].path'
Related MCP tool
See also
- Searching your vault.
reindex— rebuild the index if results look stale.
cdno review
Guided review rituals.
cdno review [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
weekly | Walk the retrospective sections into this week’s note and set next week’s goal. |
monthly | Walk the monthly sections into this month’s note. |
review ignores --json.
cdno review weekly
Walk the retrospective sections (Wins, Challenges, One Improvement) into this week’s note, then set next week’s goal as the This Week’s Goal of next week’s note. When run non-interactively, it reads the current note instead of prompting.
cdno review weekly [OPTIONS]
Options
Only the global options.
Examples
cdno review weekly # interactive: prompts for each section, sets next week's goal
cdno review weekly --no-interactive # read the current weekly note without prompting
cdno review monthly
Walk the monthly sections (Wins, Themes, Next Month’s Focus) into this month’s note. Unlike the weekly ritual there is no cross-note carry-forward — every section lands in the same month’s note. When run non-interactively, it reads the current note instead of prompting.
cdno review monthly [OPTIONS]
Options
Only the global options.
Examples
cdno review monthly # interactive: prompts for each section
cdno review monthly --no-interactive # read the current monthly note without prompting
Related MCP tools
get_weekly_context, read_weekly_note,
upsert_weekly_section, read_monthly_note,
upsert_monthly_section.
See also
- Weekly review.
- Note types.
weekly/monthly— just view the notes.
cdno project
Manage project maps: create, update state, add/complete milestones, manage waiting-on items,
park/activate, and list/show. Next actions have their own verb, cdno action.
cdno project [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create a new project map |
state | Update the Current State (auto-logs the previous) |
park | Move a project to _parked/ |
activate | Bring a parked project back (enforces the cap) |
list | List active projects |
show | Show one project |
milestone | Add / complete milestones |
waiting | Add / resolve waiting-on items |
Write subcommands honour --json (a {path, message} result, run non-interactively); list/show
emit their data under --json.
cdno project create
Create a new project map. Created parked if you’re already at the active cap.
| Flag | Description |
|---|---|
--title <TITLE> | Project title (the slug derives from it). |
--context <CONTEXT> | Life domain: work, side-project, university, family, household, legal, personal. |
--question <QUESTION> | Vault-relative core-question wikilink target (e.g. questions/research/foo). Optional. |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno project create --title "Surrogate model" --context work
cdno project create --title "Thesis" --context university --var ticket=ABC-123
cdno project create --title "Thesis" --context university --question questions/research/surrogate-cost
cdno project state
Update the Current State section. The previous state is auto-logged to today’s daily note first (see Business rules).
| Flag | Description |
|---|---|
--slug <SLUG> | Project slug. |
--text <TEXT> | The new state text. |
cdno project state --slug surrogate-model --text "Mesh scaling works; assembly is the bottleneck"
cdno project park
Move an active project to projects/_parked/, freeing a slot against the five-project cap.
| Flag | Description |
|---|---|
--slug <SLUG> | Project slug. |
cdno project park --slug surrogate-model
cdno project activate
Bring a parked project back. Fails if it would exceed the active cap — park another first.
| Flag | Description |
|---|---|
--slug <SLUG> | Parked project slug. |
cdno project activate --slug surrogate-model
cdno project list
List active projects with a state snippet. Honours --json.
cdno project list
cdno project list --json | jq '.[].slug'
cdno project show
Show a compact summary of a single project (any status). Takes the slug as a positional argument.
Honours --json (emits the project summary object).
cdno project show surrogate-model
cdno project show surrogate-model --json
cdno project milestone
Manage milestones — dated markers of progress. A --hard milestone is a real deadline counted in
cdno commitments.
add — --slug, --title, --date <YYYY-MM-DD>, --hard
done — --slug, --query (case-insensitive substring of the milestone title)
cdno project milestone add --slug surrogate-model --title "Submit to ICML" --date 2026-01-22 --hard
cdno project milestone done --slug surrogate-model --query "submit to icml"
cdno project waiting
Track external blockers.
add — --slug, --description
resolve — --slug, --query (substring of the item)
cdno project waiting add --slug surrogate-model --description "Cluster quota from IT"
cdno project waiting resolve --slug surrogate-model --query "cluster quota"
Related MCP tools
create_project, update_project_state,
park_project,
activate_project, list_projects,
get_project_context, add_milestone,
complete_milestone, add_waiting_on,
resolve_waiting_on.
See also
- Managing projects.
action— the next-action list.
cdno action
Manage a project’s next actions: add (optionally as a manifest note), promote a bullet to a note, complete, and list.
cdno action [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
add | Append a next action to a project |
promote | Promote a plain bullet to a wikilinked manifest note |
complete | Mark an action done by substring match |
list | List a project’s open actions |
Write subcommands honour --json ({path, message}, non-interactive); list emits its data under
--json.
cdno action add
Append a next action to a project. --note also scaffolds a manifest note and wikilinks the bullet.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
--title <TITLE> | Action title. |
--energy <ENERGY> | deep, medium, or light. |
--note | Also create a manifest note alongside the bullet and wikilink it. |
--var <NAME=VALUE> | Value for a custom action-note template’s prompted variable ([variables.prompt]). Repeatable. Only applies with --note (a plain bullet isn’t templated). See Prompted variables. |
cdno action add --project surrogate-model --title "Profile the assembly step" --energy medium
cdno action add --project surrogate-model --title "Characterise sample efficiency" --energy deep --note
cdno action promote
Promote an existing plain bullet to a wikilinked manifest note. Substring-matches the bullet text; energy is inherited.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
--query <QUERY> | Case-insensitive substring of the bullet text. |
--var <NAME=VALUE> | Value for a custom action-note template’s prompted variable ([variables.prompt]). Repeatable. Promotion scaffolds an action note, so it gathers the same prompts as add --note. See Prompted variables. |
cdno action promote --project surrogate-model --query "profile the assembly"
cdno action complete
Mark a next action completed by case-insensitive substring match. A wikilinked bullet also archives
its note to actions/_done/<year>/.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
--query <QUERY> | Substring of the bullet text. |
cdno action complete --project surrogate-model --query "feature set B"
cdno action list
List a project’s open action bullets, with attached-note status (active / blocked / completed) inline
when present. Honours --json.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
cdno action list --project surrogate-model
cdno action list --project surrogate-model --json
Related MCP tools
add_action, promote_action,
complete_action. (Open actions are also visible via
get_project_context.)
See also
cdno portfolio
Manage evidence portfolios: create, list, show, and link to a question or project. Filing evidence
into a portfolio is the separate cdno file verb.
cdno portfolio [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create a portfolio under portfolios/<slug>/ |
list | List portfolios with evidence counts + staleness |
show | Show a portfolio’s frontmatter + evidence inventory |
link | Link an existing portfolio to a question or project |
create/link honour --json ({path, message}); list/show emit their data under --json.
cdno portfolio create
Create a new portfolio. The slug derives from the question.
| Flag | Description |
|---|---|
--question <QUESTION> | The question this dossier accumulates evidence for. |
--project <PROJECT> | Optional wikilink to a project to associate it with. |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno portfolio create --question "Sparse vs dense attention OOD"
cdno portfolio create --question "Sparse vs dense attention OOD" --project projects/surrogate-model
cdno portfolio list
List every portfolio with its evidence count and staleness. Honours --json.
cdno portfolio list
cdno portfolio list --json | jq '.[] | {slug, evidence_count}'
cdno portfolio show
Show a portfolio’s frontmatter and its evidence inventory. Honours --json (a detail object with the
evidence list).
| Flag | Description |
|---|---|
--portfolio <PORTFOLIO> | Portfolio slug. |
cdno portfolio show --portfolio sparse-vs-dense-attention-ood
cdno portfolio show --portfolio sparse-vs-dense-attention-ood --json
cdno portfolio link
Link an existing portfolio to an existing question or project (the retrofit path — pass exactly
one of --question/--project). Backlinks are set on both sides.
| Flag | Description |
|---|---|
--portfolio <PORTFOLIO> | Portfolio slug. |
--question <QUESTION> | Question to link (mutually exclusive with --project). |
--project <PROJECT> | Project wikilink to link (mutually exclusive with --question). |
cdno portfolio link --portfolio sparse-vs-dense-attention-ood --project projects/surrogate-model
cdno portfolio link --portfolio sparse-vs-dense-attention-ood --question questions/research/surrogate-cost
Related MCP tools
create_portfolio, get_portfolio_contents
(show), link_portfolio_to_question,
link_portfolio_to_project.
See also
- Research and evidence.
file— add evidence to a portfolio.
cdno file
File a piece of evidence into a portfolio. Without --attach it writes a plain Markdown evidence
note; with --attach it copies a non-Markdown artefact (PDF, image, video) into the portfolio and
scaffolds a linked evidence stub beside it.
cdno file [OPTIONS]
Options
| Flag | Description |
|---|---|
--portfolio <PORTFOLIO> | Portfolio slug. (Prompted/fuzzy-picked if omitted interactively.) |
--source <SOURCE> | Citation, experiment id, conversation reference, … |
--origin <ORIGIN> | Bare wikilink target to whatever produced this evidence (e.g. projects/foo); the CLI wraps it into [[...]]. |
--content <CONTENT> | Inline body. For a plain note it’s the content; with --attach it’s the abstract. Optional; defaults to empty. |
--attach <ATTACH> | Path to a non-Markdown artefact. Copied into portfolios/<slug>/<evidence-slug>/ with a stub that links to it. |
--move | With --attach, remove the source file after a successful copy (move instead of copy). |
--var <NAME=VALUE> | Value for a custom evidence template’s prompted variable ([variables.prompt]). Repeatable. Ignored with --attach (attachment stubs aren’t templated). See Prompted variables. |
Plus the global options. With --json, emits a {path, message}
result and runs non-interactively.
Examples
# A plain prose evidence note:
cdno file --portfolio sparse-vs-dense-attention-ood \
--source "Chen et al. 2025, NeurIPS" \
--origin projects/surrogate-model \
--content "4x speedup at 95% accuracy on the OOD split."
# Attach a PDF (copied into the portfolio; --content is the abstract):
cdno file --portfolio sparse-vs-dense-attention-ood \
--source "Chen et al. 2025" --origin projects/surrogate-model \
--attach ~/Downloads/chen2025.pdf --content "Key result: 4x speedup."
# Move the artefact in instead of copying:
cdno file --portfolio sparse-vs-dense-attention-ood --source "fig 3" \
--origin projects/surrogate-model --attach ./fig3.png --move
Evidence notes are append-only.
Related MCP tool
See also
- Research and evidence.
portfolio— create and inspect portfolios.
cdno question
Manage question notes: create one, then transition its status. Each status transition is logged to
today’s daily note. To list active questions, use cdno questions.
cdno question [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create a new question note |
park | Set status to parked |
answer | Set status to answered |
retire | Set status to retired |
activate | Set status to active |
All honour --json ({path, message}, non-interactive).
cdno question create
Create a new question under questions/<domain>/<slug>.md. The slug derives from the text.
| Flag | Description |
|---|---|
--domain <DOMAIN> | research or life. |
--text <TEXT> | The question text (becomes the body H1). |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno question create --domain research --text "Does sparse attention beat dense OOD?"
Status transitions
park, answer, retire, and activate each take a --slug. The interactive picker offers only
eligible questions for that transition (e.g. you can only park an active question).
| Flag | Description |
|---|---|
--slug <SLUG> | Question slug. |
cdno question park --slug does-sparse-attention-beat-dense-ood
cdno question answer --slug does-sparse-attention-beat-dense-ood
cdno question retire --slug does-sparse-attention-beat-dense-ood
cdno question activate --slug does-sparse-attention-beat-dense-ood
Related MCP tools
create_question,
set_question_status. (List via
get_active_questions.)
See also
- Research and evidence.
questions— list active questions.
cdno stewardship
Manage stewardship dashboards: create (flat or expanded), list, show, and append a periodic
commitment line. Filing a tracking entry is the separate cdno track verb.
cdno stewardship [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create a stewardship (flat, or expanded with --tracking) |
list | List stewardships with variant, tracking count, staleness |
show | Show a stewardship’s frontmatter + dashboard excerpt |
add-periodic | Append a periodic commitment line |
create/add-periodic honour --json ({path, message}); list/show emit their data under
--json.
cdno stewardship create
Create a stewardship dashboard. --tracking makes it expanded (a stewardships/<slug>/ folder
with room for tracking/ and routines/); without it, the dashboard is a single flat file.
| Flag | Description |
|---|---|
--name <NAME> | Human-readable name (the slug derives from it). |
--context <CONTEXT> | Life domain (work, household, personal, …). |
--tracking | Create the expanded variant with a tracking/ folder. |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno stewardship create --name "Finances" --context household # flat
cdno stewardship create --name "Health" --context personal --tracking # expanded
cdno stewardship list
List every stewardship with its variant, tracking count, and staleness badge. Honours --json.
cdno stewardship list
cdno stewardship list --json | jq '.[] | {slug, variant}'
cdno stewardship show
Show a stewardship’s frontmatter and an excerpt of the dashboard body. Honours --json (a detail
object including variant and body_markdown).
| Flag | Description |
|---|---|
--slug <SLUG> | Stewardship slug. |
cdno stewardship show --slug health
cdno stewardship add-periodic
Append a periodic commitment line to the dashboard’s ## Periodic Commitments section. The line
becomes a row in the aggregated cdno commitments view.
| Flag | Description |
|---|---|
--stewardship <SLUG> | Stewardship slug. |
--title <TITLE> | Commitment title (e.g. “Dental check-up”). |
--every <RECURRENCE> | Recurrence: daily, weekly, monthly, yearly, or every N months. See Recurrence syntax. |
--next <YYYY-MM-DD> | Next due date. |
cdno stewardship add-periodic --stewardship health --title "Dental check-up" \
--every "every 6 months" --next 2026-09-01
Related MCP tools
create_stewardship,
get_stewardship_tracking,
add_periodic_commitment.
See also
- Stewardships and tracking.
track— file a tracking entry.
cdno track
File a tracking note under an expanded stewardship. The activity is positional and selects the
template: a vault’s .cuaderno/templates/tracking-<activity>.md if present, else the generic
built-in. No activity-specific templates ship built-in — ready-made gym/body/swim variants are
in examples/templates/tracking/.
cdno track [OPTIONS] <ACTIVITY>
Arguments
| Argument | Description |
|---|---|
<ACTIVITY> | Activity slug — anything you track (e.g. gym, swim, reading). Selects tracking-<activity>.md if present, else the generic template. |
Options
| Flag | Description |
|---|---|
--stewardship <STEWARDSHIP> | Stewardship slug. Defaults to the only expanded stewardship if there’s exactly one; otherwise required. |
--routine <ROUTINE> | Bare slug of a routine doc — wrapped into [[stewardships/<slug>/routines/<routine>]] and substituted into the template’s routine: field. Only takes effect when the resolved template has a routine: field; the generic default has none, so it silently no-ops there. |
--content <CONTENT> | Inline body. Optional; defaults to empty so you can fill in tables afterward. |
--at <WHEN> | File the entry at a past (or near-future) day rather than today: YYYY-MM-DD, or a full YYYY-MM-DDTHH:MM[:SS] as cdno log --at takes (only the date is kept). For a session recorded after the fact — a statement reconciled days later, a reading taken this morning. Only the date is kept; the entry lands on that day and the daily-log line still goes into today’s note, naming the day it describes. Bounded to 50 years back and 1 year ahead: an unbounded date makes history writable, and a mistyped year would silently reshape a trend. |
--var <NAME=VALUE> | Value for a custom tracking template’s prompted variable ([variables.prompt]). Repeatable. Prompts come from the activity’s template (e.g. tracking-gym) when one exists. See Prompted variables. |
Plus the global options. With --json, emits a {path, message}
result and runs non-interactively.
Examples
cdno track gym --stewardship health --content "Upper body A; RDL up to 25kg"
cdno track body --stewardship health --content "Weight 78.4kg, resting HR 54"
cdno track swim --stewardship health --content "1500m, 28min"
# --routine needs a template with a routine: field (the example gym.md has one):
cdno track gym --routine upper-body-a
# With one expanded stewardship, --stewardship can be omitted:
cdno track gym --content "Legs day"
Tracking entries are append-only and only land in expanded
stewardships (those created with --tracking).
Until a vault has any tracking template, cdno track prints a one-line hint (on stderr) pointing
at examples/templates/tracking/
for a structured layout. It goes quiet once you author a template, and is suppressed under --json.
Merging a day
A second cdno track for the same activity and date merges into the first rather than
erroring: --content is appended to the entry’s ## Notes, and any metrics are folded into its
frontmatter. Recording a day in two passes — a morning and an evening session, spending logged as
it happens — is ordinary rather than exceptional.
Merging is not blind concatenation. A record carrying a stable id replaces the record with
that id, so re-applying the same payload is idempotent; a record without one appends, so
re-running an import that omits ids double-counts every summed metric. Scalars are
last-write-wins: there is no array to key on, and the later reading is what a level means — but
sending a plain value for a key that already holds records is refused, since that would discard
the day’s entries on a note type that only grows.
Related MCP tool
See also
cdno templates
Inspect note templates. Use it before writing a custom template in
.cuaderno/templates/ to see which {{placeholders}} a note type supports —
unknown placeholders render verbatim, so this is how you learn the valid set
without reading the source.
cdno templates vars <type>
List the {{placeholders}} a note type’s template supports.
cdno templates vars [OPTIONS] <TYPE>
Arguments
| Argument | Description |
|---|---|
<TYPE> | Note type: project, action, question, portfolio, evidence, stewardship, tracking, commitment, daily, weekly, inbox, or a config-defined custom type. |
Takes only the global options.
Sources
Each placeholder is classified by where its value comes from:
| Source | Meaning |
|---|---|
supplied | Filled automatically by the note type’s create command. This is the type’s complete create-path key set — including body placeholders and keys the default template happens not to reference (e.g. daily’s weekday, tracking’s routine) — so it matches the per-type table in Customising templates and frontmatter exactly. |
config | A static [variables] entry in .cuaderno/config.toml, available to any template. |
prompt | A [variables.prompt] entry — a value must be provided at creation (via --var name=value, the MCP vars parameter, or interactively). The prompt message is shown. |
A config or prompt name that collides with a supplied key is omitted: the
supplied value shadows it, so it would never take effect.
With --json, emits an array of { name, source } objects (prompt entries
also carry message).
Examples
cdno templates vars project
cdno templates vars tracking
cdno templates vars question --json | jq -r '.[].name'
cdno templates eject <type>
Copy a built-in template into .cuaderno/templates/<type>.md as an editable
starting point. Note types use an in-binary default until you add a file for
them (only daily is seeded on cdno init); this materialises one so you can
customise it (add sections, reorder frontmatter, reference {{placeholders}}
from templates vars) without hand-reconstructing it from the source tree.
cdno templates eject [OPTIONS] <TYPE>
Arguments
| Argument | Description |
|---|---|
<TYPE> | Built-in note type to eject. Omit when using --all. A config-defined custom type has no built-in template to eject (author .cuaderno/templates/<type>.md by hand), so it is refused here — unlike templates vars, which does accept custom types. |
Options
| Flag | Description |
|---|---|
--all | Eject every built-in template into .cuaderno/templates/ at once. Types that already have a template file are skipped (a summary reports which), unless --force. Mutually exclusive with <TYPE> — pass one or the other. |
--force | Overwrite existing custom template(s). Without it, an existing file is left untouched (and single-type eject errors; --all skips it). |
Plus the global options. With --json, single-type
eject emits the { path, message } write result; --all emits an object with
written and skipped arrays (note-type names).
Only base note-type templates eject — no <type>-<variant> template ships
built-in. To create a tracking activity variant, copy one from
examples/templates/tracking/
to .cuaderno/templates/tracking-<activity>.md instead.
Examples
cdno templates eject project # → .cuaderno/templates/project.md
cdno templates eject tracking # → the generic tracking template
cdno templates eject project --force # overwrite an earlier customisation
cdno templates eject --all # eject all built-ins, skip customised
cdno templates eject --all --force # eject all, overwriting everything
The written file is exactly the built-in default, so a note created straight after ejecting is byte-identical to before — customise from there.
Related
- Customising templates and frontmatter — how to write a custom template and use
[variables]/[variables.prompt].
cdno frontmatter
Set typed frontmatter fields on a note through the index. Every other write
surface (cdno log, section upserts) is body-oriented; this is the one that
writes a frontmatter field, so a flag like the daily meds: true can be toggled
without a hand-edit that would desync .cuaderno/index.db.
The field must be declared in .cuaderno/config.toml under
[schemas.<type>.fields.<key>] and marked settable = true — the write is
driven entirely by that spec (see the configuration
reference).
cdno frontmatter set <note> <key> <value>
Set a declared, settable field to a new value.
cdno frontmatter set [OPTIONS] <NOTE> <KEY> <VALUE>
Arguments
| Argument | Description |
|---|---|
<NOTE> | The note to edit: today, a YYYY-MM-DD date (both resolve to the daily note), or a vault-relative note path (e.g. projects/foo.md). |
<KEY> | The frontmatter field to set. Must be declared settable = true under [schemas.<type>.fields.<key>]. |
<VALUE> | The new value, as a string. It is coerced to the field’s declared type (bool/int/float/string/date) and checked against any values allowed-set. |
Takes only the global options. With --json,
emits the { path, message } write result.
Rules
- Declared + settable, default-deny. An undeclared key is rejected; a
declared field without
settable = true(absent orfalse) is rejected. - Type-checked. A value that doesn’t parse as the declared type — or isn’t
one of a
stringfield’svalues— is rejected and nothing is written. - Reserved keys are blocked.
type,status, and a calendar type’s period key (date/week/month) are engine-owned regardless of config — use the lifecycle commands (cdno project park/activate,cdno question set-status, …) for those, so their auto-logging and index invariants are never bypassed. - No-op on no change. Setting a field to the value it already holds writes nothing and logs nothing.
- Optional auto-log. When the field declares
log_on_change = true, a real change stamps akey: old → newline into today’s daily note in the same commit. - Strict-exists (v1). The key must already be present in the note’s
frontmatter (the daily flags exist via the template default). A missing key
errors rather than being appended; ordered-insert is a planned follow-up.
Slug resolution for projects/questions is likewise a follow-up — v1 resolves
today/dates and explicit note paths.
Examples
cdno frontmatter set today meds true # toggle today's daily meds flag
cdno frontmatter set today workout true # if log_on_change, also logs it
cdno frontmatter set 2026-07-09 closed true # a specific day's daily note
cdno frontmatter set projects/surrogate.md phase review
Related
- Configuration reference — declaring
[schemas.<type>.fields], includingsettableandlog_on_change. - Frontmatter fields — the built-in per-type frontmatter shapes.
cdno note
Create and list notes of a config-defined custom type (declared under
[note_types.<name>] in .cuaderno/config.toml). Built-in types have their own verbs
(cdno project create, cdno question create, …); this is the generic surface for custom types.
cdno note create <type>
Create a note of custom type <type>, written to <folder>/<slug(title)>.md.
cdno note create [OPTIONS] <TYPE> --title <TITLE>
Arguments
| Argument | Description |
|---|---|
<TYPE> | A config-defined custom type (e.g. person). A built-in type is refused — use its own create command. |
Options
| Flag | Description |
|---|---|
--title <TITLE> | Required. The note’s title; its slug becomes the filename. |
--field <NAME=VALUE> | A frontmatter field, repeatable. Each key must be a declared required/optional field of the type; every required field must be supplied. |
--var <NAME=VALUE> | A value for the type’s template prompted variable, repeatable. |
Plus the global options. With --json, emits a {path, message}
result. If the type ships no template (.cuaderno/templates/<type>.md), a minimal note is
synthesised from the declared fields plus a # <title> heading.
cdno note list <type>
List every note of custom type <type>, by path.
cdno note list <TYPE>
Examples
cdno note create person --title "Ada Lovelace" --field name=Ada --field role=advisor
cdno note list person
Related
- Custom note types — declaring a type and the full feature.
cdno commit
Manage standalone commitments — dated promises kept as their own notes. To view all commitments
(from every source), use cdno commitments.
cdno commit [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create an active commitment at commitments/<slug>.md |
done | Mark a commitment completed and archive it |
Both honour --json ({path, message}, non-interactive).
cdno commit create
Create an active commitment note. Optionally attribute it to a project or stewardship.
| Flag | Description |
|---|---|
--title <TITLE> | Commitment title (the slug derives from it). |
--due <YYYY-MM-DD> | Deadline. |
--context <CONTEXT> | Life domain (work, personal, …). |
--project <SLUG> | Optional associated project. |
--stewardship <SLUG> | Optional associated stewardship. |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno commit create --title "Pay rent" --due 2026-06-01 --context personal
cdno commit create --title "Review Erik's draft" --due 2026-05-20 --context work --project projects/icml-paper
cdno commit done
Mark a commitment completed: stamps status and completed, and moves the note to
commitments/_done/<year>/<slug>.md.
| Flag | Description |
|---|---|
--slug <SLUG> | Commitment slug. |
cdno commit done --slug pay-rent
Related MCP tools
create_commitment, complete_commitment. (View via
get_commitments.)
See also
- Commitments and deadlines.
commitments— the aggregated view.
cdno lint
Validate every indexed note and report frontmatter problems. Errors fail the command; warnings (such
as broken wikilinks) are non-fatal unless --strict is given.
A wikilink or embed that points at an attachment — a pasted image, a filed PDF — is not a broken
link: attachments are never indexed, but the target is resolved against the filesystem (relative to
the linking note, then to the vault root) before a link is called broken. Only a target that matches
nothing at all is reported, and a missing ![[embed]] reads as a missing file rather than a link
that “resolves to no note”.
cdno lint [OPTIONS]
Options
| Flag | Description |
|---|---|
--strict | Treat warnings as failures too (exit non-zero on any issue). |
Plus the global options. lint ignores --json.
Exit status
- Clean, or warnings only without
--strict→ exit0. - Any error (e.g. unknown note type, invalid frontmatter) → non-zero.
- With
--strict, any warning also → non-zero. Useful in CI to keep a vault pristine.
Examples
cdno lint # report issues; fail only on errors
cdno lint --strict # fail on warnings too (e.g. broken links)
Related MCP tool
lint.
See also
cdno reindex
Rebuild the SQLite index from scratch off the Markdown source of truth. The recovery path for a corrupt or stale index.
cdno reindex [OPTIONS]
Options
Only the global options. reindex ignores --json.
When you need it
Almost never — Cuaderno reconciles the index automatically on every run (see
Business rules). Reach for reindex when:
- search or link results look wrong after a large external edit or a sync conflict, or
- you deleted
.cuaderno/index.dband want to rebuild it eagerly rather than on next use.
Because the Markdown files are authoritative, a full rebuild is always safe.
Examples
cdno reindex
See also
cdno normalise
Reorder note frontmatter into the canonical key order of each note’s template (a custom
.cuaderno/templates/ override if present, else the built-in). Notes cdno creates are already
canonical; this fixes hand-authored or migrated notes.
cdno normalise [OPTIONS]
Options
| Flag | Description |
|---|---|
--check | Report out-of-order notes without rewriting them. Exits non-zero if any are out of order. |
Plus the global options. normalise ignores --json.
Examples
cdno normalise # rewrite notes into canonical frontmatter order
cdno normalise --check # report only; non-zero exit if anything is out of order (CI-friendly)
normalise only reorders existing keys — it never changes values or adds/removes fields. The
canonical order is whatever the matching template
defines.
See also
- Configuration — how templates define field order.
lint.
cdno completions
Print a shell-completion script. Source it in your shell’s rc file. The script wires vault-aware
dynamic suggestions — --project, --portfolio, --stewardship, --slug, etc. are completed by
re-invoking the binary when you press TAB.
cdno completions [OPTIONS] <SHELL>
Arguments
| Argument | Description |
|---|---|
<SHELL> | Target shell. One of bash, zsh, fish, elvish, powershell. |
Options
Only the global options.
Setup per shell
# zsh — in ~/.zshrc:
source <(cdno completions zsh)
# bash — in ~/.bashrc:
source <(cdno completions bash)
# fish — write it into the completions dir:
cdno completions fish > ~/.config/fish/completions/cdno.fish
# elvish / powershell: emit the script and source it per that shell's convention.
cdno completions powershell
After reloading your shell, TAB completes commands, flags, and live vault values (project slugs, portfolio slugs, …).
See also
MCP server reference
cdno-mcp is a Model Context Protocol server that exposes your
vault to AI clients (Claude Desktop, Claude Code, Kiro, Gemini CLI, …). It runs the same domain
engine as the CLI, so anything the assistant does goes through the same rules and lands in the same
Markdown files. To wire it up, see Connect to Claude.
Transport and vault selection
Two binaries serve the same tool catalogue:
cdno-mcpspeaks JSON-RPC over stdio — the client launches it as a subprocess. It opens the vault named byCUADERNO_VAULT_PATH, or, if that’s unset, discovers one from its working directory (the same rule as the CLI).cdno-mcp-serverspeaks MCP Streamable HTTP for remote clients — see The HTTP server for its flags and security model.
The tool surface
The server advertises 46 tools. This reference groups them by purpose:
| Group | Page | What’s in it |
|---|---|---|
| Context-gathering reads | Context-gathering tools | Orientation, project/portfolio/weekly context, search, reads, lint, triage list |
| Writes | Write tools | Log, capture, file evidence, project/action/milestone/waiting edits, commitments, tracking, daily/weekly sections |
| Creation & lifecycle | Creation and lifecycle tools | Create projects/portfolios/questions/stewardships, link portfolios, park/activate, status transitions |
Every tool returns typed JSON; the shapes mirror the CLI’s --json output, so a
client gets the same structures whichever surface it uses.
Conventions
- Slugs, not paths. Tools take slugs (
surrogate-model), matching the CLI. - Substring matching for completing actions/milestones and resolving waiting-on items, exactly as on the CLI.
- The same rules apply. The five-project cap, append-only notes, auto-logged project-state history, and commitments aggregation all hold — the MCP server is not a back door around them.
Building skills on top
Multi-step rituals (a morning orientation, a guided weekly review) are best wrapped as Claude skills that call these tools in sequence. See Using with Claude skills.
The HTTP server
cdno-mcp-server serves the same tool catalogue as the stdio cdno-mcp, over the MCP
Streamable HTTP transport, for clients that reach your vault remotely — most notably Claude’s
custom-connector infrastructure, which connects from Anthropic’s cloud for every surface
(web, desktop, mobile).
cdno-mcp-server --vault ~/vault # listens on 127.0.0.1:8787, endpoint /mcp
Security model — read this first
The binary issues no OAuth of its own, on purpose. Static bearer tokens are not spec-legal
for remote MCP connectors; real deployments terminate OAuth 2.1 at an identity-aware proxy
(for example Cloudflare Access with Managed OAuth in front of a Cloudflare Tunnel). The server’s
own contribution is origin-side validation of the identity assertion the proxy injects
(Cf-Access-Jwt-Assertion): RS256 against the team’s JWKS, strict issuer/audience/expiry, fail
closed — configure it with CDNO_ACCESS_TEAM_URL and CDNO_ACCESS_AUD. If the JWKS cannot be
fetched at startup, the server refuses to start rather than serve unauthenticated.
Without that configuration, cdno-mcp-server refuses to bind anything but loopback.
Be precise about what that guarantees: the process only accepts connections arriving on its own
loopback interface. It cannot detect a tunnel or SSH forward that bridges the port outward —
never bridge this port without the authenticating proxy in front. The server logs a warning
at startup to the same effect whenever it serves real vault data unauthenticated. Configuring
the JWT validation is exactly what lifts the non-loopback restriction (e.g. binding 0.0.0.0
inside a container).
Flags
| Flag | Env | Default | Purpose |
|---|---|---|---|
--vault <path> | CUADERNO_VAULT_PATH | cwd | Vault root |
--bind <addr> | CDNO_MCP_BIND | 127.0.0.1:8787 | Listen address (non-loopback refused until #302) |
--allowed-host <host> | CDNO_MCP_ALLOWED_HOSTS (comma-separated) | — | Extra Host header values to accept on top of the loopback defaults (DNS-rebinding protection). A public deployment adds its hostname |
--smoke | — | off | Serve a single echo tool holding no vault handle — prove tunnel/auth infrastructure end-to-end with zero vault exposure |
--read-only | — | off | Advertise only the context-gathering read tools; mutating tools are absent from the dispatch table entirely |
--reconcile-interval-secs <n> | CDNO_MCP_RECONCILE_INTERVAL_SECS | 300 | Periodic index reconciliation; 0 disables |
--git-checkpoint-interval-secs <n> | CDNO_MCP_GIT_CHECKPOINT_INTERVAL_SECS | 60 | Commit-if-dirty git sweep of the vault — makes every remote write diffable and revertible. 0 disables; warns and no-ops when the vault isn’t a git repo |
--access-team-url <url> | CDNO_ACCESS_TEAM_URL | — | Cloudflare Access team URL (JWT issuer + JWKS host). Requires --access-aud; activates origin JWT validation and lifts the loopback-only restriction |
--access-aud <tag> | CDNO_ACCESS_AUD | — | The Access application’s AUD tag (expected aud claim). Requires --access-team-url |
Index freshness
Unlike a stdio session, this process is long-running while other writers — the CLI, editors, sync
tools — mutate the Markdown underneath it. Markdown is the source of truth and the index is a
cache, so the server re-runs the reconciliation pass on the configured interval as the correctness
backstop. Out-of-band edits become visible to search_notes and the context tools within one
interval at most.
Timezone — set TZ on the host
The server timestamps everything it writes — log lines, daily entries, tracking entries — with the
process’s local time (chrono::Local::now()). A container or host with no zoneinfo database and
no TZ set makes chrono fall back silently to UTC, so remote writes land hours behind the wall
clock with no error. Any deployment must therefore ship a zoneinfo DB (tzdata on Alpine, already
present on most distros) and set TZ, e.g. TZ=Europe/Stockholm.
At startup the server logs the offset it resolved, next to the “vault opened” line:
INFO local time zone resolved (server timestamps use process-local time) local_offset=+02:00 sample_local_now=2026-07-06T14:30:00+02:00
Check this line after deploying: a local_offset=+00:00 you didn’t intend is the tell-tale of a
missing tzdata/TZ. (A host legitimately in UTC is fine — the line is factual, not a warning.)
Transport details
- Endpoint:
POST /mcp. Clients must sendAccept: application/json, text/event-stream(the Streamable HTTP spec requires both). - Stateless JSON mode: every request is self-contained; responses are plain
application/json(no SSE streams, no session ids).GET/DELETEon/mcpreturn405. - Guardrails: request bodies are capped at 1 MiB and in-flight requests are bounded; the
Hostheader is validated against the allowlist (403 otherwise).
Context-gathering tools
Read-only tools an assistant uses to understand your vault before acting. None of these mutate anything. Inputs marked optional may be omitted.
| Tool | Inputs | Returns |
|---|---|---|
get_orientation | energy? (deep|medium|light) | Commitments due soon, active projects, lapsed stewardship habits, and a suggested starting point. The MCP form of cdno orient. |
get_project_context | project (slug) | A project’s state, next actions, milestones, waiting-on items, and links. |
get_portfolio_contents | portfolio (slug) | Portfolio metadata plus its evidence inventory. |
get_weekly_context | date? (any day in the week) | The weekly note’s sections (Wins, Challenges, One Improvement, This Week’s Goal). |
get_monthly_context | date? | Monthly context for a strategic scan. |
get_stewardship_tracking | stewardship, activity, period? (e.g. 30d, 6m) | Tracking entries for a stewardship/activity over a window, plus the activity’s declared contract in spec (record key, group field, and each metric’s type, unit, aggregate, and derived expression when it is computed rather than recorded — write that metric’s operands, never a field of its own name) when the vault declares one under [tracking.<activity>]. spec is null for an undeclared activity. Also returns series: this activity’s numeric trends, one per (group, metric) it declares, each point already reduced by that metric’s own aggregate, scoped and windowed by the same activity and period as the entries. |
get_active_questions | domain? (research|life) | Active question notes, optionally filtered by domain. |
get_commitments | lookahead_weeks? (default 2) | The aggregated commitments view; overdue always included. |
list_projects | — | All projects (active + parked) with summaries. |
list_note_types | — | Every note type — the built-ins plus any config-defined [note_types.*] custom type — with its folder, required/optional fields, typed [schemas.*] field specs, template, and supplied placeholders. Call before create_custom_note to discover a vault’s custom types. |
read_daily_note | date? (default today) | The daily log for a date. |
read_weekly_note | date? (default this week) | The weekly note for an ISO week. |
read_monthly_note | date? (default this month) | The monthly note for a calendar month. |
search_notes | query, note_type?, from?, to?, portfolio?, limit? (default 20) | Ranked full-text hits. The MCP form of cdno search. |
lint | — | Frontmatter problems across the vault. |
triage_inbox | — | Pending inbox captures awaiting triage. |
Notes
- Dates are
YYYY-MM-DD. Week-scoped tools accept any day within the target ISO week; month-scoped tools accept any day within the target calendar month. search_notesreturns the same hit shape as the CLI:path,note_type,title,snippet,score. See JSON output.- These pair naturally with the write tools: read context, propose an action, then write it.
Write tools
Tools that mutate the vault. Each returns a result describing what was written. The same business rules as the CLI apply (append-only notes, auto-logged project state, the project cap).
Logging, capture, triage
| Tool | Inputs | Effect |
|---|---|---|
append_to_log | text | Append a line to today’s daily note. (cdno log) |
capture | text | Drop a raw note into inbox/. (cdno capture) |
discard_inbox_item | slug | Clear a triaged capture (slug from triage_inbox). |
Evidence
| Tool | Inputs | Effect |
|---|---|---|
file_to_portfolio | portfolio, source, origin, content?, attach?, vars? | File evidence into a portfolio; attach is a server-side path to a non-Markdown artefact (vars is ignored on the attach path). (cdno file) |
Projects, actions, milestones, waiting-on
| Tool | Inputs | Effect |
|---|---|---|
update_project_state | project, new_state | Rewrite the Current State (auto-logs the previous). |
add_action | project, title, energy, with_note?, vars? | Append a next action; with_note also scaffolds a manifest note (vars applies only then). |
promote_action | project, query, vars? | Promote a bullet to a manifest note (substring match). |
complete_action | project, query | Complete an action; archives its note if any. |
add_milestone | project, title, target_date, hard? | Add a milestone; hard counts it in commitments. |
complete_milestone | project, query | Complete a milestone (substring match). |
add_waiting_on | project, description | Add a waiting-on blocker. |
resolve_waiting_on | project, query | Resolve a waiting-on item (substring match). |
Commitments and tracking
| Tool | Inputs | Effect |
|---|---|---|
create_commitment | title, due, context, project?, stewardship?, vars? | Create a standalone commitment note. |
complete_commitment | commitment (slug) | Mark a commitment done and archive it. |
create_tracking_entry | stewardship, activity, routine?, content?, vars?, metrics?, date? | File a tracking note under an expanded stewardship. metrics is a JSON object merged into the entry’s frontmatter — a scalar per reading ({"balance": 1240.5}), or an array of flat records when one entry holds several comparable items ({"detail": [{"subject": "harmony", "minutes": 25}]}); a scalar whose key is declared under [schemas.tracking.fields] is type-checked, and a key naming the note’s identity (type, stewardship, activity, date) is refused. date files the entry for a past day (bounded to 50 years back, 1 year ahead). A second call for the same (activity, date) merges into the first: content appended, metrics folded in. Records carrying a stable id replace the record with that id; records without one append, so re-sending a payload without ids double-counts summed metrics. Either way the write is journalled to today’s daily log. |
Frontmatter
| Tool | Inputs | Effect |
|---|---|---|
set_frontmatter | note, key, value | Set a declared, settable = true typed frontmatter field through the index (no desync). note is today, a YYYY-MM-DD date, or a vault-relative path. Engine-owned keys (type, status, a period key) are rejected; the value is type-checked; log_on_change fields stamp a daily-log line. (cdno frontmatter set) |
Daily, weekly, and monthly sections
| Tool | Inputs | Effect |
|---|---|---|
upsert_daily_section | section (Standup|Intention|Agenda|Meeting), content?, date?, append? | Write or append a daily-note section. |
upsert_weekly_section | section (Wins|Challenges|One Improvement|This Week's Goal), content?, date?, append? | Write or append a weekly-note section. |
upsert_monthly_section | section (Wins|Themes|Next Month's Focus), content?, date?, append? | Write or append a monthly-note section. |
Notes
append?defaults to replacing the section; set ittrueto append instead.- Dates are
YYYY-MM-DD; week-scoped tools accept any day in the target week, and month-scoped tools accept any day in the target month. vars?is an optionalname -> valuemap supplying values for a custom template’s[variables.prompt]placeholders — the MCP analogue of the CLI’s repeatable--var name=value. Omitting a required prompted variable fails with an “unresolved prompts” error. See Creation and lifecycle tools for the full list of templated tools that accept it.- See also: Creation and lifecycle tools, JSON output.
Creation and lifecycle tools
Tools that create new notes or move existing ones through their lifecycle.
Creation
| Tool | Inputs | Effect |
|---|---|---|
create_project | title, context, core_question?, vars? | Create a project (parked if at the active cap). (cdno project create) |
create_portfolio | question, project?, vars? | Create an evidence portfolio. |
create_question | domain (research|life), text, vars? | Create a question note. |
create_stewardship | name, context, expanded?, vars? | Create a stewardship; expanded adds a tracking/ folder. |
link_portfolio_to_question | portfolio, question | Retrofit a portfolio→question link (backlinks both ways). |
link_portfolio_to_project | portfolio, project | Retrofit a portfolio→project link (sets project: and appends to the project’s Links). |
Lifecycle
| Tool | Inputs | Effect |
|---|---|---|
park_project | project | Move an active project to _parked/. |
activate_project | project | Bring a parked project back (enforces the five-project cap). |
set_question_status | question, status (active|parked|answered|retired) | Transition a question’s status. |
add_periodic_commitment | stewardship, title, recurrence, next_date | Append a periodic commitment to a stewardship dashboard. |
Notes
contextis one of the fixed life domains.recurrencefollows the recurrence syntax:daily,weekly,monthly,yearly, orevery N months.activate_projectenforces the cap — if activating would exceed five active projects, the call fails and the assistant must park one first.vars?is an optionalname -> valuemap supplying values for a custom template’s[variables.prompt]placeholders — the MCP analogue of the CLI’s repeatable--var name=value. Supply an entry for each prompted variable the note’s template uses that has no static[variables]default; otherwise creation fails with an “unresolved prompts” error (MCP has no interactive prompt to fall back on).- See also: Write tools, Context-gathering tools.
Using with Claude skills
The MCP tools are building blocks. A skill composes them into a repeatable ritual the assistant can run on request — “do my daily orientation,” “walk me through the weekly review.” Cuaderno ships worked examples you can adapt.
Where the examples live
The repo’s examples/skills/
directory contains two reference skills:
quick-capture— capture a thought into the inbox with minimal friction.daily-orientation— read your orientation and help set the day’s intention.
Each is a directory with a SKILL.md (YAML frontmatter + a Markdown body of steps), plus shared
references/. The README there documents the authoring pattern.
The pattern
A skill typically:
- Surfaces context with a read tool — e.g.
get_orientationorget_weekly_context. - Talks it through with you, deciding what to do.
- Writes with the matching tool — e.g.
append_to_log,upsert_daily_section,update_project_state.
For example, a daily orientation skill calls get_orientation, presents the commitments and the
suggested start, asks for your intention, and writes it with
upsert_daily_section(section="Intention", ...).
Graceful degradation
Because each step maps to a discrete tool, a skill can degrade gracefully — if a write tool isn’t available or you decline it, the read half still gives you the briefing. The example skills show how to bind steps to tools and reference shared material.
Build your own
Start from an example, swap in the tools your ritual needs (see reads, writes, creation & lifecycle), and keep each step mapped to one tool so the flow stays inspectable. Then install it like any other Claude skill.
See also
- Connect to Claude — register the server.
- MCP server reference — the full tool surface.
JSON output
Adding --json to a supported verb swaps the formatted table for machine-readable JSON. These shapes
match the MCP server DTOs, so you get the same structures from the CLI and from an
AI client. (Which verbs support --json is covered in the CLI overview.)
Write verbs → a result object
Every write verb emits the same small object and runs non-interactively:
{
"path": "projects/surrogate-model.md",
"message": "Created projects/surrogate-model.md"
}
path is the vault-relative file written or updated; message is the human-readable line. For the
two-file cases (e.g. action add --note), path is the file the verb considers primary.
search --json → an array of hits
Ranked best-first; each hit:
[
{
"path": "portfolios/sparse-vs-dense-attention-ood/2026-03-15-chen-2025.md",
"note_type": "evidence",
"title": "Chen et al. 2025",
"snippet": "...4x speedup at 95% accuracy on the OOD split...",
"score": 1.7
}
]
A lower score ranks earlier (best match first). No matches → [].
list verbs → arrays of summaries
cdno project list --json
[
{
"slug": "surrogate-model",
"status": "active",
"state_snippet": "Mesh scaling works; assembly is the bottleneck",
"top_action": { "text": "Profile the assembly step", "energy": "medium" }
}
]
portfolio list→[{ "slug", "question", "evidence_count", "last_updated", "staleness_days" }]stewardship list→[{ "slug", "name", "context", "variant", "tracking_count" }]action list→[{ "text", "energy", "attached": { "slug", "status" } | null }]
show verbs → a detail object
cdno project show surrogate-model --json # same shape as a project-list element
cdno portfolio show --portfolio sparse-vs-dense-attention-ood --json
{
"slug": "sparse-vs-dense-attention-ood",
"question": "Sparse vs dense attention OOD",
"created": "2026-03-01",
"project": "[[projects/surrogate-model]]",
"evidence": [
{
"path": "portfolios/sparse-vs-dense-attention-ood/2026-03-15-chen-2025.md",
"created": "2026-03-15",
"source": "Chen et al. 2025",
"origin": "[[projects/surrogate-model]]"
}
]
}
projectisnullwhen the portfolio isn’t linked to one.- An evidence entry gains a
"kind"field (e.g."pdf") only for attachment stubs; it’s omitted for plain prose notes. stewardship show→{ "slug", "name", "context", "variant", "body_markdown" }.
Casing
Enumerations serialise in their canonical lowercase/kebab form, matching the MCP DTOs:
status → active/parked/completed, energy → deep/medium/light, stewardship variant →
flat/expanded, context → work/side-project/household/… So a value is identical whether you
read it from cdno --json or over MCP.
Frontmatter fields
Every note begins with a YAML frontmatter block. Cuaderno parses it into a typed structure — if it
parses, it’s valid (see Business rules). This page lists the fields
per note type. ? marks an optional field.
Notes that
cdnocreates are already well-formed. You mainly need this when hand-authoring or migrating notes — andcdno normalisewill reorder keys to the canonical order for you.
daily
type: daily
date: 2026-04-25
tags: [] # auto-populated
weekly
type: weekly
week: 2026-W17
date_start: 2026-04-20
date_end: 2026-04-26
monthly
type: monthly
month: 2026-04
date_start: 2026-04-01
date_end: 2026-04-30
project
type: project
context: work # work | side-project | university | family | household | legal | personal
status: active # active | parked
created: 2026-04-25
core_question?: "[[questions/research/surrogate-cost]]"
action
type: action
status: active # active | completed | blocked
project: surrogate-model
energy: deep # deep | medium | light
milestone?: "[[...]]"
due?: 2026-05-10
criteria?: "Definition of done"
blocker?: "What it's waiting on"
created: 2026-04-25
completed?: 2026-05-01
tags?: []
portfolio
type: portfolio
question: "Sparse vs dense attention OOD"
created: 2026-03-01
project?: "[[projects/surrogate-model]]"
evidence
type: evidence
created: 2026-03-15
source: "Chen et al. 2025"
portfolio: sparse-vs-dense-attention-ood
origin: "[[projects/surrogate-model]]"
kind?: pdf # only for attachment stubs (pdf | image | video | …)
stewardship
type: stewardship
context: personal # one of the life-domain contexts
tracking
type: tracking
stewardship: health
activity: gym
date: 2026-04-06
duration_min?: 60
routine?: "[[stewardships/health/routines/upper-body-a]]"
question
type: question
domain: research # research | life
status: active # active | parked | answered | retired
created: 2026-04-25
updated?: 2026-05-01
commitment
type: commitment
due: 2026-06-01
context: personal
project?: icml-paper
stewardship?: finances
Extending schemas
You can require additional fields per type via config.toml ([schemas.<type>] extra_required) —
see Configuration reference. Required built-in fields are always enforced on top.
Configuration reference
The vault’s settings live in .cuaderno/config.toml, written by cdno init. Every key is optional —
defaults are applied when omitted. For the conceptual tour, see
Configuration.
Full example
[vault]
name = "My Research Vault"
max_active_projects = 5 # the active-project cap
# Glob patterns excluded from the index (search, lint, link checks).
# Additive only — no "!" negation. Matched against vault-relative paths.
# NEVER deletes files; only scopes what the index considers.
ignore = ["CLAUDE.md", "README.md"]
# Per-type extra required frontmatter fields. Built-in required fields
# are always enforced; these add vault-specific requirements (cdno lint).
[schemas.project]
extra_required = ["collaborators"]
[schemas.evidence]
extra_required = []
# Typed frontmatter fields for a built-in type. Recognised by the desktop
# Templates editor and type-checked by `cdno lint`.
[schemas.daily.fields.meds]
type = "bool" # bool | int | float | string | date
default = false # static, type-checked against `type`
[schemas.daily.fields.mood]
type = "string"
values = ["low", "ok", "good"] # allowed values (a string constraint)
default = "ok"
# How an activity's tracked numbers are read back. Each metric declares how
# it collapses to one point per date; without a declaration the activity's
# series come from its body table, with each column summed.
[tracking.practice]
records = "detail" # frontmatter key holding repeated records
group_by = "subject" # one series per distinct value
[tracking.practice.metrics.minutes]
aggregate = "sum" # a TOTAL
unit = "min"
[tracking.practice.metrics.focus]
aggregate = "mean" # a RATING; sum would grow with how often you log
# Static template variables — resolve in any custom template ({{author}}).
[variables]
author = "A. Researcher"
# Prompted variables — gathered at note creation (--var, prompt, or error).
[variables.prompt]
collaborators = "Who are the collaborators?"
Keys
| Key | Type | Default | Purpose |
|---|---|---|---|
vault.name | string | "My Vault" | A human label for the vault. |
vault.max_active_projects | integer | 5 | The active-project cap. |
ignore | list of globs | [] | Files the index skips. Additive; never deletes. See Ignore globs. |
schemas.<type>.extra_required | list of strings | [] | Extra required frontmatter fields for that built-in note type, enforced by cdno lint. |
schemas.<type>.fields.<name> | table | — | A typed frontmatter field for a built-in note type (type, default, required, values, settable, log_on_change). Recognised by the Templates editor, type-checked by cdno lint, and (when settable) writable via cdno frontmatter set. See Typed schema fields. |
tracking.<activity> | table | — | Declares how an activity’s tracked numbers are read back (records, group_by, and a metrics.<name> table per metric). Without one, the activity’s series come from its body table with each column summed. See Tracking. |
note_types.<name> | table | — | Declares a config-defined custom note type (folder, required/optional fields, template, …) — a schema-only type for entities the built-ins don’t cover. See Custom note types. |
variables.<name> | string | — | Static template variable; resolves in any custom template (per-type values win on name clash). |
variables.prompt.<name> | string | — | Prompted template variable; the value is the prompt text. Gathered at creation from --var name=value, an interactive prompt, or a static [variables] default; errors if none supplies it. |
Ignore globs
ignore lists files that live in the vault directory but are not notes — repo scaffolding like
CLAUDE.md or README.md. They are excluded from the index, and therefore from search, lint and
backlinks as well. The files are never touched on disk.
Patterns are matched against each file’s vault-relative path:
| Pattern | Matches |
|---|---|
CLAUDE.md | that file at the vault root |
**/*.draft.md | a .draft.md at any depth |
folder/*/** | everything one or more levels below folder/<anything>/ |
folder/*/*/** | everything two or more levels below folder/<anything>/ |
The last two are the trap worth knowing. * stays inside one path segment but ** is recursive, so
portfolios/*/** does not mean “the level below a portfolio” — it matches the portfolio’s own notes
as well as anything nested under them. A glob written that way excludes every note in the folder it
was meant to tidy, and because an unindexed note is also unsearchable and unlinkable, the symptom
looks like a broken view rather than a misconfigured vault.
Two things guard against that:
cdno reindexprints how many files the globs excluded.- The desktop app shows a dismissible notice when the count looks disproportionate — a lone
CLAUDE.mdstays silent, a glob swallowing a large share of the vault does not.
If notes go missing, clear the pattern and run cdno reindex: every row comes back.
Note that attachment artefacts filed into a portfolio need no ignore entry — they are excluded
automatically, by location. See vault structure.
Typed schema fields
[schemas.<type>.fields.<name>] declares a typed frontmatter field on a built-in note type. It
is the richer sibling of extra_required: instead of just a name, each field carries a type (and
optionally a default and an allowed-value set). Four things consume it today:
- the desktop Templates editor recognises the field, so a custom template referencing
{{<name>}}no longer warns “renders literally”; - note creation populates the field’s
defaultat create — a custom template referencing{{<name>}}renders that default (a field with no default rendersnull), so the value lands in the new note’s frontmatter instead of a literal{{<name>}}; cdno linttype-checks the field — a note whose value doesn’t match the declared type (or isn’t one ofvalues) gets a warning;- the
set_frontmattersetter (cdno frontmatter set, MCPset_frontmatter) writes the field through the index when it is markedsettable = true— see thecdno frontmatterreference.
[schemas.daily.fields.meds]
type = "bool" # bool | int | float | string | date
default = false # optional; static, type-checked against `type`
settable = true # optional; allow `set_frontmatter` to write it (default false)
log_on_change = true # optional; stamp a daily-log line when it changes
[schemas.daily.fields.mood]
type = "string"
values = ["low", "ok", "good"] # optional; allowed values (only valid on a string)
default = "ok"
required = false # optional; default false
| Field key | Type | Default | Purpose |
|---|---|---|---|
type | "bool" | "int" | "float" | "string" | "date" | (required) | The field’s scalar type. An unknown value is a hard load error. |
default | matching type | — | A static default value, type-checked at load. Populated at create when a custom template references {{<name>}}. A date is a quoted "YYYY-MM-DD". |
required | bool | false | Reserved for create-time enforcement (a later release); parsed now, but inert — it does not yet block creation. |
values | list of strings | — | An allowed-value constraint. Valid only on a string field. |
settable | bool | false | Whether set_frontmatter (cdno frontmatter set, MCP set_frontmatter) may write this field. Default-deny: absent or false means not settable. Never overrides an engine-owned key (type, status, a period key) — those stay blocked regardless. |
log_on_change | bool | false | When a settable field’s value actually changes, stamp a key: old → new line into today’s daily note in the same commit. |
Notes and limits:
- Defaults are static — there is no
"today"token; adatedefault is a literal calendar date. - A field only lands in frontmatter if a custom template references it — rendering substitutes
the
{{<name>}}tokens a template contains; it never adds a frontmatter line. The shipped built-in templates can’t reference vault-specific fields, so populate a declared field by adding a custom.cuaderno/templates/<type>.mdthat references{{<name>}}. - A create-path value wins over a declared default — if the note type’s create path already
supplies a value for that name (an engine-supplied placeholder), that value takes precedence and
the declared default does not apply. Likewise a
[variables]static var of the same name wins over a schema default. - A
[variables.prompt]name is owned by the prompt — if a field name is also a prompted variable, its value is collected via the prompt (from--var, an interactive prompt, or a static default), and the schema default is not used. This ensures a supplied answer is never discarded. - No
enumtype — model a closed set as astringwithvalues. intandfloatare distinct —intrejects anything with a fractional part, so a currency amount, a measurement or a rate wantsfloat. Afloatfield accepts a whole number too (82and82.5both validate), because a round reading is written without a decimal point. A non-finite default (nan,inf) is a load error — those values have no meaning as data.- List fields are reserved but not yet implemented — a
list = trueis a load error today. - Engine-owned keys are protected — you can’t declare a field named
type, or a calendar type’s own period key (daily→date,weekly→week,monthly→month); the vault refuses to open.set_frontmatteradditionally refuses to writestatusfor every type — even if a vault declares astatusfieldsettable = true— so the lifecycle commands stay its sole writers. extra_requiredstill works and is equivalent to an untyped, non-requiredstringfield; on a name clash an explicitfieldsblock wins.- A malformed field declaration (unknown
type, a mistyped key,valueson a non-string, adefaultthat doesn’t type-check) fails at vault-open, like every other config error.
Tracking
[tracking.<activity>] declares how an activity’s numbers are read back. Without one, a tracking
note’s series come from the first table in its body, with each column summed — right for a rep
sheet, wrong for a balance or a rating. Declaring an activity moves it to frontmatter, where each
metric says how it collapses.
[tracking.practice]
records = "detail" # frontmatter key holding repeated records
group_by = "subject" # one series per distinct value of this field
[tracking.practice.metrics.minutes]
type = "int"
aggregate = "sum" # a TOTAL
unit = "min"
plot = "column"
[tracking.practice.metrics.focus]
aggregate = "mean" # a RATING - a sum would grow with how often you log
| Key | Where | Purpose |
|---|---|---|
records | activity | Frontmatter key holding a sequence of flat records. Omit for plain scalars read straight off the entry. |
group_by | activity | Record field the series split on — a category, a subject, a person. One series per distinct value. |
type | metric | bool | int | float | string | date. Optional. |
aggregate | metric | sum | mean | last | max | min. Defaults to sum. |
group_by | metric | Overrides the activity’s. "none" collapses across records for an entry-level series. |
derived | metric | An expression computing this metric from sibling fields, e.g. "km * rate_per_km". Evaluated per record, before aggregation. Declare type on it and the vault refuses to open. |
unit | metric | Display unit (min, kg, EUR). Carried through to the chart and the MCP series. |
label | metric | Display name for the series, when the metric’s key is not what you want on a chart (resting_hr → Resting heart rate). |
plot | metric | none | line | column | area | scatter. Defaults to none. Chooses the mark the chart draws, and whether the desktop draws it at all — see the note below. |
Derived metrics
Some tracked quantities are products of others — a cost from a rate and a distance, a load from a weight and a count. Rather than making whoever writes the entry pre-compute them, declare the expression:
[tracking.commute.metrics.km]
aggregate = "sum"
[tracking.commute.metrics.rate_per_km]
aggregate = "last"
[tracking.commute.metrics.cost]
derived = "km * rate_per_km"
aggregate = "sum"
unit = "EUR"
It is evaluated per record, then aggregated — not derived from the aggregates. With two trips
of 10 km at 0.50 and 20 km at 0.25, cost is 10.00; deriving from the totals would give a
different, wrong number the moment the rate varies.
The grammar is deliberately tiny — one binary operation, nothing else:
expr := operand OP operand
operand := field-name | number
OP := '+' | '-' | '*'
- No
/. Division is the one operator that manufactures NaN and infinity, and those must never reach an aggregate. Multiply by the reciprocal, or pre-compute the ratio. - No parentheses, calls, chaining or recursion, and no deriving from another derived metric.
- Field names are letters, digits and
_, not starting with a digit. Hyphens are excluded becausea-bwould be indistinguishable from a subtraction. - Numbers are plain decimal — digits, an optional leading
-, an optional.. Exponent notation (1e-3) is not supported; write the value out in full. - Every operand must name a metric the same activity declares. A typo is a vault-open error
naming the field, rather than a silently empty chart. The cost of that requirement: an operand
that exists only to be multiplied — a rate, say — still becomes a metric of its own. Leave its
plotundeclared (the default) and the desktop will not chart it alongside the result — see below. typemust be omitted — the output is numeric by construction, so declaring one can only contradict it.- A record missing an operand contributes nothing — a gap, on the same rule as a plain metric. So does a result that is not finite.
Choosing the aggregate is the whole point, and it follows from what the number is:
| Kind | Examples | Aggregate |
|---|---|---|
| Total | amount spent, pages read, minutes practised | sum |
| Level | account balance, a measurement, a top set | last, max |
| Rate or rating | a score out of ten, perceived difficulty | mean |
Notes and limits:
- An absent
[tracking]section is not an error — an undeclared activity keeps being served from its body table, so nothing forces a migration. - Declaring is checked at vault-open, not at first chart render: an unknown
aggregate, a mistyped key, or a blankrecords/group_byfails when the vault is opened, naming the key. - An activity may declare no metrics at all. That is a complete use — recording that something happened, with nothing to aggregate.
windowis reserved for a future time-reduction axis (month-over-month deltas, rollups) and is a load error today, so adding the behaviour later is not a breaking change.- A declared activity’s body table is no longer read once its frontmatter yields a series, so
the same metric can never appear twice under two disagreeing numbers. This rule is unconditional
— it keys on the frontmatter derivation’s full produced set, not on what any individual metric’s
plotsays, so a declared-but-unplotted metric still suppresses its body-table equivalent. plotchooses the mark, and gates whether the desktop draws it. A declaredline/columnis used as the chart’s mark (anareaorscatterresolves to the closest of the two the chart draws).plot = "none"— the default for a declared metric that names no mark — is still emitted and still queryable over MCP, but the desktop leaves it out of the chart pane. Declaring an activity is an explicit act, and is allowed to change what is drawn: its frontmatter series replace its body-table ones (the rule above, unaffected by this), and only the metrics that opt into a mark are charted.
Templates
Templates live in .cuaderno/templates/ and are pure variable substitution. cdno init writes one
starter (daily.md); other types use their built-in default until you add a file. cdno selects the
most specific template that exists: a custom variant (e.g. tracking-gym.md), then a custom type
(e.g. project.md), then the built-in variant default, then the built-in type default. Template
field order is the canonical order
cdno normalise enforces.
The per-type placeholders that resolve at creation, with a worked example, are in Customising templates and frontmatter. An unknown placeholder is left verbatim in the note.
Static [variables] resolve in any custom template (e.g. {{author}}). Prompted
[variables.prompt] are gathered at creation (via --var name=value, an interactive prompt, or a
static default) — see the
tutorial.
Editing from the desktop app
You can edit .cuaderno/config.toml directly from the desktop app’s Config view, without
hand-editing the file. It offers a Raw text editor and a structured Form for note types and
schema extensions; Check dry-runs the same validation the app runs when it opens a vault.
Saving is gated so an edit — from either view — can never leave the vault unopenable:
- The whole candidate is validated first — the exact check the app runs at open (TOML parse,
ignoreglobs, and the[note_types.*]/[schemas.*]rules). If it would not reopen, the save is refused and the file is left untouched. - A content-hash compare-and-swap then guards against a concurrent hand-edit: if the file changed on disk since the editor read it, the save is refused with a “changed on disk — reload” notice rather than overwriting the newer version.
- The vault is then reloaded live, so the edit applies with no restart. A Raw save writes the
buffer verbatim; a Form save applies a surgical edit to just the table it changed — either
way comments, key order, and the
[variables]block are preserved.
The full walkthrough of the Config view is in Editing the config in the app.
See also
- Customising templates and frontmatter — the tutorial.
- Configuration — the conceptual overview.
- Frontmatter fields — what
extra_requiredextends. - Custom note types — the
[note_types.*]table in full.
Custom note types
Cuaderno ships twelve built-in note types (see Note types). If you need
an entity the built-ins don’t cover — people, books, clients, recipes — you can declare your own
custom note type under [note_types.<name>] in .cuaderno/config.toml. No recompile, no plugin.
What a custom type is (and is not)
A custom type is schema-only. It gives you:
- a folder its notes live in,
- enforced
required/optionalfrontmatter fields (checked bycdno lint), - an optional template,
- canonical frontmatter ordering (
cdno normalise), - full participation in indexing, full-text search, backlinks, and
cdno note.
It does not get bespoke behaviour. The 5-project cap, project state history, commitment
aggregation, tracking streams, and action lifecycle belong to specific built-in types and are not
available to custom types. A custom type is therefore invisible to cdno orient, the project cap,
and the commitments view — by design. If you need that behaviour, use (or extend) a built-in type.
Declaring a type
[note_types.person]
folder = "people" # required — vault-relative, must not be a built-in folder
required = ["name"] # fields that must be present and non-null (lint errors otherwise)
optional = ["role", "org"] # fields that may be present; part of the canonical order
template = "person.md" # optional; defaults to "<name>.md" under .cuaderno/templates/
append_only = false # optional; accepted now, lint enforcement is a later addition
title_field = "name" # optional; which frontmatter field holds the display title (default: the H1)
date_field = "met_on" # optional; which field carries the note's date (for date-filtered search)
Validation runs at vault-open, so a malformed declaration fails fast. Cuaderno rejects a type whose:
folderis empty, has surrounding whitespace, escapes the vault (.., absolute,\), or collides with a built-in folder (projects,journal, …) or another custom type’s folder;templateis not a bare filename;title_field/date_fieldnames a field that isn’t inrequired/optional;- name shadows a built-in type (
project,daily, … — case-insensitive). Built-in names are reserved so a straytype:typo can’t silently mint a type.
Creating notes
cdno note create person --title "Ada Lovelace" --field name=Ada --field role=advisor
cdno note list person
--field name=value is repeatable; each key must be a declared required/optional field, and
every required field must be supplied. --var name=value supplies a template’s
prompted variables.
The note is written to <folder>/<slug(title)>.md. If the type has a template
(.cuaderno/templates/person.md), it is rendered; otherwise Cuaderno synthesises a minimal note
— a frontmatter block of your fields plus a # <title> heading — so a type works before you author
its template. (Field values are always emitted as strings, so a value with a colon, #, or newline
round-trips safely; author a template if you need richer frontmatter shapes.)
From an MCP client, the equivalent tool is create_custom_note ({ type_name, title, fields, vars }).
Discovering placeholders and searching
cdno templates vars personlists the{{placeholders}}apersontemplate may reference — its create-path built-ins (title,slug,created,date) plus your declared fields.cdno templates eject persondoes not apply — a custom type has no built-in template to materialise; author.cuaderno/templates/person.mdby hand.cdno search <query> --type personfilters results to that type.--typeaccepts any built-in or custom name; a name that is neither errors with the valid set. Shell completion offers your vault’s types.
Relationship to [schemas.*]
[note_types.*] defines a new type; [schemas.<builtin>] extends a built-in with
extra_required fields. They are separate tables with separate purposes — a name under
[note_types] may not be a built-in, and [schemas.<custom>] has no effect (a custom type’s
required fields come from its own required list).
A worked example
Tracking people walks a person type end to end — declaring it,
creating people, and linking them from your notes to answer “what was my last interaction with X?”.
Recurrence syntax
Periodic commitments on a stewardship (--every on
cdno stewardship add-periodic, or recurrence on the MCP
add_periodic_commitment tool) use a small canonical vocabulary.
| Value | Meaning |
|---|---|
daily | Every day |
weekly | Every week |
monthly | Every month |
every N months | Every N months (e.g. every 3 months for quarterly) |
yearly | Every year |
Quote any value containing a space on the command line.
Examples
cdno stewardship add-periodic --stewardship health --title "Dental check-up" --every "every 6 months" --next 2026-09-01
cdno stewardship add-periodic --stewardship finances --title "File quarterly taxes" --every "every 3 months" --next 2026-07-15
cdno stewardship add-periodic --stewardship health --title "Annual physical" --every yearly --next 2026-11-01
Each line becomes a row in the dashboard’s ## Periodic Commitments section and feeds the aggregated
cdno commitments view, advancing to its next occurrence as dates pass.
See also
Troubleshooting
Common situations and how to resolve them.
“not inside a Cuaderno vault”
A command can’t find a vault. Cuaderno discovers one by walking up from the current directory for a
.cuaderno/ folder, then falls back to CUADERNO_VAULT_PATH. Fix one of:
cd ~/notebook # run from inside the vault, or
cdno --vault ~/notebook <command> # point at it explicitly, or
export CUADERNO_VAULT_PATH=~/notebook
See Initialise a vault.
Search or links look out of date
The index is a cache; it’s reconciled automatically each run, but a large external edit or a sync conflict can occasionally confuse it. Rebuild it:
cdno reindex
The Markdown files are always authoritative, so a rebuild is safe. See Business rules.
lint is failing
Run it to see the specifics:
cdno lint # errors fail; warnings are listed but non-fatal
cdno lint --strict # warnings fail too
Errors are usually an unknown type: or invalid frontmatter; warnings are typically broken
wikilinks. Fix the reported file, or run cdno normalise if the issue is field
ordering. See Frontmatter fields.
A prompt appears when I wanted automation (or vice versa)
Write commands prompt for missing required flags only in an interactive terminal. In scripts, pipes, or CI they error instead. Force non-interactive behaviour explicitly:
cdno project create --title "X" --context work --no-interactive
Conversely, if a command errors about a missing flag when you expected a prompt, your stdout probably isn’t a TTY (it’s piped or redirected). See CLI overview.
Can’t create a sixth project
That’s the five-project cap. Park an active project first:
cdno project park --slug some-active-project
cdno project activate --slug the-one-you-want
(New projects created while at the cap are created parked rather than rejected.)
--json output won’t parse
--json is only honoured by read verbs and the write verbs that emit a result; maintenance and
interactive commands (init, lint, reindex, normalise, triage, review, weekly) ignore
it. Under --json, write verbs run non-interactively, so there are no prompts mixed into the output.
See JSON output.
Claude doesn’t see the tools
For the MCP server (cdno-mcp):
- Make sure
cdno-mcpis on the client’sPATH, or use an absolute path in the configcommand. - Set
CUADERNO_VAULT_PATH(or rely on working-directory discovery). - Restart the client after editing its MCP config.
See Connect to Claude.