Three languages

Every part of a cognet can be written in TypeScript, Rust or Python. Not three APIs — one, spoken three ways. Pick the language each part is best written in, or write the whole mind in the one you know.

This page builds a mind that follows a person with its head. Switch language on any example; the choice sticks.

follow
camera→picture→detect→people→track→target→look→neckfrom the bodyto the body

The types

Declared once, in whichever language you like. axonl prepare generates the other two, so every system sees the same People and Target, natively.

// types/follow/follow.components.ts
interface Sighting {
    x: f64
    y: f64
    width: f64
    height: f64
    confidence: f32
}

/** Everyone seen in the newest picture. */
export interface People {
    boxes: Sighting[]
    sensed: f64
}

/** The person being followed, smoothed over time. */
export interface Target {
    x: f64
    y: f64
    sensed: f64
}

Detect

Asks a detector to find people. The cognet declares it needs detect@1 under the name people — never which model; the agent decides what fills it.

// cognet.config.ts
uses: { people: { interface: "detect@1" } },
// src/follow/detect.system.ts
export default defineSystem({
    reads: ["picture"],
    writes: ["people"],

    async run() {
        const [self] = ecs.query({ components: ["picture"] })
        if (!self) return

        const boxes = await services.people.detect({ picture: self.picture, labels: ["person"] })
        ecs.set({ id: "self", components: { people: { boxes: boxes, sensed: self.picture.sensed } } })
    },
})

Track

Follows the most confident person, smoothed so the head doesn't jitter. Pure arithmetic — the kind of system Rust is best at, when it runs every tick.

// src/follow/track.system.ts
const SMOOTHING = 0.3

export default defineSystem({
    reads: ["people"],
    recalls: ["target"],
    writes: ["target"],

    run() {
        const [self] = ecs.query({ components: ["people"] })
        const best = self?.people.boxes.toSorted((a, b) => b.confidence - a.confidence)[0]
        if (!self || !best) return

        const last = ecs.query({ components: ["target"] })[0]?.target
        const next = last
            ? { x: last.x + (best.x - last.x) * SMOOTHING, y: last.y + (best.y - last.y) * SMOOTHING }
            : { x: best.x, y: best.y }

        ecs.set({ id: "self", components: { target: { ...next, sensed: self.people.sensed } } })
    },
})

Look

Turns the target into a command for the neck.

// src/follow/look.system.ts
export default defineSystem({
    reads: ["target"],

    async run() {
        const [self] = ecs.query({ components: ["target"] })
        if (!self) return

        await kernel.output("cognet:output:vector", {
            channel: "/neck",
            values: [self.target.x - 0.5, self.target.y - 0.5],
        })
    },
})

What the engine did

  • Ordered them. detect, then track, then look — from the reads and writes alone.
  • Ran each where it belongs. Python in its own process, so a slow model never holds up a tick. Rust in-process, its arrays crossing as memory, not copies.
  • Recorded all of it. The debugger answers the same questions whatever language a system is written in:
$ axonl debug perf
{"ok":true,"data":{"systems":[
  {"system":"follow/detect","meanMs":18.2,"p95Ms":24, …},
  {"system":"follow/track","meanMs":0.04,"p95Ms":0.07, …},
  {"system":"follow/look","meanMs":0.11,"p95Ms":0.2, …}
], …}}