**The Note is the primary unit of engagement.** You think, react, and explore inside Notes. Sources hold external content. Thoughts and Questions maintain their own identity but are transcluded into Notes so you see everything in one place.
Knowledge doesn't come in clean single-type atoms. Reading an article produces the source AND 3 thoughts about it AND 2 questions it raises. With separate files per type, that's 6 files across 4 folders — too much context-switching for what is really one unit of engagement.
A Note lives in notes/ and is where the user engages with ideas. It can:
sources: in frontmatter![[id]] transclusion```markdown
id: note-2026-03-27-001
type: note
title: "Reading Great Work"
sources: [src-2026-03-26-003]
tags: [creativity, philosophy]
status: active
created: "2026-03-27T10:00:00Z"
updated: "2026-03-27T11:30:00Z"
The Graham essay resonated deeply...
![[th-2026-03-27-001]]
![[th-2026-03-27-002]]
![[q-2026-03-27-001]]
This might become a blog post connecting Graham, Nietzsche, and Ericsson.
```
A Source lives in sources/ and holds external content — the article text, book metadata, PDF content. Sources are reference material. Every Source automatically gets a companion Note created alongside it.
The user never interacts with a Source alone. They interact with the Note.
A Thought lives in thoughts/ with its own ID, status lifecycle (SEED → DEVELOPING → SOLID → ARCHIVED), connections, and sparkedBy references. But instead of being a standalone reading destination, it's transcluded into one or more Notes.
A single Thought can appear in multiple Notes. Its status evolves independently.
A Question lives in questions/ with its own ID, status lifecycle (OPEN → EXPLORING → RESOLVED → EVOLVED), and lifecycle tracking. Same transclusion pattern — embedded in Notes, tracked independently.
A Note that references multiple Sources replaces the Synthesis type entirely. No need for a separate type — it's just a Note with sources: [src-001, src-002, src-003].
```
vault/
├── notes/ ← primary engagement folder
├── sources/ ← external content (articles, books, PDFs)
├── thoughts/ ← standalone ideas with own lifecycle
├── questions/ ← standalone questions with own lifecycle
├── inbox/ ← raw captures before AI processing
└── .steel/
├── index.db ← disposable SQLite index
└── sync.db ← sync state
```
![[th-2026-03-27-001]] — embed a Thought inline![[q-2026-03-27-001]] — embed a Question inline[[path/to/note]] — navigation link (existing behavior, unchanged)The ! prefix distinguishes transclusion (embed content) from navigation (link to).
1. Storage: The .md file stores ![[id]] — never the expanded content.
2. Display: At read/display time, TransclusionResolver replaces ![[id]] with the rendered content of the referenced note.
3. Indexing: TransclusionExtractor finds all ![[id]] references and stores them in the links table with link_type = "transclusion".
In the .md file:
```markdown
![[th-2026-03-27-001]]
```
Rendered in CLI or iOS:
```
┌─ Amor Fati Connection ─────────────── seed ─┐
│ │
│ The bus ticket collector analogy maps │
│ perfectly to Nietzsche's amor fati — │
│ pursuing something not because it's useful │
│ but because you can't help it. │
│ │
└──────────────────────────────────────────────┘
```
A Thought in one Note can be transcluded in another. This is how ideas connect across Notes without duplication:
notes/reading-great-work.md contains ![[th-2026-03-27-001]]notes/intrinsic-motivation.md also contains ![[th-2026-03-27-001]]The Thought file exists once in thoughts/. Both Notes display its content. If the user updates the Thought, both Notes reflect the change.
"Which Notes transclude this Thought?" is a simple query:
```sql
SELECT n.* FROM notes n
JOIN links l ON l.source_id = n.id
WHERE l.target_path = :thoughtId AND l.link_type = 'transclusion';
```
When a Source is created (via capture or steel create source), a companion Note is automatically created alongside it:
```
steel create source --title "Great Work" --url "https://..."
→ creates sources/great-work.md (the external content)
→ creates notes/great-work.md (empty engagement Note, linked to source)
```
The Note starts with just the Source reference. The user fills it in as they engage with the material.
Previously, the AI pipeline wrote everything into a single Source file (article text + user's voice note + related links). Now it splits:
| Content | Goes into |
|---|---|
| Extracted article/book/PDF text | sources/ file |
| User's voice note / capture context | notes/ file (companion Note) |
| AI-generated tags | Both files |
| AI-suggested related links | Note file |
This cleanly separates external content (Source) from user engagement (Note).
Notes have a simple status — they're living documents, not pipeline artifacts:
| Status | Meaning |
|---|---|
ACTIVE | Currently being worked on |
PARKED | Set aside for now |
ARCHIVED | Done / no longer active |
The sealed interface is renamed from Note to VaultItem. This frees up Note for the concrete class — the user-facing concept.
```kotlin
sealed interface VaultItem {
val id: String
val type: NoteType
val title: String
val created: Instant
val updated: Instant
val tags: List<String>
val filePath: String
val bodyText: String
}
// Concrete types implement VaultItem
data class Note(...) : VaultItem // NEW — the engagement unit
data class Source(...) : VaultItem // unchanged fields
data class Thought(...) : VaultItem // unchanged fields
data class Question(...) : VaultItem // unchanged fields
@Deprecated("Use Note instead")
data class Synthesis(...) : VaultItem // kept for migration
```
```kotlin
enum class NoteType(val prefix: String, val directory: String) {
NOTE("note", "notes"),
SOURCE("src", "sources"),
THOUGHT("th", "thoughts"),
QUESTION("q", "questions"),
@Deprecated("Use NOTE instead")
SYNTHESIS("syn", "syntheses");
}
```
| File | Purpose |
|---|---|
TransclusionExtractor.kt | Extract ![[id]] references from markdown body |
TransclusionResolver.kt | Replace ![[id]] with rendered content at display time |
| File | Change |
|---|---|
Note.kt | Rename interface to VaultItem, add Note data class, deprecate Synthesis |
NoteType.kt | Add NOTE, deprecate SYNTHESIS |
Statuses.kt | Add NoteStatus enum |
NoteFactory.kt | Add NoteType.NOTE parsing branch |
NoteSerializer.kt | Add Note serialization branch |
WikilinkExtractor.kt | Negative lookbehind regex to skip ![[ |
VaultManager.kt | notes/ dir, createSourceWithNote(), transclusion indexing |
VaultIndexer.kt | Transclusion link indexing |
SteelNotes.sq | Add source_refs column to notes table |
Main.kt (CLI) | steel create note, Source auto-creates Note, read resolves transclusions |
One new nullable column on the existing notes table:
```sql
ALTER TABLE notes ADD COLUMN source_refs TEXT;
```
Stores comma-separated Source IDs for Notes. No new tables needed — the links table already supports transclusion via link_type = "transclusion".
Why not embed Thoughts/Questions as headings inside the Note file?
Because Thoughts and Questions need their own identity. A Thought has a status that evolves (SEED → SOLID), connections to other Thoughts, and can appear in multiple Notes. If it's just a heading, it can't be independently tracked, searched, or linked.
Why transclusion instead of just links?
Links require the user to click through to see content. Transclusion shows the content inline — you see everything in one place while each piece maintains its own file and lifecycle.
Why does every Source get an auto-created Note?
Because you never just "have" a source — you engage with it. The Note is where that engagement lives. Without the auto-creation, users would need to manually create a Note every time they add a source.
What happens to existing Synthesis files?
A migration command (steel migrate syntheses) converts them to Notes: remaps IDs, moves sourceRefs to sources, maps status. The Synthesis class stays in the codebase (deprecated) so existing files parse correctly.
Can a Thought exist without being in any Note?
Yes. AI-generated Thoughts from the capture pipeline may not be in a Note yet. Search and the vault list still find them. The user can add them to a Note later via ![[id]].
What about AI processing?
Only captured content (photos, share sheet, URLs) gets AI processing. When the user types directly (CLI steel create, in-app editor), no AI runs — the content is saved as-is.