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.
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, thentrack, thenlook— 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, …}
], …}}