siyuan-kernel-ops
Skill1 starsChanged 13 days agoMITView on GitHub
Safely search, read, create, edit, move, and batch-process a local SiYuan knowledge base from an AI agent runtime through SiYuan's Kernel HTTP API. Use when the user mentions SiYuan/思源笔记, notebooks, documents, blocks, backlinks, attributes, SQL/full-text search, daily notes, or asks the agent to organize or write back knowledge. Prefer block-level, idempotent edits; protect block IDs, references, assets, and user data. Triggers: 思源 API, 读笔记库, 批量改笔记, 思源端口, 思源内核, 上传附件到思源.
In this repository30 other files it gives its agents.
- AGENTS.mdAGENTS.md
- notion-im-helper/CLAUDE.mdCLAUDE.md
- 3d-printing-cad-designSkill
- agent-skills-syncSkill
- captain-resourceSkill
- csdn-publish-mcpSkill
- deep-research-report-pipelineSkill
- doc-plain-zhSkill
- feishu-bot-configSkill
- first-shotSkill
- investment-analysisSkill
- layoutfixSkill
- md2docx-formalSkill
- md-to-70asunflower-latexSkill
- multi-publish-mcpSkill
- notebook-patchingSkill
- notion-im-helperSkill
- paper-figuresSkill
- paper-reviewSkill
- paper-writingSkill
- project-initSkill
- project-reviewSkill
- siyuan-backup-syncSkill
- siyuan-markdown-formula-formatterSkill
- snapSkill
- strict-docx-auditSkill
- trek-mcp-opsSkill
- visual-diagramsSkill
- weekreportSkill
- xhs-pipelineSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
16 rules. About 0 are common, 8 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈8
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 18
Respect intent: Search/read requests stay read-only — never turn a summary, checklist or suggested action into a write unless the user …
- Distinctive: line 19
Read narrowly, write minimally: Use the smallest stable block that satisfies the request.
- Distinctive: line 22
Stop on inconsistency: Never auto-retry an ambiguous write.
- Distinctive: line 23
Confirm destructive actions immediately before doing them: Never print, log or store the token.
- Distinctive: line 55
If no URL authenticates, report one diagnosis (not running / bad token / unreachable / malformed response).
- Very specific: line 100
Use dataType: "markdown" unless you need exact DOM.
- Very specific: line 109
Write exactly once, require code == 0.
- Very specific: line 110
Read the after-state from getChildBlocks (live) and assert the postcondition — correct block/path, exact content, neighbours intact, no …
- Distinctive: line 120
Verify each item; stop on the first integrity failure.
- Very specific: line 125
Never put the workspace inside a third-party sync drive.
- Distinctive: line 127
Don't start git commit/push, repo reset or data-repo maintenance unless asked.
- Distinctive: line 133
Every entry below cost real debugging or real data loss.
- Very specific: line 168
Q5 ·
insertBlockwithnextIDcan be ignored: when the neighbouring blocks lack a --- separator — the block lands after the anchor … - Very specific: line 183
Use /api/filetree/removeDocByID {id} for deletion — path-based /api/filetree/removeDoc reports block not found no matter how you spell the …
- Very specific: line 220
Read-only work: concise findings with siyuan://blocks/<id> deep links.
- Very specific: line 249
v3.8.8: every endpoint above is live.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Agent runtimes × SiYuan — kernel operations
SiYuan is a block graph, not a folder of Markdown files. Block IDs, references, attributes, assets and backlinks are the user's data — protect them. Work through the Kernel HTTP API; never write .sy, SQLite, indexes or conf.json.
Operating contract
- Resolve first. Get the exact notebook/document/block ID before writing. Ambiguous match → show candidates and ask.
- Respect intent. Search/read requests stay read-only — never turn a summary, checklist or suggested action into a write unless the user asked to change SiYuan.
- Read narrowly, write minimally. Use the smallest stable block that satisfies the request.
- Be idempotent. Re-running a task must not duplicate documents, headings or appended text.
- Verify every mutation.
code:0is not proof — read back and assert the postcondition (§5). - Stop on inconsistency. Never auto-retry an ambiguous write; re-read state, then decide.
- Confirm destructive actions immediately before doing them. Never print, log or store the token.
1. Connect
Precedence: SIYUAN_URL + SIYUAN_TOKEN → SIYUAN_CONF → D:\MyData\Warehouse\siyuan-backup\conf\conf.json → %USERPROFILE%\SiYuan\conf\conf.json → sibling D:\MyData\*\conf\conf.json. Several workspaces can coexist on one machine; sy.py tries every token it finds and keeps the first base a data endpoint accepts.
The Kernel API is plain HTTP POST + JSON. Use Python with ensure_ascii=False — Git Bash mangles Chinese inside inline curl -d '{"data":"中文"}'.
Use the tested helpers shipped with this skill: scripts/sy.py — connect() returns (base, token) after authenticating against a live kernel; call(base, token, path, payload) posts JSON and raises on code != 0. Both are already correct about port discovery, token rotation and encoding — do not re-implement them. For whole-document surgery use scripts/siyuan_surgery.py (baseline export → ZWSP-tolerant replacement → drain-aware Fix A → dual-marker verification; see Q1/Q13/Q20) — it encodes the traps below as code, so don't re-derive them per session.
import sys; sys.path.insert(0, r"<skill dir>/scripts")
from sy import connect, call
base, token = connect()
notebooks = call(base, token, "/api/notebook/lsNotebooks")["notebooks"]Replace <skill dir> with the skill's base directory. Smoke test: python scripts/sy.py (prints kernel URL, version, notebooks).
Connection rules — every one of these has silently eaten writes:
| Rule | Why |
Treat serverAddrs as a hint: try each candidate URL plus 127.0.0.1:6806, and accept only a base where the token passes lsNotebooks | A stale conf can list a dead port while the live kernel listens on 6806 under a different conf. A stale port may answer 200/code:0 for everything — including writes that are then dropped. |
With several conf.json (profile-level vs workspace-level), choose by token, never by file mtime or declared port | An old workspace's token returns 401 on the live kernel; the config that looks newest can still be dead. |
Validate with a data endpoint (lsNotebooks → data.notebooks), not /api/system/version | version answers on a dead/stale kernel. |
Write only through 127.0.0.1 | A LAN entry in serverAddrs reads fine but silently discards writes. |
Read api.token in full each time | The token rotates on reset; a truncated value yields misleading 401/404. |
Pass dangerouslyDisableSandbox: true on kernel calls | The Bash sandbox blocks localhost and fails silently. |
| Never try to launch SiYuan | The sandbox cannot headless-start it; API access needs the desktop client already running. |
| Never restart/reset SiYuan, and close it before any git op on the workspace | SQLite write conflicts; the user closes it, not you. |
If no URL authenticates, report one diagnosis (not running / bad token / unreachable / malformed response). Reads may be retried after reconnecting; writes may not.
2. Resolve targets
Accept siyuan://blocks/<id> links and extract the ID.
| Need | Endpoint |
| Notebook list | /api/notebook/lsNotebooks |
| Notebook + human path → doc ID | /api/filetree/getIDsByHPath {notebook, path} |
| ID → human path (display/verify) | /api/filetree/getHPathByID |
| Keyword / natural language | /api/search/fullTextSearchBlock |
| Metadata, attributes, dates, bulk candidates | read-only /api/query/sql |
Zero results → search. One → use it. Several → disambiguate; never silently pick the first. Don't recursively walk the whole KB unless path lookup and search both fail.
SQL: one SELECT/WITH … SELECT only, always LIMIT (default 50, max 500), escape user text, never INSERT/UPDATE/DELETE/DROP/ALTER/PRAGMA/ATTACH, and fetch full content only for selected candidates. Return compact results: title/context, notebook, hpath, block type, updated time, ID.
3. Read
| Need | Endpoint |
| Whole doc as Markdown | /api/export/exportMdContent |
| One block's source | /api/block/getBlockKramdown (stale — see Q2) |
| Direct children | /api/block/getChildBlocks (live) |
| Human path | /api/filetree/getHPathByID |
| Type / parent | /api/block/getBlockInfo |
| Attributes | /api/attr/getBlockAttrs |
| On-screen block order | /api/block/getDocBlocksOrders |
For a large doc, read the outline/children first, then only the relevant blocks. Preserve assets/... links, block refs, query embeds, IAL attributes and code fences. An exported title that repeats the doc title is not a duplicate body heading — never delete it for that reason.
4. Write
| Intent | Operation |
| Edit one paragraph/heading/list/code block | /api/block/updateBlock with id |
| Append to a document/container | /api/block/appendBlock with parentID (not id, Q18) |
| Prepend | /api/block/prependBlock with parentID |
| Insert near a known block | /api/block/insertBlock with previousID/nextID/parentID |
| Create document | /api/filetree/createDocWithMd (returns the new doc id as a plain string) |
| Rename / move document | /api/filetree/renameDocByID / moveDocsByID (confirm first) |
| Set custom attributes | /api/attr/setBlockAttrs |
| Delete | only after explicit confirmation |
Use dataType: "markdown" unless you need exact DOM. Let the kernel mint IDs for genuinely new blocks.
Whole-document replacement is exceptional — it can rebuild descendants, change block IDs, break refs and detach attributes. Only when the user explicitly asks for a full rewrite: export + timestamped backup outside data/, hash it, inspect refs/assets, re-export and abort if the hash moved, get explicit confirmation, write once, verify. Never use "insert all new, then delete all old" as a safety pattern.
5. Verify
- Resolve the exact ID and hpath.
- Read the before-state.
- Preflight: skip if already correct; detect duplicates by normalized content or an idempotency attribute (
custom-agent-op=<id>; older docs may carrycustom-workbuddy-op). - Write exactly once, require
code == 0. - Read the after-state from
getChildBlocks(live) and assert the postcondition — correct block/path, exact content, neighbours intact, no duplicate.
On failure: reconnect, re-read, decide whether it applied. Partially or ambiguously applied → stop and report; never issue a second write. Provably not applied → explain, retry only with approval.
6. Batch
- Build a read-only candidate list from
getChildBlocks; filter by type/parent/content. Never select by timestamp prefix (Q6). - Show scope when impact is non-trivial; get confirmation for destructive or structural batches.
- Chunk deterministically (default 20), sequential only — never parallel writes to one document.
- Journal outside
data/: IDs, before-hashes, intent, status, errors. No token, no note bodies. - Verify each item; stop on the first integrity failure; re-runs skip completed items.
7. Filesystem & sync safety
.syaccess is read-only diagnostics of last resort. Never modifydata/,storage/, SQLite, indexes, history or sync metadata behind a running kernel.- Never put the workspace inside a third-party sync drive. Backups: SiYuan snapshot/history/export, not ad-hoc file copies; a Git commit is not a consistent backup.
- Preserve
assets/references; upload new files via/api/asset/upload. - Don't start git commit/push, repo reset or data-repo maintenance unless asked.
- Disaster recovery (read-only): history snapshots sit at
<workspace>/history/<ts>-update|delete/<nb>/…/<doc>.sy(~10 min cadence). In the v3.syformat nodes are{ID, Type, Properties, Children}. Re-inserting a snapshot's kramdown with its original{: id=…}lines restores the original block IDs. - Companion skills — don't duplicate them here: the
robocopy /MIR+ git backup routine lives insiyuan-backup-sync; LaTeX/KaTeX cleanup and 公式入库 insiyuan-markdown-formula-formatter.
8. Quirks — read the index before writing
Every entry below cost real debugging or real data loss. Each is a still-current behaviour, not a changelog.
| Symptom | Entry |
code:0 but nothing changed | Q1, Q3, Q12, Q13 |
| Multi-paragraph markdown → only first paragraph landed | Q12 |
| Added table rows / edited list item → rest vanished | Q3 |
| Deleted a heading, orphan children remain | Q3 |
| Formula renders blank or shows raw LaTeX | Q4 |
| "看不到" but data is on disk | Q7 |
| Blocks landed at doc top or wrong order | Q5, Q11 |
| Bulk delete wiped real body blocks | Q6 |
| Child doc created one level too deep | Q9 |
| Whole callout / pasted article vanished | Q10 |
| Hand-built table has extra empty columns | Q14 |
| Verification count is double the truth | Q16 |
| Request times out right after a reload | Q17 |
Field [parentID] is required | Q18 |
| Fresh doc still starts with a blank line | Q19 |
removeDoc reports block not found | Q10 |
| Asset link points at a file that isn't there | Q10 |
| Doc won't drain to zero children while rebuilding | Q13 |
| Your edits and the user's newer manual edits race | Q20 |
Q1 · updateBlock(rootId, markdown) MERGES, it does not replace. Old children survive alongside the new ones — re-export looks unchanged, getChildBlocks shows both. Fix A: delete every child ID first, then updateBlock(rootId, markdown). Fix B (nuclear, changes the doc ID): removeDocByID + createDocWithMd. Stale blocks are the ones whose updated="<ts>" equals the doc's original creation timestamp.
Q2 · getBlockKramdown and .sy are stale caches; getChildBlocks is live. Kramdown can lag a write by 13 s+ and makes verification lie. Always assert against getChildBlocks children markdown. Direct .sy reads are a read-only last resort. Zero-width set to strip: \u200b \u200c \u200d \u200e \u200f \u202a-\u202e \u2060-\u2064 \ufeff \u061c \u034f — build the regex from a normal string, not a raw string, or \u200b never matches.
Q3 · Three partial-write traps, all code:0.
updateBlockon atablewith added rows silently keeps the old table → build a new sibling table instead, or delete + insert.deleteBlockon a heading does not cascade — children orphan and still match searches → collect and delete each child ID too.updateBlockon anllist container replaces the whole list; pass the complete item set or lose the rest.
Q4 · Formulas render only as $$…$$ / $…$. \[…\] and \(…\) show up as literal text. Spacing macros (\; \: \, \quad \qquad) and two equations crammed into one block render blank — one equation per $$, mirroring an existing m block's style. Detection, conversion and batch repair live in siyuan-markdown-formula-formatter; don't re-derive them here.
Q5 · insertBlock with nextID can be ignored when the neighbouring blocks lack a --- separator — the block lands after the anchor instead of between. Verify order immediately; on misplacement delete and re-insert, don't try to move it.
Q6 · Never bulk-delete by timestamp prefix. A doc's body and an appended section can share a minute-prefix, so 2026…-* deletes legitimate blocks. Enumerate exact IDs from getChildBlocks, filter by type + content + parent containment, print the list and count, delete one ID at a time, then assert the deleted count matches exactly and no lead-in lost its body block.
Q7 · A kernel write does not refresh the desktop UI. An open tab keeps its in-memory render; the tree may miss a new doc. This is display, never a reason to re-write. POST /api/ui/reloadUI fixes a stale tab; closeNotebook + openNotebook refreshes the tree. Verify with getChildBlocks first, then reload.
Q8 · prependBlock on a document root can eat the title block and drop the content while returning code:0. Don't use it to "add a note at the top" — edit the source and rebuild (Q1 Fix A), then assert exactly one H1.
Q9 · A / inside a title creates nested empty docs. createDocWithMd treats the final path segment's / as a separator, producing an empty parent plus a child. Strip / from titles (use 与, &, /); if it already happened, removeDocByID both and recreate.
Q10 · Endpoint names and the container-delete trap.
- Children are
/api/block/getChildBlocks, not/api/filetree/getChildBlocks(404). /api/filetree/getDocInfois 404 — usegetHPathByID+getBlockInfo./api/filetree/listDocsByPathrequires{notebook, path}, wherepathis the internal.sypath (fromgetBlockInfo.path, e.g./2026… .sy), not an hpath — passing/技术杂项fails withThe system cannot find the file specified. To list child docs by human path, resolve withgetIDsByHPath+getBlockInfo, or use read-only SQL onblocksfilteringhpath LIKE '/parent%'.asset/uploadis multipart with fieldfile[].- Use
/api/filetree/removeDocByID{id}for deletion — path-based/api/filetree/removeDocreportsblock not foundno matter how you spell the path (ID / hpath /.syall fail). asset/uploadon a filename that already exists inassets/does not upload a copy —succMapreturns the existing asset's real path (timestamp suffix included). Rewrite links to that returned name, never the original; the names differ from what you sent. (An identical-content re-upload has also been seen returning an emptysuccMap— either way, gate success onsuccMapcontents, never oncode:0.)getChildBlocksreturns a container'smarkdownincluding all nested children, so a content/asset substring can match the container — and deleting it wipes the whole pasted article. Before anydeleteBlock, asserttypeis a leaf (p/t/l/h/c/m).
Q11 · Moving and ordering. moveBlock (camelCase — move_block is 404) takes {id, parentID, previousID} and places the block after previousID; move a run block-by-block, feeding each previous ID forward. exportMdContent can briefly return data: None after a move burst — transient index rebuild, retry (4 × 3 s) instead of reporting failure. A U+200B in exported Markdown is usually Lute's inline-node guard, not dirty data: if the stored markdown has none, don't "clean" it.
Q12 · updateBlock keeps only the FIRST block of multi-block markdown, silently. One block per call; anything larger goes through insertBlock (which splits headings/code/tables/lists) or appendBlock, or goes through the whole-doc Fix A pipeline (scripts/siyuan_surgery.py). Editing an h needs the #-prefix and an m needs $$ wrapping. Every heading level reports type == "h" — the level lives only in the # count — and headings are siblings under the root, so level shifts mean rewriting each one. Callout annotations round-trip losslessly: a > [!NOTE] blockquote parsed by createDocWithMd/updateBlock lands as a callout block (SiYuan's /note). For a whole pasted article prefer one createDocWithMd over a chain of appendBlock (live: 57K chars, 54 tables, 475 top-level blocks in 0.6 s; verify block counts afterwards).
Q13 · Rebuild drains to a placeholder, and large deletes can silently half-fail. After deleting all children SiYuan regenerates exactly one zero-length p — that placeholder (or truly empty) is the drained state to wait for; a strict "must be zero children" loop never terminates. The delete loop itself has been observed to partially fail at scale (every call code:0, 56–85 % of blocks surviving), though a 163-child delete+rebuild has also succeeded in full — treat scale behaviour as unreliable and always re-read after deleting: survivors (beyond the placeholder) get deleted again before updateBlock; scripts/siyuan_surgery.py implements exactly this. Never write while getChildBlocks(): delete… blindly. When preserving the doc ID matters less than correctness, skip Fix A for Fix B (removeDocByID + createDocWithMd). Persist any new doc ID; a lost ID makes a successful rebuild look like a vanished document.
Q14 · Hand-built tables: the pipe is both separator and attribute prefix. Cells carry |{: style="…"}cell; naive concatenation yields ||, parsed as an extra empty column. Build as "|" + "|".join(S + c for c in cells) + "|", separator row "|" + "|".join(" :---: " for _ in cells) + "|". Blank lines between rows make each row a separate paragraph — the doc then reports zero table blocks. Assert the table-block count matches the source.
Q15 · Round-tripping kramdown carries block attributes back. getBlockKramdown returns {: id="…" updated="…"} with the source; feeding it to updateBlock risks writing attributes into the body. Read content from getChildBlocks(...)[i]["markdown"], or strip a leading {: … }. Those updated values are how you tell stale blocks from fresh ones (Q1).
Q16 · Container vs leaf reads. getChildBlocks(leafId) returns the leaf's children, never the leaf itself — read a block from its parent's list or build an id → markdown index from the doc root. Containers (callout, b quote, superblock) return nested content too, so counting occurrences over-counts; count only leaves whose markdown fullmatches. The walk can also visit the same list item twice — never treat traversal count as block count.
Q17 · The kernel goes unresponsive for a few seconds after reloadUI (WinError 10060). Retry (3 × 2 s) around anything that follows a reload instead of reporting a write failure.
Q18 · Write-endpoint parameters are not uniform. appendBlock/prependBlock require parentID; id is not an alias (Field [parentID] is required). insertBlock needs one of parentID/previousID/nextID. updateBlock/moveBlock take id. Probe instead of guessing — POST {} and read the Field [...] is required message; validation runs before the write, so the probe is free and survives version upgrades that rename a parameter. appendBlock does split multi-block markdown correctly (one call took 130 blocks: 23 headings, 6 tables, 39 lists, a quote, a code fence).
Q19 · A new document holds a zero-length p that regenerates if you delete it early. Order: appendBlock → getChildBlocks → if children[0] is an empty p, delete that one ID → re-read to confirm the real first block leads. Same for createDocWithMd output.
Q20 · The user may be editing the same doc while you operate on it — and their edits include deletions. A manual edit that lands between your exportMdContent baseline and the delete+rebuild gets overwritten, and the user's client can then sync its newer tree back — end state can mix both. Export the baseline immediately before surgery, not at session start; afterwards verify a fresh export for BOTH your marker strings and the user's content markers (their newest note, their added sections). Content that exists in your local copy but not in SiYuan is not automatically "lost" — the user may have deleted it on purpose; never "restore" it without asking. verify_markers in scripts/siyuan_surgery.py implements the dual-marker check.
9. Authoring workflow (primary use case)
- Dedup first — search before creating; prefer append or a block reference over a duplicate doc.
- Create or update —
createDocWithMdfor a new doc (itsdatais the doc id as a plain string, not a dict);updateBlockon the smallest block, orappendBlockfor a new section.- Child-doc trap: build the child path from
getHPathByID's human path +/子文档标题. Never pass the SQLpathcolumn (an internal.sypath) — it nests the doc under an ID-only branch.
- Child-doc trap: build the child path from
- Assets — upload local images and rewrite links to
assets/<name>; never leavefile://or absolute paths. - Verify — re-export and assert headings/links/content.
- Publish —
exportMd/exportHTMLfor 公众号/知乎/CSDN/个人站,exportDocxfor Word/报告,exportEPUBfor long-form.
The Markdown write → verify → export pipeline is the default path. Don't over-index on block-graph theory. When an endpoint is missing in the installed version, check that version's API help — never guess fields or fall back to mutating data files.
10. Response style
Read-only work: concise findings with siyuan://blocks/<id> deep links.
Writes: Changed (paths + IDs) / Skipped (already correct) / Verified (postcondition) / Warning (only if genuinely uncertain). Don't narrate raw API traffic unless debugging was asked.
Appendix · Endpoints
| Purpose | Endpoint | Note |
| Notebooks | /api/notebook/lsNotebooks | connection probe |
| Open/close notebook | /api/notebook/openNotebook · closeNotebook | tree refresh |
| Path ↔ ID | /api/filetree/getIDsByHPath · getHPathByID | {notebook, path} |
| List docs | /api/filetree/listDocsByPath | requires notebook |
| Create / rename / move / remove doc | /api/filetree/createDocWithMd · renameDocByID · moveDocsByID · removeDocByID | |
| Daily note | /api/filetree/createDailyNote | |
| Search | /api/search/fullTextSearchBlock · /api/query/sql | read-only SQL |
| Read block | /api/block/getBlockKramdown · getChildBlocks · getBlockInfo · getDocBlocksOrders | |
| Write block | /api/block/updateBlock · appendBlock · prependBlock · insertBlock · moveBlock · deleteBlock | see Q18 for parent fields |
| Attributes | /api/attr/getBlockAttrs · setBlockAttrs | |
| Assets | /api/asset/upload | multipart field file[] |
| Web clip | /api/network/forwardProxy | |
| Export | /api/export/exportMdContent · exportMd · exportHTML · exportDocx · exportEPUB · exportAttributeView · exportSY · exportData | |
| Convert | /api/convert/pandoc | |
| Templates | /api/template/render · renderSprig | |
| Databases (AV) | /api/av/getAttributeView · searchAttributeView · getAttributeViewKeys | read rows by key; never updateBlock a DB root (Q1) |
| Import | /api/import/importStdMd · importSY · importData | uploads are multipart, field file; there is no importObsidian endpoint |
| Bookmarks | /api/bookmark/getBookmark · removeBookmark | removeBookmark needs bookmark |
| File-tree sort | /api/filetree/setSort | notebookSorts / docSorts, not both empty |
| UI | /api/ui/reloadUI | then expect Q17 |
| System | /api/system/version | not a valid liveness probe |
v3.8.8: every endpoint above is live. AI/MCP/OIDC are config-layer only — there is no /api/ai/*, /api/mcp/* or /api/auth/* (all 404), and MCP is client-side, so exposing SiYuan to an external AI still needs a community plugin. Never edit conf.json while the kernel runs.
Inline formatting traps: ~text~ is SiYuan's subscript syntax — paired ~ in a page range (112~216) silently subscripts everything between them; use - instead, and don't try to "fix" it back to ~ (it re-parses). The double space after **bold** is normal serialization, not garbage. Rapid block-by-block updateBlock bursts lose or cross-contaminate writes — prefer one whole-document write for a full rewrite, and exportMdContent to a local file before touching an article.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

