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
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:
| class | half-life | example |
|---|---|---|
identity | 3650 days | “Gus works in Singapore” |
convention | 180 days | “we squash before merging” |
project_state | 14 days | “the API listens on 9090” |
episode | 7 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:
- FTS5 full-text search, top 40 candidates.
- Score by
bm25 × trust— relevance times how much this brain should currently believe it. - Fit a byte budget. Context is finite; recall returns what fits rather than everything that matched.
- Expand one hop along edges, damped ×0.5. One hop, because two hops reliably drags in something loosely associated and dilutes the result.
- Flag contradictions.
- Touch what was returned, which is what feeds reinforcement.
- 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:
| finding | threshold |
|---|---|
| a recipe worth capturing | 3 successes in one task family / 30 days |
| a preference worth storing | the same correction twice |
| drift worth surfacing | 5 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 brain → tenant 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
| tool | what it does |
|---|---|
recall | what this brain knows about a task, ranked by relevance × trust |
remember | append a memory (credentials refused) |
supersede | replace one that is no longer true |
link | relate two memories; contradicts is meaningful |
forget | archive a memory so it decays out of recall |
promote_to_core | propose 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.
Process — match_process · get_workflow · get_skill · save_process ·
list_processes
Quality — validate · precheck · save_standard
Adaptation and preferences — log_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
| variable | effect |
|---|---|
MYELIN_DATA | default data directory |
MYELIN_CONFIG | config file location |
MYELIN_KEYRING=file | use the 0600 file key store instead of the OS keychain |
MYELIN_TRUST_PROXY=1 | confirm TLS terminates in front, permitting a public bind |
Measured performance
Asserted as tests, so a regression fails the build rather than being noticed later:
recallp95 8ms over 5,000 memories, against a 30ms budgetvalidate6.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.