Skip to content

Process lessons for the KB-OS ongoing project (the KB operating system: SMTM, dept structure, routing, dashboards). Captures what bit us and what to do differently. Portal: KB-OS.

  • Structural routing changes must update every folder-scoped view in the same pass (SMTM v5.2, 2026-06-25): introducing dept-based task routing moved tasks into dept Tasks/ folders, but the _DASHBOARD.md Bases queries stayed pinned to file.inFolder("Core/_WorkingOn/Tasks") — so dept-routed tasks silently vanished from the dashboard. → When you change where files live, sweep every query/dashboard/hook/skill that assumes the old location, in the same change. A folder move isn’t done until the views that read that folder are updated.

  • Dogfood new structure on a real task immediately (2026-06-25): filing the SMTM-v5.2 task itself into Core/Processes/Tasks/ (the first task under the new dept-routing rule) is exactly what surfaced the broken dashboard. → Run a new convention on a real unit of work right away; the gaps show up in use, not in review.

  • Vault-wide Bases queries: prefer a path pattern over enumerating folders (2026-06-25): with 20+ /Tasks/ folders and growing, file.path.contains("/Tasks/") auto-covers new dept folders with zero maintenance, where an or of file.inFolder(...) is brittle and needs editing for every new dept. (Caveat: Bases renders only in Obsidian — validate query changes visually in-app.)

  • git mv doesn’t stage content edits made after the move (2026-07-07): during the Ongoing-Projects Core/<Dept>/Projects/ migration, several files got git mv-ed then had their content fixed (broken internal links, stale paths) — those edits sat unstaged until a follow-up git status sweep caught them, and almost shipped in an incomplete commit. → After any git mv + content-edit sequence, re-run git status before committing — don’t assume the rename staged everything.

  • Flat, project-named notes sidestep naming-collision debates for free (2026-07-07): consolidating scattered Ongoing-Project portals into Core/<Dept>/Projects/, naming each note after its own project/repo (ai-config.md, monorepo.md, my_backup.md) rather than a uniform generic name (DASHBOARD.md for all) avoided the shared-folder naming collision that had previously blocked a uniform-naming attempt (KB-OS-portal-rename.md, 2026-07-06). → When multiple items need portal notes in one folder, name-by-identity beats name-by-role when the role name would collide.

  • Templater corruption — open item, root cause unconfirmed (2026-07-07): Templates/Folder Note.md and Templates/SMTM Task.md were found with their <% tp.file.title %> / <% tp.date.now(...) %> placeholders replaced by literal rendered values (“ai-config”, a date) — restored via git checkout. Best guess: Templater fired against the template file itself rather than a target note, possibly during heavy git mv churn while Obsidian had these files open/cached. Not reproduced — if it recurs, check whether templates_folder/enabled_templates_hotkeys in .obsidian/plugins/templater-obsidian/data.json point at a file that’s also open in the active pane during a folder restructure.

  • Obsidian Bases: nested and/or filter groups are broken, not just unconfirmed (2026-07-07): a Core/DASHBOARD.md “Current Focus” query nested an or: (matching two path patterns) inside the top-level and: list, to combine a recency filter with a two-path scope in one block. It rendered as a table (no visible error) but silently ignored the file.mtime recency filter — every matching file showed regardless of age. Splitting into two separate flat and:-only blocks, with the exact same date filter unchanged, fixed it immediately. → Never nest boolean groups in Bases filters; use one flat block per distinct path scope instead. Full gotcha entry: KB-OS-Usage.md.

  • Isolate one variable at a time when a system fails silently (2026-07-07): the broken Current Focus query had two plausible bugs at once (a missing status != "complete" filter, and the nested or: above). Fixing both in the same pass would have left the date-arithmetic question genuinely unresolved either way. Fixing the status filter and un-nesting the boolean group together, then getting Talbot’s confirmation that it now worked, was still one round short of proof — only isolated for certain once the date filter itself was left completely unchanged across both the broken and fixed versions, making the nesting the only variable that changed. → When a silently-failing system has multiple candidate causes, change the minimum needed to test one hypothesis, and get explicit confirmation before claiming which fix worked.

  • A “recently touched” view is self-defeating during a heavy-editing session (2026-07-07): a companion “Recently Touched Projects” Bases block (mtime, last 14 days) showed almost every project-adjacent file in the vault. Not a query bug — this same session’s own restructuring work (repeated git mv, portal edits, JOB_DESCRIPTION updates) had bumped mtime across most of /Projects/ that same day. Removed the block; it had zero signal. → Recency-based “what’s active” views are least trustworthy right after (or during) a big batch-edit session — the editor’s own housekeeping pollutes the signal it’s trying to surface.

  • A rename-for-consistency can silently multiply a duplicate-filename problem (2026-07-07): renaming _DASHBOARD.mdDASHBOARD.md vault-wide (Talbot’s explicit call, overriding an initial recommendation against it) brought the vault-wide count of files literally named DASHBOARD.md to 7 (Core, MBR, SDC, FSS, KB-OS, plus the 2 renamed). Every reference touched during the rename was made path-qualified, but this is now a standing risk for any future bare [DASHBOARD](/fss/dashboard/) link (first-occurrence-wins resolution — see the existing Focus-Pages precedent in Core/CLAUDE.md). → Before a rename that increases a filename’s existing occurrence count, flag the new collision surface explicitly — it’s a permanent characteristic of the vault from that point on, not a one-time migration cost.

  • Templater file regex beats per-folder templates for any vault that grows (2026-07-08): 6 explicit folder-template entries in data.json (one per Tasks/ folder) had to be updated manually every time a new dept folder was added. Switching enable_file_templates: true + regex .*/Tasks/.* covers all current and future Tasks/ folders in one rule. → Prefer file regex over folder templates whenever the same template applies across a class of folders that will grow.

  • One adaptive template + tp.system.suggester() beats N specialized templates (2026-07-08): the temptation was to create 3 separate template files (Standalone / Repo-backed / KB-native). One file with a wizard is simpler: one SSOT, no divergence risk, no “which template do I pick?” decision for the user. → When variant behavior maps cleanly to a menu selection, use one file with a wizard rather than multiple files.

  • tp.file.rename() in Templater enables auto-naming from a description prompt (2026-07-08): calling await tp.file.rename(slug) during template execution renames the file before content is written. Combined with slug = desc.toLowerCase().replace(/\s+/g, "-"), this generates canonical <project>-<slug> filenames automatically — no manual file rename needed after creation.

  • New Ongoing Project must register in 3 places; automate or one will be missed (2026-07-08): Ongoing-Projects.md registry, SMTM Smart template repoProjects array, and KB portal note. The first two were originally manual — the smart template’s project list went stale immediately on the first new project. Fix: /setup-ongoing-project Step 4 now writes all three in the same pass. → Any system with a hardcoded list that mirrors a registry must have the update automated in the same tool that creates the registry entry.

  • Curated dashboard rows go stale the moment a source task closes — cross-check against _active.md before publishing (2026-07-09): the first draft of the Biggest Rocks table listed “ai-config-rules-upgrade Phase 1” as an active rock, sourced from a 3-day-old task file that described it as running — but _active.md showed the project completed and closed 2026-07-07. A curated (non-query) view trades silent-failure risk for staleness risk; its refresh step must include verifying each row’s source is still live. → When hand-curating any “what’s active/biggest” view, verify every row against the master tracker in the same pass.

  • Verify archive completeness at commit time, not just move time (2026-07-09, mbr-content-consolidation): mbr-cleanup.md was confirmed present in archive/2026-07/ right after the Phase-1 mv, but had vanished by commit time (deleted outside the session — Obsidian or manual). Caught only by checking the committed tree (git ls-tree) against the decision table, then recovered from git history and re-archived in an amended commit. → An archive sweep isn’t verified until the commit tree is checked against the file list; the filesystem state at move time is not durable evidence.

  • Check whether a brainstorm file already birthed an SSOT before extracting it line-by-line (2026-07-09): MBR-Upgrades-CC.md (1993L) looked like a huge extraction job, but its own final responses show it created Filter+Focus.md (all 53 rocks) and Executive-Summary.md — so the right verification was a marker-grep of ~12 distinctive ideas against those SSOTs (10/12 hits; only “Lazy Mode” + one story detail were uncaptured). → For any mega-file, first ask “did this session’s output already get consolidated?” and verify by sampling markers — extraction effort should target the gap, not the file.

  • Talbot’s inline edits to a presented decision table are first-class CEO directives (2026-07-09): approval came back partly as checkbox answers and partly as edits inside the Verdict column (“confirmed by Ta”, changed verdicts, “Move. See below (2)”, scope carve-outs like “but keep Strategy/Notes/”). Reading only the ## Talbot Response section would have missed several decisions. → When a decision table is the deliverable, diff the table itself on the next session — the table is a response surface, not static Claude output.

  • Extraction passes must specifically hunt for the last unanswered Talbot Response (2026-07-09): myMBR-OS.md ended with a Talbot Response (QuickBooks 2017→QBO migration, client-data privacy separation, what-data-lives-where) that never got a Claude reply — a real open infrastructure item buried for 3 months in a stale task file. Extracting only “ideas and decisions” would have skipped it because it was a question, not content. → When closing a stale task file, check whether the final Talbot Response was ever answered; unanswered questions route to the owning dept Inbox as open items.

  • Promoting a project to “ongoing” activates the portal/task-location conventions — and can supersede an earlier ad-hoc verdict (2026-07-09): rate-scanner’s approved verdict (“relocate task to Projects/rate-scanner/Tasks/”) was executed, then invalidated hours later when Talbot’s next directive made rate-scanner a registered ongoing project — the 2026-07-07 conventions then required dissolving _WorkingOn/Projects/rate-scanner/ (ongoing ≠ finite folder) and moving the task to dept MBR/IT/Tasks/ (repo-backed rule). → “Make X an ongoing project” is not just a registry row; walk the full convention chain (portal home, task location, live-state SSOT in repo), and when it overrides a previously-approved placement, flag the supersession explicitly rather than silently re-moving.

  • Obsidian Bases not: filter must be nested inside and:, not alongside it (2026-07-10): not: as a peer key to and: under filters: causes a Bases semantic error (raw YAML is rendered instead of a table). When you need both, not: goes inside the and: list as a nested item: - not:\n - condition. Confirmed via Grok snippet — Bases does support not:, just not as a sibling to and:.

  • Editing archive files bumps file.mtime into time-window Bases queries (2026-07-10): Any file edit — including sed for checkbox fixes — updates file.mtime. If a Bases query filters file.mtime > now() - "14 days", bulk-editing old archive files pulls them into the active view. The wrong-layer fix (editing files) made the problem worse than the original. → Diagnose which query layer is matching before touching the files.

  • file.path.contains("/Tasks/") matches /Tasks/archive/ subfolders — always add exclusion (2026-07-10): Archive files live at .../Tasks/archive/..., so the standard task-listing filter also picks them up. Every task-listing Bases query needs an explicit - not:\n - file.path.contains("/archive/") inside its and: block. Added to Core/DASHBOARD.md and MBR/DASHBOARD.md.

  • “MCP-primary” marketing ≠ MCP-only — check the CLI surface before treating a tool as a CLI>MCP violation (2026-07-13, KB-OS-memory-upgrade): CodeGraph is positioned MCP-first, and the initial review flagged it as a conflict with the standing CLI-over-MCP preference. But reading its CLI reference revealed codegraph explore returns identical output to the codegraph_explore MCP tool — so it was adopted CLI-only, no MCP server, no preference exception needed. Had the caveat been taken at face value the tool might have been rejected or forced through MCP unnecessarily. → Before accepting/rejecting a dual-use agent tool on its stated interface, verify the actual CLI surface (--help / CLI docs); the marketing framing routinely hides a first-class CLI.

  • Structural routing changes must update every folder-scoped view in the same pass → promoted to global CLAUDE.md 2026-06-26 (“WHEN FILES MOVE, UPDATE EVERY VIEW THAT READS THEM”)
  • Restructure audits must cover .obsidian/ plugin configs, not just skills/commands → promoted to ai-config/AGENTS.md 2026-07-07 (Skills Quality Standard section)
  • “MCP-primary” marketing ≠ MCP-only — verify the CLI surface first → promoted to ai-config/AGENTS.md 2026-07-13 (CLI vs MCP section)