Skip to main content

createLilyAgent reference

createLilyAgent is the entry point of the SDK. It creates and initializes an agent and resolves to a LilyAgentHandle you drive from your code.

function createLilyAgent(opts?: CreateAgentOptions): Promise<LilyAgentHandle>

It is asynchronous — it initializes the engine (loading the WebAssembly module in the browser, or selecting a backend on Node) before resolving. apiKey is required at runtime; creating an agent without one throws createLilyAgent: apiKey required.

import { createLilyAgent } from '@poolot/lily-web'

const agent = await createLilyAgent({
  apiKey: process.env.POOLOT_API_KEY,
  model: 'poolot-standard',
})

Options

Common (browser and Node)

OptionTypeDefaultDescription
apiKeystring— (required)Your Pathvela auth token — a signed-in user token or a programmatic sk-lily-… key. Sent as Authorization: Bearer <apiKey>.
baseUrlstring/api/agent-serverWhere agent requests go. The default is a same-origin path, so route it through your own proxy — or point it straight at the Pathvela backend (https://agent.pathvela.com).
modelstring"poolot-standard"The model tier: poolot-standard, poolot-pro, poolot-vision, or poolot-mini.
systemPromptstring—A leading instruction that stays stable for the agent's life (cache-friendly).
initialMessagesLilyMessage[]—A conversation to start from.
autoEnvironmentbooleantrueInject a system note describing the runtime's capabilities.
telemetryTelemetryOptions | falseon, silentError reporting. Never sends prompt or response content; stays silent until a dsn is set.

Engine loading

OptionTypeDefaultDescription
assetBasestring—Folder you serve the engine's files from (see Installation).
cdnFallbackbooleansee noteFall back to a pinned CDN copy of the engine if local assets aren't found.
versionstring—Pin the engine version.

Note on cdnFallback: the shipped type comment documents the default as true, while the package README lists it as false. Set it explicitly if it matters to you, and serve your own assets with assetBase in production.

Filesystem, storage & exec (both environments)

These work in the browser and on Node — see Browser vs Node for how each is backed in-page.

OptionTypeDefaultDescription
rootstringprocess.cwd() (Node)Folder the file tools are confined to. In the browser, a named OPFS subfolder that paths cannot escape.
projectstring"default"Scopes stored records (research, memory, audit) — disk records on Node, IndexedDB in the browser.
exec{ javascript?; python?; pythonBackend? }both offEnable code-execution tools. In the browser these run in a sandbox. pythonBackend is "auto" | "pyodide" | "system" (default "auto").
execTimeoutMsnumber30000Timeout for a single tool execution.

Node / harness-only

These are ignored in the browser.

OptionTypeDefaultDescription
dataDirstring~/.lily/sdkFilesystem path for research, memory, and audit records. Honors $LILY_SDK_HOME. In the browser these live in IndexedDB scoped by project instead.
backend"harness" | "wasm"autoForce the engine. Omitted = harness if usable, else WebAssembly.
autoDownloadbooleanfalseDownload the harness (~90 MB) if none is installed.
harnessBinarystring—Explicit harness path (overrides discovery and LILY_BINARY).
harnessVersionstring—Pin the harness release.
onProgress(p) => void—Harness download/install progress callback.
sessionIdstring—Resume an existing harness session.
cwdstring—Working directory for the harness backend.
providerstring—Bring-your-own-key provider (harness only; requires the byok entitlement).
envRecord<string, string>—Extra environment for the harness subprocess.

The agent handle

createLilyAgent resolves to a LilyAgentHandle:

interface LilyAgentHandle {
  environment: LilyEnvironment
  runDetailed(messages: LilyMessage[], opts?: LilyRunOptions): Promise<LilyRunResult>
  continue(input?: string | LilyMessage, opts?: LilyRunOptions): Promise<LilyRunResult>
  run(messages: LilyMessage[], maxSteps?: number): Promise<string>
  runStreaming(
    messages: LilyMessage[],
    onToken: (text: string) => void,
    onEvent?: (event: LilyEvent) => void,
    maxSteps?: number,
  ): Promise<string>
  complete(messages: LilyMessage[]): Promise<string>
  snapshot(): { status: string; iterations: number; messages: LilyMessage[]; usage: LilyUsage }
  restore(messages: LilyMessage[]): number
  getMe(): Promise<{ userId: string; email?: string; name?: string; tier: string; balance: number; capabilities?: { byok: boolean; providers: 'any' | 'poolot' } }>
  abort(): void
}

Methods

  • runDetailed(messages, opts?) — run the agent over a conversation and get the full LilyRunResult: text, status, usage, tool calls, and the resulting messages. This is the method to reach for when you want everything.
  • run(messages, maxSteps?) — a convenience wrapper that resolves to just the output text.
  • runStreaming(messages, onToken, onEvent?, maxSteps?) — like run, but streams generated text through onToken and lifecycle events through onEvent. Resolves to the final text.
  • complete(messages) — a single completion with no tool loop.
  • continue(input?, opts?) — run another turn, keeping the agent's accumulated history. Pass a string or a LilyMessage.
  • snapshot() — capture the current status, iteration count, messages, and usage.
  • restore(messages) — reset the conversation to a given message list; returns the count restored.
  • getMe() — fetch the account identity, tier, credit balance, and capabilities.
  • abort() — cancel the run in flight, including any tool work.

There is no sendMessage/getMessages method. You send by passing a messages array (or continue(input)), and you read the conversation from snapshot().messages or LilyRunResult.messages.

Run options

runDetailed, runStreaming, and continue accept a LilyRunOptions:

FieldTypeDefaultDescription
maxStepsnumber—Max iterations of the agent loop.
maxTokensnumber—Token budget for the run.
maxCallsnumber—Max tool/model calls.
maxConsecutiveFailuresnumber5Stop after this many failures in a row.
maxRepeatedCallsnumber3Stop after the same call repeats this many times.
onToken(text) => void—Streamed output tokens.
onEvent(event: LilyEvent) => void—Lifecycle events.
onReasoning(text) => void—Reasoning tokens. Works on both backends for reasoning-capable model routes; the default chat route emits none.

LilyRunResult

interface LilyRunResult {
  status: 'completed' | 'aborted' | 'max_steps' | 'failed'
        | 'budget_tokens' | 'budget_calls' | 'too_many_failures' | 'repeated_tool_call'
  outputText: string
  messages: LilyMessage[]
  toolCalls: LilyToolCall[]
  usage: LilyUsage | null
  iterations: number
  durationMs: number
  result?: unknown        // from a run-completing tool
  completedBy?: string | null
  reasoning?: string      // both backends, reasoning-capable routes only
  sessionId?: string | null // harness only
}

usage is LilyUsage — { inputTokens, outputTokens, cachedInputTokens, calls } — or null when the backend reported none (deliberately distinct from all-zeroes).

See also