Skip to content

id: research/design: fork a Rust KDL stack for ID — universal + multiple annotations, upstream archaeology, fork-base selection (extends #5688) #5753

Open

Description

@dezren39

Summary

Research + design for forking a Rust KDL stack for ID, extending #5688
(KDL → JSON → atomic triples → SQL/PGQ). This issue runs Drew's KDL
thread to ground in three directions:

  1. Archaeology (laptop hunt): the missing KDL material Drew
    remembered exists — it is pkgs/mappy/migrations/limit.kdl, his
    Dec-2023 scratchpad, and it is the concrete ancestor of id: design: KDL as human presentation format + triples + ANSI graph SQL (SQL/PGQ) for ID data #5688's
    triples design.
  2. Archaeology (upstream): Drew's actual capability asks to the KDL
    project were found — two GitHub Discussions in kdl-org/kdl
    (CI: goreleaser workflow fails in setup on the next tag push — go-version-file points at go.work, deleted in Feb 2024 #344 Children Annotation, mcpx tests: assert the spawn happened in TestEnsureDaemonCancelledWhileWaiting #362 Property Annotation, both
    Dec-2023) that were not adopted into KDL 2.0. Together with his
    spec-era comments and his Go-ecosystem work (kdl-go, koanf), they
    define the fork's extension list.
  3. Fork base: fork kdl-org/kdl-rs (the kdl crate, v6.7.1) —
    the only maintained, format-preserving, dual-version (1.0 + 2.0)
    Rust KDL document parser, with serde and KQL built in. The
    annotation generalization Drew wants is a contained AST change
    there, and a rebuild-everything proposition anywhere else.

Umbrella: epic #2056. Parent design: #5688 (this issue supplies its
parser/implementation substrate and its annotation model).


1. Laptop hunt — what was searched, what was found

Searched (2026-11-04, laptop online): every *.kdl file under the
laptop home (deduped across the many worktrees/clones), plus content
greps for "kdl" over the laptop's zettel corpus (~/git/zettel),
cairn (~/git/cairn), hatch-personal, and notes trees
(~/hatch/notes, ~/git/fleet/notes, ~/queue-notes).

Not covered / honest gaps: the dozens of repo worktrees are clones
of the same trees (deduplicated, not re-read); the Atlas local chunk
store was not mined for documents; no ChatGPT conversations.json
export exists on the laptop (the late-2023 prose brainstorming remains
unrecovered — the #5688 follow-on stands).

1.1 The find: pkgs/mappy/migrations/limit.kdl (106 lines, Drew, 2023-12-17)

A design scratchpad inside the mappy graph/triples experiment
(Python + SQL migrations; cf. node.sql, crdb.sql beside it).
Committed 2023-12-17 — the same day as upstream discussion #362
below. Its key positions, verbatim in spirit:

  • Triples modeled as annotations — the direct ancestor of id: design: KDL as human presentation format + triples + ANSI graph SQL (SQL/PGQ) for ID data #5688 §2:

    (subject)character "Brian" {
        (predicate)"writesBooks" (object)"Wish It, Want It, Do It" (object)"Faster Than the Speed of Love"
        (predicate)"travelsWith" (object)"Stewie" (object)"Peter"
    }
  • Parameterized / structured annotation payloads — far beyond the
    spec's single bare identifier:

    (predicate#NotVeryWell)"writesBooks" (object#book#author:BrianGriffin)"Wish It, Want It, Do It"
    (object::Baby:Lois'sBaby::Kind-Of-Annoying)"Stewie" (object:FamilyGuy)"Peter"
    (yes:yes:no:::bool)"=" "yes" "no" "yes"
    (document:object)rdf { ... }
  • Annotations on every part — node names, argument values (and, in
    the commented-out variant, (subject)character="Brian" prop-style
    modeling), plus experiments that leave the grammar entirely
    (`=(predicate), :=(object)"examples", annotations embedded in
    comments /*(result)*/).

  • The triples store sketched in comments, Dec-2023:

    // nodes: id, type, key, value
    // links: id, type, from, to
    // meta_links: id, type, from, to
    
  • An RDF / Dublin-Core record re-expressed in KDL under
    (document:object)rdf, and a tail of deliberate lexical torture
    tests (quote/escape/emoji edge cases) — parser-robustness probing.

This file is why the fork's annotation design (§4b) treats the
annotation slot as structured data, not a bare tag.

1.2 Other real KDL material on the laptop (all in developing-today/code)

File What it is
pkgs/roc/out-in.kdl (39 ln) Roc syntax brainstorm in KDL form (known fragment; links kdl-play)
pkgs/roc/platform.kdl (14 ln) Prose platform notes as KDL strings
pkgs/identity/config.kdl + pkgs/identity/configuration/embed/config.kdl Production identity-service config written in KDL
~/.config/zellij/config.kdl Tool config (zellij) — not Drew's design material

1.3 Negative results

  • No KDL documents or design notes in the laptop zettel / cairn /
    notes corpora (one flake.lock substring hit only).
  • developing-today/hygge (GitHub, 2022) is Drew's builder-only Rust
    KDL crate ("knuffel or kdl-rs for deserialization :-)"); its
    SPEC.md is a vendored copy of the KDL 1.0 grammar. It is prior art
    for the serializer half of our stack, not a parser base.

2. Drew's KDL-ecosystem threads — the capability-ask catalogue

Method: GitHub search (author: / commenter: / involves:) across
the kdl-org org and globally for "kdl", plus a full pass over
kdl-org/kdl Discussions via GraphQL (search does not index
Discussions — that is where the headline asks were hiding). All
threads below verified by reading them.

2.1 The two declined capability asks (define the fork)

Thread Date Ask Maintainer response Outcome
kdl-org/kdl discussion #344 — Children Annotation 2023-11-29 "Allow annotating the children block in the same way that values and identifiers can be annotated." tabatkins (spec editor): unconvinced — a node has only one child list, so the information "can just be expressed with a property or attribute"; asked for a use case. Not adopted. KDL 2.0 grammar has no children annotation.
kdl-org/kdl discussion #362 — Property Annotation 2023-12-17 "Allow annotating properties in the same way that values and identifiers can be annotated." Drew supplied precedents: Go's lunjon/gokdl already implements Prop.TypeAnnot ((author)name="Jonathan"); Elixir's Kuddle supports it too (with annotations: [] — a list). tabatkins: parseable, but questions the value — keys are unique per node, so "all necessary context is already baked into their name"; asked for a use case. Not adopted. KDL 2.0 grammar: prop := string '=' value — no key annotation.

The irony the design should record: the use cases the maintainers
asked for existed the same week in limit.kdl (§1.1) — repeated names
disambiguated by role ((subject) / (predicate) / (object),
parameterized (object#book#author:BrianGriffin)) — exactly the
"same name, different role" rationale the spec itself accepts for
node annotations, extended to every part.

2.2 Spec-era participation (KDL 2.0 design batch, Nov-2023)

Thread Drew's position Outcome in KDL 2.0
kdl-org/kdl #343 — prefix keywords with # Came around to supporting; asked whether # would be banned from identifiers / first-char, noting quoted names as the escape. Adopted: 2.0 keywords are #true / #false / #null.
kdl-org/kdl #342 — multiline comments in more places "i think this should be legal, i don't know what it should do." Partially adopted: 2.0 relaxed comment placement (e.g. // immediately followed by newline; comments in more positions).
kdl-org/kdljs #21 — KDL 2.0 compliance 2024-12-29: nudged for a 2.0 update post-release (playground dependency). Closed.

2.3 Implementation-era threads (Go ecosystem, Dec-2023 → Apr-2024)

Drew was simultaneously building on KDL — a KDL config provider for
koanf, via kdl-go — and pressing the permissiveness philosophy the
fork inherits:

Thread Ask / work Outcome
sblinch/kdl-go #3 (authored) Marshal panic on lists in map[string]any; discovered the key=val prop-coercion; asked "is there a toggle somewhere?" and for an error instead of a panic. Fixed upstream (kdl-go list-marshal fix; Drew pulled it via koanf #267).
sblinch/kdl-go #4 (authored) Data-model fidelity cluster: empty nodes unmarshal as nil vs empty map; duplicate node names with changing types should be last-wins, not an error; { "" 5 ; "" 6 ; } children mishandled; quoted "1" / empty-string identifiers vs bare-identifier rules; asked for behavior flags/toggles "to extend what is permissible past spec." Maintainer asked for isolated test cases; thread closed with "Fixed in 3d5e45f" (2024-04-09). Partially addressed.
knadh/koanf #265 / #266 (authored) + developing-today-forks/koanf PR #1, PR #3 Contributed a KDL parser provider for koanf (Go config library); negotiated the emptyIsNil difference generically rather than special-casing KDL in tests. PRs merged in his fork; koanf gained its KDL parser from this work.
knadh/koanf #267 (authored) kdl-go dependency update carrying the #3 fix. Closed.
typestreamio/typestream #77 Commented (local-data joins for stream filtering; libsql-as-file/URL). Tangential to KDL design. Open; listed for completeness.

Coverage caveat: issue/PR search + both ends of the Discussions list
were covered; comments Drew may have left inside other people's
discussions were not exhaustively enumerated.

2.4 The spec baseline (verified against both grammars)

Type annotations in KDL 1.0 (SPEC_v1: "a prefix to any Node Name
or Value"; prop := identifier '=' value; type := '(' identifier ')')
and KDL 2.0 (IETF-draft grammar: value := type? …,
prop := string '=' value, type := '(' string ')'):


3. Rust KDL landscape — fork candidates

Verified 2026-11-04 against crates.io + the repos' current sources.

kdl (kdl-org/kdl-rs) knuffel (tailhook) kaydle (Lucretiel)
Version / state 6.7.1, actively maintained (repo pushed 2026-10; ~2.96M downloads) 3.2.0, last release 2023-04 (stale) 0.2.0, last release 2022-07 (unmaintained)
Role Document parser + API; formatting/whitespace/comment-preserving parse & modify Derive-based config decoder (serde-like Decode) Serde ser/de layer on top of the kdl crate
Spec versions 2.0 default; 1.0 via v1 feature (sibling kdlv1 crate); converts documents between versions 1.0 only Whatever the underlying kdl crate did in 2022 (1.0 era)
Serde Built in (serde default feature; de.rs/se.rs) Is itself a derive stack (not serde) Is itself serde — superseded by kdl-rs's built-in serde
Search/query KQL built in (query.rs — the KDL Query Language) None None
Annotation model KdlNode.ty: Option<KdlIdentifier>; KdlEntry.ty: Option<KdlIdentifier> (value); prop key = bare KdlIdentifier (no slot); KdlIdentifier = value+repr only Annotations not first-class (consumed as Rust types during decode); no document fidelity Inherits kdl's AST of its era
Round-trip of unknown annotations Preserved where representable (node/value ty + full format preservation); structurally impossible for prop keys / multiples — the parser cannot even hold them N/A (decode discards) N/A
License Apache-2.0 MIT/Apache-2.0 MPL-2.0

Also noted: kdlv1 (the split-out 1.0 implementation kdl 6.x wraps)
and Drew's own hygge (builder-only serializer; a candidate to revive
for the output side, §4d).

Recommendation: fork kdl-org/kdl-rs (kdl crate)

  • It is the only candidate that is simultaneously: maintained,
    dual-version, format-preserving, serde-complete, and query-equipped
    (KQL matters for id: design: KDL as human presentation format + triples + ANSI graph SQL (SQL/PGQ) for ID data #5688's "searches efficiently" goal).
  • The fork's core change is contained: generalize
    ty: Option<KdlIdentifier> → annotation lists, and add annotation
    slots where the AST has none (prop keys, children blocks). Every
    other subsystem (dual parsers, fmt preservation, serde, KQL,
    miette spans) carries over.
  • Starting from knuffel means rebuilding 2.0 support, document
    fidelity, and a serializer from zero; kaydle is a layer, not a
    parser, and is abandoned. Neither survives contact with §4a.
  • Apache-2.0; track upstream (spec evolution, test suites — including
    the official KDL test suite the crate already runs) and upstream
    what is upstreamable.

4. Fork design commitments (Drew, 2026-11-04)

(a) Compatibility is absolute. Every valid KDL 1.0 document parses
with its spec meaning unchanged; KDL 2.0 likewise, and future spec
versions are tracked. Extensions are additive: they never change
the meaning of a spec-valid document. (A document using extensions is
a superset document — a strict parser may reject it, and that is
accepted; the spec-valid subset always round-trips as plain KDL.)

(b) Universal, multiple, structured annotations. Generalize the
spec's value-only type hint to every part: node name, each
argument, property keys, property values, and the children
block
(the #344/#362 asks), with multiple annotations per part
(Kuddle's annotations: [] precedent). Open design question, with
limit.kdl as the evidence base: annotation payloads — Drew's
practice packs structure into the annotation (#-/:-separated
parameters: (object#book#author:BrianGriffin),
(yes:yes:no:::bool)). Decide whether the payload stays an opaque
string (spec-shaped, maximally compatible) or gains a structured
sub-grammar in our dialect, and how either form decomposes into
triples under #5688.

(c) Performance and search efficiency are first-class. The
triples pipeline (#5688 §2) parses at ingest scale: streaming /
zero-copy parse paths where they matter, no format-preservation tax
on the hot path (make preservation opt-in per parse), and the
query story (KQL today; the SQL/PGQ graph layer in #5688) designed
against parsed-structure indexes rather than re-parse-per-query.

(d) Output beyond KDL text — the document-language direction.
Long-term this stops claiming to be KDL: it is our document
language
with KDL as its default text form. Required renderers, in
#5688 §1.2's conversion-contract terms: KDL text (default), JSON,
YAML, and an LLM sentence rendering (a document rendered as
prose a model — or a person skimming — can read). Files are produced
"with an option": the same underlying document emits the requested
form. hygge (§1.3) is the in-house precedent for the builder/output
half.

5. Follow-on questions for the design pass

  1. Annotation payload grammar (§4b) — opaque vs structured; triple
    decomposition of parameterized annotations.
  2. Extension marking: does a document declare itself extended (a
    header annotation? a version node?), so tooling can warn before a
    strict-KDL consumer chokes?
  3. Serde shape for annotation lists (attribute-style in derives?) and
    how the kdl-rs serde layer exposes prop-key annotations.
  4. Which pieces, if any, get proposed back upstream (the Discussions
    show the maintainers' objection was use-case-shaped, and we now
    have the use cases — but the fork does not block on upstream).
  5. Benchmarks: upstream kdl vs fork on the ingest corpus; the
    acceptance bar for §4c.

— Filed by the KDL research pass, 2026-11-04. Sources: laptop
(~/git/code primary checkout), GitHub (issues/PRs/Discussions via
search + GraphQL), crates.io, spec grammars (SPEC_v1 + the KDL 2.0
IETF draft) — all read directly during the pass.

Activity

  1. added
    source/creatorFiled by the new-issue-creator job
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    on Nov 5, 2026
  2. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor
  3. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    WS-I follow-on investigation (2026-11-04): "KDL 2 compact", hygge + parser landscape re-survey, fast serde alternates, and the fast-query form

    Four threads from Drew's 2026-11-04 20:06 CST additions. Frame remains §4's commitments: KDL 1 compat absolute, universal + multiple annotations, performance and search as first-class goals. Cross-refs: #5688 (parent design — triples + SQL/PGQ), #5674 (postcard wire), #5675 (querying ID servers), #5681 (storage tiers).


    0. "KDL 2 compact" — pinned down as a named requirement

    The reading, stated as a requirement so it can be confirmed or corrected: the fork's baseline is KDL 2.0, and it defines a compact profile: a canonical minimal-whitespace emission of a document, used for stored / interchange copies (the search corpus, the provenance payload, content hashing), while the human form stays the pretty autoformat. If Drew meant something narrower (e.g. only "emit v2, never v1"), the profile below still stands on its own evidence; the open question is recorded at the end of this section.

    What the spec allows. Nothing in KDL 2.0 defines a canonical or minimal form — formatting is implementation territory. But the 2.0 grammar is unusually minification-friendly, more so than 1.0:

    • Nodes may be separated by ; instead of newlines, and the last node in a children block needs no terminator even with } on the same line (node{foo;bar;baz} is legal 2.0).
    • Node-space around = in props is permitted but never required; zero is the minimum.
    • Whitespace inside (annotations) is legal in 2.0 — minification must therefore choose (foo), never be forced into it.
    • Bare identifiers cover more ground in 2.0, so a compact emitter can drop quotes more often than a v1 emitter could (subject to the identifier rules).

    What upstream's formatter can do — checked against the kdl 6.7.1 source (src/fmt.rs). FormatConfig has exactly four knobs: indent_level, indent (default four spaces), no_comments, entry_autoformate_keep. Every one serves pretty-printing. There is no compact/minified mode, and none can be configured into existence (autoformat always emits line-broken, indented documents). Conclusion: compact emission is a small new emitter mode in the fork — a serializer walk that joins nodes with ;, emits one space only where the grammar requires separation, strips comments, and picks bare-vs-quoted identifiers minimally. It is not a parser change and does not touch §4a compatibility.

    Measured size deltas on our real documents (script minification of the actual files; rules: comments stripped, node-internal whitespace collapsed to single spaces, ; separators, no spaces around = or braces, strings preserved verbatim — reproducible in the follow-up pass):

    Document As authored Compact Δ
    pkgs/mappy/migrations/limit.kdl (3,491 B on disk) 3,450 B 2,296 B −33.4%
    pkgs/identity/config.kdl + configuration/embed/config.kdl 633 B 497 B −21.5%

    Caveats: most of the win is comment + indentation removal, i.e. compact is a storage form, not an authoring form — the comments in limit.kdl are design content and must survive in the authored copy. Multi-line string payloads cannot shrink below their content. (limit.kdl's torture-test tail also contains constructs outside the v2 grammar; the figure measures byte volume, not a round-trip promise for that file.)

    Why compact earns its place beyond bytes: canonical byte-stable text makes a document content-hashable and dedupable (composes with the treed-hash identity idea in pkgs/id/README.original.md), and a search ingest that stores compact text scans ~⅓ fewer bytes before the parse even starts (thread 3).

    Open question for Drew (the uncertainty, stated plainly): is "compact" (a) this storage/interchange profile, (b) a v2-only emission policy ("KDL 2, compactly" as in the short version of the plan), or (c) both? Filed as (a)+(v2 baseline) pending correction.


    1. hygge, read in full — and the parser landscape since

    hygge (developing-today/hygge, created 2022-02, last push 2023-12; v0.4.0; depends on kdl = "3.0.0"). Read end-to-end this pass: src/lib.rs is 780 lines.

    • What it actually is: fluent builder wrappers over the kdl v3 AST — KdlNodeBuilder (name/values/props/children), KdlValueBuilder (terse constructors: str/int/flt/y/n/nil), KdlValuesBuilder, KdlPropertiesBuilder — with build() producing real KdlNodes. It contains no parser (by design; the README defers input to knuffel/kdl-rs) and no serializer of its own — output is upstream's Display.
    • The one live idea: HashSetMap — a hash-consing register that deduplicates values by content hash into Rc<T>. As code it's a sketch; as a concept it's the direct ancestor of two things the fork wants anyway: string/value interning in the AST (§4c memory posture) and ID's content-hash identity thinking.
    • What's obsolete: the &mut String plumbing (the source's own trailing comments list the ergonomics as unresolved — "unify str vs String", "&mut self vs Self"); the v1-shaped KdlValue model it wraps (the kdl crate is three major versions past it, with a different numeric model); and it never touches annotations at all.
    • Verdict: mine hygge for two patterns — the builder-API shape for §4d's renderer/output side, and hash-consing as prior art for interning — and port no code. Nothing in it competes with, or could accelerate, the parser fork.

    The landscape since #5753's survey (verified this pass):

    Candidate State now Annotation posture Verdict vs the fork's needs
    kdl (kdl-rs) 6.7.1 Actively developed. v6 (Dec 2024, alongside KDL 2.0.0) is a rewritten v2 parser (multi-error collection, span-carrying AST); v1 is delegated to the sibling kdlv1 crate (the v4.7 lineage); v1⇄v2 conversion both ways; serde (src/de.rs/se.rs), KQL (src/query.rs), and the FormatConfig pretty formatter all in-tree. Current docs put MSRV at Rust 1.95 — noted, not blocking for us. Unchanged from #5753 §3: ty: Option<…> on nodes/entries, bare prop keys, no children slot. The surgery is still exactly where §3 located it. Base — confirmed (below).
    knuffel Repo declares itself unmaintained. — Dead; superseded by knus.
    knus (knuffel's maintained fork) 3.4.x, releases into 2026 — but the implementations table marks it KDL v1 only, and its maintainer describes a review-and-merge posture and names facet-kdl as the intended successor. Best-in-class derive UX + miette errors for typed decode. Consumed as types during decode; no document fidelity. Pattern donor (derive ergonomics, error quality for #5688's non-developer authors), not a base: v1-only grammar fails §4a's dual-version bar on its own.
    kaydle Unchanged: 0.2.0, self-described early alpha (2022), a serde skin over the old kdl crate. Inherits its era's AST. Dead end, re-confirmed. The niche it filled is now kdl-rs's own built-in serde.
    facet-kdl New since #5753 was framed. The facet project's KDL format crate: parsing by kdl 6.x, typed decode by facet reflection. In real use (e.g. bearcove/tracey at facet-kdl 0.35 over kdl 6.5). The knus maintainer independently points at it as knus's successor. Whatever the kdl AST exposes — i.e. it inherits the fork's annotation work for free once landed there. Not a base — a consumer of the base, and the strongest external validation of the fork's shape (see below).
    kdly Go; format/comment-preserving; v2-only. — No Rust relevance; noted as cross-language evidence that document-API designs converged on the kdl-rs shape.

    Fork-base verdict: CONFIRMED, and strengthened. Every living Rust KDL effort now either is kdl-rs or sits on top of it (facet-kdl, kdlfmt, historically kaydle). The alternatives are v1-only (knus), maintenance-mode (knus), or abandoned (knuffel, kaydle). The annotation generalization remains a contained AST change in exactly one place — and it is the place the rest of the ecosystem already depends on, so the fork's improvements propagate to facet-kdl/knus-style consumers rather than stranding us on a private island.


    2. Serde, but faster — the alternate-path shortlist

    Baseline stands: the kdl crate's built-in serde (default feature) is the fork's serde story. What follows is what can sit beside it, feature-flagged, per Drew's "support serde but alternate performance versions maybe."

    Candidate Maturity / evidence Perf & RAM posture Integration cost with a kdl-rs fork Slot
    Borrowed / zero-copy decode (serde 'de lifetimes, Cow, borrowed scalar views) Standard serde practice; no new dependency. Eliminates per-string allocation on string-heavy documents — the common KDL case. The bigger half lives one layer down: a no-format-preservation parse profile (already §4c) so the AST itself stops paying for repr/span fidelity on the ingest hot path. Low: expose borrowed views alongside owned AST accessors. Shortlist — do first.
    Arena / bump AST allocation (bumpalo-style) Mature pattern in parser engineering. One arena per document: fewer allocator round-trips, better cache locality for parse-once/query-many ingest. Fights the mutable, format-preserving AST (in-place format edits + arena lifetimes don't mix). Medium: a second AST storage mode behind the ingest profile. Shortlist — scoped to the no-preserve ingest profile only.
    facet (reflection framework; SHAPE const per type; format crates per format) Young (started Mar 2025), moving fast, pre-1.0 churn; facet-kdl exists and is used in production by its author's tools. Not a speed play today, by its author's own measurements: facet-json started 5–7× slower than serde_json; later dashboard results are publicly flagged by the author himself as not yet trustworthy ("zero-copy apples-to-oranges" concern); compile times went from worse to roughly even after sustained work; facet-postcard is reported faster than the reference postcard implementation (author-reported). Unusually low for us: facet-kdl consumes the kdl-rs parser layer, so a fork that keeps that layer's shape gets facet support nearly free. Shortlist — as an optional decode layer (one derive, many formats + schema generation), explicitly NOT as the performance path.
    postcard (binary form) Mature; already ID's wire choice (#5674, #5688 §1.3). Not a KDL path at all — it is the stored/binary form candidate. Third-party micro-benchmark (single small struct, indicative only): postcard ≈ 41 B on the wire vs ≈ 99–100 B for JSON text, ~1.5× bincode's runtime at ~70% of its size. Zero conflict: postcard is serde-based, so it composes with the baseline rather than competing. Adopt in its lane: serialized AST / triple-row payloads where a binary stored form is wanted.
    miniserde Self-described prototype — its own docs say it is "not a production quality engineering artifact"; JSON-centric. By its own account: on par with serde_json in some cases, 1.5–2× slower in most. Its real win is compile time / code size (no monomorphization), not runtime. Would be a second, weaker data-model to maintain. Reject as a performance alternate; the honest summary is that it optimizes a different axis (build weight) than Drew's ask (parse/serialize speed + RAM).
    SIMD-accelerated scanning (simdjson-style structural indexing) Proven technique for JSON; no KDL implementation exists anywhere (this pass found none). KDL scanning is delimiter/whitespace-bound; the cheap 80% is memchr-class delimiter search plus thread 0's compact profile (fewer bytes to scan). Full structural-index SIMD is a research project, not a feature flag. High, speculative. Defer, benchmark-gated: revisit only if the §5 benchmark pass shows scanning dominating profiled parse time.

    Net: the credible "alternate performance version" is a parse-profile feature set (no-preserve + borrowed views + arena) inside the fork, with facet as an optional ergonomics layer and postcard as the binary form. There is no drop-in faster serde for KDL, and miniserde is not one either.


    3. The fast-query form — what querying should run against

    Framing note: #5688 §1.3 already rules that KDL text is not the query substrate. This thread answers the remaining question — what the substrate is, per tier — against §4c's "designed against parsed-structure indexes rather than re-parse-per-query."

    (a) Rows at ingest (the triples / node-edge normalization). The limit.kdl comment sketch from 2023 (nodes: id, type, key, value / links / meta_links) is the same shape as #5688 §3.2's triple table (subject, predicate, object + statement linkage) with property-graph views over it. The decisive external evidence: every shipping SQL/PGQ implementation is exactly this architecture — property graphs defined as views over ordinary relational tables: Oracle 23ai (first commercial SQL/PGQ, in production), DuckPGQ (the CWI research extension bringing SQL/PGQ to DuckDB), and PostgreSQL's in-core work (below). Indexing is the known triple-store playbook: B-tree permutations (subject-predicate-object and its rotations) on the embedded engines; path queries via recursive CTE where PGQ isn't available. Ingest pays parse + decompose once; no query ever re-parses text.

    (b) A Postgres extension — split the idea in two, because the halves have opposite fates.

    (c) SQLite / Turso tier. Same rows as (a) — that sameness is the point. Permutation B-tree indexes and recursive CTEs are native-shaped here; SQLite's JSONB storage (3.45+) covers the stage-2 JSON anchor if it needs local search; TursoDB is evolving quickly but offers no PGQ and no KDL surface, and the design should not wait on either. The answer is explicitly per-tier: one row model everywhere; the query surface differs — SQL/PGQ where an engine grows it (Oracle-class servers today; Postgres from PG20-era; DuckPGQ for analytics), a translated PGQ subset over CTEs on SQLite/Turso (the capability matrix is #5688 §3.3's deliverable), and KQL inside documents only.

    (d) Native KQL over purpose-built indexes. Two independent problems. First, status: the QUERY-SPEC's current text describes "KQL next" as unreleased, and the 2.0.0 release notes already flagged KQL as not finalized — the fork would be building store infrastructure on a draft. Second, shape: KQL is a CSS-selector-style document extraction language — node selection within a document, with no joins, aggregation, or graph patterns. It is the right tool where upstream already puts it (in-document selection, CLI/human surface, the fork inherits it free) and the wrong substrate for cross-document graph search. Building dedicated indexes for it duplicates (a) with a weaker model.

    Recommendation.

    1. Rows are the query substrate on every tier (triple table + node/link tables per the limit.kdl sketch, permutation indexes); KDL text — authored pretty, stored compact (thread 0) — is the provenance/interchange payload attached to the statement, never the thing queried.
    2. Query surface per tier: SQL/PGQ as the target model (id: design: KDL as human presentation format + triples + ANSI graph SQL (SQL/PGQ) for ID data #5688 §3 stands); translated subset + recursive CTEs on SQLite/Turso until/unless those engines grow PGQ; DuckPGQ as the analytics-side proof the model works embedded; KQL confined to in-document selection.
    3. No Postgres extension work. Revisit only if a Postgres-resident tier becomes real, and then via jsonb, not a bespoke type.

    Experiments that settle the remainder (proposed for the follow-on pass):

    • Rows-vs-document bake-off on a limit.kdl-shaped generated corpus (10k / 100k / 1M statements in the character/predicate/object shape): (i) triple rows + permutation indexes, CTE and translated-PGQ arms; (ii) canonical-JSON-in-jsonb + GIN on a Postgres side instance (the (b) control arm); (iii) KQL over parsed documents with an in-memory index (the (d) control arm). Query set drawn from id: design: KDL as human presentation format + triples + ANSI graph SQL (SQL/PGQ) for ID data #5688's shapes: path patterns, annotation filters including parameterized payloads ((object#book#author:…)), qualifier joins. Measure ingest s/1k docs, index bytes, p50/p95 per query class.
    • Compact-arm test: the same bake-off with the stage-2 anchor stored as compact KDL vs canonical JSON — bytes, and reparse cost when a statement must be re-expanded to its authored form.
    • Parse-profile benchmark (feeds thread 2): upstream kdl vs fork profiles (preserve / no-preserve / no-preserve+arena) on the same corpus — this is also §5 question 5's acceptance bar, now with the corpus defined.

    Amendments this pass makes to §5's follow-on list

    • §5.5 (benchmarks) gains a defined corpus (the limit.kdl shape, generated at three scales) and the three-arm query bake-off above.
    • New follow-on: the compact emitter (thread 0) — small, serializer-side, with the canonicalization rules written down (identifier quoting choice, number rendering, annotation spacing) before implementation, since canonical bytes are what get hashed.
    • §5.3 (serde shape for annotation lists) should now also answer for facet: facet-kdl consumes the same AST, so the annotation-list shape must be exposable through both derive ecosystems.

    — WS-I investigation pass, 2026-11-04. Sources: hygge repo read in full; kdl 6.7.1 source (src/fmt.rs, tree listing) and docs; KDL 2.0.0 release notes + QUERY-SPEC current text; kdl.dev implementations table; knus/knuffel/kaydle docs; facet author's own performance posts (figures attributed and caveated in-thread); miniserde/postcard docs and a third-party serialization micro-benchmark (flagged as such); PostgreSQL PGQ commit/revert coverage, DuckPGQ and Oracle 23ai documentation, PostgreSQL GIN documentation; size measurements computed from the laptop's real files.

  4. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    work/assignedQueue worker lifecycle state
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 5, 2026
  5. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)queue/parkedQueue task is parked (waiting on CI, time, or an event)source/creatorFiled by the new-issue-creator jobstatus/parkedDeliberately paused with a recorded resume conditiontask/triageQueue packet kind: verify, reproduce, and route this itemwork/assignedQueue worker lifecycle state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions