The agent surface
Reference for the jotura CLI agent commands: context, conventions and memory, with every flag and exit code, plus where the Jotura skills come from.
The commands on this page exist so that an automated agent can carry knowledge from one session to the next. They cover two mechanisms. One is a memory store of decisions, facts and follow-ups written as ordinary notes. The other is a shared conventions note that every agent runtime reads.
Everything here writes plain Markdown into your vault, with no hidden database or separate agent format behind it. If you open the vault in the desktop app you will see the memory notes and the conventions note exactly as the CLI wrote them. For the commands that read and edit notes generally, see Reading and searching and Editing notes.
The global flags work on every command on this page. --vault picks the vault for one invocation, --json asks for structured output where the command honors it, and -v or --verbose logs the resolved vault path to standard error. Install and setup covers them in full. The tables below list only the arguments that are specific to each command.
Where agent content lives in the vault
| Path | Written by | What it holds |
|---|---|---|
Agent Conventions.md |
jotura conventions (first use), then hand edits |
The shared, synced rules every agent runtime follows in this vault. |
Memory/Decisions/, Memory/Facts/, Memory/Followups/ |
jotura memory log |
One note per memory. The root folder is configurable. |
Because these are ordinary vault files, cloud sync carries them between your machines end to end encrypted, like any other note. Cloud sync is a paid feature, and pricing covers what it costs. See Sync and shares for how the CLI relates to it.
The Jotura skills
The commands on this page are what an agent runs. The skills are what tell it when to run them. They live in the jotura-agents repository, and you install them the way you install any other skill. In Claude Code, run these two commands inside a session:
/plugin marketplace add adamrichardson14/jotura-agents
/plugin install jotura@jotura-agents
For Codex, Gemini CLI, GitHub Copilot, OpenCode or any other agent, copy the SKILL.md files and their references from that repository into wherever the agent loads skills or instructions. Jotura never writes to an agent’s config directory, so your instruction files stay exactly as you wrote them.
jotura context
context is the single call that makes a session memory-aware. It prints a context pack containing the conventions note, any open follow-ups and a set of relevant memories.
jotura context "rewrite the sync retry backoff"
| Argument or flag | Type | Default | What it does |
|---|---|---|---|
[TASK] |
string | none | Task description to recall memories against. Omit it for a session digest. |
--limit <LIMIT> |
number | 8 |
Maximum memories to include. This applies only to the task form. |
The two forms fill memories differently. With a task, the memories field is a full recall against that task with stale entries excluded, capped by --limit. Without a task, memories lists every non-closed memory marked importance: high, and that list is unbounded because --limit does not apply to it.
context respects --json. The JSON shape is:
{
"conventions": "<full text of Agent Conventions.md>",
"openFollowups": [ { "path": "…", "project": "…", "date": "…" } ],
"memories": []
}
With a task, each entry in memories carries the full recall shape of path, kind, status, importance, project, date, occurred, snippet and score. Without a task, entries carry only path, kind, project and date. If the conventions note does not exist, conventions is an empty string. context does not create the note; only jotura conventions does that.
The text form prints named sections:
=== Conventions ===
…
=== Relevant memory ===
- path (kind)
=== Open follow-ups ===
- path
Empty sections are omitted. To have Claude Code start every session with this pack, add a SessionStart hook that runs jotura context to your own Claude Code settings. Jotura leaves that file alone, so the hook is yours to add, change or remove.
jotura conventions
conventions prints the Agent Conventions note and creates it from a bundled seed the first time you run it.
jotura conventions
The note lives at the fixed vault path Agent Conventions.md, at the vault root, with a space in the filename. It has no command-specific arguments and it respects --json.
{ "path": "Agent Conventions.md", "hash": "…", "body": "…", "created": false }
Note the shape difference against jotura read: the JSON carries body with the frontmatter stripped and there is no frontmatter field. The text form prints the whole file including frontmatter.
The note is an ordinary vault note. It is user-visible, it syncs, every agent runtime shares it, and no installer touches it. To change it, edit it with the normal editing commands. conventions is only the read and bootstrap half of the loop.
jotura memory
Memory records decisions, work-world facts and follow-ups. The schema deliberately has no field for personal traits, and the source says so directly: memory exists to record the work, and describing the person doing it falls outside that job.
Notes are laid out as <root>/<Decisions|Facts|Followups>/<YYYY-MM-DD> <slug>.md, where <root> defaults to Memory. The slug is lowercased and reduced to ASCII alphanumerics, with spaces, hyphens and underscores collapsed to single hyphens and then trimmed.
A note counts as memory if its vault path starts with Memory/ or contains /Memory/. That is what lets you keep two entirely separate roots, for example Whitbread/Memory/ and Personal/Memory/, without them bleeding into each other.
Decisions and facts start with status: active. Follow-ups start with status: open. The closed statuses are superseded, reversed and resolved. A memory is considered stale when it is not closed and its occurred frontmatter key is more than 180 days old, falling back to date when occurred is absent. Staleness comes from those frontmatter keys and ignores the file’s modification time, so editing the body later leaves a stale memory stale, while editing occurred refreshes it.
memory log
jotura memory log --kind decision --title "Keep tantivy for body search" --project jotura --importance high
| Flag | Type | Default | What it does |
|---|---|---|---|
--kind <KIND> |
string | required | decision, fact or followup, case-insensitive. Anything else exits 9. Sets both the subfolder and the initial status. |
--title <TITLE> |
string | required | Becomes the H1 and the filename slug. A title that slugifies to nothing, such as pure punctuation, exits 9. |
--project <PROJECT> |
string | none | Written as a project frontmatter key. recall and list filter on it. |
--importance <IMPORTANCE> |
high or normal |
normal |
Ranking weight. Any other value exits 9. |
--tags <TAGS> |
comma-separated string | none | Split on commas, each value trimmed, written as an inline flow sequence such as tags: [a, b]. |
--root <ROOT> |
string | Memory |
The memory root folder. Use it to keep work and personal memory apart. |
--body <BODY> |
string or - |
see below | The note body. - reads from stdin. |
--occurred <DATE> |
YYYY-MM-DD |
none | When the memory became true, if that is not today. Anything not in YYYY-MM-DD form exits 9. |
The frontmatter block log writes carries four keys always, in this order, plus two more when you supply them:
| Key | Written when | Value |
|---|---|---|
kind |
always | decision, fact or followup. |
status |
always | active for decisions and facts, open for follow-ups. |
importance |
always | high or normal. |
date |
always | The date it was logged, as YYYY-MM-DD. |
occurred |
with --occurred |
When it became true in the world. Every staleness rule on this page reads this key, falling back to date when it is absent, so memories written before the key existed keep working. |
project |
with --project |
The project string verbatim. |
tags |
with --tags |
An inline flow sequence such as tags: [a, b]. |
If you omit --body, the result depends on the kind. A decision gets a section template with ## Context, ## Options considered, ## Decision & why and ## Outcome headings. A fact or followup gets an empty body. Passing - reads the body from standard input, which is the natural form when a script is generating it:
printf 'Chase the Play review on Monday.\n' | jotura memory log --kind followup --title "Chase Play Store review" --body -
Marking a memory --importance high has two effects: it boosts its recall score, and it makes the note appear in the bare jotura context digest.
The command always emits JSON, whether or not you pass --json:
{ "path": "Memory/Decisions/2026-08-30 keep-tantivy-for-body-search.md",
"hash": "…", "kind": "decision",
"similar": [ { "path": "…", "snippet": "…" } ] }
The similar array is a duplicate warning. Before writing, log recalls the title against the same kind with a limit of 5 and returns whatever it finds, on the expectation that you will supersede an existing memory rather than log a near-duplicate. It is advisory only and never blocks the write.
The same-day overwrite gotcha. The filename is derived from the date and the title slug, and the write is unconditional with no expected hash. Logging the same title twice on the same day therefore replaces the first note instead of creating a second one. The similar array is the only guard against this, and it does not stop anything. To record a different memory on the same day, give it a different title.
memory recall
jotura memory recall "sync retry backoff" --kind decision --limit 5 --json
| Argument or flag | Type | Default | What it does |
|---|---|---|---|
<QUERY> |
string | required | Free-text query. See the ranking notes below. |
--kind <KIND> |
string | none | Restrict to decision, fact or followup. An invalid kind exits 9. |
--project <PROJECT> |
string | none | Exact match on the project frontmatter key. |
--limit <LIMIT> |
number | 10 |
Maximum results returned. |
--include-stale |
boolean | false |
Include superseded, resolved and stale memories. Without it they are filtered out entirely. |
--mode <MODE> |
keyword, semantic or smart |
smart |
How candidates are found. An invalid mode exits 9. |
recall respects --json. The JSON is an array of objects with path, kind, status, importance, project, date, occurred, snippet and score. The text form is tab-separated: path, kind, status, snippet.
Modes. smart, the default, runs a semantic pass and a keyword pass and fuses them by reciprocal rank, weighting the semantic side twice as heavily because a task description rarely repeats a stored note word for word. semantic uses the vector index alone. keyword is full-text only.
Unlike jotura search, the semantic-backed modes never exit 13. Recall is a mandatory step in the agent loop, so when the vault has no usable vector index it quietly falls back to keyword rather than failing. The semantic pass also scans at full depth and filters to memory paths before applying any limit: memory is typically a low single-digit percentage of a vault, so a vault-wide top-K returns no memory notes at all.
The keyword pass. The underlying search engine ANDs its terms together, which is the wrong behavior for recall. So the keyword pass runs the full query and then each significant term on its own, where a significant term is 4 or more alphanumeric characters. Terms are deduplicated, and at most 7 of them run beside the full query, each with an internal limit of 200 results. The results are merged by path and their scores are summed, so a note that matches more of the query ranks higher than one matching a single word.
Multipliers. Recall keeps two sets of multipliers because the two score scales differ. Keyword-only results span hundreds, where a 1.5x boost is a nudge; fused rank scores put adjacent results about 1.6% apart, where the same multiplier would outrank relevance outright.
| Condition | Keyword-only | Semantic or fused |
|---|---|---|
importance: high |
1.5 | 1.05 |
Stale, meaning open or active and an occurred more than 180 days old |
0.5 | 0.95 |
| Closed, meaning superseded, reversed or resolved | 0.25 | 0.85 |
Any result whose path is not a memory path is discarded. The vector index is built by the desktop app, so a memory written seconds ago is not semantically searchable until the app next indexes it. smart covers that gap because its keyword pass always runs. For general semantic search over the rest of the vault, see Reading and searching.
memory list
jotura memory list --kind followup --status open --json
| Flag | Type | Default | What it does |
|---|---|---|---|
--kind <KIND> |
string | none | Filter by kind. An invalid kind exits 9. |
--status <STATUS> |
string | none | Exact match on the status key. |
--project <PROJECT> |
string | none | Exact match on the project key. |
--stale |
boolean | false |
Show only active or open memories whose occurred (or date, when occurred is absent) is more than 180 days old. Closed statuses are excluded entirely. |
list respects --json. The JSON array entries carry path, kind, status, importance, project, date and occurred, with no snippet and no score. Text output is tab-separated: path, kind, status.
Two behaviors differ from recall. First, list walks the note tree directly instead of searching, and it skips any note whose frontmatter has no kind. Second, --status is not validated while --kind is. An invalid --kind exits 9, but an invalid --status is accepted and matches nothing, returning an empty list.
memory supersede
jotura memory supersede "Memory/Decisions/2026-05-02 use-polling-for-sync.md" \
--by "Memory/Decisions/2026-08-30 use-websockets-for-sync.md"
| Argument or flag | Type | Default | What it does |
|---|---|---|---|
<OLD> |
string | required | The note being superseded. Its status becomes superseded and a superseded-by key is added. |
--by <BY> |
string | required | The superseding note. A supersedes key is added to it. |
Both notes are checked for existence before either one is modified. The command always emits JSON: { "superseded": "<old>", "by": "<new>" }.
The link itself is written as three separate hash-checked writes, for the status, the superseded-by key and the supersedes key. If another writer edits either note partway through the sequence, the pair can end up half-linked, so check both notes if a supersede reports a conflict.
memory resolve
jotura memory resolve "Memory/Followups/2026-08-12 chase-play-store-review.md"
It takes one positional <PATH> and sets status: resolved. It always emits JSON: { "resolved": "<path>" }.
Despite the help text saying “Mark a follow-up resolved”, the command is not restricted to follow-ups. It will set resolved on a decision or a fact just as readily, so check the kind before running it.
memory doctor
jotura memory doctor
jotura memory doctor --fix --json
memory doctor sweeps the memory store for the problems a long-lived store accumulates that no individual write can catch. It changes nothing unless you pass --fix.
| Check | Status when clean | What it flags |
|---|---|---|
memory-total |
always info |
How many memory notes were found. |
memory-stale-actives |
pass |
Decisions and facts still active whose occurred is more than 180 days old. Confirm them or supersede them. |
memory-open-followups |
pass |
Follow-ups still open after 60 days. |
memory-orphan-supersedes |
pass |
A superseded-by edge pointing at a note that no longer exists. |
memory-split-roots |
pass |
The bare default Memory/ root sitting beside a namespaced one. Several namespaced roots, such as work and personal, are a deliberate split and pass. |
memory-missing-occurred |
pass |
Notes with no occurred key, which fall back to date. Reported as info, not a problem. |
Output matches jotura doctor: a JSON array of { name, status, detail } with --json, otherwise tab-separated status, name, detail lines. status is one of pass, warn or info.
What --fix will and will not do. It backfills a missing occurred from date, and adds a memory-fix-applied check reporting the count. That is the only automatic edit, because it records what the file already implies. A stale active, a stalled follow-up and a broken supersede edge all need a judgement call, so they are reported and left alone, since silently rewriting someone’s memory is the failure the plain-note design exists to prevent.
Exit codes on this page
| Code | Symbol | When you will see it here |
|---|---|---|
| 0 | none | Normal completion. |
| 2 | HashConflict |
A hash-checked write lost a race, for example during a memory supersede sequence. |
| 3 | NotFound |
A memory path passed to supersede or resolve does not exist. |
| 6 | NoVault |
No vault resolved. |
| 8 | PermissionDenied |
A vault path that resolves outside the vault, including through a symlink or a Windows junction. |
| 9 | InvalidArgs |
An unknown memory kind, an importance value other than high or normal, or a path argument that is empty or contains . or .. components. |
The code field in the error JSON carries the symbol, and that string is the stable thing for a caller to match on. Scripts should guard against one collision. Clap, the argument parser, defaults to exit code 2 for its own usage errors, such as a missing required argument or an unknown flag, and that number is also HashConflict. Usage errors print human-readable text on standard error rather than the JSON error envelope, so a caller that sees exit 2 should check whether stderr parses as JSON before concluding it hit a hash conflict. The full table is in Exit codes and JSON output.
A note on –json
The --json flag is not uniform across the CLI, and the agent commands sit on both sides of that split. context, conventions, memory recall and memory list branch on the flag. memory log, memory supersede and memory resolve emit JSON whether or not you pass it. Errors are always a single-line JSON object on standard error in every case.
Related pages
- Install and setup for getting the
joturabinary onto your PATH and choosing a vault. - Editing notes for the hash-checked write loop that memory notes use underneath.
- Frontmatter and tags for editing the
kind,status,importance,date,projectandtagskeys directly. - Sync and shares for how memory and conventions travel between machines.
- Exit codes and JSON output for the complete error envelope.
- Jotura for agents for the wider picture of what an agent can do with a vault.