Practical step-by-step guide for migrating an OSB workspace to mykb. Based on the plandent migration (2026-03-17) — the first full workspace migration including knowledge, workspace metadata, journal, and documents.
OSB stores everything in a single BRAIN.md per workspace:
~/.osb/main/brain/workspaces/<id>/
├── BRAIN.md # YAML frontmatter + markdown sections
├── decisions.md # Expanded decision log (secondary, not source of truth)
├── journal/ # One .md file per date (## HH:MM — text)
└── docs/ # Standalone document files
BRAIN.md sections:
## State— key-value bullets (Phase, Active, Blocked, Next)## Knowledge— fact bullets with inline(verified:date — source)## Decisions— bullets with inlineWhy:andRejected:## Gotchas— plain bullets## Patterns— plain bullets## Links— markdown table (Resource | Location)## Documents— filename list## Journal— date-prefixed bullets (DUPLICATED — OSB appends on every save)
mykb separates areas (knowledge) from workspaces (project metadata):
~/.mykb/
├── manifest.json # Area index (for Tier 1 injection)
├── kb.db # SQLite FTS5 cache (gitignored)
├── areas/<id>/
│ ├── area.json # {id, name, summary, owner, tags, created, updated}
│ ├── facts.jsonl # One JSON per line
│ ├── decisions.jsonl # + why, rejected fields
│ ├── gotchas.jsonl # + failed, resolution fields
│ ├── patterns.jsonl
│ └── links.jsonl # + url field
└── workspaces/<id>/
├── workspace.json # {id, name, state, areas[], links{}, documents[]}
├── journal.jsonl # {date, text} per line
└── docs/ # Markdown files (scanned for frontmatter)
kbCLI installed and working (kb listshould respond)osbCLI or direct filesystem access to~/.osb/main/brain/- Node.js (for manifest regeneration workaround)
Before starting, verify:
- Load the OSB workspace —
osb load <id>and review the output. Note exact counts of facts, decisions, gotchas, patterns, links, journal entries, documents. - Check for duplicates in OSB journal — OSB appends journal entries on every save, creating duplicates. Count unique entries, not total lines.
- Check BRAIN.md for multi-line entries — Some bullets may span multiple lines. The
kb addCLI expects a single string argument per entry. - Check for special characters — Single/double quotes, angle brackets, dollar signs, backticks in fact text. These need shell escaping in the migration script. Tested safe:
' " ( ) < > | $. Not tested: backticks, exclamation marks in bash. - Verify the area ID doesn't already exist —
kb listshould not show the target area. - Verify the workspace ID doesn't already exist —
kb work listshould not show the target workspace. - Read decisions.md separately — It may contain expanded
Why:andRejected:text not fully captured in BRAIN.md bullet format. Use the richer source.
kb init area <id> "<Name>" "<one-line summary>"The summary appears in the manifest and Tier 1 system prompt injection. Make it descriptive enough for an LLM to decide relevance.
Write a bash script with sequential kb add commands. One command per OSB bullet.
Facts:
kb add fact <area> "<text>" --source "<source>" --tags "<comma,separated>"Map OSB provenance (verified:2026-03-10 — from wiki:Plandent) to:
--source "wiki:Plandent"(the source part)- The verified date is lost —
kb adddefaults to--unverified. See Known Tradeoffs below.
Decisions:
kb add decision <area> "<text>" \
--why "<reason>" \
--rejected "<alternative>" \
--source "<source>" \
--tags "<tags>"OSB format: - PLANDENT-001: text. Why: reason. Rejected: alternative.
Split into --why and --rejected flags. Check decisions.md for the full expanded text.
Gotchas:
kb add gotcha <area> "<text>" --source "<source>" --tags "<tags>"No --resolution flag exists. OSB gotchas don't typically have resolutions.
Links:
kb add link <area> "<label>" "<url>" --source "<source>" --tags "<tags>"OSB stores links in a markdown table — parse the Resource and Location columns.
Patterns:
kb add pattern <area> "<text>" --source "<source>" --tags "<tags>"Running the script:
chmod +x /tmp/migrate-<id>.sh
bash /tmp/migrate-<id>.shVerify after each type:
wc -l ~/.mykb/areas/<id>/facts.jsonl # Should match OSB count
wc -l ~/.mykb/areas/<id>/decisions.jsonl
wc -l ~/.mykb/areas/<id>/gotchas.jsonl
wc -l ~/.mykb/areas/<id>/links.jsonlkb work create <id> "<Name>" \
--areas <area-id> \
--jira "<JIRA-KEY>" \
--wiki "<wiki-url>" \
--repos "<repo1>,<repo2>"This creates the workspace directory and workspace.json.
kb work start <id>
kb work state \
--phase "<phase>" \
--active "<what is active>" \
--next "<what is next>"Copy the state values exactly from OSB's ## State section.
Write a bash script with kb work journal commands:
kb work journal "<entry text>"Important: Deduplicate OSB journal first. OSB duplicates entries on every save. Count unique entries by comparing text content, not line count.
Timestamps: kb work journal uses the current timestamp. Original OSB dates are lost. Preserve them in the entry text (e.g., prefix with "Session 2026-03-10:").
Add a final migration marker entry:
kb work journal "Migrated from OSB to mykb on $(date +%Y-%m-%d). All knowledge, docs, and journal entries migrated. Original dates preserved in text."mkdir -p ~/.mykb/workspaces/<id>/docs/
cp ~/.osb/main/brain/workspaces/<id>/docs/* ~/.mykb/workspaces/<id>/docs/mykb scans workspace directories recursively for .md files and extracts YAML frontmatter description: field. OSB docs don't have frontmatter — they'll appear in the document index with null descriptions. This is fine for now; add frontmatter later if needed.
# Regenerate manifest (kb init area doesn't do this — known bug)
node --input-type=module -e "
import {regenerateManifest} from '$(echo ~/GitHub/mykb/dist/core/manifest.js)';
regenerateManifest('$(echo ~/.mykb)');
"
# Rebuild SQLite cache
kb rebuildkb save --message "Migrate <id> workspace from OSB: N facts, N decisions, N gotchas, N links, N docs, N journal entries"# Compare entry counts
import json, os
area = os.path.expanduser('~/.mykb/areas/<id>')
for typ in ['facts', 'decisions', 'gotchas', 'patterns', 'links']:
path = f'{area}/{typ}.jsonl'
count = sum(1 for _ in open(path)) if os.path.exists(path) else 0
print(f'{typ}: {count}')
# Journal count
ws = os.path.expanduser('~/.mykb/workspaces/<id>/journal.jsonl')
print(f'journal: {sum(1 for _ in open(ws))}')
# Docs count
docs = os.path.expanduser('~/.mykb/workspaces/<id>/docs/')
print(f'docs: {len(os.listdir(docs))}')# Load the area and visually compare against osb load <id>
kb load <id>
# Show workspace state
kb work show <id>
# Test search works
kb search "<distinctive term from a fact>"# All entries have valid JSON
python3 -c "
import json
for typ in ['facts', 'decisions', 'gotchas', 'links']:
with open(f'$HOME/.mykb/areas/<id>/{typ}.jsonl') as f:
for i, line in enumerate(f):
e = json.loads(line)
assert e.get('id'), f'{typ} line {i} missing id'
assert e.get('text'), f'{typ} line {i} missing text'
assert e.get('provenance', {}).get('source'), f'{typ} line {i} missing source'
print(f'{typ}: all entries valid')
"
# Decisions have why+rejected
python3 -c "
import json
with open(f'$HOME/.mykb/areas/<id>/decisions.jsonl') as f:
for line in f:
d = json.loads(line)
assert d.get('why'), f'{d[\"text\"][:40]} missing why'
assert d.get('rejected'), f'{d[\"text\"][:40]} missing rejected'
print('all decisions have why+rejected')
"OSB facts have inline (verified:2026-03-10 — from wiki:Plandent). The kb add fact CLI has no --verified-date flag — all adds default to status: unverified. The kb verify <area> <id> command sets today's date, not the original.
Mitigation: The original provenance text is preserved in the fact text itself (via --source). A future enhancement could add --verified --verified-date flags to kb add.
kb work journal always uses the current timestamp. All migrated journal entries will show the migration date, not the original session date.
Mitigation: Preserve original dates in the journal entry text itself (e.g., "Session 2026-03-10: ...").
OSB facts don't have explicit tags. Tags in the migration script are manually assigned based on content. This is subjective and may miss some useful categorizations.
Mitigation: Tags can be updated later with kb update <area> <id> --tags "new,tags".
OSB docs are plain markdown without YAML frontmatter. mykb's document scanner returns null descriptions for these files.
Mitigation: Add frontmatter to docs post-migration if descriptions are needed for the document index.
createArea() in src/core/area.ts does not call regenerateManifest(). The manifest only gets updated when appendEntry() auto-creates an area. After kb init area, the manifest stays stale.
Impact: Tier 1 system prompt injection won't include the new area until manifest is regenerated.
Workaround: Run the node one-liner in Step 7 after every migration.
Fix: kb init area should call regenerateManifest(). File a bug or fix in src/cli/cli.ts line ~105.
kb rebuild only rebuilds the SQLite database. It does not touch manifest.json.
Impact: Running kb rebuild alone after creating a new area won't fix a stale manifest.
Searching for terms like "PLANDENT-004" causes a SQLite error (no such column: 004). FTS5 interprets the hyphen as a column operator.
Impact: Cannot search for decision IDs or other hyphenated identifiers directly.
Workaround: Quote the search term or search for a substring without the hyphen.
kb work link <area> silently succeeds even if the area doesn't exist. A typo in the area ID creates a broken link.
Impact: Workspace may reference a nonexistent area, causing silent failures in scorer boost and context injection.
Workaround: Always verify with kb list before linking.
Tested safe in bash: ' " ( ) < > | $
Not tested: backticks `, exclamation marks ! in non-interactive bash, heredoc-style multiline text, null bytes, unicode emoji. If a fact contains these, test a single kb add manually before scripting.
As of 2026-03-17, the following OSB workspaces have not been migrated:
| ID | Description | Estimated effort |
|---|---|---|
| budgetsport | Budget Sport | Small |
| chiller | Chiller Fieldwork | Small |
| compass-group | Compass Group | Small |
| dr | Disaster Recovery | Medium (infra knowledge) |
| dsw | Danisco Sweeteners | Small |
| karkkainen | Karkkainen | Medium (similar to plandent) |
| osb | OpenSecondBrain | Medium (self-referential) |
| postnord | PostNord Abakus Warehouse | Medium |
| stark-picking | Stark Picking Dashboard | Small |
| transval | Transval Warehouse | Medium |
| tvv | TVV Warehouse | Medium |
| vfa | vf-agents | Medium |
| vff | ViloForgeFactory | Medium |
| vm-provisioning | VM Provisioning | Small |
| vmctl | vmctl | Small |
| vmgate | VMGate | Small |
Tip: For each migration, load the OSB workspace first (osb load <id>) and count entries before writing the migration script. Some workspaces may be nearly empty.
#!/bin/bash
set -e
AREA="<id>"
NAME="<Name>"
SUMMARY="<one-line summary>"
# Step 1: Create area
kb init area "$AREA" "$NAME" "$SUMMARY"
# Step 2: Facts (one per line, copy from OSB)
kb add fact "$AREA" "<text>" --source "<source>" --tags "<tags>"
# ... repeat for each fact
# Step 3: Decisions
kb add decision "$AREA" "<text>" --why "<why>" --rejected "<rejected>" --source "<source>"
# ... repeat
# Step 4: Gotchas
kb add gotcha "$AREA" "<text>" --source "<source>" --tags "<tags>"
# ... repeat
# Step 5: Links
kb add link "$AREA" "<label>" "<url>" --source "<source>"
# ... repeat
echo "=== Knowledge migration complete ==="
echo "Next: create workspace, set state, migrate journal, copy docs"