Documentation

How Myelin works

Five cores over one encrypted SQLite file, an MCP surface with eighteen tools, and a CLI for everything a model is not allowed to do. This page explains the reasoning, not only the behaviour — a design you cannot argue with is one you cannot trust.

On this page
  1. How Myelin fits
  2. The memory hub
  3. The process registry
  4. The quality engine
  5. The adaptation loop
  6. Preferences
  7. The MCP contract
  8. The CLI

How Myelin fits

Myelin is one binary and one SQLite file per brain. It speaks the Model Context Protocol, so an MCP-capable host — Claude Code, Claude Desktop, or anything else that implements the protocol — connects to it the same way it connects to any other server.

What it exposes is not a chat interface and not an agent. It is a set of tools, resources, and prompts that the host can reach during a session it was already having. A task begins; the host recalls what this brain knows about that kind of task; it works; it validates the result; it logs the outcome. Next time, the recall is better because the outcome was recorded.

your editor / desktop app        Myelin                      your disk
┌──────────────────────┐     ┌────────────────┐        ┌──────────────────┐
│  MCP host + model    │────▶│  18 tools      │───────▶│  brain.db        │
│                      │◀────│  4 resources   │◀───────│  (AES-256-GCM)   │
│                      │     │  MCP prompts   │        │                  │
└──────────────────────┘     └────────────────┘        └──────────────────┘
                              no model calls

Five cooperating cores sit over that one file: the memory hub, the process registry, the quality engine, the adaptation loop, and preferences. Each is independently testable. None of them calls a model.

Two transports serve the identical surface:

myelin serve            # stdio, local — your key never leaves the machine
myelin serve --http     # stateless multi-tenant, behind TLS, for a server

The same tool handlers back both, so the two surfaces are identical by construction rather than by discipline. The differences that do exist are security boundaries, not feature gaps: an HTTP session cannot read or write a local_only memory through any path, and that is enforced in the engine rather than in the transport, so there is no route around it.

What “brain” means

A brain is one project’s accumulated context: its memories, its processes, its standards, its preferences, and its outcome history. Brains are separate on purpose. The conventions of one codebase are noise in another, and a single shared store degrades into exactly the vague, half-relevant recall that makes people stop trusting memory features.

You will typically have one brain per project, plus a tenant-level meta-brain for the things that are true of you rather than of any one repository.

The memory hub

Why append-only, rather than an updatable row

The obvious design for a memory store is a row per fact with a confidence column you update as you learn more. It is obvious, and it is wrong, for a reason that only shows up later.

When a fact changes, the update destroys the previous state. Six weeks on, the question you actually have is never “what do we believe now” — you can read that off the row. It is “when did this become true, what did we believe before, and was the model working from the old value when it made that decision?” An updatable column cannot answer any of those, because the evidence was overwritten by the thing you wanted to know about.

So Myelin appends. Correcting a memory writes a new one and marks the old one superseded, pointing at its replacement. The chain stays readable. Recall returns the current fact; the history is there when you need to explain something.

There is exactly one exception, purge, which hard-deletes a single memory — the row, its full-text index entry, and its edges — and leaves a tombstone recording that an id once existed, so doctor can verify the deletion was complete. It exists because “append-only” is not an acceptable answer to someone who needs a thing gone. It is CLI-and-admin only, and deliberately not an MCP tool, so a model cannot reach it.

Bi-temporal, and why that matters

Two clocks, not one. observed_at is when the fact was true in the world; the record’s own timestamp is when Myelin learned it. Decay measures from observed_at when it is known, so importing a two-year-old design note does not make it look like this morning’s decision.

Trust is computed at read time

Trust is never stored. It is derived when you read:

trust = (½ ^ (age / halfLife)) ^ (1 / reinforce)

Half-lives are per class, because different kinds of knowledge go stale at very different rates:

classhalf-lifeexample
identity3650 days“Gus works in Singapore”
convention180 days“we squash before merging”
project_state14 days“the API listens on 9090”
episode7 days“the deploy failed on Tuesday”

Tier multiplies the half-life: core ×4 with a 0.5 floor, so a promoted memory never decays out of reach; normal ×1; archive ×0.25.

Note that reinforcement is an exponent, not a bonus added to a score. That choice does real work. It keeps trust inside (0, 1] with no clamping anywhere, and it makes the function provably decreasing in age and increasing in use — properties the test suite asserts rather than assumes. A bonus term would need a cap, and a cap is a place where two very different memories start returning the same number.

Because trust is computed rather than stored, nothing in the database can be quietly stale. There is no background job that has to run for the numbers to mean anything, and no window during which the store disagrees with itself.

The recall pipeline

In order, every time:

  1. FTS5 full-text search, top 40 candidates.
  2. Score by bm25 × trust — relevance times how much this brain should currently believe it.
  3. Fit a byte budget. Context is finite; recall returns what fits rather than everything that matched.
  4. Expand one hop along edges, damped ×0.5. One hop, because two hops reliably drags in something loosely associated and dilutes the result.
  5. Flag contradictions.
  6. Touch what was returned, which is what feeds reinforcement.
  7. Prepend the core tier — up to 2KB of standing context, and not touched: core memories are offered rather than asked for, so reading them should not count as use.

Retrieval is keyword search plus graph-lite expansion. There is no embedding model, which is the point: recall is free, explainable, and instant. Vector search is a v2 question, held back until semantic retrieval earns the complexity it costs.

Contradictions are surfaced, not hunted

Two ways a contradiction is flagged: an explicit contradicts edge, or two live memories asserting the same subject differently. The second is what catches a supersede race — two people correcting the same memory at once. Both corrections land, and recall flags them as contradicting. Losing a write to avoid an awkward result would be the worse failure.

Myelin does not run a background job hunting for inconsistency across the whole brain. Contradictions surface at the moment they are relevant, which is when something recalls both sides of one. Myelin flags; you decide.

The process registry

Memory answers what is true here. The process registry answers how work of this kind gets done here — which is the part that usually lives in someone’s head, or in a CONTRIBUTING file nobody opens.

Three kinds, deliberately distinct:

Workflows — how a type of task gets done. Ordered steps, with checks and owner gates between them.

Skills — how a specific artifact gets produced. Guidance rather than sequence.

Loops — how an autonomous cycle knows when to stop. Bounded phases with an exit condition.

All three are YAML, and all three are validated when saved rather than when read. A broken process can never be retrieved, because it was never stored.

The validation rules, and why they are strict

Validation is collect-all: you get every problem at once, not the first one. Fixing a process one error per attempt is a bad experience and it hides whether the errors are related.

It rejects cross-kind fields. A skill carrying steps is refused rather than merged, because that document is a mistake — someone meant to write a workflow — and silently accepting it produces a skill that behaves in a way its author will not predict.

Loops must declare both exit_when and a bounded max_iterations. An unbounded loop is not a process, it is a bug, and the registry will not hold one. The bound is required even when exit_when looks obviously reachable, because “obviously reachable” is exactly the assumption that produces a cycle running until someone notices the bill.

Matching is deterministic

Processes are matched against a task by keyword containment on word boundaries, so a trigger of test does not fire on latest. Multi-word triggers must match as phrases, and they count double, because a phrase trigger misfires far less often than a single word.

Nothing about matching involves a model. The same task text selects the same process every time, which means when the wrong process fires you can look at the trigger list and see precisely why.

Served as MCP prompts

This is the part that changes how it feels in practice. Stored workflows are registered with the host as native MCP prompts — one prompt per workflow, plus the myelin-loop prompt — built from what the brain actually holds.

The difference is not cosmetic. A process in a file is something the model may read, may summarise, or may skip when context gets tight. A process exposed as a prompt is something the host offers deliberately and the model receives whole. It travels with you to a different model, or a different host, without being rewritten.

Versioning

Saving a new version supersedes the old one, in the same way memory does. Old versions stay retrievable. A process that changed last Tuesday and broke something on Wednesday should be diffable on Thursday.

The quality engine

A standard is one YAML document with two tables: rules, which drive validate, and risks, which drive precheck. Standards are versioned the same way processes are.

validate

validate grades an artifact against a standard deterministically. Measured on the developer machine and asserted as a test so a regression fails the build: 6.2ms per 100KB, against a budget of 10ms. There is no model in the loop, so that number is a property of the code rather than of the weather.

It composes thirteen checker primitives, and that set is closed:

require_pattern · forbid_pattern · require_all_of · require_one_of · max_line_length · max_lines · max_bytes · forbid_trailing_whitespace · max_consecutive_blank_lines · balanced_delimiters · require_prefix · require_suffix · require_unique_lines

A standard composes these; it cannot introduce new logic. That is the constraint that keeps validation fast and deterministic. The moment a standard can supply its own predicate, validation becomes arbitrary code with arbitrary runtime, and the guarantee that it always terminates in milliseconds is gone.

Every violation names the rule, the standard, the line, the column, and what to do about it. That last field is the one that matters for a host: given a location and an expectation, a model repairs the specific thing. Given “this does not meet the standard”, it rewrites the whole artifact and introduces two new problems.

precheck

precheck grades an action before anything expensive runs, and returns one of LOW, HIGH, or BLOCK. The highest matching risk wins.

One thing about it is worth stating precisely, because the obvious reading is wrong: LOW means nothing in the risk table matched. It is not a claim that the action is safe. Myelin reports what this brain knows, and knowing nothing about an action is not the same as knowing it is fine. A tool that returned “safe” for anything it had no rule about would be lying at exactly the moment the answer mattered.

Secret refusal

Content that looks like a credential is refused before it reaches the database: seventeen structured rules plus entropy checks. A refusal names the rule and the byte offset, and never the matched text — so refusals are safe to write to a log, which is the only way they are useful.

The honest limit: this is a guard against accident, not a guarantee against determination. Someone who wants to store a secret in Myelin can obfuscate it past the scanner. What it reliably stops is the ordinary case — pasting a block of config that happens to contain a key.

The adaptation loop

log_outcome records what happened and writes a matching episode, so the same event is both queryable as structured data and recallable as text. One write, two shapes, because the two are useful at different moments and keeping them in sync manually is the kind of thing that stops happening after a fortnight.

The reflector notices repetition

At fixed thresholds:

findingthreshold
a recipe worth capturing3 successes in one task family / 30 days
a preference worth storingthe same correction twice
drift worth surfacing5 failures or partials in one family / 14 days

These are constants in code, not settings. They live under the protected reflect. namespace specifically so that Myelin cannot lower the bar at which it starts proposing changes to itself. A system that can tune its own threshold for proposing changes has a feedback loop with nothing on the other end of it.

It proposes; it never applies

The reflector raises a proposal. Proposals wait for you. Nothing is rewritten unasked, and nothing is enabled quietly.

The asymmetry is deliberate and slightly awkward on purpose: the reflector can notice that a workflow should exist without being able to write one. So Myelin tells you what it saw, and you supply the body. That is a worse experience than autogeneration and a much better outcome, because a process you did not write is a process you will not trust, and a process you do not trust is one you will route around.

Accepting a proposal takes a snapshot first, and a failed snapshot aborts the accept. Regret should be recoverable, and it is only recoverable if the snapshot is a precondition rather than a courtesy.

Reviewing them is a CLI action, not a tool:

myelin proposals list
myelin proposals accept prp_7f2a
myelin proposals reject prp_7f2a

Accepting a proposal is deliberately not reachable over MCP. A model can see that proposals exist. It cannot act on one.

Preferences

Preferences are the fifth core. They are the standing settings that shape how everything else behaves, which is exactly why they are the part with the strictest rules about who may write them.

Protected namespaces

Six namespaces are compiled in rather than stored: gates. risk. reflect. trust. core. secrets.

Because they are compiled in, no brain and no import can widen them. A malicious or merely careless export cannot arrive carrying a new “protected” list that happens to be shorter.

The gate keys on origin. An automatic write to a protected key is refused outright. An owner write, made through the CLI, is allowed. So the settings that govern risk classification, reflection thresholds, and secret handling can never be changed by the loop that they constrain.

The explicit-durable test

Not everything you say is a preference.

“Use tabs from now on” — a preference. “Use tabs here” — not a preference.

A statement has to carry an explicit durability marker to become a stored preference. Any one-off qualifier (“just this once”) or hedge (“maybe”, a trailing question mark) vetoes it outright.

set_preference requires the owner’s words verbatim in its statement field, and applies the test to those words. The host cannot paraphrase an instruction into permanence.

This is a small rule that prevents a specific, corrosive failure: a passing remark in a frustrated moment becoming a permanent rule that quietly shapes every session afterwards, long after anyone remembers saying it. When the test refuses, the refusal says what to write instead.

Resolution order

session (host-held, never stored) → project braintenant meta-brain.

Nearest wins. A session-scoped instruction overrides the project’s stored preference for the length of that session and leaves nothing behind.

The MCP contract

Myelin speaks MCP over two transports with an identical surface: myelin serve (stdio) and myelin serve --http (streamable HTTP, stateless).

Tools

Eighteen, in four groups.

Memory

toolwhat it does
recallwhat this brain knows about a task, ranked by relevance × trust
rememberappend a memory (credentials refused)
supersedereplace one that is no longer true
linkrelate two memories; contradicts is meaningful
forgetarchive a memory so it decays out of recall
promote_to_corepropose standing context — raises a proposal

Note that promote_to_core proposes rather than promotes. Standing context is prepended to every recall, so a model that could write to it directly would be able to install instructions for its own future sessions.

Processmatch_process · get_workflow · get_skill · save_process · list_processes

Qualityvalidate · precheck · save_standard

Adaptation and preferenceslog_outcome · list_proposals · get_preferences · set_preference

Not tools, by design

purge · push · pull · restore · shred · key · accepting a proposal.

A model cannot reach any of them. Each is either destructive, or moves key material, or changes what the system will do in future sessions. They live in the CLI, where a person runs them.

Resources

myelin://core · myelin://preferences · myelin://processes · myelin://standards

Reading the core resource does not touch anything. Reading standing instructions is not the same as recalling something, and counting it as use would inflate the reinforcement of exactly the memories that are already privileged.

Prompts

myelin-loop, plus one prompt per stored workflow, registered from what the brain actually holds.

HTTP

POST /mcp
Authorization: Bearer myl_pat_...
X-Myelin-Brain: project-a        # or omit, with a single-brain token
X-Myelin-Key: <base64 32 bytes>  # TLS only

The pipeline, in order: auth → brain selection → key intake → quota → dispatch → audit. Every request re-authenticates and re-resolves. Nothing persists between calls except a 10-minute idle key cache.

Errors: 401 unauthenticated or wrong key · 403 scope · 404 unknown brain · 413 body too large · 426 a key sent over plaintext · 429 rate limit.

The 426 Upgrade Required is not a formality. Myelin refuses to accept a key header over a plaintext connection rather than accepting it and warning, because a key that has crossed a plaintext network is already compromised and a warning arrives too late to help.

local_only over HTTP

A memory marked sensitivity='local_only':

  • is invisible to every HTTP read path — recall, get, list, edge expansion, and the core-tier prepend;
  • cannot be written over HTTP at all;
  • has its edges dropped when it is withheld, because an edge pointing at a memory that is not there would itself reveal that something was withheld.

This is enforced in the engine rather than in the transport, so there is no layer where a future change could route around it.

Admin REST

/v1/health · /v1/brains · /v1/brains/{id}/pull|push|snapshots|shred · /v1/tokens · /v1/restore

/v1/restore only lists snapshots. The server has no key, so restoring always happens on your device.

The CLI

Global flags: --data <dir> (default .myelin, or MYELIN_DATA) and --name <brain> (default project) apply to the local commands. Flags may appear before or after positional arguments.

Local

myelin init creates a brain seeded with the starter pack, then prints both the stdio and remote-connector setup snippets.

myelin init --name project --default-sensitivity normal

--default-sensitivity local_only makes a brain whose memories never leave the machine unless you say otherwise.

myelin serve

myelin serve                                    # stdio
myelin serve --http --listen 127.0.0.1:8787     # multi-tenant

--http refuses a non-loopback address unless MYELIN_TRUST_PROXY=1 confirms TLS terminates in front.

myelin doctor [--repair] checks database integrity, FTS synchronisation, dangling supersedes and edges, tombstone consistency, required meta keys, and absolute paths. --repair fixes the fixable classes.

myelin stats — counts, revision, and default sensitivity.

myelin export [--format md|jsonl] — Markdown is meant to survive Myelin. It should be legible with no tooling at all.

myelin core add|rm <id> — promote a memory to standing context, or return it to normal.

myelin proposals list|accept|reject <id> — accepting takes a snapshot first.

myelin purge <brain> <id> --reason "..." — permanently deletes one memory. Requires a reason and a typed confirmation. This is the one thing Myelin cannot undo.

Server

myelin admin bootstrap --email you@example.com — run on the server. Creates the first tenant and prints an admin token once. It does not create a brain, because a brain’s key must be generated on your device.

myelin login <server-url> — saves the server and token to ~/.myelin/config.json (mode 0600) and verifies them immediately. Refuses plaintext URLs.

myelin brains create|list

myelin brains create meta          # the tenant meta-brain
myelin brains create project-a

The key is generated here, on your machine, stored in your keychain, and the 24-word recovery phrase is printed once. Write it down before running anything else.

myelin pull <brain> / myelin push <brain> — pull downloads ciphertext plus snapshots and decrypts locally. Push refuses if the server moved on since your pull; there is no merge engine, and it will tell you to pull first. A brain holding local_only memories is pushed as a filtered export, never a file copy.

myelin restore <brain> --at <snapshot|timestamp> [--to <path>] — nearest snapshot plus the archived changes after it. It always writes to a fresh path: a restore that destroys what you were recovering from is not a restore.

myelin token create|list|revoke

myelin token create --name claude-web --scopes write:brn_abc123

Prefer brain-scoped tokens for connectors. Revocation takes effect on the next request and evicts any key cached under that token.

myelin key export|import <brain>export prints the recovery phrase; --base64 prints the raw key for a connector header. import restores a key from a phrase.

myelin shred <brain> — destroys the server copy, offers a final pull first, and asks separately about destroying your local key. That second question is the irreversible half.

Environment

variableeffect
MYELIN_DATAdefault data directory
MYELIN_CONFIGconfig file location
MYELIN_KEYRING=fileuse the 0600 file key store instead of the OS keychain
MYELIN_TRUST_PROXY=1confirm TLS terminates in front, permitting a public bind

Measured performance

Asserted as tests, so a regression fails the build rather than being noticed later:

  • recall p95 8ms over 5,000 memories, against a 30ms budget
  • validate 6.2ms per 100KB, against a 10ms budget

Both measured on the developer machine. They are a floor for what the design allows, not a promise about your hardware.

Next

Get it running, or read the trust model — including the two ways the encryption story does not save you.