The note is written.
A short decision record: what was chosen, when, and why. Ingested once with remember, either from the CLI or from the agent.
You write down a decision in a Markdown note and move on. A week later an agent is asked about it. This is the sequence of tool calls it makes, the shape of what comes back, and why the answer carries a path, a byte range, and a content hash instead of just a paragraph.
The vault is a folder of Markdown you already keep. One file in it,
decisions/0012-queue-backend.md, records that the job queue will run on Redis Streams,
with the date and the reasoning. Nobody tags it, links it, or moves it. The graph is derived from the
file on ingest, and the file stays the source of record.
A short decision record: what was chosen, when, and why. Ingested once with remember, either from the CLI or from the agent.
Someone asks the agent in Claude Code: what did we decide for the job queue, and why? The agent has no memory of the note. It has the MCP tools.
A month later you move the queue to a different backend and edit the same note. The old fact has to stop being the answer without anyone deleting anything.
Some steps are server behavior. Others are conventions the agent follows because the tool descriptions and llms.txt ask it to. Each node is labelled with which one it is.
Returns the vaults this server can reach: name, whether this connection uses it, and backend. Never paths.
A JSON file in the project names the vault that applies. The server does not read it; the agent does, and passes the name as vault=.
One-shot retrieval and synthesis. The answer comes back with sources[] attached.
ok is the only success value. Anything else changes what the agent is allowed to say.
The reply names the vault-relative path, byte_start, byte_end, and content_hash.
If the edit replaced the old text and the correction judge confirms the change, the old claim is dated, dropped from recall, and linked by an edge.
Empty text, honest status. The agent retries or fixes the LLM config. It does not conclude the graph is empty.
Shown in section 04Tool, parameter and key names below match the 0.3.0 MCP tools. Payloads are abridged. Values (vault names, block ids, byte offsets, hashes, the answer text) are illustrative and would differ in your vault.
The agent asks the server which vaults it can reach. Each entry has a name, a current flag for the vault this connection would use, and the backend. Paths are never returned, so nothing about the machine's layout leaks into the transcript.
// illustrative: two vaults reachable from this daemon
{
"vaults": [
{ "name": "platform-notes", "current": false, "backend": "grafx" },
{ "name": "personal", "current": false, "backend": "grafx" }
]
}
The repository carries a .okto-neuron-vault file in its working directory, JSON of the form {"vault": "<name>"}. The server does not read this file. The agent reads it, as the tool descriptions tell it to, and passes the name on every later call, so a question asked from this project is answered from this project's vault.
One call. include_sources=true asks for the block pointers alongside the text. The context the model sees carries an [EXCERPT ...] line per source, stating which bytes of which file it is looking at and how many bytes the file has in total, so it can say when the excerpts do not cover a period instead of extrapolating.
// the excerpt line the model sees for this source
[EXCERPT source=decisions/0012-queue-backend.md bytes=0-388,880-1364 of 2210]
// what comes back (abridged)
{
"status": "ok",
"text": "On 2026-03-04 the team chose Redis Streams for the job queue, citing a smaller operational footprint than Kafka for the current team size.",
"citations": ["..."],
"subgraph_evidence_ids": [],
"retrieval": {
"synthesis_status": "ok",
"finish_reason": "stop",
"vault": "platform-notes"
},
"sources": [
{
"block_id": "blk_4e11c0",
"path": "decisions/0012-queue-backend.md",
"byte_start": 880,
"byte_end": 1364,
"content_hash": "sha256:7c0e...91af"
}
]
}
retrieval.synthesis_status is ok and retrieval.finish_reason is stop, so the text is complete and came from the model. retrieval.vault confirms which vault answered. Had the agent's vault argument been discarded, retrieval.vault_override_ignored would be present.
The agent replies with the decision and, under it, the pointer: vault-relative path, byte range, content hash. A reviewer opens the file at those bytes. If the text there matches, and the hash of the block matches, the claim is checked without trusting the agent's paraphrase.
decisions/0012-queue-backend.md · bytes 880-1364 · sha256:7c0e...91afDecided 2026-03-04: Redis Streams for the job queue. Reason given: smaller
operational footprint than Kafka for the current team size.
Source: decisions/0012-queue-backend.md, bytes 880-1364 (sha256:7c0e...91af)
A month later Redis Streams is retired. You replace the decision line in the same note: the queue now runs on NATS JetStream, dated 2026-04-11. The file is re-ingested with remember. The block that held the old sentence no longer exists, so the claim read from it becomes a candidate for correction. Extraction proposes the new claim, and the relation gate commits it once its endpoint types, predicate and grounding in the new block check out.
The ingest asks an LLM correction judge whether the new claim corrects the one whose text was removed. The judge leans towards no correction when it is unsure. When it confirms, and both claims come from the same document, the old claim is dated and dropped from recall, and a supersedes edge goes from the new claim to the old one. Nobody deletes anything. The next ask answers with NATS JetStream and a new byte range; the Redis Streams claim is still in the graph, reachable through the edge, no longer in the answer. If the judge says no, nothing is superseded.
[EXCERPT source=decisions/0012-queue-backend.md bytes=0-388,1402-1790 of 2638]
{
"text": "As of 2026-04-11 the job queue runs on NATS JetStream; Redis Streams was retired.",
"retrieval": { "synthesis_status": "ok", "finish_reason": "stop", "vault": "platform-notes" },
"sources": [
{ "block_id": "blk_a90d3e", "path": "decisions/0012-queue-backend.md", "byte_start": 1402, "byte_end": 1790, "content_hash": "sha256:c41b...0e77" }
]
}
Same question, same vault, but the LLM endpoint the daemon points at is unreachable. This is what comes back, and what the agent should do with it.
synthesis_status is always present and has six values: ok, empty,
provider_error, truncated, abnormal_stop, no_llm. Only ok means the
text can be trusted as complete. provider_error with empty text means the model could not be
reached. It says nothing about whether the graph holds the answer.
{
"status": "degraded",
"text": "",
"citations": [],
"subgraph_evidence_ids": [],
"retrieval": {
"synthesis_status": "provider_error",
"vault": "platform-notes"
},
"sources": []
}
What the agent should do: say the model was unreachable, not that no decision was recorded. Check the
configured LLM endpoint (the default is http://127.0.0.1:8123/v1, a server you run) and
retry once it is up. If an answer is needed now, call explore with the same topic: it makes no
LLM call and returns the claims and their block_ids as structure rather than prose.
What the agent should not do: treat empty text as an empty graph, paraphrase from its own memory, or
drop the status field from its reply. truncated and abnormal_stop deserve the
same care; the text is incomplete and should be labelled as such.
The daemon enforces its own limits, such as loopback-only writes and vault-relative paths. The agent's habits, such as reading synthesis_status and citing the pointer, come from the tool descriptions and llms.txt, and the agent has to follow them.
remember from the CLI or lets the agent do itlist_vaults, ask, explore, remember over MCP on 127.0.0.1:8201synthesis_status before trusting any text (by instruction)retrieval.vaultask, and sources[] when include_sources=trueremember or init_vault from anything but the local machineThe installer is the only supported install path. It starts the daemon and registers Claude Code as the MCP client.
curl -fsSL https://raw.githubusercontent.com/OktoLabsAI/okto-neuron/main/install.sh | bash
claude mcp add --scope user.
Create a managed vault in the UI (or with init_vault), then create a .okto-neuron-vault file containing {"vault": "<name>"} at the root of the project you will ask from.
Put a decision record in the vault, then call remember with its path. Entities at or above confidence 0.75 commit, claims pass a grounding gate, and anything held back waits in Curation.
If Claude Code is not registered yet, follow the Claude Code setup guide. Ask the question. The agent should call ask with include_sources=true, check synthesis_status, and cite the block pointer. The rules it follows are in llms.txt.
// 1. is the answer healthy?
retrieval.synthesis_status == "ok"
retrieval.finish_reason == "stop"
// 2. did the right vault answer?
retrieval.vault == the pinned name
// and retrieval.vault_override_ignored is absent
// 3. can a human check it?
sources[].path // vault-relative
sources[].byte_start
sources[].byte_end
sources[].content_hash
The MCP tools this walkthrough uses, and what each one did:
| Tool | Used for |
|---|---|
list_vaults | Discover the vault names this daemon can reach. Names only. |
ask | Retrieve and synthesize the decision with include_sources=true; returns text, citations, retrieval.synthesis_status, retrieval.finish_reason, retrieval.vault, and sources[] |
remember | Ingest the note the first time, and again after the edit. Loopback-only. The second ingest writes the supersedes edge if the correction judge confirms the change |
explore | Fallback when the model is unreachable: claims and relationships with block_id, no LLM call |
Path, byte range, and content hash ride with the answer. A reviewer can open the file and read those bytes without asking the agent.
An unreachable model returns empty text and provider_error, never a paragraph that looks finished.
The note stays the source of record. Replace a fact, re-ingest, and once the correction judge confirms it, the old claim is dated and dropped from recall with an edge to the new one.
The daemon binds 127.0.0.1 and the writes are loopback-only. In this walkthrough your text went only to the LLM endpoint you configured, so if that endpoint is hosted, it left the machine there. The full list is under what leaves the machine.
Prerelease 0.3.0. Free to run locally. No account required.