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.
A team writes down a decision in a Markdown note and moves on. Days 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 the team already keeps. 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 trust root.
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 the team moves to a different queue and edits the same note. The old fact has to stop being the answer without anyone deleting anything.
Every node below is a real mechanism in the product, not a convention the agent has to remember.
Returns the vault names this server can reach. Names only, never paths.
A marker file in the project names the vault that applies. The agent passes it 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.
The changed fact supersedes the old claim at ingest. Old claim dated, dropped from recall, edge written.
Empty text, honest status. The agent retries or fixes the LLM config. It does not conclude the graph is empty.
Shown in section 04Key names below are the real ones. 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. The response is a list of names. Paths are never returned, so nothing about the machine's layout leaks into the transcript.
list_vaults// illustrative: two vaults reachable from this daemon
["platform-notes", "personal"]
The repository carries a .okto-neuron-vault marker in its working directory. The agent reads it and passes the name on every later call, so a question asked from this project is always 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)
{
"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)
Weeks later the team retires Redis Streams and appends an update to the same note: the queue now runs on NATS JetStream, dated 2026-04-11. The file is re-ingested with remember. Extraction proposes the new claim; the gate commits it because it clears confidence 0.75.
Because the changed fact lives in the same document lineage, the ingest dates the old claim and drops it from recall, writing a supersedes edge 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.
[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 five values: ok, empty,
provider_error, truncated, abnormal_stop. 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.
{
"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, returns the claims and their block_ids as structure rather than prose, and works
fully offline.
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.
Three parties, and the boundaries between them are enforced by the product, not by instruction.
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 textretrieval.vaultsources[] and the honesty fields to every askremember 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 write its name into a .okto-neuron-vault file at the root of the project you will ask from.
Put a decision record in the vault, then call remember with its path. Candidates at or above confidence 0.75 commit; the rest wait in Curation.
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 |
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 checks the bytes, not the agent.
An unreachable model returns empty text and provider_error, never a paragraph that looks finished.
The note is the trust root. Change it, re-ingest, and the old claim is dated and dropped from recall with an edge to the new one.
The daemon binds 127.0.0.1, the writes are loopback-only, and the only outbound call is to the LLM endpoint you configured.
Prerelease 0.3.0. Free to run locally. No account required.