---
title: "Use with AI agents · tnotes"
description: "Use tnotes as a notes backend for AI agents: a headless CLI with JSON output, stable ids, links and backlinks, no daemon. Recipes for Claude Code, Codex and cron."
canonical: https://tnotes.app/agents
---

# tnotes for AI agents

Use tnotes as a notes backend for AI agents: a headless CLI with JSON output, stable ids, links and backlinks, no daemon. Recipes for Claude Code, Codex and cron.

## Why a CLI and not an MCP server

Every coding agent can already run shell commands. A CLI that prints JSON is the smallest possible integration surface: nothing to install on the agent side, nothing to keep running, nothing to authenticate. The vault is a folder of Markdown files, so an agent can also just `cat` and `grep` it — tnotes adds the index: titles, tags, links, backlinks, timestamps and fuzzy search.

## The five commands an agent needs

```
$ tnotes ls --json                                  # every note: id, path, title, folder, tags, links, backlinks, created, modified, preview$ tnotes search "standup" --json | jq '.[0].path'   # best match first$ tnotes cat vault/weekly-plan                      # full text (--json adds "text")$ echo "- [ ] follow up with Ann" | tnotes new "Meeting notes" --tag work --stdin$ tnotes append meeting-notes "- [ ] send the slides"
```

A note is addressed by its id (`root/sub/stem`, as printed by `ls`), a path, or a unique file stem. The id is the same in every command. `--dir` pins the vault for one invocation, so an agent never depends on the user's config.

## Walking the note graph

`links` lists the `[[titles]]` a note references; `backlinks` lists the notes that reference it. A "what's related to X" prompt is one `ls --json` plus a `jq` filter:

```
$ tnotes ls --json | jq '.[] | select(.links[]? == "Weekly plan" or .backlinks[]? == "Weekly plan") | .title'"tnotes roadmap""Meeting notes"
```

## What the agent cannot break

-   A note that is open with unsaved edits in the TUI is never overwritten: the TUI keeps its text and writes a conflict copy next to the agent's version, and `write` returns an error.
-   A note open and clean in the TUI follows `write`/`append` live.
-   `new` refuses a title that already exists unless `--duplicate` is passed, and rejects a blank title.
-   `write` refuses empty input. A changed title renames the file and rewrites every `[[link]]` to it.
-   `trash` moves to `.Trash/`; `restore` brings it back. Nothing is deleted outright.
-   Saves are atomic; a syncer or a second process never sees a half-written note.

## Claude Code / Codex: a snippet for AGENTS.md

Drop this into the project's `AGENTS.md` or `CLAUDE.md` (adjust the path):

```
## NotesMy notes are a tnotes vault at ~/Documents/notes. Use the CLI, never edit files directly:- list:   tnotes ls --json [--tag t] [--folder f] [--limit n]- search: tnotes search "<query>" --json- read:   tnotes cat <id>- create: tnotes new "<Title>" [--tag t] [--folder f] --stdin   (body on stdin)- append: tnotes append <id> "<line>"   or   --stdin- rewrite: tnotes write <id> --stdinIds look like vault/sub/stem and come from ls/search. Link notes with [[Title]]; tag with #tag in the text.
```

## Daily note from cron

```
# crontab: create today's note at 08:00 if it does not exist0 8 * * * tnotes new "$(date +%F)" --tag daily --folder journal --stdin <<< "- [ ] " 2>/dev/null || true
```

## Handy jq one-liners

```
$ tnotes ls --json | jq -r '.[] | select(.tags | index("work")) | .id'      # ids tagged #work$ tnotes ls --json | jq -r 'sort_by(.modified) | reverse | .[0:5][] | .title'   # five most recent titles$ tnotes ls --json | jq -r '.[] | select(.preview | test("\\[ \\]")) | .title'   # notes with open tasks$ tnotes ls --json | jq '[.[] | .tags[]] | group_by(.) | map({(.[0]): length}) | add'   # tag counts
```

See the [CLI reference](/docs/cli) for every flag, and [sync and encryption](/docs/sync) for running agents against a synced vault.
