Appearance
Notes - Markdown vault with agent co-writing
The Notes view is a personal Markdown vault (an Obsidian-style alternative) built into the Hive app. It gives you a file tree with a name filter, a source + live-preview Markdown editor, and - the headline feature - a Claude Code agent rooted in the vault so you can talk about, organize, and co-write your notes.
Notes live in an S3-compatible bucket as the shared source of truth and are synced to a local directory on each node, so agents operate on real local files (fast, and Claude Code needs a real filesystem).
Enabling the vault
Add a [notes] section to the daemon's config.toml on the node you connect to:
toml
[notes]
enabled = true
bucket = "my-notes" # S3 bucket (sync is off without this)
endpoint = "https://s3.amazonaws.com" # omit for AWS; set for MinIO/R2/B2
region = "us-east-1" # many S3-compatible providers accept "auto"
access_key = "AKIA..." # falls back to AWS_ACCESS_KEY_ID env var
secret_key = "..." # falls back to AWS_SECRET_ACCESS_KEY env var
local_dir = "~/.hive/notes" # default: <data_dir>/notes (node-local)
user = "daniel" # root the vault in this OS user's home (node-local)
sync_interval_secs = 300 # background pull cadence (min 60)- Local-only is fine. With
enabled = truebut nobucket, the vault works entirely locally (editor, agents); it just never reaches S3. - Credentials. Keys may live in
config.tomlor be injected at runtime via the standardAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYenvironment variables (leaveaccess_key/secret_keyunset to use them).
Configuring from the app
You don't have to hand-edit config.toml. The Notes view has a settings gear (top right) that opens a Notes vault settings dialog - the same fields as above (enable sync, local directory, bucket, endpoint, region, access/secret keys, sync interval). Saving writes the [notes] section to the connected node's config.toml and takes effect without a restart. When the vault isn't configured yet, the "vault not configured" hint also offers a Configure vault button that opens the same dialog.
Saving also replicates the shared S3 settings to every node in the cluster (see Cluster replication below), so you only configure the vault once and every node can serve it - no need to repeat the settings on each node.
Per-node directory and user. The dialog has a Node picker (shown when the cluster has more than one node): choose a node to view and edit its local vault directory and OS user. These two fields are node-local - only the S3 settings replicate. The User home dropdown lists the node's OS user accounts; picking one roots that node's vault under the user's home directory (e.g. /home/<user>/.hive/notes). This is useful when the daemon runs as root or via systemd but you want your notes in your own home directory. Leave it on Daemon user (default) to use the daemon process's home / data directory. A ~-prefixed or relative Local vault directory resolves under the selected user's home; an absolute path is used as-is. The field has a Browse button that opens a directory picker rooted on the targeted node, so you can pick an existing folder (or create one) instead of typing the path by hand.
The dialog never displays stored S3 secrets: once a key is saved it shows as "stored"; leave the field blank to keep it, type to replace it, or tick Clear to remove it. The dialog works at both desktop and phone widths.
A Test connection button in the S3 section checks that the saved configuration can reach the bucket (it builds the store and lists the bucket without changing anything) and reports the object count or the exact error. It uses the saved config, so save any edits before testing.
If [notes] is absent, the Notes view shows a "vault not configured" hint.
Using the view
- File tree (left): the whole vault, folders first. Click a folder to collapse/expand, a note to open it. Use the Collapse all / Expand all toggle in the tree header to fold or unfold the whole vault at once. On desktop the tree panel is hidden by default; collapse it with the panel button next to the filter box, and re-open it from the slim rail that takes its place on the left edge. That choice is remembered for the current app session and resets to hidden when the app restarts. On desktop, drag the handle on the tree's right edge to resize it (180-480 px); the width persists across restarts via
localStorage["hive-notes-tree-width"]. - Row actions: right-click a row (desktop) or tap its kebab (
⋮, touch) for a context menu - open, new note in a folder, collapse/expand that folder, collapse/expand all, rename, and delete. - New creates a note (a
.mdextension is added if you omit one); parent folders are created as needed. - Editor (center): a CodeMirror Markdown editor with a live preview. Toggle source / split / preview in the editor toolbar; the chosen mode is a per-device preference that persists across restarts. In split mode, drag the divider between the source and preview panes to rebalance them (20-80%); the ratio persists via
localStorage["hive-notes-editor-split"]. The editor auto-saves a short delay after you stop typing (so the agent and S3 always see your latest words);Ctrl/Cmd+Sstill saves on demand. The editor and preview font size is configurable in app Settings → Notes → Font size (a per-device preference, synced with the rest of your UI preferences). The preview understands Obsidian-flavoured Markdown - see Obsidian-flavoured Markdown below. - Info panel (desktop): the panel button in the editor toolbar opens a right rail with the note's Outline (click a heading to jump), Backlinks (other notes that link here - click to open at the reference), and Tags (click to search the vault for that tag).
- Attachment previews: opening an image, audio, video, or PDF file from the tree shows a read-only viewer instead of the text editor; any other binary type shows an info card with a Download button (desktop only). The tree uses type-specific icons for these files.
- Inline images: a Markdown image whose path points at a file in the vault (
, relative to the note, or/-prefixed for vault-absolute) renders inline in the preview pane. Externalhttp(s)/data:image URLs render as usual. Attachments load on demand (capped at 25 MiB) and are never edited from the app - drop them into the vault folder or let the notes agent manage them. - Name filter: the search box at the top of the file tree filters the tree to files and folders whose name matches; matching folders keep their ancestors visible. It does not search note contents.
- Agent (right): opens a Claude Code session whose working directory is the vault - see Agent co-writing below.
On phones the three panes become a bottom switcher (Files / Editor / Agent); all surfaces respect the safe-area insets. The info panel is desktop-only.
Obsidian-flavoured Markdown
On top of GitHub-Flavoured Markdown, the preview renders the Obsidian syntax most notes rely on. Everything is rendered in the existing source / split / preview panes - there is no separate "live preview" editing mode.
- Wikilinks -
[[Note]],[[Note|alias]], and[[folder/Note]]link to another note, resolved the way Obsidian does (exact path first, then a unique basename match). Click one in the preview to open it; a link whose target doesn't exist is shown in red.[[Note#Heading]]jumps to a heading. In the source editor, typing[[pops up an autocomplete of every note in the vault. - Embeds / transclusion -
![[image.png]]embeds a vault image (or audio / video / PDF) inline, and![[Note]](or![[Note#Heading]]) transcludes that note's content into the current one. The embedded note's title links back to the source; embeds inside an embed degrade to plain links rather than recursing. - Tags -
#tag(including nested#area/topic) renders as a pill. Click it to open the full-text search seeded with that tag, or use the Tags section of the info panel. - Callouts -
> [!note],> [!warning],> [!tip], etc. render as coloured callout blocks. An optional title after the type (> [!info] Heads up) is used as the callout heading. - Math - inline
$E = mc^2$and block$$ ... $$are typeset with KaTeX. - Diagrams - fenced
```mermaidblocks render as Mermaid diagrams (loaded on demand; the diagram theme follows the app's light/dark setting). - Code highlighting - fenced code blocks are syntax-highlighted with highlight.js, with colours that follow the app theme.
Backlinks and tag search reuse the vault's native full-text search, so they work on every platform (including mobile) without any extra index.
Full-text search
The Search button in the toolbar (or Ctrl+Shift+F) opens a standalone search dialog that looks inside every note in the vault - distinct from the sidebar's name filter, which only matches file and folder names.
- Query: matched as a plain substring (never a regex) with ripgrep-style smart-case - case-insensitive unless the query contains an uppercase letter.
- Results: one row per matching line, showing the matched text (the query highlighted) with the file name and line number at the end of the row. Binary files, dotfiles, and dot-directories are skipped; a broad query is capped at 500 hits.
- Preview: selecting a result (single click, or the arrow keys) shows a few lines of surrounding context below the list, with the matched line highlighted.
- Open: double-click a result, press Enter, or use the Open button in the preview header to jump to the note with the cursor placed on the match and the line scrolled into view. On phones this switches to the editor pane.
Search runs natively on the daemon over the synced local vault, so it works on every platform - including mobile, where no external search binary exists.
Command palette
Pressing F1 anywhere in the Notes view opens a command palette - a fuzzy-searchable list of every Notes action, mirroring the full-text search modal. Type to filter, navigate with the arrow keys, and press Enter (or click) to run the selected action; Esc closes it.
The palette only lists actions that are runnable right now: open-note actions (rename, delete) appear when a note is open, the S3 Pull/Push entries appear only when sync is configured, and the agent and folder collapse/expand entries reflect the current state. Each row shows its keyboard shortcut where one is bound.
Agent co-writing
The Agent pane runs a hidden SDK-mode Claude session rooted in the vault directory, so the agent reads and writes the real note files. It is not a terminal: the daemon runs Claude headless in bidirectional streaming mode (claude --print --input-format stream-json --output-format stream-json) and the pane parses that newline-delimited JSON into a chat window - message bubbles, with collapsible thinking and tool-call blocks - instead of an xterm. Because the session is hidden, it never appears in the Terminals list. The architecture leaves room for other agent providers later (the session carries a provider; today only Claude is wired).
- One long-lived process per chat. The daemon keeps a single Claude process running and writes each of your messages to its stdin, so turns start instantly (no per-turn CLI cold start) and a message sent while the agent is still working is queued and answered next instead of interrupting it. The daemon captures Claude's own conversation id from the stream and uses
--resumeonly when it has to respawn (first message, daemon restart, or a crashed process), so context survives respawns too. - Live progress. The agent's reply streams in token-by-token as it is generated, and while a turn runs the status row shows what the agent is doing right now (
Running WebFetch…,Thinking…,Writing reply…) plus how long the turn has been running. A Stop button kills the in-flight turn without losing the conversation - the aborted turn is marked in the transcript and the agent restarts ready for your next message. - Persistent, resumable chat. The transcript (your messages, the agent's stream, and the Claude resume id) is persisted in the daemon DB keyed by the node's vault. Reopening the pane - even after an app reconnect or a daemon restart - rehydrates the history and re-attaches with
--resume, so the conversation continues where it left off. Stored per-node on the connected daemon; multiple app instances on the same node see the same chat. (The blob rides the daemon DB, not S3 - it is not part of the synced vault files.) - Model. The Claude model the notes agent runs with is set in Notes vault settings → Notes agent → Model (e.g.
claude-opus-4-7). It is node-local and applies to new agent chats; leave it blank to use the Claude CLI default. - Resizable chat. On desktop the agent panel has a drag handle on its left edge; drag to set its width (280-720px). The width is remembered across restarts. On phone widths the chat is a full-width swappable pane instead.
- Vault instructions (
AGENTS.md+CLAUDE.md). The daemon seeds anAGENTS.md(the durable brief - the assistant's role, theFile:marker convention, and the co-editing rules) and a thinCLAUDE.mdthat links to it (@AGENTS.md) into the vault root the first time it is listed. They are created only if missing and are never overwritten - tweak them to shape how the agent behaves, and the changes sync to S3 like any other note. The first message of a conversation nudges the agent to readAGENTS.md. - Chat box. Type in the message box under the chat and press Enter (Shift+Enter for a newline) or tap Send. Your message appears as a bubble; the agent's reply streams in as it works.
- Active-file context. Each message you send carries the currently-open note as a trailing
File: <path>line so the agent knows what you are looking at without you having to say so (the marker is sent to the agent but not shown in your bubble).AGENTS.mdtells the agent to treat that line as context only - read the file, don't act on the line itself. Before the message is sent, any unsaved editor changes are flushed so the agent reads your latest text. - Live co-editing. When the agent edits the open note, a vault file watcher fans out a change notification and the editor live-reloads the agent's edit (when your buffer isn't mid-edit). Combined with auto-save, you and the agent stay on the same version; the agent is instructed to always re-read a note before editing and to preserve your recent wording rather than revert it.
- Permissions. The session runs in the Claude CLI's
acceptEditsmode, so reading, writing, and editing the vault's note files never prompt - that is the agent's whole job. WebFetch and WebSearch are allowed out of the box, so "make a note from this URL" works on the first try. Any other tool it requests (e.g. running a shell command) is denied by the CLI and reported back; the chat then shows a Permission needed card naming the tool. Click Allow & retry to allowlist that tool and re-run the turn so the agent can proceed. Approved tools stay allowed for this vault's agent until you start a new chat (they are persisted with the transcript, scoped to the notes agent only - never your terminal sessions). - New chat. The message-plus button in the agent header clears the persisted transcript and starts a fresh conversation (no resume) on the spot, and resets any tools you had approved.
- Closing. The X in the agent header stops the running agent: it flushes the transcript, then kills the session so no orphaned process lingers. The persisted chat is kept, so re-opening rehydrates it and resumes. Toggling the agent pane off with the toolbar Agent button just hides it.
Sync model
- Push on save. Saving (or creating/deleting/renaming) a note through the app uploads it to S3 immediately, independent of the poll.
- Periodic pull and push. Every
sync_interval_secs, each node lists the bucket and downloads any object newer than its local copy, then pushes its own local changes (and deletions) back up. This runs on every node whether or not a client is connected, so edits made on disk - for example by the Notes Agent or a terminal session - propagate to S3 and the rest of the fleet without anyone opening the app. After syncing it notifies connected apps (the view re-lists, and a non-dirty open note re-reads). - Manual sync. When sync is configured, the Notes toolbar shows Pull (download remote changes now), Push (upload notes changed since the last sync now), and a Synced … ago indicator of the node's last successful sync. These trigger the same engine as the periodic poll without waiting for the next tick - handy to confirm a freshly-configured vault is actually reaching S3.
- Change tracking. A hidden
.hive-sync.jsonmanifest in the vault root maps each note's path to the sha256 of the bytes last synced. Push consults it to upload only notes whose content actually changed and rewrites it afterward; per-save push, delete, and pull keep it current. It is an optimization only - a missing or corrupt manifest just makes the next push re-upload everything. - Conflict resolution is last-write-wins by modification time. Pull and push are symmetric: on pull a remote object only overwrites a local note when it is newer and its bytes differ; on push a locally changed note only overwrites its S3 object when the local mtime is strictly newer than the remote, so the background push never clobbers a newer remote edit (it leaves that for the next pull). Simultaneous edits of the same note on two nodes keep the newer one and lose the older edit - there are no conflict-copy files in this version.
- Deletions. A local delete is mirrored to S3 - both the app's explicit delete and a file that disappears on disk and was previously synced (the background push removes it from the bucket so other nodes drop it on pull). Deleting a folder removes the folder's object and every object beneath its prefix, so the notes inside it do not survive in the bucket and reappear on the next pull. Remote deletions are not propagated to a node's local copy (a node never deletes a note it didn't delete itself).
- Names with non-ASCII / reserved characters. Object keys are percent-encoded for S3 (e.g.
Práce→Pr%C3%A1ce); pull decodes them back to the real name before writing to disk, so a folder likePráceround-trips unchanged instead of accumulatingPr%C3%A1ce/Pr%25C3%25A1ceduplicates. - Self-healing of legacy encoded duplicates. Earlier builds (before the pull decode above) wrote encoded keys to disk and re-encoded the
%on every cycle, leaving stray copies such as%C5%A0pajzkaand%25C5%25A0pajzkabeside the realŠpajzka. Sync now removes these automatically: when a note's name still percent-decodes to a different note that already exists in the vault, push deletes the stray local copy and pull deletes the stray S3 object - so the junk clears itself across the fleet within a sync cycle or two. The guard only fires when the decoded clean sibling actually exists, so a legitimately%-bearing filename (with no clean twin) is never touched.
Cluster replication
The vault data is shared through S3, but each node also needs the S3 settings (bucket, endpoint, region, credentials, enable flag, interval) to sync and serve it. Those settings are replicated across the cluster so notes survive leader changes and stay reachable when the node you originally configured is offline.
- Configure once. Saving the notes settings on any node stamps a write timestamp, persists them locally, and broadcasts them to every peer (
PeerMessage::SyncNotesConfig). Each node applies the shared fields to its own[notes]config and starts syncing the vault from S3. - Last-writer-wins. A peer only applies an incoming config when its timestamp is newer than what it already has, so an out-of-order or stale broadcast cannot clobber a fresher setting.
local_dirstays node-local. Only the shared S3 fields are replicated; each node keeps its own working-copy directory (default<data_dir>/notes).- Catch-up on reconnect. A node that was offline when the settings changed receives the current config when it rejoins the cluster, then resyncs the vault.
- Pull on promotion. When a node becomes leader it pulls the vault from S3 immediately rather than serving a copy that may be a sync-interval stale, so a leadership change never hands the app an out-of-date vault.
- Credentials spread to every node. Because each node syncs independently, the S3 access/secret keys are stored on every node's
config.toml. This is required for notes to remain available when any single node goes down. - Any node serves notes. Notes are served by whichever node the app is connected to; with the config replicated, that works regardless of leadership or which node first held the data.
Wire protocol
Client → daemon (ClientMessage): ListNotes, ReadNote, ReadNoteBinary (base64 bytes + MIME for attachments and inline images), WriteNote (base_sha256 for optimistic concurrency), CreateNote, DeleteNote, RenameNote, SearchNotes, GetNotesSettings / SetNotesSettings (both take an optional node_id to target a specific node; SetNotesSettings also carries user), SyncNotes (direction: pull / push / both), and TestNotesS3. A node-targeted settings request is forwarded to that node via the cluster (forward_to_node), so the node picker can read and write each node's local directory and user.
Daemon → client (ServerMessage): NoteList (vault_dir + flat FsEntry tree + last_sync_unix), NoteContent, NoteBinary (base64 data + mime), NoteSaved, NoteWriteConflict, NoteActionResult, NoteSearchResults, NoteError, NotesChanged (broadcast after a sync pulls remote changes), NotesSettings, NotesSyncResult (per- direction change counts + last-sync time), and NotesS3TestResult. These messages are served by the connected daemon against its own local vault - they are not request-routed to a single owner node. Instead, the S3 settings are replicated cluster-wide (PeerMessage::SyncNotesConfig, last-writer-wins) so every node keeps its own synced copy and can serve them. See Cluster replication above.
Limitations (v1)
- Last-write-wins only; no Obsidian-style conflict copies.
- No graph view, and no "live preview" inline-rendering edit mode - rich rendering happens in the preview pane (source / split / preview).
- Backlinks and tag listings reflect the vault as it exists at the moment the panel loads; they refresh when you reopen a note, not continuously as you type.
- The preview HTML is rendered from your own trusted vault content and is not sanitised; treat the vault as you would your own files.
- Attachments are read-only in the app (preview/download). Add or replace them by dropping files into the vault folder on disk or via the notes agent; there is no in-app upload yet. Inline-image refs must resolve to a file in the vault (or be an external URL).
- Agent co-editing is not a true three-way merge: if you type in the brief window between the agent writing a note and the editor reloading it, the next auto-save can hit a write conflict (resolve it with the editor's overwrite prompt).
- The vault file watcher binds the resolved vault directory and re-binds when that directory changes in settings; if a watcher fails to start, agent edits still reach the editor on the next periodic sync rather than instantly.