The Kernel Contract
A cognet is versioned against the kernel exactly like a binary is versioned against syscalls. The kernel evolves freely underneath; the contract is this shape, and it is the only thing a cognet ever holds.
Read this page before the others. Everything that follows is a consequence of it.
The privilege inversion
Axon already treats model-authored code as untrusted. The capsule can request; only the kernel can take.
Cognets extend the same inversion one ring further in. The cognet is the cognition, and the kernel protects the user's resources from it with equal severity. Not because a cognet is malicious — you wrote it — but because a boundary that only holds for well-behaved code isn't a boundary.
| Ring | Component | Holds |
|---|---|---|
| 0 | kernel | engine, scheduler, the capsule constructor, the session log |
| 3 | cognet | seven verbs |
| 3 | capsule | model-authored code, policy-gated |
The cognet and the capsule are both unprivileged, for different reasons and through different doors.
The seven verbs
type KernelAbi = {
output<K>(type: K, data: AxonOutputEvent[K]): Promise<void>
stream(req: AxonEngineRequest): AsyncGenerator<AxonEngineEvent>
run(code: string, opts?: { signal?: AbortSignal }): Promise<AxonRunResult>
run(code: string[], opts?: { signal?: AbortSignal }): Promise<AxonRunResult[]>
scope(): AxonScope
base(): Promise<string>
emit<K>(type: K, data: CognetEventMap[K]): void
store: KernelStore
}
That's the whole surface. There is no eighth verb and no escape hatch.
output — unmediated emission
await kernel.output("cognet:output:text", { content: "done" })
The cognet already has the full content and nothing external can fail it, so this is unmediated at the call site — writing to your own stdout can't itself harm anything. The kernel commits it durably and forwards it live, but never refuses it.
Filtering or redaction, if a host wants it, happens downstream on the delivery path. It's never a gate this call has to pass.
run — mediated request
const result = await kernel.run(code, { signal })
const results = await kernel.run([blockA, blockB], { signal }) // concurrent
The cognet doesn't know the outcome until the capsule responds, so this is policy-gated. It never rejects: success and failure are both ordinary values on the result.
type AxonRunResult = {
ok: boolean
value?: unknown
stdout: string[]
error?: { kind: "timeout" | "interrupt" | "exception"; message: string }
}
Timeout, abort, and exception collapse into one error.kind rather than three control
flows you'd have to catch and re-discriminate. An array of blocks runs concurrently and
returns results in order — the common "await several tool calls" case needs no manual
fan-out.
The kernel commits both cognet:action:typescript and cognet:action:result to the log
itself, the moment each block settles. The cognet reads the returned value for its own
control flow and writes nothing.
stream — inference
for await (const event of kernel.stream({ messages, signal })) { /* ... */ }
The kernel mediates provider access — auth, metering, quotas. The cognet owns everything about what the messages contain.
It yields raw engine:* wire events. The kernel does not pre-label them as the
cognet's output: when a text block arrives, it's the cognet that decides whether to
output() it; when a typescript block arrives, it's the cognet that decides whether to
run() it. That decision belongs to the cognet alone.
This is one verb of seven. A cognet that never calls it is still a cognet.
scope — executable reality
const scope = kernel.scope()
The complete TypeScript surface implemented by the current capsule incarnation. The kernel reports what's executable; the cognet decides whether and how that enters model context.
base — the user's identity contract
const identity = await kernel.base() // "" when none declared, never undefined
The agent's rendered boot.vue. Kernel-mediated because it's the user's contract, not
cognet strategy: a swapped brain decides where the base context sits in its rendering,
never what it says.
emit — narrate your own world
kernel.emit("cognet:phase:start", { tick, phase })
Fire-and-forget for the cognet, durable in the machine: committed to the log and forwarded to the runtime bus as flame-graph material.
Restricted to cognet:* and enforced at the call site, not just in the types. A cognet
narrates its own world; it can never forge kernel machinery events.
store — private state, read-only history
kernel.store.session.get({ after: seq }) // read-only, entries only, synchronous
await kernel.store.get("checkpoint")
await kernel.store.set("checkpoint", { seq, savedAt: Date.now() })
Covered in full in Memory.
What a cognet cannot do
The absences are the design. None of these are enforced by convention — there is no API to call.
No filesystem, no network, no shell. Not restricted: absent. The only path to any of them is asking the kernel to run code in a capsule the kernel alone constructs, under the agent's policy.
No writing to the log. There is no commit verb. The cognet emits and requests; the
kernel writes on its behalf. A cognet cannot forge history, backdate a decision, or
record something it didn't do.
No reading kernel telemetry. store.session.get() returns entries only — the same
envelopes stimuli arrive as. Kernel spans and error machinery aren't filtered out, they're
unreachable: the store reads a projection already classified by type namespace before the
cognet asks.
No configuration. A cognet cannot read the agent's config, paths, deploy settings, engine choice, or module list. It does not know what kind of world it is in — which is precisely what makes it portable to a different one.
No clock of its own. Wakes are scheduled by the kernel. A cognet cannot decide to run.
No thread or session concept. There is no id to address anything by. One cognet instance is exactly one continuous stream. Multiple conversations are multiple Axon instances — a host concern this ABI has no opinion on.
No identity. A cognet has no name it can act on, no owner it can query, no credentials. Identity belongs to the perimeter, not the mind inside it.
What the kernel will not do
The boundary runs both ways. The kernel has no opinion on cognition.
No grammar. No context assembly. No prompt format. No loop strategy, no stop condition, no compression, no memory policy. It guards the user's base identity and reports the capsule's executable scope; the cognet alone decides how either enters model context.
The AIR format that zero uses is zero's choice, not the platform's. Another cognet
can use a different grammar, or no grammar at all. Nothing above this line has an opinion.
Wake scope
Process-lifetime things are ambient. Wake-scoped things arrive as arguments:
loop(async ({ stimuli, signal, stop }) => { /* ... */ })
stimuli— what arrived since the last wake. An empty diff is ordinary, not an edge case.signal— the wake's abort leash. Forward it tostream()andrun().stop()— end the wake after this tick. The brain stays warm.
The ABI itself carries nothing per-wake. It's one process-lifetime object, and it is the only thing a cognet ever holds.
There is deliberately no push or delta channel. A temporally-extended emission — streaming
speech, a progressive result — is expressed through output() itself using the chunking
standard: correlated entries, closed by a final marker. Chunks are ordinary committed
entries, so they reach every observer through the same commit pipeline as any other fact.
Versioning
A cognet declares the ABI it targets:
export default defineCognet({
name: "zero",
version: "0.1.0",
abi: "9",
})
axon prepare verifies that against the kernel this Axon provides and fails loudly on a
mismatch, naming both versions. A cognet built for an older ABI never half-loads.
The kernel is free to change anything beneath this contract. The contract itself changes only with the ABI number.