kernel

The syscall table. An ambient global, bound at load(), process-lifetime — this one object is the entire vocabulary a cognet has for touching the world.

Nothing here is wake-scoped. What a wake delivers arrives as loop arguments instead.

type KernelAbi = {
    // Emission. Unmediated — the kernel commits and forwards, never refuses.
    output<K extends keyof AxonOutputEvent>(type: K, data: AxonOutputEvent[K]): Promise<void>

    // Inference, by role. See kernel.engine.
    engine: KernelEngines

    // Execution. Mediated, policy-gated, NEVER rejects.
    run(code: string): Promise<AxonRunResult>
    run(code: string[]): Promise<AxonRunResult[]>   // concurrent, results in order

    // Reading the world the kernel guards.
    scope(): AxonScope        // what the capsule can execute right now
    base(): Promise<string>   // the user's rendered identity. "" when none, never undefined

    // Telemetry. cognet:* only — a cognet cannot forge kernel events.
    emit<K extends keyof CognetEventMap>(type: K, data: CognetEventMap[K]): void
    fault(input: { code: string; message: string; excerpt?: string }): Promise<void>

    // Persistence. See kernel.store.
    store: KernelStore
    knowledge: KernelKnowledge

    // Rhythm. Continuous cognets only.
    wake(): Promise<number>
    clock(): KernelClock
}

type KernelClock = {
    /** Wakes admitted since boot. Monotonic, process-lifetime. */
    wakes: number
}

That is the whole surface. There is no escape hatch, and no verb below carries a signal — cancellation is applied by the kernel unconditionally.

output — emission

await kernel.output("cognet:output:text", { channel, content: "done" })
TypePayload carries
cognet:output:textcontent, optional format (json / markdown)
cognet:output:audioref, optional transcript, durationMs
cognet:output:visualref, kind: "image" | "video", optional caption
cognet:output:vectorvalues: number[], optional unit/units/labels/profile

Every payload takes a channel and an optional chunk correlation group.

Unmediated at the call site: the cognet already holds the full content and nothing external can fail it, so there is nothing to refuse — writing to your own stdout cannot itself harm anything. The kernel commits it durably to the session's one log and forwards it live. Redaction, if a host wants it, happens downstream on the delivery path, never as a gate this call passes through.

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 pipeline as any other fact.

run — execution

const result  = await kernel.run(code)
const results = await kernel.run([blockA, blockB])   // concurrent, order preserved
type AxonRunResult = {
    ok: boolean
    /** The block's completion value when ok. Undefined on failure. */
    value?: unknown
    /** console.* from this block, in order, one entry per call. */
    stdout: string[]
    /** Top-level bindings the block declared — what a template interpolates against. */
    scope: CapsuleScope
    error?: { kind: "timeout" | "interrupt" | "exception"; message: string }
}

A mediated request — the cognet does not know the outcome until the capsule responds, so it is policy-gated. It never rejects: success and failure are both ordinary values. Timeout, abort and exception collapse into one error.kind rather than three control flows to catch and re-discriminate.

stdout is captured server-side and returned inline. There is no callback to wire: the kernel already forwards the capsule's own event stream onto the bus, and a cognet folding console output into a committed result just reads it off the result.

scope is a diff of the sandbox's globals across the submission — bindings that crossed in values, and by name in unavailable those that could not (function, circular, unserializable, oversized). Empty on failure and empty for a block that declared nothing, never absent, so a caller never distinguishes "no scope" from "scope unavailable".

An array runs every block concurrently and returns results in the same order. The common "await several tool calls before answering" case needs no manual fan-out.

The kernel commits cognet:action:typescript and cognet:action:result itself, the moment each block settles. The cognet reads the value for its own control flow and writes nothing.

scope — executable reality

const scope = kernel.scope()

The complete TypeScript surface the current capsule incarnation implements: modules, each with named members carrying a declaration and optional jsdoc.

scope.unavailable is the half that matters. A module whose tools failed to compile contributes nothing to modules — correct, because the model must never be told about a tool the capsule cannot load — but that absence is silent, and an agent that cannot see its own loss answers as though the capability never existed. Rendering unavailable lets it say "I cannot search arXiv right now" instead of guessing. Empty in the normal case.

The kernel reports what is executable. Whether and how that enters model context is the cognet's decision alone.

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 is the user's contract, not cognet strategy: a swapped brain decides where the base context sits in its rendering, never what it says. The seam normalizes, so callers never branch on absence.

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. Never rendered to the user.

Typed against CognetEventMapcognet:* and nothing else. A cognet narrates its own world; it can never forge kernel machinery events. In practice you rarely call this directly: phase and system emit the clock families for you.

fault — the model broke your grammar

await kernel.fault({
    code: "UNDECLARED_BINDING",
    message: "interpolated {total}, which the script never declared",
    excerpt,
})

Separate from emit because the outcome is not telemetry: the kernel commits it as axon:system:message, so the next tick's rendered context carries it in a <system> block and the model reads its own violation and corrects.

That is a system fact, and a cognet may not forge one directly — so it describes the fault and the kernel writes it.

Why the verb exists at all: the runtime detects violations visible in the token stream (an unclosed block), but a cognet that renders the model's output detects its own class of them — an interpolation naming a binding the script never declared. Without this, the only way to make such a fault visible was to speak it as the agent's own message, which puts a diagnostic in the agent's voice and shows the user machinery they cannot act on.

Deliberately not user-facing. Hosts hide these, so a violation is a retry the model performs rather than an error the user watches it make.

wake — the brain's own rhythm

// plugins/clock.ts
export default definePlugin(({ hooks }) => {
    hooks.on("boot", () => {
        setInterval(() => void kernel.wake(), 1000 / 30)
    })
})

Continuous cognets only. An invocation cognet is woken by a stimulus arriving; waking itself would be a second, contradictory trigger.

The body emits stimuli and never decides when the brain looks at them: how fast frames arrive is a property of a sensor, how often it is worth thinking about them is a property of a mind. A body that drove a specific brain would have to be rewritten when the brain changed — exactly the coupling the split exists to prevent — and it has no answer under composition, where two sensors at 31Hz and 60Hz cannot both be "the" tick rate.

Resolves with the wake's ordinal as soon as it is admitted, never when it completes. A driver that awaited completion would serialise the overlap continuous mode exists to allow.

Named wake, not tick, because that is what it does: it invokes the loop. A tick is one iteration inside a wake.

clock — a snapshot of that rhythm

const { wakes } = kernel.clock()
if (wakes % 4 === 0) { /* the slow path, every fourth wake */ }

A function, not a live getter: with wakes overlapping, the count moves between reads, and a value read at a moment is honest where a live reference is not.

Deliberately thin — wakes admitted since boot, and nothing else has earned a place yet. Not the per-wake tick counter phase() stamps against. Two different numbers at two scales.

Availability

Every verb resolves the bound ABI at the moment it is called, never at module scope. Touching one before load() throws COGNET_ACCESSED_BEFORE_LOAD — do work inside loop(), not at the top level of main.ts.

wake() and clock() are the exception to wake-scoping: they resolve through the bound kernel rather than the ambient scope, because a plugin's clock lives in a setInterval registered at boot and that callback fires outside every wake.