interfaces
An interface is the contract between a use and whatever fills it. The standard library covers
the common ones — chat@1, depth-metric@1. When nothing standard fits, a cognet writes its own
in interfaces/. See
Services for where it fits.
// interfaces/<name>@<major>.interface.ts — always TypeScript, generated into every language
export interface <Name> {
describe(): { … } // required — what a provider reports of itself
<method>(...): Promise<Answer> // a call
<method>(...): AsyncIterable<Event> // a stream
<method>(): Session<Push, Answer> // a session
}
export type <Kept> = Handle<"<Kept>"> // an opaque handle, kept by the provider
| Part | Means |
|---|---|
| the file name | the interface's name and major version: scene@1.interface.ts is scene@1 |
| the exported interface | the contract — exactly one per file |
describe() | the provider's features and limits — the only thing a use can require |
| the methods | what a use may call, each one of four kinds |
Its full name is the cognet's: scene@1 in @cody/terry is @cody/terry/scene@1, published
with it.
Methods
The return type says what kind of call a method is. Nothing else does.
| Returns | Kind | A use calls it |
|---|---|---|
Promise<T> | a call — one in, one out | await run.services.x.m(…) |
AsyncIterable<T> | a stream — one in, events out | for await (const e of run.services.x.m(…)) |
Session<Push, Answer> | a session — pushes that share state | run.services.x.m().push(…) |
Handle<"Name"> | an opaque handle — inside a Promise | passed back to the same use |
export interface Scene {
describe(): { maxSurfaces: u32; gpu: boolean }
/** Encodes what is in view. The costly part — done once, asked about many times. */
encode(picture: Picture, depth: DepthMap): Promise<SceneCode>
/** The surface under a point of the picture. */
surface(scene: SceneCode, at: [f64, f64]): Promise<Surface>
/** Follows the scene as the camera moves: push each picture, get what changed. */
follow(): Session<Picture, Changes>
}
export type SceneCode = Handle<"SceneCode">
Arguments and answers follow the rules of components: plain data, sized numbers, typed arrays — which cross as memory. They may be imported from the cognet's components, or from another interface.
import type { Picture } from "../types/sense/sense.components"
import type { DepthMap } from "@arcforge/cognet/interfaces/depth-metric@1"
The doc comment on each method is the contract's meaning — units, frames, what an empty answer holds. Write it; a provider has nothing else to go on.
describe()
Every interface declares it, with no arguments, returning plain data. It is how a provider says what it is, and the only thing a use can constrain:
// cognet.config.ts
uses: {
scene: { interface: "./interfaces/scene@1", requires: { maxSurfaces: { min: 64 }, gpu: true } },
},
requires on a field | Means |
|---|---|
| a value | describe() must report exactly it |
{ min, max } | a number within the bounds |
{ includes: [...] } | a list holding each of these |
A grant whose provider fails requires is refused at axonl prepare, or when the agent boots.
Generated
The interface is written once and generated into each language that uses it, as components are.
// .cognet/ — ambient
run.services.scene.describe(): { maxSurfaces: number; gpu: boolean }
run.services.scene.encode(picture: Picture, depth: DepthMap): Promise<SceneCode>
run.services.scene.follow(): Session<Picture, Changes>
A provider in any language implements the same generated shape.
Conformance
Beside each interface, the cases any provider of it must answer. axon conform fills every use an
agent's cognet declares as its boot would, and runs each interface's cases against what filled it.
interfaces/
├── scene@1.interface.ts
└── scene@1.conformance/
├── cases.json
└── table.rgb # raw RGB bytes, for a picture the cases name
[
{
"name": "a point picks out the surface under it",
"call": "surface",
"given": {
"scene": { "$call": "encode", "given": { "picture": { "$picture": { "width": 640, "height": 480, "file": "table.rgb" } } } },
"at": [320, 400]
},
"expect": { "normal": [0, 0, 1], "distance": 0.82 },
"within": { "normal": 0.05, "distance": 0.02 }
}
]
| Field | Means |
|---|---|
call / stream | the method — a stream's answers are checked as { events, last } |
given | its arguments, by parameter name |
expect | the answer, field by field |
within | the tolerance on each number checked exactly; absent, it must match exactly |
A case writes its inputs as values, or as:
| Input | Is |
|---|---|
{ "$picture": { width, height, fill: [r, g, b] } } | a picture of one colour |
{ "$picture": { width, height, file } } | a picture from raw RGB bytes beside the cases |
{ "$u8": [...] }, { "$f32": [...] } | a typed array |
{ "$call": "<method>", "given": { … } } | another call's answer — a handle, usually |
And checks an answer's fields as values, or with:
| Rule | Passes when |
|---|---|
{ "min": n, "max": n } | the number is within the bounds |
{ "oneOf": [...] } | it is one of these |
{ "type": "string" } | it is of this type |
{ "length": n } · { "length": "width*height" } · { "length": { "min": 1 } } | a list or array is this long — or the product of the answer's own fields |
{ "each": <rule> } | every item passes |
Write the cases a provider would most likely get wrong — the conventions, not the easy path. A case for any provider can't know what a particular model sees; it can know the units, the shape, and what a value may never be.
Versions
A major version is a promise never to break it. A breaking change is a new file — scene@2
beside scene@1 — and uses move to it one at a time. Adding a method, or a field to
describe(), is not breaking; changing or removing one is.
Errors
| Error | When |
|---|---|
INTERFACE_NOT_FOUND | a use names an interface that doesn't exist |
COGNET_SCHEMA_INVALID | no describe(), more than one interface in a file, a method returning no kind, or a type no language can hold |
INTERFACE_VALUE_INVALID | an argument or answer a component couldn't hold |
INTERFACE_BROKEN | a published major version changed incompatibly |