BlaiseBlaise
Documentation
Runner and PlugsReference

Runner API types

The type declarations of the runner API that the CLI calls, with their TSDoc.

The rest of @blaiseagent/runner: configuration loading, plug loading, persisted-state loading and runSession. This is the API that @blaiseagent/cli uses; plug authors do not need it.

AgentConfig

export type AgentConfig = z.infer<typeof agentSchema>;

BundledPlugTable

/**
 * The plugs bundled with the runner, by `blaise:` name. Generated from `src/plugs/bundled`,
 * so that a compiled binary can load them: a bundler cannot follow a computed `import()`.
 */
export type BundledPlugTable = Readonly<Record<string, () => Promise<unknown>>>;

capacitySchema

/** The most runs this runner holds at once, when the file sets a limit. */
export const capacitySchema: z.ZodNumber;

checkExpressions

/**
 * Parses every expression found in the string values of `value`. Evaluation is not attempted,
 * so this works before the contexts exist. Typos in context names fail here.
 */
export function checkExpressions(
  value: unknown,
  contexts: readonly ContextName[],
  path?: string
): ExpressionIssue[];

CONFIG_VERSION

export const CONFIG_VERSION = 1;

ConfigError

/** The configuration file is invalid. `issues` lists every problem found, in file order. */
export class ConfigError extends Error {
  readonly issues: readonly string[];
  constructor(issues: readonly string[], options?: ErrorOptions);
}

configJsonSchema

/** JSON Schema for editors, with the same structure as the runtime validation. */
export function configJsonSchema(): Record<string, unknown>;

configSchema

export const configSchema: z.ZodObject<
  {
    version: z.ZodLiteral<1>;
    server: z.ZodOptional<
      z.ZodObject<
        {
          url: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodString]>>;
          token: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodString]>>;
        },
        z.core.$strict
      >
    >;
    log: z.ZodOptional<
      z.ZodObject<
        {
          level: z.ZodOptional<
            z.ZodUnion<
              readonly [
                z.ZodEnum<{
                  debug: "debug";
                  error: "error";
                  fatal: "fatal";
                  info: "info";
                  trace: "trace";
                  warn: "warn";
                }>,
                z.ZodString,
              ]
            >
          >;
          format: z.ZodOptional<
            z.ZodUnion<
              readonly [
                z.ZodEnum<{
                  json: "json";
                  pretty: "pretty";
                }>,
                z.ZodString,
              ]
            >
          >;
        },
        z.core.$strict
      >
    >;
    labels: z.ZodOptional<
      z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>
    >;
    capacity: z.ZodOptional<z.ZodNumber>;
    agent: z.ZodOptional<
      z.ZodObject<
        {
          driver: z.ZodOptional<z.ZodString>;
          command: z.ZodOptional<z.ZodString>;
          args: z.ZodOptional<z.ZodArray<z.ZodString>>;
        },
        z.core.$strict
      >
    >;
    setup: z.ZodOptional<
      z.ZodArray<
        z.ZodObject<
          {
            id: z.ZodOptional<z.ZodString>;
            use: z.ZodString;
            with: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
          },
          z.core.$strict
        >
      >
    >;
  },
  z.core.$strict
>;

DEFAULT_SERVER_URL

export const DEFAULT_SERVER_URL = "https://askblaise.com";

defaultModuleLoader

export const defaultModuleLoader: ModuleLoader;

describeEntry

/** `plug 4 "db" (blaise:shell)`: how a plug is named in logs and errors. */
export function describeEntry(entry: SetupEntry): string;

Directive

/** What Blaise tells a running session. */
export type Directive =

evaluateAgent

/** Evaluates the `agent:` section when a run starts, when `session` and `run` exist. */
export function evaluateAgent(config: RunnerConfig, contexts: ExpressionContexts): AgentConfig;

evaluateValue

/**
 * Evaluates every `${{ }}` in the string values of `value`, recursively. The contexts that are
 * present decide which context names the expressions may use.
 */
export function evaluateValue(
  value: unknown,
  contexts: ExpressionContexts,
  path?: string
): JsonValue | undefined;

ExpressionContexts

export interface ExpressionContexts {
  readonly env: Readonly<Record<string, string | undefined>>;
  readonly session?: {
    readonly id: string;
    readonly executionId: string;
    readonly agent: {
      readonly name: string;
    };
  };
  readonly run?: {
    readonly kind: string;
    readonly number: number;
  };
  readonly setup?: Readonly<
    Record<
      string,
      {
        readonly outputs: Readonly<Record<string, string>>;
      }
    >
  >;
}

ExpressionIssue

export interface ExpressionIssue {
  readonly path: string;
  readonly message: string;
}

findNonResumable

/** The plugs that cannot handle a `resume` run. Empty means the chain is resumable. */
export function findNonResumable(entries: readonly LoadedEntry[]): LoadedEntry[];

hasExpression

export function hasExpression(value: unknown): boolean;

inMemoryStateStore

/**
 * Persisted plug state kept in the runner's memory. It does not survive the process, so it is
 * for tests and for runs of a session that cannot resume.
 */
export function inMemoryStateStore(initial?: readonly StoredPlugState[]): InMemoryStateStore;

InMemoryStateStore

export interface InMemoryStateStore extends PlugStateStore {
  /** The records as they are now, by plug id. */
  readonly records: ReadonlyMap<string, StoredPlugState>;
}

isPlug

/**
 * Whether a module's default export is a plug made by `definePlug`. A plain `plugApi` field is
 * used rather than `instanceof` or a symbol, so that it survives bundling and duplicate copies
 * of this package in `node_modules`.
 */
export function isPlug(value: unknown): value is LoadedPlug;

isResumable

/** Plugs are resumable unless they say otherwise. */
export function isResumable(plug: Pick<LoadedPlug, "resumable">): boolean;

JsonValue

export type JsonValue =
  | string
  | number
  | boolean
  | null
  | readonly JsonValue[]
  | {
      readonly [key: string]: JsonValue;
    };

LABEL_KEY_PATTERN

export const LABEL_KEY_PATTERN: RegExp;

LabelMap

/** A label map after evaluation: every key holds a non-empty, de-duplicated list of values. */
export type LabelMap = Record<string, string[]>;

labelMapSchema

/**
 * The labels after evaluation, normalised: scalars become one-value lists, values are trimmed and
 * de-duplicated, and the bounds of the SDK's label map apply.
 */
export const labelMapSchema: z.ZodRecord<
  z.ZodString,
  z.ZodPipe<
    z.ZodPipe<
      z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>,
      z.ZodTransform<string[], string | string[]>
    >,
    z.ZodArray<z.ZodString>
  >
>;

loadConfig

/** Loads, includes, validates and partly evaluates a `blaise.yml`. */
export function loadConfig(path: string, options: LoadConfigOptions): Promise<RunnerConfig>;

LoadConfigOptions

export interface LoadConfigOptions {
  /** The runner's environment, for `${{ env.* }}` in `server` and `log`. */
  readonly env: Readonly<Record<string, string | undefined>>;
}

LoadedEntry

export interface LoadedEntry {
  readonly entry: SetupEntry;
  readonly plug: LoadedPlug;
  readonly source: PlugSource;
}

LoadedPlug

/**
 * A plug as the runner sees it after loading: the author's generics are erased, because the
 * runner checks inputs, state and persisted values against the plug's schemas at run time.
 * `isPlug` is the single place where a module's export is accepted as this type.
 */
export interface LoadedPlug {
  readonly name: string;
  readonly description?: string;
  readonly inputs?: StandardSchemaV1;
  readonly persisted?: StandardSchemaV1;
  readonly resumable?: boolean;
  readonly onLoad?: (ctx: LoadContext<unknown, unknown>) => unknown;
  readonly onMigrate?: (ctx: LoadContext<unknown, undefined>, stored: unknown) => unknown;
  readonly onSession?: (
    ctx: PlugContext<unknown, unknown, unknown>,
    next: (options?: { readonly host?: Host }) => Promise<PauseInfo>
  ) => Promise<void>;
  readonly onTeardown?: (
    ctx: PlugContext<unknown, unknown, unknown>,
    next: (options?: { readonly host?: Host }) => Promise<TeardownInfo>
  ) => Promise<void>;
  readonly onMessage?: (
    ctx: PlugContext<unknown, unknown, unknown>,
    message: Message,
    next: (message: Message) => Promise<MessageResult<Message>>
  ) => Promise<MessageResult<Message>> | MessageResult<Message>;
  readonly plugApi: {
    readonly major: number;
    readonly minor: number;
  };
}

LoadedStates

export interface LoadedStates {
  /** By entry index. Entries without a `persisted` schema get an inert state. */
  readonly byIndex: ReadonlyMap<number, PersistedState<unknown>>;
  readonly orphans: readonly OrphanedState[];
}

loadPersistedStates

/** Loads the state of every entry at once. Used where inputs do not depend on earlier plugs. */
export function loadPersistedStates(options: LoadStatesOptions): Promise<LoadedStates>;

loadPlugs

/**
 * Resolves, imports and checks every plug of the configuration. All problems are collected, so a
 * typo in the third entry does not hide a missing package in the fifth. Inputs without
 * expressions are validated now; the others when their plug runs.
 */
export function loadPlugs(
  config: Pick<RunnerConfig, "setup" | "dir">,
  options?: LoadPlugsOptions
): Promise<LoadedEntry[]>;

LoadPlugsOptions

export interface LoadPlugsOptions {
  readonly bundled?: BundledPlugTable;
  readonly loader?: ModuleLoader;
  /** Where the runner lives. Defaults to this package. */
  readonly runnerDir?: string;
}

localHost

/**
 * The runner's own machine. Its `fs` is the vfs API over the real file system (so the sync
 * methods work), and its processes are children of the runner. Nothing to connect or close.
 */
export function localHost(options?: LocalHostOptions): Host;

LOG_FORMATS

export const LOG_FORMATS: readonly ["pretty", "json"];

LOG_LEVELS

export const LOG_LEVELS: readonly ["trace", "debug", "info", "warn", "error", "fatal"];

LogConfig

export type LogConfig = z.infer<typeof logSchema>;

MAX_LABEL_KEY_LENGTH

/**
 * Limits and key rules for labels. `@blaiseagent/runner` does not depend on the SDK, so these
 * mirror `runner/labels.ts` of `@blaiseagent/sdk`; a test keeps the two in step.
 */
export const MAX_LABEL_KEY_LENGTH = 63;

MAX_LABEL_KEYS

export const MAX_LABEL_KEYS = 32;

MAX_LABEL_VALUE_LENGTH

export const MAX_LABEL_VALUE_LENGTH = 128;

MAX_LABEL_VALUES_PER_KEY

export const MAX_LABEL_VALUES_PER_KEY = 32;

MAX_RUNNER_CAPACITY

/**
 * Bound of a declared capacity. Mirrors `RUNNER_MAX_CAPACITY` of `@blaiseagent/sdk`, which
 * `@blaiseagent/runner` does not depend on; a test in the CLI keeps the two in step.
 */
export const MAX_RUNNER_CAPACITY = 1000;

ModuleLoader

/** Resolves and imports modules. Replaceable in tests. */
export interface ModuleLoader {
  /**
   * The absolute path `specifier` resolves to when imported from a module in `fromDir`, or
   * `undefined` when it cannot be found.
   */
  resolve(specifier: string, fromDir: string): string | undefined;
  import(path: string): Promise<unknown>;
}

nonResumableMessage

/** The error `listen` raises when the agent pauses idle sessions but a plug cannot resume. */
export function nonResumableMessage(entry: LoadedEntry): string;

OpenedStates

/** The records of a session, fetched once, handed to each stateful plug when its turn comes. */
export interface OpenedStates {
  /** Records that no plug of the configuration owns. Known as soon as the records are fetched. */
  readonly orphans: readonly OrphanedState[];
  /**
   * The plug's own validated (or migrated) value. Stateless plugs get an inert state. Throws
   * `PlugStateError` when the record cannot be validated or migrated (the run then fails).
   */
  load(
    loaded: LoadedEntry,
    migrateContext: LoadContext<unknown, undefined>
  ): Promise<PersistedState<unknown>>;
}

openStates

/**
 * Fetches the records of a session in one call. The engine loads each plug's state lazily, when
 * the plug before it calls `next()`, so that `onMigrate` sees inputs that may depend on the
 * outputs of earlier plugs.
 */
export function openStates(
  store: PlugStateStore,
  entries: readonly LoadedEntry[]
): Promise<OpenedStates>;

OrphanedState

/** A record that no plug of the current configuration owns. Reported, never deleted. */
export interface OrphanedState extends StoredPlugState {
  readonly reason: OrphanReason;
  /** The plug now at that `id`, for `replaced`. */
  readonly currentPlug?: string;
}

OrphanReason

export type OrphanReason =

PlugContractError

/** A plug broke the contract of a handler (for example, `next()` called twice). */
export class PlugContractError extends Error {
  constructor(message: string, options?: ErrorOptions);
}

PlugLoadError

/** One or more plugs could not be resolved, loaded or accepted. */
export class PlugLoadError extends Error {
  readonly issues: readonly string[];
  constructor(issues: readonly string[], options?: ErrorOptions);
}

PlugSource

export type PlugSource = "bundled" | "package" | "file";

PlugStateError

/** Persisted plug state could not be loaded, validated or migrated. */
export class PlugStateError extends Error {
  constructor(message: string, options?: ErrorOptions);
}

PlugStateStore

/** The Blaise-facing side of persisted state. */
export interface PlugStateStore {
  /** All the records of the session, fetched once before a run. */
  loadAll(): Promise<readonly StoredPlugState[]>;
  /** Resolves when the write is durable. */
  set(record: StoredPlugState): Promise<void>;
  clear(id: string): Promise<void>;
}

RawConfig

export type RawConfig = z.infer<typeof configSchema>;

referencedSetupIds

/** The ids used as `setup.<id>` in the expressions of a value. */
export function referencedSetupIds(value: unknown): string[];

ReportedMessage

/** One ACP message, as the transcript records it. */
export interface ReportedMessage {
  readonly direction: "to_agent" | "from_agent";
  readonly method: string;
  readonly payload: object;
  readonly protocolVersion: string;
  /** Correlates `tool_call` updates and permission requests. */
  readonly toolCallId?: string;
  /** The plug that answered a request itself instead of passing it on. */
  readonly answeredBy?: string;
}

RunnerConfig

export interface RunnerConfig {
  /** Absolute path of the file that was loaded. */
  readonly file: string;
  /** Its directory: local `use:` paths and packages resolve from here. */
  readonly dir: string;
  readonly server: ServerConfig;
  readonly log: LogConfig;
  /** What this runner declares about itself, evaluated and normalised. */
  readonly labels: LabelMap;
  /** Most runs this runner holds at once; `undefined` when the file sets no limit. */
  readonly capacity?: number;
  /** Not evaluated yet: see `evaluateAgent`. */
  readonly agent: AgentConfig;
  readonly setup: readonly SetupEntry[];
}

runnerConfigSchema

/**
 * The shape of a `RunnerConfig`, for checking one that crossed a process boundary (the listener
 * sends its loaded configuration to the `run` child). Optional keys stand for properties that
 * serialization drops when they are `undefined`.
 */
export const runnerConfigSchema: z.ZodObject<
  {
    file: z.ZodString;
    dir: z.ZodString;
    server: z.ZodObject<
      {
        url: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodString]>>;
        token: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodString]>>;
      },
      z.core.$strict
    >;
    log: z.ZodObject<
      {
        level: z.ZodOptional<
          z.ZodUnion<
            readonly [
              z.ZodEnum<{
                debug: "debug";
                error: "error";
                fatal: "fatal";
                info: "info";
                trace: "trace";
                warn: "warn";
              }>,
              z.ZodString,
            ]
          >
        >;
        format: z.ZodOptional<
          z.ZodUnion<
            readonly [
              z.ZodEnum<{
                json: "json";
                pretty: "pretty";
              }>,
              z.ZodString,
            ]
          >
        >;
      },
      z.core.$strict
    >;
    labels: z.ZodRecord<
      z.ZodString,
      z.ZodPipe<
        z.ZodPipe<
          z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>,
          z.ZodTransform<string[], string | string[]>
        >,
        z.ZodArray<z.ZodString>
      >
    >;
    capacity: z.ZodOptional<z.ZodNumber>;
    agent: z.ZodObject<
      {
        driver: z.ZodOptional<z.ZodString>;
        command: z.ZodOptional<z.ZodString>;
        args: z.ZodOptional<z.ZodArray<z.ZodString>>;
      },
      z.core.$strict
    >;
    setup: z.ZodArray<
      z.ZodObject<
        {
          index: z.ZodNumber;
          id: z.ZodOptional<z.ZodString>;
          use: z.ZodString;
          with: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
        },
        z.core.$strip
      >
    >;
  },
  z.core.$strip
>;

RunResult

export interface RunResult {
  /** How the agent run ended. Absent for a dedicated `teardown` run. */
  readonly pause?: PauseInfo;
  /** Set when the session was torn down: in this run, after the pause, or in a `teardown` run. */
  readonly teardown?: TeardownInfo;
  /**
   * Whether the teardown itself failed (a plug threw, or broke the `next()` rules). A session that
   * ended with `reason: "failed"` and was torn down without trouble is not a failed teardown.
   */
  readonly teardownFailed?: boolean;
  /** Whether every plug can handle a `resume` run. Reported to Blaise with every run. */
  readonly resumable: boolean;
  readonly acpSessionId?: string;
  /**
   * Preset values the agent could not honour (it has no such setting, or no such value), one
   * sentence each. Never dropped silently: they are logged and meant for the run's report.
   */
  readonly presetIssues?: readonly string[];
}

runSession

/**
 * Runs one run of a session: a `start` or `resume` run attaches the plugs, runs the agent until
 * Blaise stops it, and pauses the plugs; if the session cannot resume, the plugs are torn down
 * right after. A `teardown` run only tears down. Plug failures are reported in the result (pause
 * or teardown reason `failed`); the promise rejects only when the run cannot be set up at all
 * (for example, the state store is unreachable).
 */
export function runSession(options: RunSessionOptions): Promise<RunResult>;

RunSessionOptions

export interface RunSessionOptions {
  /** The loaded plugs of `setup:`, in order. */
  readonly entries: readonly LoadedEntry[];
  readonly session: SessionInfo;
  readonly run: RunInfo;
  /** The agent before any plug has changed it: the adapter's command, `agent:` of the config. */
  readonly agent: AgentSpawnSpec;
  readonly link: SessionLink;
  readonly store: PlugStateStore;
  /** The runner's environment, without Blaise credentials. */
  readonly env: Readonly<Record<string, string | undefined>>;
  readonly logger: (scope: string) => PlugLogger;
  /** The ACP session of an earlier run: set for `resume` runs. */
  readonly acpSessionId?: string;
  /** Why a dedicated `teardown` run happens. Default: `expired`. */
  readonly teardownReason?: Exclude<EndReason, "idle">;
  /** The runner's machine. Default: the real one. */
  readonly local?: Host;
  readonly signal?: AbortSignal;
  /**
   * Persisted records no plug owns any more, reported before the plugs load their state (an empty
   * list when there are none, so an earlier report can be replaced). They are logged unless this
   * is set.
   */
  readonly onOrphans?: (orphans: readonly OrphanedState[]) => void | Promise<void>;
  /**
   * Called once the plugs have paused, before a teardown that follows (a session that cannot
   * resume is torn down at once). Blaise learns here that the run ended and whether the session
   * may resume, while the process still has the teardown to do. If it throws, the run fails
   * with the error before the plugs are torn down.
   */
  readonly onPause?: (
    pause: PauseInfo,
    result: {
      readonly acpSessionId?: string;
      readonly pendingPrompts: ContentBlock[][];
    }
  ) => Promise<void>;
  readonly clientInfo?: {
    readonly name: string;
    readonly version: string;
  };
  readonly killTimeoutMs?: number;
  /** The agent's preset: its model and effort are set on the ACP session. */
  readonly preset?: SessionPreset;
}

ServerConfig

export type ServerConfig = z.infer<typeof serverSchema>;
/**
 * The Blaise side of a session. The runner depends on this interface only, so it does not
 * depend on the SDK or on how the CLI reaches Blaise (an oRPC WebSocket today).
 */
export interface SessionLink {
  /** Records a message in the transcript. Failures stop the run so the transcript is never silently lost. */
  report(message: ReportedMessage): Promise<void>;
  /** Asks Blaise for a human's decision on a permission request that no plug answered. */
  requestPermission(
    request: RequestPermissionRequest,
    protocolVersion: string
  ): Promise<RequestPermissionResponse>;
  /** The directives of this run, until the signal aborts or Blaise closes the stream. */
  directives(signal: AbortSignal): AsyncIterable<Directive>;
  /** Remembers the ACP session id so that a later `resume` run can `session/load` it. */
  saveAcpSessionId(id: string): Promise<void>;
}

SessionPreset

/** The preset a run is configured with, as Blaise sends it. */
export interface SessionPreset {
  /** Version of the preset block. Newer than {@link SUPPORTED_PRESET_VERSION} adds fields unknown here. */
  readonly version?: number;
  readonly model?: string;
  readonly effort?: string;
  /** Settings the agent asks to restore to its driver default. */
  readonly resetToDefault?: readonly ("model" | "effort")[];
}

SetupEntry

export interface SetupEntry {
  /** 0-based position in `setup:` after includes are spliced. */
  readonly index: number;
  readonly id: string | undefined;
  readonly use: string;
  readonly with: Readonly<Record<string, unknown>> | undefined;
}

splitTemplate

/**
 * Splits a string into text and expression parts. A `}}` inside a single-quoted string literal
 * (with `''` as the escape) does not close the expression.
 */
export function splitTemplate(input: string): TemplatePart[];

StoredPlugState

/** A record as Blaise stores it. */
export interface StoredPlugState {
  readonly id: string;
  /** Name of the plug that wrote it (`definePlug({ name })`). */
  readonly plug: string;
  readonly value: unknown;
}

SUPPORTED_PRESET_VERSION

/** The newest preset version this runner understands. */
export const SUPPORTED_PRESET_VERSION = 2;

On this page