kernel.store
Two persistence doors, deliberately asymmetric. store is a private cache over the log;
knowledge is shared input that outlives every session and is published with the agent.
type KernelStore = {
session: {
/** The session's entries so far, in seq order. Synchronous — a live projection. */
get(opts?: { after?: number }): readonly AxonEntry[]
}
/** Null when absent OR unreadable — cache doctrine: rebuild, don't crash. */
get<K extends keyof CognetStoreSchema>(key: K): Promise<CognetStoreSchema[K] | null>
/** Atomic (temp+rename); resolves when durable. */
set<K extends keyof CognetStoreSchema>(key: K, value: CognetStoreSchema[K]): Promise<void>
}
type KernelKnowledge = {
list(opts?: { match?: string; limit?: number }): Promise<readonly KnowledgeEntry[]>
/** THROWS KNOWLEDGE_NOT_FOUND when absent. Not a cache. */
read(name: string): Promise<string>
write(name: string, content: string): Promise<void>
/** Idempotent — a no-op when already absent. */
remove(name: string): Promise<void>
}
type KnowledgeEntry = {
name: string // a NAME, never a path the cognet derived
description: string // "" when the entry declares none, never undefined
size: number // bytes on disk
path: string // absolute, resolved BY the kernel
}
Both are mediated for the same reason inference and execution are: the cognet declares intent, the kernel owns mechanism — paths, atomicity, substrate. Filesystem today, anything tomorrow, invisible either way.
store.session — read-only history
// boot: read everything, remember the high-water mark
let seq = 0
for (const entry of kernel.store.session.get()) {
fold(entry)
seq = entry.time.seq
}
// steady state: never re-read what you already folded
for (const entry of kernel.store.session.get({ after: seq })) { fold(entry) }
Synchronous, because this is the kernel's live in-memory projection, not a disk read.
after is a seq cursor.
This is how a cognet reaches its own history — cold-boot rehydration, context rebuilds — without the runtime passing anything in at boot. A brain that had to be handed its past would be a brain the host had an opinion about.
There is no write verb here and never will be. The cognet's only writes to the world are
output() and run(), both committed by the kernel on its behalf. A patchable record is a
footgun by construction: a cognet cannot forge history, backdate a decision, or record
something it did not do.
Entries only. Kernel spans and error machinery are not filtered out — they are unreachable: this reads a projection already classified by type namespace before the cognet asks.
The kv — private consolidated state
A cognet declares its own schema once, by declaration merging:
// anywhere in the cognet's own sources
declare global {
interface CognetStoreSchema {
checkpoint: { entries: unknown[]; seq: number }
}
}
await kernel.store.set("checkpoint", { entries: state.entries, seq })
const saved = await kernel.store.get("checkpoint") // null when absent
get/set are typed against keyof CognetStoreSchema, so a cognet with no declaration
has no callable kv at all — keyof {} is never. Declaring the schema is opting in.
Each bundle compiles against its own augmentation and nothing leaks between cognets.
Declared global rather than module-scoped on purpose: a cognet augments it with a plain
declare global block, where module augmentation would force every author to name this
package's specifier — exactly the plumbing the ambient-globals surface exists to hide.
The cache doctrine
This is a cache over the log. One flat namespace per cognet; the kernel imposes no lifetime taxonomy — a cognet that wants session-scoped keys prefixes with the sessionId it already has from every entry's envelope.
Atomic per key, last-write-wins. It is the one writable surface concurrent instances of the same agent share.
Unreadable or stale state is discarded and rebuilt from session. That is why get()
returns null for unreadable as well as absent: there is a correct recovery, and it is
the same one. Deleting data/state/ is always safe.
knowledge — long-term material
const catalogue = await kernel.knowledge.list({ match: "deploy", limit: 40 })
const content = await kernel.knowledge.read("axon/deploy.md")
await kernel.knowledge.write("notes/incident-2026-08.md", summary)
await kernel.knowledge.remove("notes/stale.md")
Durable, human-readable, human-editable material the brain consults and maintains. The long-term counterpart to the private kv. Three things separate it from every other durable surface:
It is input, not output. A session log records what the agent did; a knowledge entry is
what someone — a human, a module, or the brain itself — decided is worth keeping. That is
why it lives outside .agent/ and is published with the agent.
It is not a cache. Losing it is real data loss, so writes are atomic (temp + rename — a kill mid-write leaves the previous content, never a torn file) and a missing entry throws rather than returning null. If a brain was told an entry exists and it does not, something is wrong and silence would hide it.
It is format-agnostic by refusal. The kernel hands over bytes and a name; what those bytes mean is the cognet's business. Markdown, JSON, CSV, a serialized graph — the store has no opinion and will never grow one.
Names, not paths
name is a stable identifier the author chose. "axon/terminal.md" is a name whose
separators happen to nest, not a path.
path is present on a catalogue entry because knowledge is no longer one directory: a
module's corpus lives inside its own package, so a name cannot be joined to a root. A
cognet that wants the model to open a file with ordinary fs tools has to render something
openable, and teaching the model two path rules and which entries follow which is worse
than handing it the answer.
Reading a path you were given is ordinary. Deriving one from an assumed layout is not — that is what stops a brain being portable to a body that stores things differently.
Writes are confined to the agent's own store and enforced, not trusted: a name that
resolves outside it throws KNOWLEDGE_ESCAPE. Traversal is refused rather than sanitised,
because a caller that wrote "../../.env" meant something and quietly rewriting it to a
different file is worse than failing. The cognet gains one more mediated door, never a
filesystem.
Ordering
list() is ordered by name, bytewise ascending — stated rather than left to the
filesystem, because an unspecified order is a ranking nobody declared and every cognet
silently inherits. A caller wanting a different one sorts the result it was given.
match is a case-insensitive substring test against name and description. limit
truncates after ordering, so a capped render is a stable prefix rather than an arbitrary
sample.
description is "" when an entry declares none, never undefined, so a renderer never
branches. The catalogue's whole job is being cheap enough to hold ambiently: a brain
renders name + description for everything it has and reads only what the task needs. A
brain that already knows its names ignores all of it and calls read() directly.
Retrieval is cognition and lives in the cognet
There is no semantic search, no ranking, no embedding here, and there must never be. Choosing how a mind recalls is a memory policy, which the kernel is forbidden from holding.
list's match is a predicate the caller supplies — the kernel executes one it is
given, it never supplies one. A brain that wants full-text search greps through run(). A
brain that wants embeddings builds them and keeps the index in store, where derived state
belongs.
Mutations are traced
The kernel emits cognet:knowledge:write and cognet:knowledge:remove when a cognet
mutates the store. The cognet never records its own mutations — the same rule that governs
run()'s action/result pair.
Not a span: a write is a settled act, not a bracket, and there is no meaningful "in progress" for an atomic temp+rename. Traced because "the agent modified its own long-term memory" is exactly the fact you want when behaviour drifts weeks later. Name only, never content — the log records that memory changed, not a second copy of it.