ArcGrid Brain

Brain SDK

@arcgrid/brain-client - the TypeScript SDK for all brain calls from Surface, Trebek, agents, and tenant apps. Routes through ArcRouter with automatic model selection, entitlement enforcement, usage metering, and user memory.

TypeScript ESM Node 18+ ArcRouter BRAINLIC

Overview

Every LLM call in the Grid should go through the brain - not directly to Anthropic, Ollama, or any provider. The brain is the routing layer that selects the best available model, enforces tenant entitlements, meters usage, and handles failover. @arcgrid/brain-client is the only entry point your code needs.

01 / CHAT
LLM calls
chat(), stream(), streamCollect(). OpenAI-compatible wire format. Model aliases route to the best available descriptor.
02 / MEMORY
User profiles
Per-user state scoped to a tenant namespace. STANDARD, HEALTH, and BIOMETRIC data classes. Immutable audit trail for non-STANDARD writes.
03 / CONSENT
Compliance kernel
Grant, revoke, and check consent records before writing BIOMETRIC data. BIPA and CPRA compliance. SHA-256 disclosure hash required.
Draft PR

The SDK lives in agent-space-co/brain on branch feat/brain-sdk (PR #35). It is not yet published to a registry. Install from the repo directly until #35 merges and is published.

Install

Until the package is published to GitHub Packages, install from the brain repo directly:

npm install git+ssh://git@github.com/agent-space-co/brain.git#feat/brain-sdk:sdk

For local development inside the brain monorepo:

npm install ../../brain/sdk

Once PR #35 merges and the package is published:

npm install @arcgrid/brain-client

Quick start

import { BrainClient } from '@arcgrid/brain-client'; const brain = new BrainClient({ apiKey: process.env.BRAIN_API_KEY!, // baseUrl defaults to process.env.BRAIN_API_URL or http://arcrouter:4000 }); // Non-streaming const reply = await brain.chat({ messages: [{ role: 'user', content: 'Summarize this in one sentence.' }], }); console.log(reply.choices[0].message.content); // Streaming for await (const chunk of brain.stream({ messages: [{ role: 'user', content: 'Walk me through this step by step.' }], model: 'deep', })) { process.stdout.write(chunk.choices[0]?.delta.content ?? ''); }

Chat

brain.chat(params)

Non-streaming completion. Returns a ChatCompletion when the model finishes.

const result = await brain.chat({ model: 'standard', // effort alias: trivial | minimal | standard | deep messages: [ { role: 'system', content: 'You are a concise assistant.' }, { role: 'user', content: 'What is ArcCycle?' }, ], module_id: 'core.chat', // entitlement gate - required for licensed modules max_tokens: 512, temperature: 0.3, }); console.log(result.choices[0].message.content); console.log(result.usage); // { prompt_tokens: 24, completion_tokens: 91, total_tokens: 115 }

Constructor defaults

Set defaultModuleId and defaultAttribution once on the client instance rather than per-call:

const brain = new BrainClient({ apiKey: process.env.BRAIN_API_KEY!, defaultModuleId: 'surface.assistant', defaultAttribution: { agent_id: 'surface', flow_id: 'ticket-triage' }, });

Streaming

brain.stream(params)

Returns an async generator that yields ChatCompletionChunk objects as SSE frames arrive. Use when you need to display output as it generates.

let full = ''; for await (const chunk of brain.stream({ messages, model: 'deep' })) { const delta = chunk.choices[0]?.delta.content ?? ''; full += delta; process.stdout.write(delta); } console.log('final:', full);
brain.streamCollect(params)

Streams internally, returns the full ChatCompletion when done. Use when you want streaming latency (time-to-first-token) but a single awaitable result.

const result = await brain.streamCollect({ messages }); console.log(result.choices[0].message.content);

Entitlement check

brain.checkEntitlement(moduleId)

Check whether the current tenant is entitled to a module before making a call. Useful to gate UI features without attempting a full request.

const check = await brain.checkEntitlement('vision.hair.transfer'); if (!check.allowed) { // check.reason: 'not_entitled' | 'tenant_frozen' | 'expired' | ... console.log('module not available:', check.reason); }

ArcRouter also enforces this on every chat() call when module_id is set - checkEntitlement() is for pre-flight checks in UI or routing logic.

User memory

brain.memory.* reads and writes per-user profile state within the tenant namespace. The userId is always an opaque token your app controls - never an email, phone number, or any PII. The brain stores and returns the profile without interpreting its shape.

BIOMETRIC gate

Profiles with data_class: 'BIOMETRIC' require a valid consent record. Call brain.consent.grant() first and pass the returned id as consent_reference. A missing or revoked consent returns 403.

getProfile

const profile = await brain.memory.getProfile('u_a1b2c3'); if (profile) { console.log(profile.data_class); // STANDARD | HEALTH | BIOMETRIC console.log(profile.profile); // your app's stored object console.log(profile.updated_at); }

upsertProfile

// STANDARD profile - no consent needed await brain.memory.upsertProfile('u_a1b2c3', { data_class: 'STANDARD', profile: { preferred_language: 'en', onboarding_complete: true, last_session_id: 'sess_xyz', }, }); // BIOMETRIC profile - consent reference required const consent = await brain.consent.grant({ user_id: 'u_a1b2c3', data_class: 'BIOMETRIC', purpose: 'hair_visualization', consent_text_hash: sha256(disclosureText), }); await brain.memory.upsertProfile('u_a1b2c3', { data_class: 'BIOMETRIC', consent_reference: consent.id, profile: { hair_type: '4C', last_scan_at: new Date().toISOString(), }, });

deleteProfile

Deletes the profile and all history, then writes a deletion attestation. Required for right-to-deletion requests under CCPA/CPRA.

const result = await brain.memory.deleteProfile('u_a1b2c3'); console.log(result.deleted); // true console.log(result.data_class); // what class was deleted

exportProfile

Returns the current profile plus immutable audit history. Use for GDPR/CCPA data subject access requests.

const export_ = await brain.memory.exportProfile('u_a1b2c3'); console.log(export_.profile); // current state console.log(export_.history); // [ { snapshot: {...}, recorded_at: '2026-09-23T...' }, ... ]

Model aliases

Pass an effort alias as model. ArcRouter resolves it to the best available descriptor given the tenant tier, license class, and current rig availability.

AliasIntended useCurrent mapping
trivialClassification, tagging, yes/no decisionsllama3.1:8b (ArcRig1/2)
minimalShort answers, formatting, extractionqwen2.5-coder or llama
standardGeneral tasks - default when model is omittedqwen3-fast (ArcRig3)
deepComplex reasoning, long-form, architectureClaude (frontier fallback)

To pin to a specific descriptor, pass its full id instead of an alias: model: 'arcrig3/qwen3-fast'.

Attribution

Pass ledger fields to track which agent, flow, or pack generated each request. ArcRouter records these in the usage ledger for cost attribution, debugging, and billing. All fields are optional - the router fills from the key record when omitted.

await brain.chat({ messages, attribution: { agent_id: 'trebek', flow_id: 'ticket-triage-v2', node_id: 'classify', client_id: 'beyond-sleep', matter_id: 'ticket-4821', }, });

Errors

All errors from the brain surface as BrainError instances with status (HTTP status), type (error code), and message.

import { BrainError } from '@arcgrid/brain-client'; try { await brain.chat({ messages, module_id: 'vision.hair.transfer' }); } catch (err) { if (err instanceof BrainError) { console.log(err.status); // 403 console.log(err.type); // 'not_entitled' console.log(err.message); } }
TypeStatusCause
not_entitled403Tenant not entitled to the requested module_id
license_class_denied403All candidate models are research_only for this tenant tier
consent_required422BIOMETRIC upsert attempted without consent_reference
consent_denied403Consent record not found or revoked
rate_limited429Request rate or token budget exceeded
no_candidates503No models match routing criteria or all rigs down
unauthorized401Invalid or missing API key

Internal wiring

The brain is the language layer for every intelligent feature in the Grid. Two wiring targets are pending:

Surface backend

Create a shared brain client instance used across all Surface API routes that need LLM calls. Wire it once at startup:

// surface/src/lib/brain.ts import { BrainClient } from '@arcgrid/brain-client'; export const brain = new BrainClient({ apiKey: process.env.BRAIN_API_KEY!, defaultAttribution: { agent_id: 'surface' }, defaultModuleId: 'surface.assistant', }); // In any API route: const result = await brain.chat({ messages: buildMessages(context), model: 'standard', });

Trebek

Replace Trebek's direct Anthropic call with brain.chat(). The neo4j MCP connection stays unchanged - only the language layer changes. This means Trebek calls are metered, logged in the ledger, and can be rerouted to local models for client privacy sessions.

// Before: direct Anthropic call const res = await anthropic.messages.create({ model: 'claude-opus-...', messages }); // After: through the brain (same interface, all routing handled by ArcRouter) const res = await brain.chat({ model: 'standard', messages, module_id: 'trebek.route' });
Provider toggle

ArcRouter's routing table controls which provider serves each alias. Switching between on-prem ArcRig, cloud self-hosted, and frontier API requires only a routing-table config change - no application code changes needed. This is how we protect client data: T2/T3 client calls never hit an external API unless explicitly configured.

Environment variables

VariableDefaultPurpose
BRAIN_API_KEY-Set on the BrainClient constructor. Required.
BRAIN_API_URLhttp://arcrouter:4000Override for local dev: http://localhost:4000

Type reference

// All exports from @arcgrid/brain-client import { BrainClient, BrainError, } from '@arcgrid/brain-client'; import type { // LLM ChatParams, ChatCompletion, ChatCompletionChunk, StreamDelta, ChatChoice, Message, ContentPart, TextPart, ImageUrlPart, ToolCall, ToolDefinition, ToolChoice, Usage, EffortAlias, Role, Attribution, EntitlementCheck, // Memory UserProfile, UpsertProfileInput, ProfileExport, DataClass, // Consent ConsentRecord, GrantConsentInput, // Client BrainClientOptions, } from '@arcgrid/brain-client';