cognet.config.ts

What this brain declares. Pure data, no logic, no lifecycle. Behaviour lives in src/main.ts, identity in package.json, and the compile step composes the three.

export default defineCognet({
    mode: { kind: "invocation" },

    // runaway guard: a wake that hasn't converged in this many
    // render→infer→act ticks is a loop bug or a stuck model, not progress
    maxTicksPerWake: 32,
})

Every field is optional except mode, and the whole file is optional too — a cognet with no cognet.config.ts is invocation-mode with no wake mask, which is what most first brains are.

Fields

FieldRequiredWhat it declares
modeyesHow the scheduler wakes it.
wakeOnnoDefault wake mask. Absent means wake on everything.
maxTicksPerWakenoHard safety bound for one wake. Absent means unbounded.
enginesnoThe inference roles this brain needs.
modelsnoModel weights this brain needs. Fetched, verified and cached by Axon.
abinoPin the kernel ABI. Absent means the one it was compiled against.

What identity isn't here

name and version are not declared. A cognet is an ordinary package, and its package.json already carries both — it is what the registry publishes under, what the installer resolves, and what an agent writes in its dependencies.

They used to live here as well, and two writable copies of one fact drifted exactly as duplicated facts do: @axon/zero shipped as 1.0.44 while telling the kernel it was 0.1.2, because publish read the package and the runtime read the config. Identity has one home now.

abi — the compatibility contract

A cognet is versioned against the kernel the way a binary is versioned against syscalls. Omit it and the compile step stamps in the ABI it built against, which is the truthful answer: a cognet publishes as source and is compiled by the consumer against the kernel it will actually run on.

abi: "11"

Pin it only to make a cognet refuse a kernel it hasn't been validated against. axon prepare then checks the pin against the kernel the installed Axon provides and fails there, naming both versions and the file to edit — a cognet pinned to an older ABI never half-loads.

mode — invocation or continuous

Part of the cognet's own declared shape, deliberately not blueprint-overridable. Same trust direction as abi: a cognet written for stimulus-driven wakes was never written to tolerate an empty-stimuli tick, so an agent author can't flip it from outside.

Invocation — woken once per admitted stimulus arrival, handed the full diff accumulated since the last wake. If several stimuli arrived while the previous wake was running, they arrive together.

mode: { kind: "invocation" }

Continuous — woken on the brain's own rhythm, regardless of whether anything arrived. An empty diff is the ordinary steady state, not an edge case.

mode: { kind: "continuous" }

No rate here on purpose: this declares the cognet's shape ("wake me, don't hand me a chat prompt"), not its frequency. The brain sets its own rate from a plugin calling kernel.wake() — see The Loop.

wakeOn — the wake mask

wakeOn: ["cognet:stimulus:text", "cognet:stimulus:field"]

Which entry types should wake this cognet. Absent means everything.

This is the cognet's default, and unlike mode it is overridable by the agent's blueprint — the cognet declares what it was built to handle, the agent narrows it for its own deployment.

maxTicksPerWake — the runaway guard

maxTicksPerWake: 32

A hard bound, not a scheduling mechanism. A wake that hasn't converged in this many ticks is a loop bug or a stuck model, not progress, and the host throws rather than spinning.

Strategy may stop earlier and usually does — zero typically converges in two or three ticks. Omitted means unbounded, which is the right default for open-ended work: what bounds a wake there is <done/>, the user's interrupt, and the engine failing loudly.

engines — the inference this brain needs

engines: {
    main:    { type: "generate", in: "text", out: "text", context: 100_000 },
    percept: { type: "generate", in: ["text", "image"], out: "text", optional: true },
    vad:     { type: "stream", in: "audio", out: "score" },
}

A map, because the key is your vocabulary. main says what the engine is for; what fills it is decided at boot against whatever providers the user declared. The user never types these names — a user who had to name a brain's roles would be wiring one specific brain into their setup.

Constraints are structural only: the things that break a brain rather than slow it down. There is deliberately no way to demand a good model.

A required role nothing can fill fails axon prepare, never the first tick. Mark a role optional and the cognet asks kernel.engine.has(name) before using it — that is the whole degradation story.

Full field reference and the three handle shapes: kernel.engine.

What isn't here

No engine selection, no prompt, no policy, no paths. Those belong to the agent, in axon.config.ts.

models is the one apparent exception, and it isn't: a cognet declares which weights it needs, never where they live. The resolved path is environmental and arrives through the kernel like everything else — the same brain gets a different absolute path on every machine and never learns that.

A cognet cannot read any of them. It declares how it wants to be woken and what it needs; everything about the environment it runs in is on the other side of the kernel contract.