Docs Core Concepts Writing Content

Writing Content

Stable v0.1.0
Schema.md v2
Updated APR 2026

Goal

Syntax mastery. Zero guesswork. Natural writing flow. Structure opt-in. Prose preserved.


3.1 Free-Form First

Write like Obsidian/Notion. Markdown flows naturally. Structure embeds only where needed.

  • Text before TOON = preserved.
  • Text after TOON = preserved.
  • Text between TOON = preserved.
  • Schema validation = TOON rows + properties only. Free-form ignored. Never touched by onde lint.
  • Parser attaches free-form to nearest ## Heading as Body blocks. Round-trip safe.
  • Pre-heading text (before first ##) = discarded. Start files with heading or attach prose to previous node.

Rule: Document = prose first. Database second. TOON = embedded island.


3.2 Two Node Modes

Pick mode by content shape. One graph output. Mix freely in same file.

ModeShapeToken CostUse Case
Standard (Mode 1)Fat. Properties + body + checklists~100-500/nodeUnique items, deep rationale, complex edges
Dense/TOON (Mode 2)Skinny. Pipe rows. Positional cols~15/nodeUniform lists, dashboards, agent context

Standard Example:

## Fix auth bug
type:: task
id:: task-882
status:: BLOCKED
priority:: A
assigned:: [[sarah-chen]]
depends:: [[task-881]] {critical: true}

Token refresh fails silently. Need retry logic.
- [x] Audit service
- [ ] Implement backoff

Dense Example (flat):

## Sprint Backlog:: task[3]{id title status priority}
- task-881 | Fix auth middleware | DONE | A
- task-882 | Fix token refresh | BLOCKED | A
- task-883 | Update docs | TODO | B

Dense Example (indented visual tree):

## Sprint Backlog:: task[6]{id title status priority}
- task-881 | Fix auth middleware | DONE | A
  - task-882 | Fix token refresh | BLOCKED | A {depends: [[task-881]]}
    - task-890 | QA auth flow | BLOCKED | A {depends: [[task-882]]}
  - task-895 | Migrate OpenAPI 3.1 | TODO | B {depends: [[task-881]]}
- task-870 | Update API rate limiter | BLOCKED | A
  - task-883 | Update API docs | TODO | B
  - task-885 | Write integration tests | TODO | B

Indent = visual grouping only. Same data, same graph. Human-readable hierarchy, not a tree structure.


3.3 TOON Syntax (Typed Object Outline Notation)

Header encodes type, count, columns. Rows map positionally. Dual purpose: view + definition.

## Title:: type[N]{col1 col2 col3}

TokenMeaningRequired?
typeNode type from schemaYes
[N]Row count hintNo. Auto-fixed by lint if wrong.
{cols}Positional column mapYes (dense)
- val | valPipe-delimited rowYes
Shared propsstatus:: TODO above rowsNo. Applies to all rows in block.
Indent (2-space)Visual grouping on pipe rowsNo. Parser strips it.

Rules:

  • Column mismatch = error. Count mismatch = auto-fixed.
  • Same ID under multiple parents = same node in graph. Deduplicated.
  • No auto-edge from indent. Agent declares edges via {depends: [[task-1]]}.

3.3.1 Indented Pipe Rows (Visual Tree)

Dense pipe rows can be indented with 2-space increments. This creates a visual hierarchy for human scanning — but the parser treats every row as flat. Indent depth is stripped and discarded during parsing. Each - line becomes an identical DenseRow in the AST regardless of nesting level.

Core Rules

RuleDetail
Indent = visual onlyParser strips leading whitespace. Each - row = flat DenseRow. Indent depth discarded.
### by prop:: val mixingGroup headers and indented rows coexist freely. Both parsed independently — indent on a row doesn’t affect group scope.
Edge props build the graph{k: v} on last column or [[link]] cells declare actual edges. Multi-target: {depends: [[a]] [[b]]}.
DeduplicationSame ID under multiple visual parents = one node in graph. Visual tree is display only.
No auto-edge from indentIndentation never creates parent/child edges. Agent decides all relationships explicitly via {}.

Example: Dependency Chain

## Sprint Backlog:: task[6]{id title status priority}
- task-881 | Fix auth middleware | DONE | A
  - task-882 | Fix token refresh | BLOCKED | A {depends: [[task-881]]}
    - task-890 | QA auth flow | BLOCKED | A {depends: [[task-882]]}
  - task-895 | Migrate OpenAPI 3.1 | TODO | B {depends: [[task-881]]}
- task-870 | Update API rate limiter | BLOCKED | A
  - task-883 | Update API docs | TODO | B
  - task-885 | Write integration tests | TODO | B

The indented structure communicates “task-882 depends on task-881, task-890 depends on task-882” to a human reader. But to the parser, these are 6 flat rows. The {depends: ...} edge props create the actual graph edges. Without those {} braces, no edges exist — indent alone does nothing.

Example: Mixing with Group Headers

## Backlog:: task[5]{id title status priority}
by status:: BLOCKED
- task-882 | Fix token refresh | BLOCKED | A
  - task-890 | QA auth flow | BLOCKED | A {depends: [[task-882]]}
by status:: TODO
- task-883 | Update API docs | TODO | B
  - task-885 | Write tests | TODO | B {depends: [[task-883]]}

Group headers (by status::) are structural — they influence onde organize output and grouping queries. Indented pipe rows are cosmetic. Both coexist without conflict: the indent on task-890 is purely visual, while by status:: TODO is a structural grouping boundary.

When to Use Indent

Use indent when you want human-readable hierarchy without changing the graph structure. Common patterns:

PatternExampleWhen
Dependency chainsParent → child → grandchildTask backlogs, build orders
Feature clustersEpic → sub-tasksSprint planning, roadmaps
Status nestingBlocked → blocking itemsStatus dashboards

When NOT to Rely on Indent

Indent does not create edges. If you need actual graph relationships, always add explicit {k: v} edge properties. A deeply indented row without {depends: [[...]]} is visually nested but graph-orphaned.

Rule: skinny rows, fat files. Dense rows show scan-friendly columns. Indent for visual hierarchy. {} for graph edges. Full detail lives in Mode 1 files linked by ID.


key:: value syntax. Strict spacing. One space after ::.

Property Types

Inferred or schema-enforced: string, int, float, bool, date, datetime, enum.

status:: BLOCKED          # enum
deadline:: 2026-04-15     # date
confidence:: 0.85         # float
active:: true             # bool

[[target]] creates directed edge. Braces attach edge data.

assigned:: [[sarah-chen]]
depends:: [[task-881]] {critical: true, reason: "auth first"}
  • Multi-target: depends:: [[task-1]] [[task-2]]. Space-separated. Requires @many in schema.
  • No comma inside [[ ]]. Comma splits {key: val} pairs.
  • Edge props typed. {critical: "yes"} fails if schema expects bool. Use {critical: true}.
ModeSyntaxLookupUse
ID[[task-882]]O(1) exactAgent default. Stable.
Title[[sarah-chen]]Fuzzy matchHuman-friendly.
Path[[/tasks/auth.md]]File pathUnambiguous.
Relative[[./tests.md]]Relative pathCompact clusters.

Resolution priority: path > relative > ID > title. Convert anytime: onde relink --to id.


3.5 Grouping & Sorting

Structure output without breaking graph. Pure markdown.

Group Headers

by property:: value. No - prefix. Separates rows.

## Active Tasks:: task[4]{id title priority}
by status:: BLOCKED
- task-882 | Fix token refresh | A
by status:: DOING
- task-881 | Fix auth middleware | A

Nested Groups

Indent 2 spaces per level. Inner inherits outer context.

by status:: TODO
  by priority:: A
  - task-881 | Fix auth | [[sarah]]
  by priority:: B
  - task-883 | Update docs | [[tom]]

Sort Directive

@sort: field asc, field desc. Instruction to parser/lint. Nodes listed in declared order.

## Backlog:: task[5]{id title priority}
@sort: priority asc, deadline asc

Schema defaults (@sort, @group) apply when no CLI flags given. Override anytime: onde find task --sort deadline.


3.6 Complex Nodes → Split to Mode 1

Dense row need body text? Checklists? Multi-line rationale? Split it.

  • Keep dense row for scan. Link to full file by ID.
  • Rule: skinny rows, fat files.
  • Dense shows columns. {} declares edges. Full detail lives in tasks/fix-token-refresh.md.
  • Automate: onde organize --split tasks/overview.md → extracts rows to individual files.

3.7 Syntax Traps (Avoid These)

TrapWrongRightWhy
Space before ::status : TODOstatus:: TODOParser expects exact ::
Empty valuepriority::priority:: AMissing value = syntax error
Comma in links[[task-1, task-2]][[task-1]] [[task-2]]Parser breaks at comma
Wrong edge type{critical: "yes"}{critical: true}Schema enforces bool
Column mismatchHeader 3 cols, row 4Match exactlyError. Count mismatch auto-fixed.
Pre-heading textProse before first ##Start with ##Discarded by parser

3.8 How Free-Form Text Survives

  • Parser classifies lines. Non-structural → Body accumulator.
  • Attached to most recent ## Heading.
  • onde lint never rewrites body/free-form text. Only touches properties, links, TOON rows, counts, IDs.
  • Schema validation skips Block::Body entirely.
  • Round-trip guarantee: write prose → lint → prose identical. Zero mutation.

Next Step:The Lint Loop: Your Sync, Heal & Validate Command