src/state.ts

There is no required file here. state.ts is a convention, not a contract: the engine never reads it, and a cognet that keeps everything in main.ts is perfectly valid.

The convention exists because resident memory is worth isolating. main.ts is the pipeline; state.ts is what the pipeline operates on, shared by plain import with whatever else needs it.

import type { AxonEntry } from "@arcforge/types"

export type ZeroState = {
    /** the folded episodic log, in seq order — what render() sees */
    entries: AxonEntry[]
    /** high-water mark: the last seq folded. -1 = nothing yet. */
    seq: number
}

export const state: ZeroState = {
    entries: [],
    seq: -1,
}

export function sync(): void {
    for (const entry of kernel.store.session.get({ after: state.seq })) {
        state.entries.push(entry)
        state.seq = entry.time.seq
    }
}

That's zero's entire memory system.

One copy per brain

The compile step bundles main.ts, plugins/, and everything they import into a single artifact. So a plain import { state } from "./state" from two places gives you the same object — module scope is the sharing mechanism, and there is exactly one copy per brain.

// main.ts
import { state, sync } from "./state"

// plugins/setup.ts
import { state } from "./state"

No registry, no injection, no singleton ceremony. Ordinary module semantics.

Shape it however you like

zero holds the folded log and nothing else. That's a deliberate reference pattern — see Memory for why a log-shaped model is trivially rebuildable and verifiable — but it is not the only shape.

A perception cognet might hold a spatial index. A planner might hold a task tree. A controller might hold three floats:

export const state = {
    integral: 0,
    previous: 0,
    lastCommand: 0,
}

The engine has no opinion. It never reads your state, never serializes it, and never restores it.

The one rule

Nothing here is authoritative.

Resident memory must be reconstructible from the durable record. If losing this object on a restart would lose information the cognet needs, it belonged in the session log or in kernel.store — not here.

That constraint is what makes kill -9 free. Kill the process at any point, restart, and the brain rebuilds from the log with nothing missing. There is no shutdown hook that must run and no flush that must complete, because nothing in RAM was the source of truth.

Get this wrong and you've built a mind that can only survive a graceful shutdown. For a long-running agent that's an inconvenience. For an embodied system that can lose power mid-decision, it's a design failure.

Typing the private store

If your cognet uses kernel.store, declare its schema here by declaration merging:

declare global {
    interface CognetStoreSchema {
        checkpoint: { seq: number; savedAt: number }
    }
}

kernel.store.get("checkpoint") is then typed, and a key you never declared is a compile error rather than a runtime surprise.

Keep what goes in there small. Reads return null when absent or unreadable — cache doctrine: rebuild, don't crash. If losing a key can break your cognet, it belonged in the log.