DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path scoping-notes/loomworks-b40-markdown-routing-scoping-note-v0_1.md

Loomworks — B-40 scoping note: where contributed Markdown should route — v0.1

Version. 0.1 Date. 2026-08-07 Author. Claude Code (Wave 0 session). Operator: Marvin Percival. Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. Scope. Wave 0 investigation 0.4 of standing-notes/loomworks-development-schedule-v0_1. Read-only. No code changed. Read against. Engine loomworks-engine at afdeff5; surface loomworks at baec093. Both clean. Reads with. investigations/loomworks-upload-pathway-investigation-v0_1 (the architectural commitments); investigations/loomworks-upload-pathway-ground-truth-v0_1 (partly stale — its headline "Rail B never lands in Memory" was closed by CR-2026-130).


Recommendation, in one line

The routing question is already answered by the existing convention — Markdown routes correctly today and needs no change. B-40 as literally posed ("where should it route") is a non-issue. But the investigation surfaced a real collision the item did not name: the same .md file means two incompatible things depending on which of two live rails it arrives through. That collision is the decision worth making, and it is the one this note recommends taking to a CR.


1. What Markdown does today

Rail B (the live upload pathway). POST /engagements/{id}/uploads detects by filename extension plus magic bytes, never MIME. .md has its own registered rule (uploads/skills/text_extraction.py:109) whose only difference from the .txt rule is a content_type_hint="text/markdown" string. Both route to the identical text_extraction skill, which is a UTF-8 decode and hardcodes output_content_type="text/plain" (:85). The module docstring says so outright (:15). Result: one held assertion, grammar_element="definition", source_mode="text" — indistinguishable in Memory from a typed note.

This is exactly what the convention prescribes. Every other type does the same thing: one file → at most one held assertion, content flattened to text, provenance in metadata, Operator commits. summarization.py:13 records single-assertion-per-file as a deliberate scope decision. So "consistent with existing conventions" means: change nothing about the routing.

2. The collision the item did not name — and the actual decision

A second live rail gives .md an entirely different meaning. src/loomworks/skills/registry.py:201–206 registers:


registry.register("text/markdown", extract_discovery_to_seed_skill,
                  label="Discovery document (Markdown)", mode="discovery")

POST /contributions resolves its skill by MIME content-type (api/routers/contributions.py:202). So a Markdown file contributed through Rail A runs a single-shot LLM seed extraction, yielding source_mode="discovery", an assertion whose content is a summary rather than the document, and a draft_seed payload in metadata.

The registration's own comment says it "supports content-type-based dispatch through the existing contribution pipeline" — so this is intended to fire, not vestigial. It was registered for Phase 53 Discovery documents specifically.

Two rails, one file type, two incompatible meanings. Rail A treats every Markdown file as a discovery document to extract a seed from; Rail B treats it as text to remember. Which one a contributor gets depends on which endpoint the client happens to call. This is the real B-40 decision, and it is a correctness question, not a filing-convention question.

Recommended resolution: Rail A's blanket text/markdown → discovery registration is too broad. Discovery extraction is a purpose, not a format — which is precisely the "operation-not-format granularity" commitment at §2.5 of the upload-pathway investigation. The canonical Phase 53 path is already the dedicated /seed/extract endpoint (api/routers/seed_extraction.py:155 requires content_type == "text/markdown" explicitly). The general contribution pipeline should route Markdown the way Rail B does. Narrow or remove the Rail A registration; keep /seed/extract as the deliberate discovery door.

3. Three smaller findings, each independently actionable

(a) A one-line gap between surface and engine. The surface's document-create flow accepts .markdown (DocumentCreateFlow.tsx:258, accept=".md,.markdown,.txt,…"), but the engine registers only .md (text_extraction.py:111 — verified, .markdown appears nowhere). A .markdown file offered by the UI fails detection. One-line fix, independent of every other decision here.

(b) File length silently decides how Markdown is treated. Under 2000 chars (summarization.py:32) the held assertion carries the raw Markdown source verbatim# headings, bullets, tables, fences — sitting in an assertion body. Over the threshold, the summarizer runs a prompt that says "Write plain prose, no preamble, no markdown formatting" (:41), deliberately destroying the structure. Neither branch is Markdown-aware; which one a contributor gets depends on file size. Worth a deliberate decision even if the answer is "accept it."

(c) The text/markdown hint dies at the skill boundary. It survives to the upload event's content_type_observed but is never copied into the assertion metadata (uploads.py:895–904). The docstring's promised "downstream consumers" do not exist on the assertion side. If Markdown-ness should ever be recoverable from Memory, this is the one-line place to thread it.

4. What is genuinely open, and deliberately not decided here

Should Markdown's structure be preserved into Memory? Markdown is the only supported format where structure survives extraction for free — the transform is a byte decode. Every other type flattens by necessity (docx headings are detected at docx_extraction.py:94 then dropped at uploads.py:956; PDF loses layout; HTML loses tags). So Markdown is where "one file = one flat assertion" is most visibly lossy: a .md with ten ## sections is ten distinguishable claims arriving as one.

This is not a B-40 decision and should not be smuggled into one. Preserving structure would make Markdown the first type to break the one-file-one-assertion convention, and that is exactly the multi-output granularity question the upload-pathway investigation filed as open at §6.5 on 2026-05-08 and never resolved. Nothing in the engine parses Markdown today (the only markdown library use runs the opposite direction, HTML→PDF for the Manifestation room), so this would be net-new machinery. Recommend it stay parked as its own question, and that B-40's CR explicitly say it is not answering it.

5. Suggested CR shape

One small CR, no multi-option decision needed:

  1. Narrow or remove the Rail A text/markdown → discovery registration (§2) — the correctness fix.
  2. Register .markdown alongside .md (§3a) — one line.
  3. Decide explicitly on §3b and §3c; both are one-liners either way, and both currently fail silently rather than loudly.
  4. State in the CR that structural Markdown parsing (§4) is out of scope and remains parked.

No option table is offered because the existing convention makes items 1–2 obvious. Manufacturing alternatives here would be false balance.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-40 scoping note — v0.1 — 2026-08-07