The language provider seam
Default off. Configure nothing and the seam is still there — every job refused by name, with the cure in the message.
In this section
const brain = new Brainy({
storage: { path: './brain' },
language: {
baseUrl: 'http://models.internal:8000/v1', // you name the endpoint
bearer: process.env.MY_ENDPOINT_TOKEN, // you hold the key
model: 'my-model',
contextWindow: 32_768,
},
})
const { result, stamp } = await brain.language.run(
{
kind: 'judge',
premise: 'the lease is held by the departing process',
hypothesis: 'the lease is held by the successor',
question: 'supersedes',
persona: '',
budgetMs: 8_000,
maxTokens: 32,
},
{ purpose: 'normalize', seam: 'normalization' },
)
// result → { kind: 'judge', verdict: 'yes', confidence: 0.87 }
// stamp → { model: 'my-model', promptDigest: 'c1e1…', latencyMs: 940, … }TSDefault off. Configure nothing and the seam is still there — every job refused by name, with the cure in the message.
No model ships inside the engine
Brainy carries small encoders as reflexes: the embedder, and the cross-encoder the rerank stage scores with. Both are bounded, deterministic and local, and both ship in the package because a store's own vectors have to be reproducible from the store's own bytes.
Generative work is not like that. It is somebody's model, on somebody's hardware, under somebody's terms — and the engine has no business choosing any of the three for you. So there is no language model inside Brainy, and:
The engine never contacts a host you did not name. No default endpoint, no discovery, no "try localhost first".
language.baseUrlis required and has no fallback.The engine holds no key. The bearer token lives in the config object you pass. It is never persisted, never logged, and never quoted in an error.
Nothing is reported anywhere. The only requests the seam makes are the ones your jobs require: one identity handshake per process, and one completion per cache miss.
Absent a provider, the engine refuses. It has nothing to fall back to and invents nothing.
LanguageProviderUnavailableError, with the cure named.
That last one is why the base install ships a real seam over a "none" provider rather than leaving the field empty: "no model is configured" is a typed, shipped behaviour, not an undefined discovered three call frames away.
Jobs, not prompts
The seam has one operation — run this job — and a job is a member of a closed union with a fixed input shape and a fixed output shape. It is deliberately not "send this text and parse what comes back".
A free-form prompt would make your endpoint the author of the engine's semantics. Swap one model for another and classify would mean something different, with nothing in the store recording that it happened. A typed job inverts that: the engine owns the question and the shape of the admissible answer, and a provider either produces that shape or is refused. What varies between hosts is quality, which the stamp records. Never meaning.
Job | What it asks |
|---|---|
| put this text in one of these boxes, and/or fill this declared output shape |
| say what these texts say, once — optionally with verified citations |
| find these kinds of thing, with their spans |
| entities, their relationships with evidence, and free-text insights |
| one closed question about two texts ( |
| a speculative bridge between unrelated anchors |
| one fixed-shape text from a digest and constraints |
| recompose one text under constraints and a voice |
Every job carries budgetMs and maxTokens. Every result comes back with a stamp.
Structured classification is one call
A real enrichment pass needs six fields about a row — its type, its subtype, its tags, an intent, a summary, a confidence — and it needs them from one model call, not four composed ones. Declare the shape and the seam validates the answer field by field:
const { result } = await brain.language.run({
kind: 'classify',
text: row,
labels: [], // the schema carries the axes instead
multi: false,
schema: [
{ name: 'nounType', kind: { type: 'enum', values: ['Document', 'Person'] } },
{ name: 'subtype', kind: { type: 'text', maxChars: 40 } },
{ name: 'tags', kind: { type: 'text-list', minItems: 2, maxItems: 5, maxChars: 32 } },
{ name: 'summary', kind: { type: 'text', maxChars: 200 } },
],
budgetMs: 30_000,
maxTokens: 256,
}, { purpose: 'enrich', seam: 'consolidation' })
result.fields.nounType // { type: 'enum', value: 'Document' }TSA declared field the answer omits is refused, not defaulted. A value outside its enum is refused, not mapped to the nearest member. A text over its maxChars is refused, not truncated — a truncated sentence is a different claim.
Grounding is a contract term
A summary that cannot say which of its inputs it drew on is a claim with no provenance. Ask for grounding and the engine verifies every citation:
const { result } = await brain.language.run({
kind: 'summarize',
texts: memories,
style: 'abstraction',
grounding: 'required',
budgetMs: 60_000,
maxTokens: 400,
}, { purpose: 'consolidate', seam: 'consolidation' })
result.citedIndices // VERIFIED indices into `texts` — invented ones are goneTSAn index pointing at an input that was never sent is stripped. If nothing survives, you get LanguageOutputUngroundedError — a typed refusal you handle, never a fabricated answer wearing a citation's clothes. With grounding: 'none' an empty citation list is a legitimate answer, and invented indices are still stripped: an index nobody can resolve is not worth keeping either way.
The budget is real, and it composes
Two bounds, and both matter:
Projected before the call. Tell the seam your endpoint's rate (
outputTokensPerSecond) and it computesmaxTokens / rateand refuses a job that cannot fit — having spent nothing at all.refusedBeforeCall: trueon the error says so.The effective budget is the minimum of your
budgetMsand any timeout the endpoint declares (declaredTimeoutMs). A caller with a harder race of its own — a panel seat that will wait eight seconds and no longer — expresses it as a job budget instead of building a timer around the call. A caller asking for more than the endpoint will ever give is told so by the same typed refusal rather than left waiting.
There is never a partial answer. A truncated generation is not a shorter version of the right answer; it is a different one, and a caller could not tell them apart.
The same question is never asked twice
Completed jobs are cached per store, per process, keyed by (job kind, prompt digest, model, weights digest).
This is a correctness feature before it is an optimisation. Background passes overlap — a resumed job re-covers the page it was interrupted on, two legs ask whether the same two rows duplicate each other — and generation is not deterministic. Without a cache, a store could answer "do these duplicate?" yes on Monday and no on Tuesday for unchanged rows. With one, a job's answer is a function of its inputs for as long as one process holds the store.
budgetMs is not part of the key: how long you were prepared to wait does not change what the right answer is. maxTokens is: a summary capped at 60 tokens is a different answer from the same summary capped at 600.
brain.language.report().cache // { hits: 41, misses: 12, entries: 53, evictions: 0 }TSThe cache is never written to the store. A cached answer is an optimisation over asking again; the durable record is the row you derived, and it carries its own stamp.
Every answer is stamped
{
ts: '2026-09-08T16:41:07Z',
purpose: 'enrich', seam: 'consolidation', // your words, carried through
provider: 'openai-compatible', model: 'my-model',
inputTokens: 812, outputTokens: 94,
cacheReadTokens: 640, cacheWriteTokens: undefined,
tokenSource: 'provider', // or 'estimated', said so
costUsd: undefined, rateTableVersion: undefined,
groundingVerdict: 'grounded', outcome: 'ok', latencyMs: 1_204,
promptDigest: 'c1e1…', digest: 'sha256:9f…', cached: false,
}TSA metadata value a model wrote is not the same kind of fact as one a user wrote, and a store that cannot tell them apart cannot be re-derived, audited, or corrected when a model turns out to have been wrong. So the stamp goes on whatever row you derive, under languageStamp, and languageStampOf(metadata) reads it back.
Two fields the engine always leaves empty: costUsd and rateTableVersion. Pricing is yours — the engine does not know what you pay for your own endpoint, and inventing a number would be worse than leaving the field for you to fill.
On a cache hit, latencyMs and the token counts describe the original call. ts, purpose and seam describe this one, and cached is true.
The refusals
Five, each named, none with a fallback arm.
Class | When |
|
|---|---|---|
| none configured, unreachable, unidentified, or unauthorized |
|
| the provider cannot do this kind, or the input will not fit the window |
|
| projected not to fit, or overran |
|
| the answer did not parse to the job's shape |
|
| grounding required, no citation survived |
|
A malformed job — a zero budget, a one-label classification with no schema, an imagine with one anchor — is a TypeError instead. That is your own call, not your provider's answer, and dressing it as a refusal would send you to look at your deployment.
Every refusal carries ledgerOutcome, so a call that produced no stamp still writes its row and your inference ledger keeps no blind spot where the seam said no.
Where the work runs
Never on a foreground door. A generation takes seconds on hardware the engine does not control, and a write door that waited for one would hand its own latency to your endpoint.
The engine's two callers run on its background job runtime — installment-bounded, abortable, marker-backed, narrated:
language-classify-write— a row written withlanguage: { classify }in its options leaves a durable request in the store, and a background pass reads the row, classifies it, and writes the label as stamped metadata. The queue is durable because an in-memory one turns a kill into a silent loss. The cost is one small object write, paid only by rows that asked.Two things that write refuses, at the call rather than three passes later: a request that asks nothing (fewer than two labels and no schema), and a request on a brain whose host named no provider at all — which nothing could ever serve, so queuing it would leave a debt that fails on every pass and latches. A configured endpoint that is merely unreachable is a transient, and the durable queue is exactly right for one.
If the row is written but its request cannot be queued, you get
LanguageClassifyNotQueuedErrornaming the id. The row exists — re-request the classification rather than re-writing, which would create a second row.language-summarize-cluster— the work behindbrain.language.summarize({ ids, style }). The door returns your summary and the generation still runs on the runtime, so aclose()landing mid-summary stands it down within one installment instead of holding your shutdown for the endpoint's timeout.
Configuring a provider
new Brainy({
storage: { path: './brain' },
language: {
baseUrl: 'http://models.internal:8000/v1', // required, no default
bearer: process.env.MY_TOKEN, // optional
model: 'my-model', // verified against /models
contextWindow: 32_768, // required
outputTokensPerSecond: 50, // enables the pre-call refusal
declaredTimeoutMs: 75_000, // composes with every budget
weightsDigest: 'sha256:9f…', // joins the cache key
precision: 'fp16',
supportsJsonSchema: true, // else JSON-object mode
},
})TSAny OpenAI-compatible chat-completions endpoint works: one you run, one you pay for, or none.
The model name is verified, not trusted. Before the first job the seam calls GET {baseUrl}/models and refuses by name if the endpoint does not list the model you named — an endpoint serving something else would otherwise stamp your rows with a model that never saw them.
supportsJsonSchema decides how the answer is constrained, not whether it is checked. With schema support the job's exact result shape is sent as a JSON schema; without it the request falls back to JSON-object mode. Either way the answer is parsed strictly and then validated against your job's closed sets, ranges and spans. An endpoint that cannot produce the shape is refused, never patched up.
What it refuses to do
Contact any endpoint you did not name.
Read an endpoint or a key from the environment or a file.
Report anything anywhere.
Repair an answer — no clamped confidence, no label mapped to its nearest member, no defaulted field, no truncated text.
Return a partial generation.
Return a summary that reads as sourced and is not.
Run a generation on a foreground door.