Services

A mind often needs more than its own systems: a language model to talk with, a depth model to see with, speech to hear with. It asks for these as services — by what they do, never by what provides them.

a service
cognetuses depth-metric@1→agent→Depth Pro · on this GPUgrants a provideror any provider of depth-metric@1

The cognet never names a provider, never bundles one, and never learns which one it got. The agent owns the machine; the cognet only says what it needs. So the same mind runs against a frontier model in the cloud, or one small model on a laptop, without a change.

Four nouns

IsOwned by
InterfaceA typed, versioned contract: each call, what it takes, what it returns, what the data means. chat@1, depth-metric@1.Published, like a package
UseA cognet's declaration that it needs an interface, under a name of its own.The cognet
ProviderAnything that implements an interface — a local model, a cloud API, a ROS service, another agent.The machine, or the registry
GrantWhich provider fills which use, and what it may cost or reach.The agent, and its user

Declaring a use

// cognet.config.ts
export default defineCognet({
    mode: { kind: "continuous", hz: 20 },
    uses: {
        main: { interface: "chat@1", requires: { context: { min: 100_000 } } },
        depth: { interface: "depth-metric@1", optional: true },
        hear: { interface: "speech-to-text@1", optional: true },
    },
    suggests: {
        depth: "@axon/depth-pro",
    },
})

The name — main, depth, hear — is the cognet's own. The user never types it.

requires constrains what the provider's describe() reports. It is what breaks the mind when unmet: a context window too small for what it renders, an image interface for a mind that sends pictures. There is deliberately no way to demand a good provider. Quality is the user's trade-off; a mind that could refuse one would be overruling the person whose machine it runs on.

suggests names a provider the cognet recommends — fetched at axonl prepare, offered to the agent. A recommendation, never a requirement: the agent may fill the use with anything that implements the interface.

Using one

Every use a system declares is a handle on its run's services, typed from its interface in every language.

export default defineSystem({
    on: ["depthBeat"],
    reads: ["picture"],
    writes: ["depth"],
    uses: ["depth"],

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

        const depth = await run.services.depth.estimate(seen.picture)
        ecs.set({ id: seen.id, components: { depth: depth } })
    },
})

estimate takes a Picture and returns a DepthMap in metres because depth-metric@1 says so — a typo or a wrong argument is an error in the editor, not at the first tick.

A system names the uses it calls, as it names what it reads. Its services holds those and no others, so the graph knows which systems depend on depth, and a use no system calls is flagged at axonl prepare. The config says what each use is; the system says who calls it.

Available, or not

A required use with nothing to fill it fails before the mind starts — at axonl prepare, or when the agent boots. An optional use may be unfilled, and may come and go while the mind runs: a provider goes down, a GPU frees up.

available is how a mind asks. Like presence, it is state — as it stood when the run began — so a system can react to it changing. Calling a use that is not available throws, by name: a mind asks first, and takes a cheaper path when the answer is no.

describe() says what the granted provider actually is — its features and limits. It is a method of the interface like any other, so each interface defines what it reports:

run.services.main.describe()    // { context: 200_000, modalities: { in: ["text", "image"], out: ["text"] }, slots: 4 }

Three kinds of call

An interface's methods each take one of three shapes:

KindShapeFor
callone in, one outdepth, detection, embeddings, transcribing a clip
streamone in, events outa chat reply, as it is written
sessionpushes that share statevoice activity, streaming speech, a tracker

The kind is the call's shape, not the model's architecture: speech synthesis generates sample by sample and is still a call — it takes text, not a conversation.

Handles

Some work is done once and asked about many times. A segmentation model encodes a picture — the costly part — then answers any number of prompts against it. An interface says so by returning a handle: an opaque reference to something the provider keeps.

const encoded = await run.services.segment.encode(picture)          // a handle — the provider keeps it
const cup = await run.services.segment.mask(encoded, { point: [120, 80] })
const table = await run.services.segment.mask(encoded, { box: [0, 200, 640, 480] })
await encoded.release()

The picture's embedding never leaves the provider — on a GPU, it stays there. A handle is valid only for the use that made it, and only until it is released, the provider changes, or the run that made it ends. Using one after that is an error, never a quietly different answer.

Off the hot path

A service that takes forty milliseconds cannot sit inside a tick that has fifty — so a system that calls one is async. The tick carries on; the answer lands on a later tick, about the picture it was asked about. A mind's reflexes run every tick on what it already believes; what services add catches up when it can.

Interfaces

ArcLabs publishes a standard library of interfaces, so providers are interchangeable:

InterfaceDoes
chat@1a conversation in, a reply out, streamed
depth-metric@1a picture in, depth in metres out
depth-relative@1a picture in, nearer-and-farther out — no unit
detect@1a picture and what to look for in, boxes out
segment@1a picture and prompts in, masks out
embed@1text or a picture in, a vector out
speech-to-text@1sound in, words out
text-to-speech@1words in, sound out
voice-activity@1sound in, a session saying when someone speaks

Where two kinds of answer mean different things, they are two interfaces — metric and relative depth are not the same measurement, and a mind that needs one must never be handed the other. Where they differ only in how good they are — resolution, speed — one interface, and describe() says.

Anyone can publish an interface; a major version is a promise never to break it.

Held to their meaning

An interface is more than a shape: depth-metric@1 says metres, which way the picture faces, what a pixel with no answer holds. Every interface ships conformance cases — fixed inputs, and what any correct provider answers to them — and axon conform runs them against whatever an agent's grants fill each use with. A strict interface is only strict if something checks.

Your own interface

When nothing standard fits — a model of your own, a research model nobody has an interface for — a cognet declares its own, and suggests the provider that implements it:

// interfaces/scene@1.interface.ts
export interface Scene {
    describe(): { maxSurfaces: u32 }
    /** Every surface in view, from a picture and its depth. */
    encode(picture: Picture, depth: DepthMap): Promise<SceneCode>
}
// cognet.config.ts
uses: { scene: { interface: "./interfaces/scene@1" } },
suggests: { scene: "@cody/scene-encoder" },

Its name is the cognet's (@cody/terry/scene@1), and it is published with it. The provider still belongs to the agent — the cognet names a contract, never a dependency — but in practice it will be the one the cognet suggests. That is the price of using something nobody else provides, and often a fine one.

Interfaces are written in TypeScript, as cognet.config.ts is, and generated into every language that uses them. See interfaces for how one is written.

Granting

The cognet says what it needs. The agent decides what fills it — and owns everything that takes: the machine, the credentials, the network, the money.

// axon.config.ts — the agent's, never the cognet's
services: {
    main: { provider: "anthropic:claude-sonnet-5-5", limit: { calls: 600, per: "hour" } },
    depth: "auto",                      // whatever this machine has that implements depth-relative@1
    features: "./providers/features",   // a provider the agent writes
    hear: "off",                        // granted nothing: the use is unavailable
},
A grantMeans
a route — "anthropic:claude-sonnet-5-5"this model, from the agent's inference sources. Not a preference: if nothing supplies it, the use is unfilled
"./providers/<name>"a provider the agent writes
"auto" — or left outthe agent's own provider of the interface if it wrote exactly one; otherwise the best model the machine offers that meets the use's requires
"off"nothing — an optional use goes unavailable; a required one stops the agent from starting
limithow many calls each second, minute, hour or day — past it, calls are refused with SERVICE_LIMIT

The machine's models serve the standard interfaces they can: chat@1 from any model the agent's inference sources supply, depth-relative@1 and segment@1 from local vision models. Any other interface — a cognet's own — is filled only by a provider the agent writes.

What the cognet never sees: which provider answered, its credentials, where it ran, what it cost. It sees the interface, available and describe(). Every call is recorded — with the provider that answered it — in the agent's session, which the agent owns.

Where the work happens: on the agent's side of the boundary, never in the cognet's process. A confined agent's credentials stay with its supervisor; a model on the GPU is loaded once by the machine and shared by every agent that is granted it.

Language models

chat@1 is raw: a conversation in, the model's reply out — streamed or whole. It knows nothing of how a cognet renders its context or parses a reply. A cognet that wants a grammar — AIR, with its scripts and blocks — uses the AIR library on top of chat@1, which renders, parses, and asks again when a reply breaks the grammar. The grammar is the cognet's choice; a provider never needs to know it.

Learned weights

A model the mind trains as it runs is the mind's own — not a provider's. Its weights are stored through the agent's storage, versioned, and recorded in the session, so a replay knows which weights answered. To run them where they need to — a GPU — the cognet hands them to a provider whose interface runs weights of that kind.

What else it buys

  • Tests. A test grants a fake provider. It implements the same interface, so it is held to the same contract as a real one.
  • Replay. Every call is recorded with what answered it, so a replay can serve the recorded answers rather than call the model again — a model-heavy session replayed exactly, and free.
  • Learning. A learner can be a provider that swaps its weights behind an unchanged interface; the systems using it never notice.

The full surface is services.