Abjects is an LLM-mediated distributed object system. Objects communicate exclusively through asynchronous message passing, describe themselves via manifests, and negotiate communication protocols using an LLM. When objects with incompatible interfaces need to communicate, the LLM generates a proxy object that translates messages between them. When communication degrades, the system detects errors and regenerates the proxy automatically.
The core thesis: an LLM can serve as a runtime mediator - generating protocol adapters on the fly, creating objects from natural language, and healing broken connections.
All system services are Abjects: Registry, Factory, LLM service, UIServer, HealthMonitor, Negotiator, and all capability objects. Each Abject has:
- id - UUID (v4), assigned at construction
- manifest - Self-description: name, description, version, interfaces, required/provided capabilities, tags
- capabilities -
CapabilitySetof granted permissions - state machine -
initializingβreadyβbusyβerrorβstopped
The manifest's interfaces array contains machine-readable InterfaceDeclaration objects with typed method signatures, parameter types, return types, and event declarations. This is what the LLM reads to generate proxy objects.
Four message types: request, reply, event, error.
AbjectMessage {
header: { messageId, correlationId?, sequenceNumber, timestamp, type }
routing: { from, to, interface, method? }
payload: unknown
protocol: { version: '1.0.0', negotiationId? }
}
- Request/reply correlation via
correlationIdlinking reply to original request - Per-sender sequence numbers for ordering (tracked in module-level state in
message.ts) - Message builders:
request(),reply(),event(),error(),errorFromException() - Type guards:
isRequest(),isReply(),isEvent(),isError(),isReplyTo() - Serialization: JSON via
serialize()/deserialize()
Contracts are always enabled - correctness over performance. Three assertion types:
require(condition, message)- Preconditions: validates inputs at function entryensure(condition, message)- Postconditions: validates results before returninvariant(condition, message)- Class state: checked after construction and state mutations
All throw ContractViolation with type discrimination ('require' | 'ensure' | 'invariant').
Helpers: requireDefined(), requireNonEmpty(), requireNonEmptyArray(), requirePositive(), requireNonNegative().
Capabilities are permissions granted to objects. Each has an ID string in the format abjects:category:action.
| Category | Capabilities |
|---|---|
| Core (granted to all) | SEND_MESSAGE, LOG, TIME |
| System | REGISTRY_READ, REGISTRY_WRITE, FACTORY_SPAWN, LLM_QUERY |
| UI | UI_SURFACE, UI_INPUT |
| External | HTTP_REQUEST, STORAGE_READ/WRITE, TIMER, CLIPBOARD_READ/WRITE, FILESYSTEM_READ/WRITE, CONSOLE |
CapabilitySet manages grants per object with has(), satisfies(), missing(). WASM imports enforce capabilities at the sandbox boundary.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser Main Thread β
β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββββββ β
β β Message β β Network β β LLM β β UI Compositor β β
β β Bus β β Layer β β Gateway β β (WebGL2 3D) β β
β ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ βββββββββ¬ββββββββ β
βββββββββΌβββββββββββββΌβββββββββββββΌββββββββββββββββΌβββββββββββ
β postMessage API β β
βββββββββΌβββββββββββββΌβββββββββββββΌββββββββββββββββΌβββββββββββ
β βΌ βΌ βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Object Runtime Worker β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββ β β
β β β Registry β β Factory β β LLM Obj β β UI Obj β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β User Objects (WASM Modules) β β β
β β β Each object = sandboxed WASM instance β β β
β β β Imports: send(), log() (capability-based) β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β Web Worker β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The foundation that everything else depends on.
types.ts- All type definitions: identity types (AbjectId,MessageId, etc.),AbjectMessage,InterfaceDeclaration,MethodDeclaration,TypeDeclaration,CapabilityGrant,AbjectManifest,ProtocolAgreement,AbjectStatus,WasmObjectExportscontracts.ts- Design-by-contract assertions:require(),ensure(),invariant(), helpersmessage.ts- Message factory functions, type guards, serialization, per-sender sequence number trackingabject.ts- AbstractAbjectbase class with handler registration (on()), message sending (send(),request<T>()), pending reply tracking with timeout (30s default), state machine, invariant checking. AlsoSimpleAbjectfor inline handler objects.capability.ts-Capabilitiesconstant object,CapabilitySetclass,getDefaultCapabilities()(SEND_MESSAGE, LOG, TIME for all objects)
Execution infrastructure that objects run on top of.
runtime.ts- Main orchestrator. State machine:createdβstartingβrunningβstoppingβstopped. Bootstrap creates MessageBus, Registry, Factory, wires them together.spawn()delegates to Factory. Singleton viagetRuntime(),resetRuntime()for testing. Invariant: running runtime must have >= 2 core objects.message-bus.ts- Central message router.register()creates a Mailbox per object.send()runs interceptor pipeline then delivers to mailbox and invokes handler. Interceptors:LoggingInterceptor(debug logging),ProxyInterceptor(reroutes AβB traffic through proxy). Undeliverable messages notified to subscribers (for network bridging). Invariant: mailbox count == handler count.mailbox.ts- Bounded async queue (default 1000). Blockingreceive(), non-blockingtryReceive(),receiveTimeout(),peek(),drain(),clear(),close(). Key invariant: cannot have both queued messages and waiters simultaneously. AlsoPriorityMailboxwith multiple queues sorted by priority level.supervisor.ts- Erlang-style supervision tree. Strategies:one_for_one(restart failed child),one_for_all(restart all),rest_for_one(restart from failed onward). Restart tracking within time window (default 3 restarts in 5s). Escalation when max exceeded. Is itself an Abject (listens forchildFailedmessages).
System-level objects providing core services.
registry.ts- Central object directory. Multi-indexed by ID, interface, capability, name.DiscoveryQuerysupports filtering by name, interface, capability, tags. Subscribers notified on register/unregister. Well-known ID:REGISTRY_ID.factory.ts- Object lifecycle.spawnInstance()for pre-constructed objects,spawn()from manifest + code. Auto-registers with Registry.kill()stops and deregisters. Tracks all spawned objects. Well-known ID:FACTORY_ID.llm-object.ts- LLM service wrapper. Provider-agnostic (Anthropic, OpenAI, Ollama). Methods:complete,generateCode,analyze,listProviders,setProvider. Code extraction strips markdown fences. Well-known ID:LLM_OBJECT_ID.proxy-generator.ts- LLM-generated proxy objects.generateProxy()takes two manifests, produces TypeScript proxy code + manifest +ProtocolAgreement.regenerateProxy()includes error context from previous failures. Caches for regeneration. Well-known ID:PROXY_GENERATOR_ID.object-creator.ts- Natural language object creation. Discovers relevant objects via LLM-assisted filtering. Builds prompts including available object interfaces. Parses LLM response for JSON manifest + TypeScript code blocks. Methods:create,modify,suggest,listAvailable,getObjectGraph. Well-known ID:OBJECT_CREATOR_ID.ui-server.ts- X11-style display server. Surface ownership tracking (objects own their surfaces). Input routing: mouse to surface under pointer, keyboard to focused surface. Focus management with focus/blur events. Well-known ID:UI_SERVER_ID.
Built-in objects wrapping browser APIs or virtual services, exposed through standard Abject message interfaces.
http-client.ts- HTTP via Fetch API. Domain allow/deny lists. AbortController timeout (30s). Capability:HTTP_REQUEST.storage.ts- IndexedDB key-value store (abjects-storagedatabase,kvstore). In-memory fallback. Capabilities:STORAGE_READ,STORAGE_WRITE.timer.ts-setTimeout/setIntervalwrapper. Owner-only cancellation. FirestimerFiredevents. Cleans up inonStop(). Capability:TIMER.clipboard.ts- Browser Clipboard API. Graceful permission handling. Capabilities:CLIPBOARD_READ,CLIPBOARD_WRITE.console.ts- Buffered logging (max 1000 entries). Multi-level: debug/info/warn/error. Filterable retrieval. Capability:CONSOLE.filesystem.ts- In-memory virtual FS. Tree structure withFileEntrynodes. Path normalization with..traversal.Uint8Arraycontent. Capabilities:FILESYSTEM_READ,FILESYSTEM_WRITE.
Connection management, protocol agreements, and self-healing.
negotiator.ts- Connection establishment.connect(sourceId, targetId): fetches manifests from Registry, checks compatibility (shared interface IDs). If compatible β direct agreement, no proxy. If incompatible βProxyGenerator.generateProxy()β installsProxyInterceptoron MessageBus.disconnect()removes interceptor, kills proxy.renegotiate()hot-swaps proxy (kill old β generate new with error context β install new interceptor). Well-known ID:NEGOTIATOR_ID.agreement.ts- Agreement utilities andAgreementStore. Utility functions:isExpired(),needsHealthCheck(),createAgreementId(),validateAgreement(),renewAgreement(),mergeBindings(). Store: indexed by ID and participant.getBetween(),getExpired(),getNeedingHealthCheck().health-monitor.ts- Connection health monitoring. Per-connection tracking: messageCount, errorCount, timestamped errors. Rolling window (default 60s) with error pruning. Trigger:errorRate >= errorThreshold(default 10%) whenmessageCount >= minMessages(default 10). Periodic checks (default 5s). CallsNegotiator.renegotiate()with recent error context. Well-known ID:HEALTH_MONITOR_ID.INCOMPREHENSION_ERRORS:PARSE_ERROR,UNKNOWN_METHOD,INVALID_PAYLOAD,SCHEMA_MISMATCH,TYPE_ERROR,SEMANTIC_ERROR.
Provider-agnostic LLM integration.
provider.ts-LLMProviderinterface:name,isAvailable(),complete(),stream?().LLMMessage:{ role, content }.BaseLLMProviderabstract base with shared fetch and header building.LLMProviderRegistryfor managing multiple providers.anthropic.ts- Claude API. Separates system message. Default model:claude-3-5-sonnet-20241022. SSE streaming.x-api-keyauthentication.openai.ts- Chat Completions API. Default model:gpt-4-turbo-preview. SSE streaming with[DONE]sentinel. Bearer token auth.ollama.ts- Local LLM athttp://localhost:11434. Default model:llama3.2. Availability detection via/api/tags(2s timeout). NDJSON streaming. No API key.
Cross-machine communication abstraction.
transport.ts- AbstractTransportwith state machine:disconnectedβconnectingβconnectedβerror.TransportEventscallbacks: onConnect, onDisconnect, onMessage, onError, onStateChange.MockTransport.pair()for in-process paired testing.TransportRegistryfor managing named transports.websocket.ts-WebSocketTransportwith reconnection (exponential backoff:delay * 2^attempt) and heartbeat.WebSocketConnectionManagerfor multi-peer management.
Integration: MessageBus notifies 'undeliverable' subscribers when a message target isn't locally registered. The network layer subscribes to these and routes through the appropriate Transport.
WASM abject hosting: abjects written in other languages (C++ SDK in sdk/cpp/) run as first-class objects. The contract is docs/WASM_ABI.md; the object wrapper is src/objects/wasm-abject.ts (WasmAbject extends Abject, so WASM objects ride the mailbox, Registry, Supervisor, typeIds, and worker placement unchanged).
wasm-abi.ts- ABI v1 surface: JSON envelope types (guestβhost), length-prefixed buffer codec,validateWasmModule()(required exports + kinds).wasm-instance.ts-WasmInstancewrapper around oneWebAssembly.Instance: compile + validate + ABI-version check,init()/handle()/snapshot()guest calls, capability-gatedabjectsimports (emit/log/time_ms), minimal WASI preview1 shim (stdoutβlog, clock, random; no fs/sockets),extractWasmManifest()for package time.wasm-module-store.ts- Content-addressed module storage at$ABJECTS_DATA_DIR/wasm/<sha256>.wasm; modules referenced everywhere aswasm:sha256:<hex>riding the normalsourcefield, so AbjectStore snapshots, clone/instantiate, and respawn work unchanged.extensions.ts- Package scan/ingest: bundled native system packages (native/, shipped in the desktop app asresources/native) first, then user extensions ($ABJECTS_DATA_DIR/extensions/, installed viapnpm forge); a package withreplacesoverrides its built-in Factory type.
Browser application shell and canvas rendering.
app.ts- Application shell. Creates canvas element, Compositor, UIServer, Runtime.App.start()starts Runtime and sets up input listeners.createApp()factory for one-line bootstrap.compositor.ts- 3D surface compositor (hand-rolled WebGL2,src/ui/gl/). Each surface is anOffscreenCanvaswith its own 2D context β painted by the 2D draw-command vocabulary β uploaded as the texture of a slab in a perspective 3D scene. Slabs get theme-driven shadows, focus rim/bloom + z-lift, and drag tilt. A retained scene vocabulary (mesh/light/group nodes,$tokentheme colors) attaches 3D content to a window's subtree.requestAnimationFramerender loop (only renders whenneedsRenderflag set). Hit testing: camera-ray picking per slab + per-pixel alpha test.
App.createApp({ container }) β
creates HTMLCanvasElement, Compositor, UIServer, Runtime β
Runtime.start() β
MessageBus created β
Factory.setBus(bus), Factory.setRegistry(registry) β
Registry.init(bus) β registers itself β
Factory.init(bus) β registers itself β
ensure(registry.objectCount >= 2) β
main() β
LLMObject.configure({ apiKeys }) β runtime.spawn(llm) β
runtime.spawn(httpClient, storage, timer, clipboard, console, filesystem) β
ProxyGenerator.setLLM(llm) β runtime.spawn(proxyGenerator) β
Negotiator.setDependencies(registry, factory, proxyGenerator, bus) β runtime.spawn(negotiator) β
HealthMonitor.setNegotiator(negotiator) β startMonitoring() β runtime.spawn(healthMonitor) β
ObjectCreator.setDependencies(llm, registry, factory) β runtime.spawn(objectCreator) β
// System ready - all objects spawned and health monitoring started
Object A:
this.send(msg) β
Abject.send() β
require(bus !== undefined)
bus.send(msg) β
require(messageId !== '', recipient !== '')
interceptor pipeline (each returns 'pass', 'drop', or transformed msg) β
if recipient not local β notifyUndeliverable (for network layer)
else β mailbox.send(msg) β messageCount++ β
handler = handlers.get(recipient) β
msg = mailbox.tryReceive() β
await handler(msg) β
Abject.handleMessage() β
if reply/error β resolve pending request promise
else β find method handler β execute β if request, auto-reply
Negotiator.connect(sourceId, targetId) β
registry.lookupObject(sourceId) β sourceManifest
registry.lookupObject(targetId) β targetManifest
checkCompatibility(source, target) β
if shared interface IDs β createDirectAgreement (no proxy)
else β
proxyGenerator.generateProxy(sourceId, targetId, sourceManifest, targetManifest) β
LLM receives both manifests β
LLM generates TypeScript proxy code + manifest β
returns GeneratedProxy { code, manifest, agreement }
spawnProxy(generated) β proxyId
interceptor = new ProxyInterceptor(sourceId, targetId, proxyId)
bus.addInterceptor(interceptor)
connections.set(agreementId, { agreement, proxyId, interceptor })
notifyConnectionEstablished(agreement) β
event β sourceId: 'connectionEstablished'
event β targetId: 'connectionEstablished'
HealthMonitor (every 5s):
checkAllHealth() β
for each tracked connection:
pruneOldErrors(health) β remove errors outside 60s window
if messageCount < 10 β skip
errorRate = (errorCount / messageCount) * 100
if errorRate >= 10% β
errorContext = last 5 errors formatted as strings
triggerRenegotiation(agreementId, errorContext) β
negotiator.renegotiate(agreementId, errorContext) β
proxyGenerator.regenerateProxy(agreementId, errorContext) β
LLM receives original manifests + error context
LLM generates improved proxy code
factory.kill(oldProxyId)
spawnProxy(regenerated) β newProxyId
bus.removeInterceptor(oldInterceptor)
bus.addInterceptor(new ProxyInterceptor(..., newProxyId))
reset counters (messageCount=0, errorCount=0, errors=[])
Boot (server/index.ts):
ingestAllExtensions(factory) β
native/ (bundled) then $ABJECTS_DATA_DIR/extensions/ (user, wins collisions)
module bytes β content-addressed store (wasm:sha256:<hex>)
factory.registerWasmType(name, { manifest, source, scope })
Spawn (Factory):
spawn({ manifest }) β wasmTypes lookup (overrides built-in constructors) β
spawnWasmInWorker: WorkerPool worker constructs
new WasmAbject({ manifest, source, owner, data })
worker reads module bytes from the store on disk (bytes never cross threads)
Inside the object (WasmAbject.onInit):
WasmInstance.create(bytes) β
validate exports + abi version β instantiate with
abjects.emit / log / time_ms (capability-gated)
wasi_snapshot_preview1 shim (no fs/sockets)
_initialize() (WASI reactor) β abject_init({ objectId, typeId, data })
Message flow (JSON envelopes, docs/WASM_ABI.md):
inbound request/event β '*' handler β abject_handle({ kind: 'message' })
guest replies sync (reply/error envelope) or defers (later handle call)
guest requests: { kind: 'request', to: '@Name' | id } β host this.request()
β result envelope back into abject_handle
changed / persist / log envelopes β dependents fan-out, Registry data
upsert, host logging
Uniform interface for discovery, monitoring, and lifecycle management. System services can be replaced, extended, or composed like any other object. The Registry discovers capabilities the same way it discovers user objects. The LLM can read manifests of system objects the same way it reads user objects.
From the source code: "correctness over performance." In a dynamic message-passing system where objects may be LLM-generated, early failure detection is critical. Contracts document expected behavior at every boundary and catch violations immediately rather than propagating corrupt state.
Objects with incompatible interfaces can still communicate. The proxy is a real object in the system - it has a manifest, receives messages, and follows the same protocol. When it fails, the LLM can regenerate it with error context from the previous attempt, progressively improving the translation.
Each object gets an isolated OffscreenCanvas surface painted via 2D draw commands; the compositor renders every surface as a textured slab in a WebGL2 3D scene (depth, lighting, focus bloom, drag tilt). 3D objects attach to a window's subtree through a retained scene vocabulary (scene ops on UIServer) with theme-token colors. No DOM manipulation by objects - all rendering through messages. This provides:
- Isolation between objects (each rasterizes to its own canvas)
- Centralized compositing (one render loop, one scene)
- 2D content as a first-class texture type on 3D surfaces β text keeps the browser's rasterizer
- Familiar model (X11 surface/window semantics) with a spatial presentation layer
User objects run in a Web Worker (isolated from main thread). WASM provides CPU-level sandboxing. Capability-enforced imports prevent unauthorized system access - a user object can only send messages (if it has SEND_MESSAGE), log (if LOG), or read time (if TIME). All other system access goes through capability objects via message passing.
- Create file, extend
Abject, define manifest withInterfaceDeclaration - Register handlers for all interface methods
- Use contracts throughout
- Export well-known ID constant
- Add to
src/index.tsexports and spawn inmain()
- Create in
src/objects/capabilities/ - Define capability IDs in
src/core/capability.ts - Set
providedCapabilitiesin manifest, tag with['capability', '<name>'] - Follow existing patterns (domain security, fallbacks)
- Create in
src/llm/, implementLLMProvideror extendBaseLLMProvider - Include
complete()and optionallystream() - Add configuration to
LLMObject.configure()
- Create in
src/network/, extendTransportabstract class - Implement
connect(),disconnect(),sendMessage() - Follow the state machine:
disconnectedβconnectingβconnectedβerror - Register with
TransportRegistry