0012 — Shared neurotype-shaping layer
Source:
docs/decisions/0012-shared-neurotype-shaping-layer.mdis the canonical artefact. This page is a short summary; read the full ADR for the three alternatives considered, the binding rules, the phasing, and the implementer’s notes.
- Status: Accepted
- Date: 2026-06-18
- Deciders: Thomas Lennon (maintainer),
mcp-architect - Resolves: ADR 0011 open question 3
Context
Section titled “Context”ADR 0011 fixed the schema policy for per-neurotype tailoring and left the implementation locus of the shaping layer as its open question 3, recommending a single shared source of truth in core. R1 now builds that: making per-neurotype prompt tailoring reach every MCP client, not just the browser extension. Today the tailoring content is hard-coded inside the extension’s TypeScript, so a user on Claude Desktop, Cursor, or any other client gets the generic, untailored prompt.
The decision is load-bearing because the extension does not call mcp-translation — it assembles the addendum client-side and POSTs to the user’s own provider — while every other client reaches the model through the server. So the content has to be available to two runtimes (TypeScript and Python) and assembled identically in both, or the same user gets different tailoring depending on which surface they came through.
Decision
Section titled “Decision”A language-neutral neurotype-addenda artifact in @neurodock/core is the single source of truth for per-neurotype prompt shaping. Both the TypeScript assembler (browser extension) and a Python assembler (mcp-translation, a follow-up PR) read it. mcp-translation assembles the addendum from an optional reader_context input (or a profile.yaml fallback) and injects it into its existing prompt_for_llm_refinement.content, so every MCP client receives per-neurotype tailoring. The boundary is HYBRID: language-neutral data artifact + a thin assembler in each runtime + server-side injection.
View alternative approaches and technical debates
Alternatives rejected:
- The server re-implements the tailoring content in Python — guarantees content drift. The blocks are curated lived-experience prose; two independently edited copies diverge invisibly until a user notices their Desktop output differs from their extension output. This is exactly the failure mode R1 is meant to prevent.
- A TypeScript-only shared library — cannot reach the Python servers.
mcp-translationis Python; Desktop and Cursor reach the model through the server path. A TS-only library leaves every non-extension client untailored, which fails R1’s reason for existing. Shelling out to Node from the server (or the hosted Worker) is a deployment and latency liability.
The artifact
Section titled “The artifact”- Location:
packages/core/data/neurotype-addenda/v1.json, validated bypackages/core/schemas/neurotype-addenda.schema.json. - Versioning: an
artifact_versionsemver field; additive-only within a major (a new tool, a new neurotype, or a new optional top-level key is non-breaking; a breaking re-shape forks to a newvN.jsonwith a new schema$id). - Contents: the per-(tool × neurotype) blocks; the AuDHD fusion rule; the priority order; the conflict footer (at three-or-more effective neurotypes); the cross-cutting
voice_inputblock; thetouretteandotherspecials; the generic fallback; and theoutput_formatguidance. The only interpolation tokens are{max_chunk_size}and{notes}.
Binding rules established here
Section titled “Binding rules established here”- The artifact is the single source of truth; assemblers are thin and pure. Neither runtime hard-codes content. Each has one pure assembler that reproduces the same order: fusion → priority → per-tool block (with generic fallback) →
tourette/otherspecials →voice_input→ conflict footer → token interpolation. - The two assembly paths must be provably identical. Because the extension assembles client-side and the server assembles server-side, the standing risk is content drift between two assembly paths. Mitigated by (i) the one artifact, (ii) a cross-language parity test (TypeScript ≡ Python), and (iii) a byte-identical golden snapshot captured at the extension cutover (already implemented: 9,720 cases).
- The server gains exactly one optional, additive input.
reader_contextcarriesneurotypes,output_format,max_chunk_size,voice_input_preferred, andadditional_notes, with aprofile.yamlfallback. When both are absent, the assembled prompt is byte-identical to today’s generic prompt. The output shape is unchanged. - No model call, no LLM SDK on the server. The server assembles prompt text and injects it into the prompt it already returns — preserving ADR 0005’s vendor-neutrality doctrine.
- Scope is the four server tools (
translate_incoming,check_tone,rewrite_outgoing,brief_meeting).describe_imageis extension-only (no server tool) and out of R1 server scope.
Where the moving parts live
Section titled “Where the moving parts live”| Concern | Mechanism | Boundary impact |
|---|---|---|
| Tailoring content | One language-neutral JSON artifact in @neurodock/core | Single source of truth; no per-runtime copy |
| Extension assembly | TS assembler reads the artifact; POSTs to the user’s provider | No call to mcp-translation |
| Server assembly | Python assembler reads the same artifact | No call out to Node; no model call |
| Reaching non-extension clients | Server injects into prompt_for_llm_refinement.content | Output shape unchanged |
| Reader preferences on the server | Optional reader_context + profile.yaml fallback | Absent ⇒ byte-identical generic prompt |
| Keeping the two paths identical | Parity test (TS ≡ Python) + golden snapshot | Drift is a CI failure, not a user report |
ADR 0011 compliance
Section titled “ADR 0011 compliance”- Enum-keyed content is not an enum-keyed schema. The artifact is keyed by tool and neurotype but carries prose, not field constraints. No discriminated union, no per-neurotype required field, no type narrowing. The single output shape is unchanged.
- The new server input is optional and additive.
reader_contextis optional, defaulted by the profile fallback and then by neutral absence, exactly as ADR 0004 requires. - One conformance suite still validates one shape. Per-neurotype correctness is a behavioural slice (the parity test plus eval slices), not a schema variant.
This resolves ADR 0011 open question 3 — the shaping layer is shared in @neurodock/core, not re-implemented per server.
Phasing
Section titled “Phasing”- PR-A (done): the artifact + schema, the TypeScript assembler, and the extension cutover — proven byte-identical (golden snapshot, 9,720 cases).
- PR-B: the Python assembler, the
profile.yamlread, thereader_contextwiring inmcp-translation, and the cross-language parity test. - PR-C: per-neurotype translation eval slices.
Open questions
Section titled “Open questions”The full ADR carries three open questions:
- The merge granularity when both
reader_contextandprofile.yamlare present. (Recommended: field-by-field override.) - Where the cross-language parity test runs in CI. (Constraint: a divergence must fail the build before merge.)
- Hosted-remote artifact loading — the Worker must bundle the artifact at build time, not read the filesystem at request time.
What’s next
Section titled “What’s next”- Read the full ADR.
- ADR 0011 — Neurotype schema strategy — the schema policy this layer implements (and whose open question 3 it resolves).
- ADR 0005 — Translation tool design — the vendor-neutrality doctrine the server-side injection preserves.
- ADR 0004 — Profile schema design — the additive-only input rule
reader_contextfollows. - Translation server reference — the
mcp-translationtool surface the addendum is injected into.