In Part 1 I described the Research Logbook Method — six pillars distilled from how Faraday, Darwin, Hamming, Knuth and Tao actually worked. The method works with a text editor and three folders. So, naturally, I am building a tool.
Why build anything
Faraday did it with a quill. But the method has just enough structure that a small opinionated tool drops daily friction to zero. And I wanted an MCP server so Claude can read and write into the vault without copy-pasting like it is 2023. That part now works.
Also: I already built something like this once. mdvault was a markdown vault manager — CLI, MCP server, Claude skills. It worked. I used it daily. But it was built around generic PKM assumptions (projects, tasks, areas), not the logbook method. Cuaderno is mdvault's successor, bent entirely around the six pillars.
What it is
Cuaderno. Spanish for notebook, because that's my mother tongue and I am out of clever names.
- Rust CLI (
cdno) — capture, log, projects, portfolios, questions, stewardships, commitments, and the queries that tie them together. Static binary, no runtime. - MCP server (
cdno-mcp) — Claude as guest in the vault, not host. Local, under your control. 47 tools, reading and writing the same files the CLI does. A second binary serves the same catalogue over HTTP for remote clients. - Plain markdown on disk. Always. A SQLite index rides along as a throwaway cache, but the files are the source of truth. If Cuaderno disappears tomorrow you still have a folder of dated
.mdfiles. Hill I will die on.
What it is not
Not a second brain. Not a graph database. Not an "AI-first" anything. Not a replacement for your editor.
Architecture
A Rust workspace, one crate per job:
| Crate | Role |
|---|---|
cdno-core | File I/O, markdown parsing, frontmatter, templates, the SQLite index |
cdno-domain | Business logic — note types, portfolios, commitments, reviews |
cdno-cli | CLI binary (cdno) |
cdno-mcp | MCP servers — cdno-mcp over stdio, cdno-mcp-server over HTTP |
Domain layer owns all business rules. Core is a pure toolkit with no opinions. CLI and MCP are thin shells that call into domain.
If you have followed the monorepo descent before, this shape will look familiar. I have learned a few things since then. Probably.
The daily loop
Enough architecture. Here is a normal day — not aspirational, this is the actual command surface.
Morning, I ask what deserves my attention:
cdno orient --energy deepCommitments due soon, my active projects with their state and the single next action each, and a suggested place to start given the energy I claimed to have. One screen. No inbox-zero theatre.
Through the day I log what happened and park what distracts me:
cdno log "batched the index writes; rebuild is 4x faster, still correct"
cdno capture "should the HTTP server support an SSE fallback?"log appends to today's journal. capture throws a thought into the inbox to triage later, so I do not derail chasing it now.
Working a project looks like this:
cdno action add \
--project cuaderno \
--title "Profile the index rebuild" \
--energy medium
cdno project state \
--slug cuaderno \
--text "Batched writes landed; the rebuild is the bottleneck"
cdno action complete \
--project cuaderno \
--query "batch the writes"Each project carries a current state, a short stack of next actions tagged by energy, and milestones. When I update the state, the old one is logged to today's note before it is overwritten — the history is never lost.
Results I care about get filed into a dossier, not dumped in a folder:
cdno file --portfolio index-performance \
--source "rebuild benchmark, 10k notes" \
--origin projects/cuadernoAnd the day closes where it started — a line in the log, a habit ticked:
cdno track reading \
--stewardship learning \
--content "Two chapters of the SQLite book"
cdno log "good focus day; pick up the rebuild profiling tomorrow"Every command speaks --json too:
cdno project list --json | jq '.[].slug'Because the moment a tool refuses to be scripted, all the fun is gone.
What that actually writes
Every one of those commands is just editing markdown. Here is projects/cuaderno.md after the ones above — Cuaderno is tracked in its own vault, which is the only reason I am comfortable showing you a real one:
---
type: project
context: side-project
status: active
created: 2026-06-07
core_question: can the vault stay fast at 10k notes without giving up plain files?
---
# Cuaderno
## Current State
Batched writes landed; the rebuild is the bottleneck.
## Next Actions
- [ ] Profile the index rebuild (medium)
## Waiting On
(nothing yet)
## Milestones
- [ ] HTTP server documented — target: 2026-08-01
## Links
- Portfolio: [[portfolios/index-performance/_index]]That is the whole storage format. No database rows, no proprietary container, no export button. grep works. git log works. Your editor works — I write into these files by hand as often as through the CLI, and nothing minds. The SQLite index is derived; delete it and cdno reindex rebuilds it from the files. Every invocation — CLI, MCP session, app launch — reconciles the index against the filesystem by mtime and content hash before doing anything else, so editing notes behind Cuaderno's back is a supported workflow rather than a way to corrupt something.
When tomorrow's cdno project state overwrites that Current State paragraph, the old one lands in today's journal entry first. Nothing is quietly replaced.
Cheap by default
Two places where the small version is the default and the heavy version has to earn its existence. This is the design call I am most attached to, because the way note-taking systems die is that capturing a thing ends up costing more than the thing was worth.
Actions are one line until they are not
An action is normally a single bullet. cdno action add appends a checkbox to the project's next-actions list, tagged with energy, and that is the entire object — no note, no folder, no frontmatter:
cdno action add \
--project cuaderno \
--title "Profile the index rebuild" \
--energy mediumMost next actions live and die as that bullet, which is the point.
When one turns out to be an investigation rather than a step — spans days, produces evidence, needs success criteria — promote it:
cdno action promote --project cuaderno --query "profile the index"The bullet is rewritten as a wikilink and an action note is scaffolded behind it, inheriting the energy tag. The query is a case-insensitive substring, so you address the action by roughly what you remember writing rather than by a slug you would have to look up. If you already know it is going to be big, cdno action add --note starts it promoted.
Completion works the same from either side. cdno action complete ticks the bullet and logs it to today's journal, and if there was a note behind it, that note is archived to actions/_done/<year>/ and joins the append-only set. Finished work stops being something Cuaderno will rewrite.
Evidence does not have to be markdown
A portfolio accumulates evidence about one question, and evidence is usually a prose note. But the artefact that actually matters is often a PDF, a figure, a video. So --attach takes the real file:
cdno file --portfolio index-performance \
--source "flamegraph, 10k-note rebuild" \
--origin projects/cuaderno \
--attach ~/Downloads/rebuild-profile.svg \
--content "Most of the time is frontmatter parsing, not I/O."Cuaderno copies the artefact into the portfolio and scaffolds a markdown stub beside it that links to it — --content becomes the abstract, and --move moves rather than copies if you want it out of Downloads for good.
The stub is the thing that participates in the system: it gets indexed, searched, linted, and linked, and it is append-only like any other evidence note. The binary just sits next to it. Which is how the plain-text promise survives contact with a folder full of PDFs — nothing is swallowed into a database, and if Cuaderno vanishes you are left with your files plus a markdown index that explains what each one was for.
Although in practice I rarely type that command. Filing a paper properly means having read it first — and whatever read it already knows the citation, where it came from, and what the abstract ought to say. That is a thread I pick up once Claude is in the room, further down.
The rules it enforces
Opinions, enforced, so I do not have to supply the discipline myself.
- Five active projects. Create a sixth and it lands parked; try to activate it and the command stops you until you park something else. The number is configurable, which rather misses the point.
- Append-only where it counts. Daily, weekly and monthly notes, evidence, tracking — they grow, never rewrite. The log is a record, not a draft. This governs what Cuaderno does on your behalf, not what your editor can do: nothing stops you opening the file in vim and doing as you please. The guarantee is that the tool will not.
- Commitments are computed, not curated. Cuaderno aggregates every dated promise — project milestones, recurring stewardship items, standalone commitments, action due dates — into one register. I never maintain that list by hand. It looks two weeks ahead and thirty days back, so a deadline you blew through does not quietly scroll out of view.
- Contexts and energy are closed vocabularies. Seven contexts, three energy levels, compiled in and not configurable. Every tool in this space sells you on inventing your own taxonomy; the taxonomy is not the hard part, and a small shared one you cannot fiddle with is worth more than a perfect one you rebuild every six months.
- The weekly review hands forward.
cdno review weeklywrites wins, challenges and one improvement into this week's note, then puts next week's goal into next week's note — a file for a week that has not started. Monday arrives with its anchor already in place. - Twelve note types, and that is the whole vocabulary.
daily,weekly,monthly,project,action,portfolio,evidence,stewardship,tracking,question,commitment, and the inbox. No freeform sprawl.
The escape hatch
That last rule is a closed enum on purpose, but not a locked door. When you need an entity the built-ins do not cover — people, books, clients — you declare it in .cuaderno/config.toml:
[note_types.person]
folder = "people"
required = ["name"]
optional = ["role", "org"]And that is the entire feature. A custom type gets a folder, lint-enforced frontmatter, a template, indexing, search and backlinks. It gets no bespoke behaviour: it cannot join the five-project cap or the commitments register, because those rules belong to the types that earned them. Schema, not plugins. It took me an embarrassing number of design rounds to land on refusing to make this powerful.
Templates
Every type — built-in or yours — scaffolds from a markdown template, and every one is overridable per vault. Take a copy of the built-in and edit it:
cdno templates eject project this writes .cuaderno/templates/project.md, and then we add by hand the highlighted lines
---
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)New projects are now born with a ## Risks section. Existing ones are untouched — editing a template is not a migration, and I would rather you ran a script you understood than have me rewrite five hundred notes on your behalf.
Where the values come from
Three sources, and the distinction between them is the part worth knowing.
Contextual — supplied by whichever command creates the note. {{title}}, {{created}}, {{energy}}, {{due}}. Each type offers its own set, and cdno templates vars project prints exactly which, so you are never guessing.
Static — the same on every note in the vault:
[variables]
author = "A. Researcher"
institution = "University of Examples"Prompted — for a value that changes per note. The string is the question to ask:
[variables.prompt]
ticket = "Ticket reference?"cdno then fills it one of three ways: --var ticket=ABC-123 on the command, or an interactive prompt when you are on a TTY, or — failing both — an error.
That last branch is the deliberate one. It refuses to write a note containing a literal {{ticket}}, because a placeholder that survives into a file is the kind of corruption you discover six months later in a note you can no longer reconstruct. Name collisions resolve by specificity — contextual beats static beats prompted. And a placeholder the type does not supply is left verbatim rather than blanked, so a typo surfaces as {{titel}} sitting in the note rather than a title that quietly went missing.
Fields can also carry a type, which gets you value checking from cdno lint and a default at creation:
[schemas.daily.fields.mood]
type = "string"
values = ["low", "ok", "good"]Which makes the vault lintable
All of that schema only matters because something checks it:
cdno lint # non-zero on errors
cdno lint --strict # warnings count too — broken wikilinks and friends
cdno normalise --check # non-zero if any frontmatter is out of canonical ordernormalise reorders frontmatter keys into the order the type's template defines. It never changes a value, and never adds or removes a field — it is a formatter, and a formatter that edited your content would be a bug.
The exit codes are the point. A git-tracked vault gets a pre-commit hook and stops rotting. Notes rot exactly the way code rots: you rename a folder, thirty wikilinks quietly go nowhere, and you find out eighteen months later at the precise moment you needed one of them. clippy for prose.
No guilt engine
Orientation surfaces habits that have lapsed. It does not work out for itself that they have.
A habit is lapsed when the stewardship dashboard says so, in prose — - Swimming 1x/week — lapsed since March. cdno orient reads that line and repeats it back. It does not count backwards through your tracking entries, reconstruct a streak, and inform you that you have broken it.
This is deliberate, and of everything here it is the decision I would defend hardest. Every habit tracker I have used turns eventually into a machine for manufacturing small daily shame, and the shame has never once produced any swimming. So the inference is yours: you declare the lapse during a review, in your own words, when you have looked at it and decided it is true. The tool's job is to show you what is on the record, not to keep score against you.
Orientation leads with what is there rather than what is missing, for the same reason.
Claude as a guest
The part I was most excited about. Point Claude at the vault and it works inside the method instead of around it. Drop cdno-mcp into your MCP client config:
{
"mcpServers": {
"cuaderno": {
"command": "cdno-mcp",
"env": { "CUADERNO_VAULT_PATH": "/Users/you/notebook" }
}
}
}That wires up 47 tools — context reads (get_orientation, get_project_context, search_notes), writes (append_to_log, file_to_portfolio, add_action), and lifecycle (create_project, park_project). So in the morning I just say:
Show me my orientation and help me set today's intention.
Claude reads get_orientation, we talk it through, and it writes the intention back to the note. It is a guest with a key, not the landlord — everything it does is a plain-text diff I can read and revert.
More than not typing
It would be fair to read all of this as a natural-language wrapper over the CLI, and for the first few weeks that is genuinely all I used it for. The part that turned out to matter is the work the CLI structurally cannot do.
Filing evidence is the clearest case. file_to_portfolio takes a portfolio, a source, an origin, an optional abstract, and an optional path to attach. Every one of those is a judgement, which is why the CLI makes me supply them: I have to have read the paper, decided how to cite it, and written the abstract before I am allowed to file it. But Claude has just read the paper with me — that was the whole activity. So it fills them in, attaches the PDF, and the note comes out carrying a real abstract instead of the empty --content I would have shrugged at and moved past at six in the evening.
That shape recurs wherever the vault wants a judgement rather than a value. Which of five active projects a stray thought belongs to. Whether a captured line is an action, a question, or noise. What the Current State paragraph should say now the day is over and something has changed.
Skills are where this stops being ad hoc. A skill is a markdown file describing a ritual — surface context with a read tool, talk it through, write with the matching tool — so "do my weekly review" becomes a repeatable sequence instead of an improvisation each time. Cuaderno ships two worked examples in examples/skills/. The authoring pattern is deliberately dull: one step, one tool. That keeps the whole thing inspectable, and it degrades gracefully — decline the write and the read half still gives you the briefing.
That is the stdio server. There is a second binary, cdno-mcp-server, serving the same catalogue over MCP's Streamable HTTP transport, for clients that reach the vault remotely rather than over a pipe.
It deliberately ships no OAuth of its own. Static bearer tokens are not spec-legal for remote MCP connectors, so the honest design is to terminate OAuth 2.1 at an identity-aware proxy and have the server validate the identity assertion that proxy injects — fail-closed, against the issuer's JWKS. Until that is configured, it refuses to bind anything but loopback. Exposing a personal vault to the internet should be a deliberate act, not something you can do by accident.
Where it is at
Less early than the last time I wrote about it.
- Method: written down, in Part 1.
- CLI: the daily loop works — capture, log, projects, portfolios, questions, stewardships, commitments, search, lint.
- MCP: 47 tools, over stdio locally and over HTTP for remote clients.
- Desktop app: I am working on it. Not ready to show yet.
The repo is public — github.com/agustinvalencia/cuaderno — and on a Mac you can try it now:
brew install agustinvalencia/tap/cuadernoThis post is the tour, not the manual. The manual is at agustinvalencia.github.io/cuaderno — installing, initialising a vault, every CLI command, the full MCP tool catalogue, the configuration and frontmatter references, and tutorials for the parts that need more than a paragraph — templates and variables among them, since I have only shown you the shape of those here.
Still building in public. Probably one post where I admit I overengineered something and rip it out. Place your bets.
Follow along
Blog, GitHub (agustinvalencia), or email. I always reply eventually. Eventually.
Right. Back to the actual research.
— Agustin