Skip to content

Latest commit

 

History

History
238 lines (199 loc) · 11.4 KB

File metadata and controls

238 lines (199 loc) · 11.4 KB

Agentkeep vault spec: point your agent at your vault

Agentkeep is a self-hosted, MIT markdown vault that your agent keeps tidy. The vault is just markdown + JSON in a folder. Open it in a compatible editor, edit it by hand, or let an agent drive it through MCP. Nothing to export, no lock-in.

MCP is the agent write path:

Run agentkeep-mcp <vault-path> and point any MCP client at it. Every agent write goes through the write-core: content-hash compare-and-swap, atomic write, git commit, and agentkeep-agent attribution. This provides governed undo, attribution, and conflict protection.

Raw folder access is supported for read-only interoperability with file-only tools. External or manual file edits remain a compatibility reality, and Agentkeep reindexes them while it runs, but those edits bypass write-time hash guards, automatic Agentkeep git attribution, and MCP { error, code } values. They are not an agent connection method and are not recommended for writes.

Through MCP or the web app, every mutation is a git commit. Agent edits to an existing note can be undone in-app from that note's history; a deletion stays recoverable in git history because the removal is a commit, not an erase. External or manual file edits are ordinary filesystem edits until you or another tool commit them.

Vault layout

vault/
├── inbox/                 # raw captures, one markdown file per item
├── notes/                 # prose notes: markdown + frontmatter; [[wikilinks]]
├── memory/                # the agent's memory, one note per topic (the `remember` tool)
├── tasks/                 # optional sharded JSON, one file per task
├── brief/2026-06-08.md    # optional generated brief note
├── north-star.md          # optional goals file used by brief helpers
└── .agentkeep/            # app state + rebuildable index cache (NOT your prose)
└── .git/                  # git history at the vault root

An existing markdown vault keeps its own structure. These folders are the fresh-vault convention, not a requirement. agentkeep init currently creates only inbox/ and memory/; the other folders/files above are optional core conventions and are created on demand by tools or by your agent. Agentkeep indexes every .md file wherever it sits (dotfolders like .agentkeep/, .obsidian/, .git/ are skipped).

Notes (notes/, prose)

A note is markdown with an optional YAML frontmatter block:

---
title: Widgets research
tags: [research, widgets]
created: "2026-06-08T09:00:00.000Z"
agent_edited: true
source: https://example.com/widgets
---

# Widgets research

Connects to [[beta]] and the #widgets project.

Conventions:

  • [[wikilinks]] support aliases ([[target|alias]]), heading ([[target#H]]) and block ([[target^id]]) anchors and the ! embed prefix are all understood; only the bare target feeds the backlink graph. The resolver is basename-based. In the web editor a [[target#H]] / [[target^id]] link navigates. Clicking it opens the target note and scrolls to that #heading / ^block. ![[image.ext]] renders as an inline image (served from the vault via /api/image); ![[note]] renders as a read-only embed block (a card linking to the note, not a full inline copy). Full inline transclusion of a note's body is roadmap, not v1.
  • Tags use frontmatter tags (a YAML list, or a comma string) and inline #tag (nested #proj/sub allowed). People are a tag convention: person/<name>.
  • title uses frontmatter title, else the first # H1, else the filename.
  • source / citation records where an agent learned something. For a URL or note, record it in source: frontmatter or an inline citation. Claims cite their source. Optional brief helpers can surface uncited agent prose for your eyes.
  • agent_edited: true is set by the agent when it touched a note, so the UI can visually mark agent edits. (Git authorship agentkeep-agent is the source of truth; this flag is a convenience for fast rendering.)

Frontmatter rules (so diffs stay clean)

  • Read frontmatter by splitting only (we use gray-matter); write a single key with a format-preserving editor (we use eemeli/yaml) so comments, key order, and whitespace survive. Never rewrite the whole block with a dumb stringify. It reorders keys and drops comments. Golden rule: an untouched file must round-trip to zero diff.
  • The web editor edits raw markdown source, so your exact bytes round-trip (CRLF stays CRLF, quoting is preserved).
  • Quote ambiguous scalars (ISO timestamps, yes/no/on/off) as strings, or the YAML-1.1 read side coerces them to a Date/boolean.

Tasks (tasks/<id>.json)

Structured fields markdown can't carry → one JSON file per task:

{
  "id": "t_3f9a2b10",
  "title": "Email Sam about the invoice",
  "status": "today",
  "due": "2026-06-10",
  "priority": "high",
  "tags": ["billing"],
  "created": "2026-06-08T09:00:00.000Z",
  "source": "inbox/cap_8a1c0e44.md",
  "closedAt": "2026-06-09T17:30:00.000Z"
}
  • id (required) · title (required) · status (required): one of inbox | today | doing | done · created (required, ISO).
  • Optional: due (ISO date) · priority (low|med|high) · tags (string[]) · source (where it came from) · closedAt (ISO, set when moved to done; the brief uses it to count "loops closed overnight").
  • Pretty-printed JSON + a trailing newline so git diffs are reviewable. A malformed shard is skipped, never fatal.

Capture (the fastest way in)

Drop a timestamped markdown file in inbox/ and let the agent file it later:

---
id: cap_8a1c0e44
created: "2026-06-08T09:00:00.000Z"
type: capture
---

Remember to ship phase 5.
  • Filename and id are cap_<8 hex of sha256(text)> (content-derived, so the same text maps to the same inbox file, so a double-capture is idempotent, not a dupe).
  • Captures are a human action; the agent reads inbox/, then proposes notes/ tasks and (on approval) files them.
  • Via MCP: the capture tool does exactly this for you.

Memory (memory/<slug>.md)

The agent's durable memory, one plain markdown note per topic, is what the web app's Memory page ("What your agent believes") renders. Written by the remember MCP tool. You can open and edit a memory note like any other, but the agent owns the file (see the clobber note below), so durable human prose belongs in a normal note that the memory note [[wikilinks]]:

---
title: Coffee preference
type: preference
source: session 2026-06-10
updated: "2026-06-10"
---

The user takes their coffee black, no sugar. See [[Morning routine]].
  • Path: memory/<slug>.md. The slug is the lowercased topic, with runs of non-alphanumerics collapsed to single dashes ('My Topic!!' becomes my-topic).
  • Frontmatter: title keeps the human topic (the UI shows titles, never slugs); type is one of fact | preference | person | project (default fact); source is free text recording where the agent learned it (omitted when unknown); updated is the date of the last remember (quoted, so it stays a string).
  • The remember tool owns the whole file: each call replaces the frontmatter AND body for that topic. Re-remembering supersedes cleanly. An human-authored update requires the hash the agent previously read, so a later human edit returns a conflict instead of being overwritten. Treat a memory note as the agent's scratch copy of one belief. To keep durable human prose, put it in a normal note and [[wikilink]] it from the memory note; correcting a belief for good means telling the agent, so its next remember writes the corrected version.
  • Body: the memory in plain markdown, wikilinked liberally so it joins the backlink graph (memory nodes are accented in the graph view).
  • Honest scope: Agentkeep stores and shows memory. It does no embedding, ranking, or recall. Your agent's own context and retrieval do that.

north-star.md (optional)

One pinned goals file. Goals are the top-level list items (- / * ) and ## headings; plain prose paragraphs are not goals. The Morning Brief weights "what matters" against these. The current memory-first app does not require this file; it is a core helper convention for agents that choose to generate briefs.

Agent etiquette (the two-driver contract)

  • Never clobber human prose. write_note and delete_note always require the caller's observed baseHash for existing content. remember requires the current baseHash when the target memory note was last edited by a human. A conflicting human edit wins until the agent reads the new version and decides what to do.
  • Prefer MCP write_note with a baseHash. Read first, write with the hash you read. A stale hash returns a 409 conflict (your write is rejected, the human's bytes are safe). That is the feature, not a failure.
  • Cite your sources. Record source for anything learned externally.
  • Mark agent edits (agent_edited: true) and let git carry the real provenance.
  • Governed writes are reversible. Each MCP/web change is one git commit attributed to agentkeep-human or agentkeep-agent. Web Undo uses the governed write core. External or manual edits need their own commit discipline.

MCP tool reference (the governed seam)

agentkeep-mcp <vault-path> serves these nine tools over stdio. write_note, capture, remember, and delete_note mutate through the governed seam (CAS / git rm + git commit + agentkeep-agent); the rest read.

  • search { query } → ranked { path, title, score }[], best first.
  • read_note { path }{ content, hash }. Use hash as the baseHash for a safe write_note. Missing file → a not-found result (not an error throw).
  • write_note { path, content, baseHash? }{ hash, commit }. Omit baseHash to create; pass it to update. Stale hash → a 409 conflict result.
  • list_notes {} → every markdown path, sorted.
  • list_tasks {} → every task object.
  • get_backlinks { path } → notes that link to the target.
  • capture { text }{ path, id }; lands an inbox/ file.
  • remember { topic, content, type?, source?, baseHash? }{ path, hash, commit }. Upserts memory/<slugified-topic>.md as the agent (frontmatter title / type (default fact) / source / updated: today + the content body. The tool owns the WHOLE file, see the Memory section above). CAS-guarded against a concurrent edit. Replacing a human-authored memory requires its current baseHash; missing or stale hashes return 409. Empty topic/content returns 400 (values, not throws).
  • delete_note { path, baseHash }{ ok, commit }. Removes a note as the agent (git rm + commit). Use it to clear an inbox/ capture once you've filed it, so the inbox empties. The removal is one commit, not a destructive erase: the note stays recoverable from git history. Read the note first and pass its hash; a stale or omitted hash returns 409. Missing path returns a 404 result; a path escaping the vault returns 400 (values, not throws).

A failing tool result comes back as an MCP tool error (isError: true) carrying { error, code }, for example { "code": 409, "error": "Conflict on ..." }, so the client can react without the call throwing.