v4.0 — The editor where agents live
SoloMD has always been two halves of one product: an editor
that writes plain .md to disk, plus a bundled MCP
server pointing at the same vault. v4.0 adds a third half — a
first-class agent surface inside the editor,
with safety rails (AutoGit branch sandbox, per-run write-cap,
accept/reject UI) that make autonomous writes reviewable
instead of scary.
The brand evolves with it: from "the editor + the MCP endpoint" to "the editor where agents live."
Why this drop took 12 weeks
v3.6 was already feature-stable. The 5 pillars below could
have shipped as five separate minor versions across the
spring. We deliberately didn't — held them under a quiet phase
on main (patch-only since 3.6.2) and treated v4.0
as the next architectural-shift moment instead. The trade-off:
no incremental marketing wins for 12 weeks, but every pillar
gets shipped against the same safety contract, with the same
run handle, the same trace format, the same write-cap registry.
Agent Panel, Recipes, REST API, MCP Federation — different
entry points, one trust model.
Six weeks of that was code; two were finishing the cookbook, wizard, and ja/ko scaffolding; the last four were dogfooding — running v4-beta on real vaults, finding the bugs that integration tests miss. About a third of v4.0's commits landed in that final dogfood window.
The five pillars
1. Inline Agent Panel
Right-side first-class panel, peer to Outline / Backlinks /
Tags / History. Streamed chat-with-vault routed through the
in-process MCP surface and the existing 14-provider AI stack.
[[wikilink]] citations resolve to real notes —
click to open. Tool-call cards expand inline showing every
read/write the agent makes, args and results visible. Reply
too long? Insert drops it into the active
editor at the cursor (or replaces selection); Copy
sends it to the clipboard. Run history persists as plain
markdown under .solomd/agent-runs/ — grep-able,
git-trackable, auditable months later.
2. Agent Recipes / Scheduled Runs
Declarative agent jobs as YAML in your vault:
<workspace>/.solomd/agents/*.yml. Triggers:
schedule (cron), on-save,
on-commit, on-tag-add, manual.
Safety rails are non-negotiable:
-
Every run gets its own AutoGit branch
agent/<recipe>/<run-id>. The recipe runner refuses to start on a dirty workspace — your work-in-progress is never swept into an agent commit. -
Writes land on that branch only. The accept/reject UI
shows the diff before merging to
main. -
A per-run
write-cap(default 5, hard ceiling 50) prevents runaway loops. A model that asks to write 50 files when the cap is 5 gets refusals at the 6th tool call, with no half-applied state. - Rejected runs have the branch hard-deleted — zero residue in your history.
Ships with an 11-recipe cookbook: weekly review, daily summary, TODO extraction, translation pass, citation cleanup, CJK proofread agent, link-rot detector, frontmatter normalizer, outline-to-blog, refactor pass, weekly tag triage. Browse it in Settings → Recipes → Browse cookbook. Install one, edit the prompt, run it.
3. Agent Trace View
Every run (Panel chat OR Recipe) emits a
trace.jsonl per step:
prompt / model_call /
tool_call / tool_result /
git_commit / done. Step cards
expand to show args, results, token counts, cost estimate,
AutoGit branch refs. Replay-from-step rewinds
the run to step N and re-executes from there with
edited inputs — the path to self-correcting recipes. New MCP
tool read_agent_trace(run_id) exposes traces to
other agents, so a recipe can read its own previous failures
and try again differently.
4. Workspace Federation
solomd-mcp --workspace path1 --workspace path2 --workspace path3
— one MCP session, many vaults. Tool signatures gain an
optional workspace parameter; default = first
passed (single-workspace clients keep working unchanged).
AutoGit branches stay isolated per workspace. Settings
→ Integrations adds an MCP profiles UI: name a
bundle of vaults, copy the Claude Desktop config snippet
with one click. Stops being three Claude Code sessions to
bridge two vaults — it's one session, point at both.
5. Ollama first-class
We do NOT bundle a local LLM runtime — Ollama already does that well, and re-implementing it violates "write less code we maintain forever." v4.0 polishes the integration:
- Auto-detect at
localhost:11434(green status indicator in Settings → AI). - One-click install hint when not found (links to ollama.com — we don't proxy the download).
- Three model presets:
rewrite→ qwen2.5:7b ·quick→ qwen2.5:1.5b ·cjk→ qwen2.5:14b. - Empty model list → "Pull recommended (Qwen2.5 1.5B, ~1 GB)" inline button.
- Recipes can specify
provider: local(alias of ollama) for cheap autonomous loops at zero cloud cost.
Your notes never leave the machine — a true claim through BYOK Ollama since v2.2; v4.0 just makes that path discoverable on day one.
Quality bar — the part nobody sees but everybody feels
- First-run wizard. New install meets the Agent Panel within 60 seconds. Guides you through BYOK key entry OR Ollama setup. Re-launchable from Help.
- Public REST API (localhost only, token auth). Same surface as MCP for clients that don't speak MCP yet — Alfred / Raycast / n8n / your own scripts.
- BYOK cost meter. Per-provider running tokens-spent counter, opt-in. Settings → Integrations.
- Localization. ja / ko translations scaffolded for README + iPad metadata. en/zh remain the full-coverage UI languages.
- The MCP server gained a
--workspaceflag (repeatable). Older single-workspace MCP clients keep working; federation is opt-in.
What dogfooding caught (so you don't)
The dogfood window — opening the build on a real machine, with a real workspace, with a real BYOK key — surfaced bugs that 262 unit + integration tests didn't:
- Window UX on macOS. Launch activation
timing was wrong —
set_focus()fromsetupfired before NSApp's run loop had finished launching, so the activation request got dropped and SoloMD opened behind whatever was previously frontmost. Fixed by deferring toRunEvent::Ready+ the window-state plugin's first restore signal. - Sidebar resize handle hover flooded the panel.
A 5px drag handle with
position: absolute; top: 0; bottom: 0got swept up by a global:deep(*)rule that overrodewidth: 5pxto100%. With a translucent accent background-on-hover andz-index: 10, this turned the entire sidebar orange and intercepted every click. Looked like a stuck modal; was a stretched div. - Stuck "streaming…" with no error. A
mistyped Ollama model name made the OpenAI-compat layer
return HTTP 404, the Rust side correctly emitted
solomd://ai-error, but the JS listener gated oncurrentRunIdwhich was set after theawait invoke()resolved — and 404 came back so fast that the error event arrived beforecurrentRunIdexisted. Fixed by minting the request id on the frontend and passing it in via the invoke payload, so the listener is armed before any backend event can fire. - IME composition Enter on 7 input surfaces.
Pinyin user typing Chinese, Japanese kana mode, Korean
Hangul — pressing Enter to commit a candidate triggered
send / rename / open instead of letting the IME handle it.
Added
e.isComposingguards to AgentPanel, FileTree rename, command palette, quick switcher, global search, RAG search, preview search. - Path traversal in agent_tools. When a
parent directory didn't exist,
resolve_in_workspacefell back to the unresolved candidate path;..segments survived thestarts_with(workspace)check and escaped at filesystem level. Confirmed exploit:{"path": "../../tmp/pwn/x.md"}wrote outside the workspace. Rewrote to mirrormcp-server::safety::resolve_in— reject..and absolute paths upfront, walk to the deepest existing ancestor before canonicalising. - AutoGit recipe sandbox swept user WIP. The
checkout migrated uncommitted edits onto the agent branch,
add_all(["*"])swept them into the agent's commit, and a force-restore could destroy in-flight edits. Recipe runner now refuses to start on a dirty workspace, andrestore_headtries safe checkout first.
That last category is the thing this drop is most carefully about. Agents writing to your notes is a data-stake feature. The whole product fails if a recipe quietly corrupts a paragraph and you don't notice until next week. Hence the AutoGit branch sandbox, the write-cap, the accept/reject UI, the path-traversal hardening, the dirty check. Five different invariants holding the same line.
What v4.0 explicitly skips
Saying "no" is part of the product. These were considered and consciously not shipped:
- Bundled local LLM runtime. Ollama already covers this. Re-implementing violates "write less code we maintain forever." BYOK provider stays the path.
- Online recipe marketplace. Server ops + moderation = off-mission. Cookbook ships in-tree.
- Multi-user / team agents. Violates "one window, one writer." This product is for the single author; collaboration belongs to a different product.
- Copilot-style ghost-text autocomplete. Different brand. Ghost-text dilutes the writer's voice; our agents work at vault granularity instead, with batched reviewable writes.
Upgrade notes
No file-format changes. Your existing .md,
AutoGit history, sync setup, BYOK keys all carry over
untouched. The Agent Panel is auto-enabled on first launch
via a one-shot migration (v3.6.x users had a hidden
showAgentPanel: false from the v4-beta
scaffolding period). Don't want it? ⌘⇧P → View:
Toggle Agent Panel turns it off and the choice
sticks.
What's next
The v4.x and beyond open directions on the roadmap: a sandboxed scripting API (capability-scoped, Trilium-style "I want to script my workflow"), CLI v2 (rewrite / commit / semantic search / recipe-trigger from terminal), and the long-deferred "should SoloMD become a business?" question — the original v3.0 paid-sync question, still unanswered, still gates any engineering on the sync tier.
For now: download v4.0, point it at a vault, and try the cookbook's "Weekly review" recipe on a Sunday. If it touches something it shouldn't, the accept/reject UI catches it. If it does what you wanted, you've just spent 30 seconds setting up a thing that runs every week without you.
Thanks to everyone who ran the v4-beta builds during the quiet phase. The bugs above made it into the release notes because dogfooding caught them; the bugs that didn't make it into the release notes are the reason any of this works at all. Issues: on GitHub.