dialeto
v0.1.1
Published
An MCP server that gives an agent a dialect with its owner: beliefs with standing, a feedback loop, and a gate on action. No model calls, no API key.
Maintainers
Readme
You explain yourself on Monday. Again on Tuesday. By Friday, your agent confidently "knows" something you never said.
A dialect forms between people who talk enough. Dialeto gives your agent one.
It keeps what you said, what the agent saw happen, and what it merely inferred apart. Then it tests that picture through use. Corrections change it. Beliefs that hold up earn standing.
The agent you already use does the thinking. Dialeto checks the contract and does the arithmetic.
The shorthand forms
Imagine telling your agent:
Ask before publishing.
The next time it is about to publish, that sentence should matter. When you correct it, "Drafts are fine; ask before anything goes public", the picture should change in the same conversation.
Eventually, you should be able to leave it unsaid.
| What goes in | How it is held | | :--- | :--- | | What you said | In your voice, with provenance. | | What happened | As an observation, in the agent's voice. | | What the agent inferred | As a question for you, with what would prove it wrong. |
An inference can never be stored as a fact: a position needs at least one owner or observed support, and a proposal resting only on the agent's own generalisations is rejected at the boundary. Whether you explicitly ruled on something, and whether it survived use, are recorded separately.
Every answer is a bet
The bet is simple: these beliefs govern this. What happens next settles it.
- Recall. Ask in plain words. Get the positions that bear on it, their standing, and the open questions and tensions around them.
- Gate. Before a costly or irreversible action, get allow, ask or block, with the arithmetic behind it.
- Outcome. Report whether the answer held, was corrected, or went unused.
- Standing. A belief held five times across at least two sessions, with no correction since its words last changed, earns consequence. A correction becomes a receipt in your words and hands the agent the revision in the same reply.
- Consolidation. What was said becomes positions. Inferences become answerable questions, each with a falsifier. Beliefs that pull against each other become named tensions.
- Again. The next recall walks the result.
When nothing applies, it says:
No position holds this.It never fills the silence.
Subjects and the walk
What a position is about is a node, not a word. Your agent gives a subject a kind (person, place, project, topic, thing) and links it to the owner or to a parent with one of five relations: about, is_a, part_of, belongs_to, has. Recall then names the subjects your question says, follows those links up to two steps in either direction, and shows the road it took before the answer:
via Family → has → Sam (sister)A subject only starts a walk once it means something (a kind, an alias, an about or an edge); a bare node made from a word in a note is where positions attach, not what a question is about. A name matches when an alias matches whole, or when at least half of its words appear in the question, so "gardens" finds Garden And Kitchen. That is how "Maren's sister" reaches a position that never uses the word. The server only walks what the agent drew; it makes no model call, and when the walk finds nothing it falls back to matching words. dialeto init --owner <name> (or $DIALETO_OWNER) tells it what you go by.
Structure has standing, the way a position does. Every link, alias and kind (and every entailment or tension resolution) cites the receipts or positions that make it true, and it is live only while at least one of them is. Retract or purge the note behind an edge and the edge goes OUT: it stays in the journal, but the walk, the comparison and the worklist no longer see it, and the retraction says which edges, aliases and kinds went with it. An agent cannot settle a tension or mark a view entailed in its own favour either: unless it cites a receipt, the resolution waits as a proposal for your verdict. Ask recall with trace: true to see, under each position the walk reached, the edges it crossed and how many receipts cite each (via Family → has → Sam (edge cited by 2 receipts)). Structure written before this rule is kept and counted by status as unjustified until it is cited again.
Altitude
Altitude is how many derivation steps a held thing is from something you said or the agent saw: a receipt is 0, a position is one above the highest thing it cites, an edge, alias or kind one above its supports, a view one above the positions that justify it, so a mortgage, a bundle of mortgages, a tranche and a view built on views each sit one rung higher than what they stand on. Recall shows it beside the tier ([fact · seen · alt 1]) and along the road it walked (via Family → has → Sam (alt 2)), status prints the histogram, and the gate reads it: irreversible actions want support at altitude 1, costly at 2, and above that, or on structure with no trace (?), the gate asks. Staleness compounds with altitude too. Structure written before the citation rule has no trace; consolidate lists it with the positions nearest it, and re-issuing the link or alias with supports gives it an altitude.
Limbs. A limb is the tool or connector that acts (git, email, mcp:<server>), named when an action is passed to recall. The arrow records the limb and the subjects it was routed to, and an outcome settles the bet for each (limb, subject) pair the way it settles a position's tally. Five held outcomes across two sessions since the last correction make the pair a reflex: a costly act is gated as a reversible one (never past the altitude ceiling). A correction in the last 30 days makes it cautious: any act is gated one class worse. Until then the declared reversibility stands. The gate says which one it read (costly, but git on dialeto is a reflex (12 held, 0 corrected)), status prints a Limbs table, a pair corrected twice in 30 days becomes a hurt gap ("stop and ask before acting with email on school"), and the weekly series counts actions and the share that were reflexes. Everything is computed from the journals.
A folder you can read
Your beliefs live in one directory. Markdown for what was said, one claim per file. Append-only journals for what happened. A derived view you can delete and rebuild.
~/.dialeto/
notes/<id>.md what was said: frontmatter + the claim
config.json who the owner is (written by init)
journal/arrows.jsonl what happened: every row with `at` and `session`
journal/outcomes.jsonl
journal/proposals.jsonl
journal/verdicts.jsonl
journal/retractions.jsonl
held/state.json what is held: derived, safe to deleteretract says a receipt is no longer believed: the file stays and the journal records why. purge is for what must not exist here at all (ex-employer material, a secret, a person's data): the note files are deleted, every journal row that quotes them is rewritten to [purged], and journal/purges.jsonl records the act without the content. It is a command for your hand, not a tool the agent has.
dialeto purge --grep sfr3 --dry-runA purge leaves a hidden tombstone position for every claim it scrubbed, so later position ids do not move; after a few purge-and-reseed cycles they pile up as "OUT" counts. dialeto compact [--dry-run] replaces each with a one-line id reservation that holds nothing, and installs the smaller journal only after refolding it proves the live state (positions, views, tensions, graph, every id) is unchanged; journal/compactions.jsonl records the act.
A claim file, as written:
---
id: n-050e18ac0b
at: 2026-10-18T03:34:40.498Z
voice: owner
stance: decision
subjects: ["dialeto","github"]
asOf: 2026-10-18
session: first-session
---
Keep the dialeto repo private for now.Open it. Read it. Grep it. Version it. Carry it to another agent.
Retract a claim and Dialeto tells you which positions go out, which drop to a single witness, and which questions lose their ground. Tensions are surfaced beside the answer. You can see what governs an answer before trusting it.
Looking at it
dialeto map [--out file.html] [--dir D] writes the folder as one self-contained HTML page (inline CSS and script, no network, no CDN, no external font): each cell is a subject the agent drew, edges are the links it cited, and where a cell sits is only where the layout puts what is there (deterministic: the same folder lays out the same way). A cell's size is the statements held (square-root scale); its colour is the tier most of its positions stand at (said, ruled, consequence, seen; the owner is ringed); a warm halo is its tension, shown above 0.25 because a lone single-witness position alone scores that; a slow ring pulses while something is owed on it; it is paler the older its newest support; it is hollow when it is a dead end (structured, a walk anchored on it, at most one position within two hops); dashed outlines and edges mean untraced structure that cites nothing; and a cell stands on a thin stem at a height of its altitude, so structure built on structure visibly floats. Gaps are labelled empty circles beside their subject (or in the right-hand column when they have none), with the move as the tooltip. Five lenses (who you are, how you like to work, what's important to you, what decompresses you, idiosyncrasies) dim what a named rule does not select and show the count; the rule is the tooltip, kept as data in src/map-data.ts. Click a cell to read its positions and, under each, the receipts behind it. Every one of these properties is a number the fold already computes, and nothing in the picture is absent from the journal.
Install
Dialeto is an MCP server for any host that speaks the protocol: Claude Code, Cursor, Codex, and the rest. It makes no model calls, needs no API key, and never touches the network.
claude mcp add --scope user dialeto -- npx -y dialeto serveThat installs the published package from npm; the server starts on first use. To run it from a clone instead (for development, or to read the code):
git clone https://github.com/rodrigopsasaki/dialeto.git dialeto && cd dialeto
pnpm install && pnpm build
node bin/dialeto initinit creates the folder and prints the exact claude mcp add line (and the JSON block for other hosts), pointing at npm or at this clone depending on where it runs.
Other hosts: init prints the same server as a JSON block and a short paragraph for your CLAUDE.md or equivalent. The server also sends its instructions to the host on its own, so the paragraph is a reminder, not a requirement. The folder is ~/.dialeto, or --dir, or $DIALETO_DIR.
A fresh install holds nothing, so seed it from what you have already written down. Name a file or a directory, or let --discover list what it can see and ask before reading each source; nothing is opened until you say so, and no model is called.
npx dialeto seed ~/.claude/CLAUDE.md
npx dialeto seed --discoverMemories seed as observed (an agent wrote them about you), rules as said (you wrote them), and a verdict lifts either to ruled.
The conversations you have on claude.ai are not on your machine, but you can export them (Settings > Privacy > Export data), unzip the download, and seed from the conversations.json inside. Only your own messages are read; the assistant's words never are.
npx dialeto seed ~/Downloads/claude-export/conversations.jsonChat seeding keeps only statement-shaped messages by default (a sentence about you, or a standing instruction on how to work; questions and task requests are skipped and counted), --all takes everything, and --since YYYY-MM-DD skips messages created before that day.
Chat receipts wait for consolidation: a message is evidence, not a belief, so no position is minted until your agent's consolidate() fuses receipts into one (--mint makes one position per receipt anyway). The export may contain other people's words that you quoted; dialeto stores them as your receipts because you typed them, and dialeto purge --grep <word> --dry-run shows what would remove them.
Until the first npm release, run it from a checkout: pnpm install && pnpm build, then point your host at node bin/dialeto serve.
The tools
| Tool | What it does |
|---|---|
| recall(question, action?, reversibility?, limb?) | Routes to bearing positions, writes an arrow (id on the first line), lists open questions and tensions, and gates the action when one is named (an action names its limb). |
| remember(text, voice, stance, subjects) | Writes one receipt. |
| outcome(arrow, result, note?, concerns?) | held, corrected (note required) or unused. A correction returns the consolidation worklist. |
| consolidate() | Lists what is waiting, with instructions. No model call. |
| propose(ops) | mint, revise, split, addView, entail, addTension, resolveTension, and for the structure of subjects addSubject, link, unlink, alias; all or nothing. |
| verdict(target, verdict, note?) | Your ruling: confirm, correct, retract. Only when you have actually ruled. |
| retract(note, reason) | Withdraws a receipt and says what loses support. |
| status() | Since last time, what is held by tier, tension per subject, and the weekly series below. |
Everything comes back as compact text. On a folder of 3000 receipts, 300 positions and 3000 arrows, recall answers in about 10 ms and status in about 40 ms.
Hold us to this
Dialeto is a bet that a belief structure with a feedback loop beats retrieval of similar sentences. It records what would show the bet is wrong. status prints four weekly series, computed from the journals:
- how often the agent is corrected;
- how often answers draw on beliefs that survived use;
- how often familiar questions still get "no position";
- how many open questions are waiting.
If, after three weeks of real use, corrections have not fallen and survived-use answers have not risen, the loop does not learn and the idea is wrong. A week counts only if it had at least 20 recalls; below that, status says "too little use to say" rather than guessing.
That is the test.
Does it grow? The structure experiment
The subject graph in a seeded folder is the tree claude.ai's memory had already discovered. dialeto graph export writes that graph (subjects with kind, aliases and about, the statements attached to each, the edges) as the reference; dialeto seed --flat re-seeds the same notes as bare receipts with the structure stripped; and dialeto graph compare reference.json reports how much of the reference a grown graph recovers (subjects, edges, aliases, kinds). scripts/grow.ts is the dev-only driver that runs headless claude -p rounds with nothing but the server's own instructions, compares after each, and records the series in growth.jsonl. The imported structure is a target, not a result. --exercise (needs --battery) asks every battery question as a real recall before each round, after the baseline, so arrows are written and misses feed the gap list the next round works from; it reports no outcomes and records how many of those recalls missed. --repeat N copies the seeded folder to <scratch>-r1 … -rN before any round, runs each independently in turn, and finishes with the mean, min and max per round for reachable, battery hits and via-walk plus total seconds, because a single run of a stochastic agent is noise.
Where it is thin
Gaps are computed from what the folder already records: recalls that found nothing (clustered by their words), subjects a walk anchored on that lead to almost nothing, bare subjects holding many statements, and subjects whose tension is high. status lists them with the move each suggests, and consolidate starts there, showing the nearest held positions for each miss so the agent can alias or link before it asks the owner. No model is involved; the ranking is arithmetic with visible weights in src/gaps.ts. Because structure must cite what makes it true, each block also prints the position and receipt ids it can be cited by (cite: P12, P15 …), so citing costs the agent no search.
The conscience
After every closed arrow, and for every gap, the folder asks what kind of signal this was and where proof would come from. A correction that shares fewer than half its content words with the position it corrects said something new (specific: the position may need revising); one that mostly restates the position objected to where it was offered, not to what it says (generic: the position is not flagged, and its subject becomes a scope gap: narrow its aliases or about). A held outcome is confirmed, and a position left unused is noise, which becomes a scope gap after three unused outcomes in 30 days on questions that shared none of its subjects. A new view whose words a held position already covers (0.8 of their idf weight) is entailed by that position at once, citing the receipt it stands on. Each of the top gaps also prints a look: line naming where the evidence could be: the origins of the nearest positions for a miss, the receipts under an untraced edge's subjects, and so on. Verdicts are written to journal/conscience.jsonl, one row per arrow, and status backfills the arrows closed before the pass existed. The pass wants proof before it changes anything: it records and points, and the agent does the looking within its budget.
How it differs
- It holds; it does not store. A position is a claim with standing: who said it, how many sessions witnessed it, whether you ruled on it, whether it survived use.
- It refuses rather than confabulates. When nothing bears on a question, it says so and names the nearest subjects.
- Inference is kept apart. What the agent guessed is a question for you, never merged into what is held.
- Tensions are surfaced, shown beside the answer and counted in the gate.
- Retraction propagates. Withdraw a claim and you are told what else loses support.
- Every answer is a bet that
outcomesettles. - The same structure that answers questions decides whether to act, and shows its arithmetic.
Develop
Node 22 or later, pnpm.
pnpm install
pnpm lint # biome check
pnpm typecheck
pnpm build # tsup -> dist/
pnpm test # vitest, including a stdio smoke test of all eight toolsStrict TypeScript, ESM, no type assertions (rules/no-type-assertion.grit makes them a lint error). Runtime dependencies: @modelcontextprotocol/sdk and zod. AGENTS.md has the principles and the commit conventions.
Releasing
scripts/release.sh <version> bumps package.json, commits chore(release): v<version>, tags it, pushes main and the tag, and opens a GitHub Release whose generated notes are the changelog. The tag push fires .github/workflows/release.yml, which checks the tag matches package.json, reruns the gate on the tag, and runs npm stage publish, so the version is staged on npm and not public. The owner promotes it on npmjs.com, and that promotion is the approval.
One-time setup: publish the first version by hand from the owner's npm login (npm publish), then on npmjs.com open the package's settings, add a trusted publisher for GitHub Actions, repository rodrigopsasaki/dialeto, workflow release.yml, no environment, allowing stage publish only (not direct publish), and delete any NPM_TOKEN secret. Until then an NPM_TOKEN repository secret makes the workflow stage with the token instead.
License
MIT
