Skip to content

Architecture

A high-level tour of how the pieces fit together. Read this once and the rest of the docs will make more sense.

Connection flow

+----------+   1. HTTP GET conversationUrl      +--------------------+
|  Client  | ---------------------------------> |  LLMOR HTTP API    |
|          |                                    |                    |
|          | <--------------------------------- |  { conversation,   |
|          |   { relay_url, relay_path,         |    messages }      |
|          |     token, ... }                   +--------------------+
|          |
|          |   2. Socket.IO connect(relay_url, path: relay_path)
|          | ------------------------------------------------------> +---------+
|          |                                                         |  Relay  |
|          |   3. emit 'join' { token }                              | server  |
|          | ------------------------------------------------------> |         |
|          |                                                         |         |
|          |   4. 'state' { functions, context } (server snapshot)   |         |
|          | <------------------------------------------------------ |         |
|          |                                                         |         |
|          |   5. JSON-patch /autopilot/functions + /autopilot/...   |         |
|          | ------------------------------------------------------> |         |
|          |                                                         |         |
|          |   6. streaming completions, function calls, user input  |         |
|          | <-----------------------------------------------------> |         |
+----------+                                                         +---------+

Steps 1–4 happen inside AutopilotClient.connect(). Once the server snapshot is received the client sends its initial state (registered functions + context) and the connection is ready.

State synchronization

Two pieces of client state are mirrored to the server:

StateJSON Patch pathDebounce
Registered function descriptors/autopilot/functions25 ms
Free-form context (Record<string, unknown>)/autopilot/context100 ms

The client keeps a local copy, diffs against it with fast-json-patch, and sends only the patches. Debouncing collapses bursts of updateContext calls into a single network message.

Need an immediate sync (for example before redirecting away)? Call flushContextSync().

Streaming protocol

The server streams a completion as a sequence of typed Socket.IO events:

completion.started      ─► isStreaming = true
completion_stream*      ─► chunks appended to streamingMessage
function_call.begin*    ─► record function being called
function_call.end*      ─► record result
function_call_iteration ─► reset for next round
completion.ended        ─► commit streamingMessage to conversationMessages
completion.finished     ─► isStreaming = false
user_input              ─► server is waiting for the user

The client surfaces each of these as a typed event you can listen to. See Streaming and Events.

Function calls

When the server invokes a registered function:

  1. Server emits function_call.begin with { id, name, arguments }.
  2. Client looks up the handler, runs it (sync or async), captures the result or error.
  3. Client sends a function_response with { id, status, result | error }.
  4. Server emits function_call.end with the captured result.

Errors thrown by the handler are caught and reported as a functionError event without breaking the connection.

Vue layer (optional)

useAutopilotStore (Pinia) subscribes to every relevant client event in its initialize() action and copies the payload into reactive refs. The store is autoloaded the first time useAutopilotStore() is called, so components do not need to wire listeners themselves.

A key computed, visibleMessages, filters the raw message list down to roles users actually care about (user, assistant, error) and merges consecutive empty assistant messages that only carry function calls.

Bar layer (optional)

enableAutopilotBar(client):

  1. Calls setAutopilotClient(client) so the store's singleton accessor resolves.
  2. Creates a regular-DOM <div> for the floating toggle button (so it can use the host page's font and z-index stack).
  3. Creates a separate host element with attachShadow({ mode: 'open' }), injects the bundled Tailwind CSS into the shadow root, and mounts the overlay Vue app there.
  4. Returns a { destroy() } handle so you can tear both apps down.

The two Vue apps share one Pinia instance, which is why opening the bar and reacting to its state from your own store works.

Where the code lives

The repo is an npm-workspaces monorepo of three packages:

packages/core (@llmor/autopilot-core) — the headless client. AutopilotClient is a thin facade over focused collaborators:

  • src/index.tsAutopilotClient (connection, conversation, orchestration) + useAutopilotClient().
  • src/types.ts — the public type surface (re-exported from the entry).
  • src/events/TypedEventEmitter.ts — the typed event bus.
  • src/streaming/StreamingSession.ts — the streaming-message state machine.
  • src/registry/FunctionRegistry.ts — client-function storage + descriptors.
  • src/state/ContextStateManager.ts — context state + JSON-Patch diffing.
  • src/conversation/enrichMessages.ts — pairs tool results onto assistant function calls.

packages/vue (@llmor/autopilot-vue) — the headless Vue layer (no markup, no CSS).

  • src/stores/autopilot.ts — the Pinia store (aggregates the composables below).
  • src/composables/useAutopilotContext, useConfirmations, useAskUser, usePendingPrompts, plus the renderless-component logic: useAskUserForm, useMessageComposer, useFunctionCallDetails.
  • src/stores/useAutopilotConversation.ts — conversation composable.
  • src/markdown.tsrenderMarkdown (the one bit of view formatting kept here).
  • src/components/ — renderless components: logic + scoped slots, no markup or styles.

packages/bar (@llmor/autopilot-bar) — the styled, drop-in layer (the only one that ships markup + CSS).

  • src/index.tsenableAutopilotBar, which mounts the styled components in a Shadow DOM.
  • src/components/ — styled SFCs that wrap the renderless components from @llmor/autopilot-vue.
  • src/styles/skin.css — the skin (@apply rules keyed to the components' ap-* classes), compiled into the injected stylesheet.