Little documentation
Guides and APIReference

generateHarness()

Run a Little Harness agent turn and return a completed result.

Little Harness: published on npm as little-harness@0.2.0-alpha.0 (2026-10-05). Sections labeled UNRELEASED describe source-only changes. Release status

generateHarness() runs the same harness as streamHarness(), but waits for the turn to complete and returns the final result directly.

Use it for workers, cron jobs, tests, server actions, queue consumers, and Little Workflow nodes.

Import

import { generateHarness } from "@little-stack/harness";

Basic Usage

import { Output } from "ai";
import { generateHarness } from "@little-stack/harness";

const result = await generateHarness({
  harness,
  type: "support.ticket_triage",
  input: ticket,
  output: Output.object({ schema: triageSchema }),
  session: ticket.id,
});

// Compiles against published 0.2.0-alpha.0 and against the unreleased source.
if (result.status === "completed") {
  console.log(result.output.priority);
}

A turn can park when a tool returns a parked result. The published 0.2.0-alpha.0 types do not say so: there, generateHarness() returns GenerateHarnessResult, which names the completed result only, even though the runtime can still return a parked one. Guard on status === "completed" before reading completed-only fields.

UNRELEASED: in the current source, generateHarness() returns HarnessTurnResult, the status-discriminated union, so the parked branch type-checks. The GenerateHarnessResult alias is removed. No npm version includes this yet. See Typed parked results.

// UNRELEASED: needs the unreleased source SDK.
if (result.status === "parked") {
  // The agent is waiting in await_tasks, for example for a person's answer.
  // resumeParked() continues the turn once the wait is over.
  return;
}
console.log(result.output.priority);

API Signature

async function generateHarness<
  TOutput = string,
  TInput = unknown,
  TExtraBody = unknown,
>(
  options: GenerateHarnessOptions<TInput, TExtraBody, TOutput>,
): Promise<HarnessTurnResult<TOutput>>;

Parameters

generateHarness() accepts the same core input options as streamHarness().

harness

Harness

Reusable harness returned by createHarness().

messages

UIMessage[]

Raw chat messages. Use this when a non-streaming caller still wants to run a chat transcript to completion.

const result = await generateHarness({
  harness,
  messages,
  session: chatId,
});

prompt

string

One user message, as plain text. The quickest way to run a turn from a script or a worker.

const result = await generateHarness({
  harness,
  session: "loan_APP-1001",
  prompt: "Pre-approve APP-1001.",
});

Pass prompt or messages, not both, and not with type or input.

type

string

Open string that names a custom Input Type.

type: "account.renewal_brief"

input

TInput

App-defined payload for the custom Input Type.

input: {
  accountId: "acct_123",
  includeOpenTickets: true,
}

session

string | HarnessSession

Stable continuation key or explicit session handle.

session: `account:${accountId}:renewal`

extraBody

TExtraBody

App-defined request context passed to tools, file hooks, Persistent Dirs, and events.

model

LanguageModel

Per-call model override. createHarness() still requires a default model in the TypeScript API; use this when a worker, route, or remote configuration temporarily overrides that default.

await generateHarness({
  harness,
  type: "support.ticket_triage",
  input: ticket,
  model: requestModel,
});

system

string | SystemModelMessage | SystemModelMessage[]

Per-call system override. When provided, it replaces the harness-level system prompt for this turn. Input Type instructions, skills, memory, and runtime hints are still added around the selected system prompt.

runtime

HarnessRuntimeOptions

Per-call runtime policy override. It merges with the harness-level runtime policy.

await generateHarness({
  harness,
  type: "support.ticket_triage",
  input: ticket,
  runtime: { bash: false },
});

temperature

number

Per-call AI SDK temperature setting. Little Harness includes it in durable model request identity, so replay does not reuse a response generated with a different temperature.

output

Output

AI SDK output specification for parsing the completed result.

output: Output.object({ schema: renewalBriefSchema })

abortSignal

AbortSignal

Cancels the run when possible.

runId

string

Durable identity of this run for kill-and-resume. Re-invoking with the same runId (and the same inputs) against the same session log replays the recorded run instead of re-calling models and re-executing tool side effects. Defaults to a fresh id per call.

const result = await generateHarness({
  harness,
  messages,
  session: chatId,
  runId: "run_123",
  sessionLog: remoteSessionLog({ baseUrl, authToken }),
});

sessionLog

HarnessSessionLog (type from @little-stack/harness/events)

Per-call session-log override (the durable event log this run appends to and replays from). When omitted, the log configured on createHarness() is used.

UNRELEASED: the current source removes the durability alias. Published 0.2.0-alpha.0 accepts durability as an alias of sessionLog (sessionLog wins when both are set). See Alpha Migrations.

See Execution Environments for the remote session-log adapter.

onEvent

Function that receives a HarnessEvent.

Per-call trace callback.

onPersistenceError

Function that receives a PersistenceError.

Per-call Persistent Dir failure callback.

toolResultSpooling

false | { maxInlineBytes?: number; previewBytes?: number; outputDir?: string }

Per-call override for automatic oversized tool-result spooling. When omitted, the setting from createHarness() is used. If neither is configured, Little Harness spools tool results larger than 16 KB into /artifacts/tool-results. The compact result includes summary metadata with text counts and, for JSON content, a bounded structure sketch.

trace

false | HarnessTraceOptions

Per-call override for trace capture. When omitted, the setting from createHarness() is used.

await generateHarness({
  harness,
  type: "support.ticket_triage",
  input: ticket,
  trace: {
    content: {
      maxInlineBytes: 4096,
    },
    fileDiffs: {
      enabled: true,
    },
  },
});

Set trace: false on a single call for requests that should not persist a Local Host trace.

Agent Loop Options

generateHarness() forwards the common AI SDK loop controls:

await generateHarness({
  harness,
  messages,
  stopWhen,
  prepareStep,
  toolChoice,
  activeTools,
  temperature,
  providerOptions,
  headers,
  maxRetries,
});

stopWhen takes AI SDK 7 stop conditions such as isStepCount(8) (imported from ai). When it is omitted, Little Harness uses isStepCount(workflowBudgets.maxModelSteps) โ€” 20 by default โ€” so a model can continue after executed tool calls instead of stopping after the first step.

Returns

A turn ends in one of two states:

  • status: "completed": the turn finished. Every field below is present.
  • status: "parked": a tool parked the turn until pending work settles. The result carries continuationId and pending, plus artifacts, trace and warnings. It has no output, persistence or commitManual, so check status before reading them. When the turn parked in await_tasks, resumeParked() continues it once the wait is over; see Asking A Person.

How the type says so depends on the version:

VersionDeclared return type
Published 0.2.0-alpha.0GenerateHarnessResult<TOutput>, an alias of HarnessCompletedResult<TOutput> only. The runtime can still return a parked result; the type does not include it.
UNRELEASED source (no npm version yet)HarnessTurnResult<TOutput>, the union of the completed and parked results. GenerateHarnessResult no longer exists.

HarnessTurnResult already declares both states in 0.2.0-alpha.0; only the declared return type changes. A status === "completed" guard compiles with both. See Typed parked results.

The fields below describe a completed result.

text

string

The completed text response.

output

TOutput

The parsed structured output when output is provided.

session

HarnessSession

The session that ran the turn.

artifacts

ArtifactRef[]

Artifacts created during the run.

trace

TraceRef

Host-facing trace reference.

persistence

PersistenceStatus

Persistent Dir commit status.

commitManual

() => Promise<PersistenceStatus>

Commits Persistent Dirs configured with commit: "manual" for the completed turn. The method is idempotent for a result object; repeated calls return the same commit promise.

warnings

HarnessWarning[]

Warnings that should be visible to the host app.

if (result.warnings.some((warning) => warning.code === "undefined_input_type")) {
  logger.warn("Missing Input Type definition", result.warnings);
}

Examples

From A Script (No HTTP Request)

In a plain script or worker there is no req.json() to read messages from. Pass the request as prompt:

import { generateHarness } from "@little-stack/harness";

const result = await generateHarness({ harness, prompt: "Summarize the latest incident." });
if (result.status === "completed") console.log(result.text);

For more than one message, build the UIMessage[] array yourself. Each UIMessage needs an id and parts; the AI SDK's { role, content } model messages are refused with an error that says so.

import type { UIMessage } from "ai";

const messages: UIMessage[] = [
  { id: "u1", role: "user", parts: [{ type: "text", text: "Summarize the latest incident." }] },
];
const result = await generateHarness({ harness, messages });

Generate A Structured Brief

const result = await generateHarness({
  harness,
  type: "account.renewal_brief",
  input: {
    accountId: "acct_123",
    quarter: "2026-Q3",
  },
  output: Output.object({ schema: renewalBriefSchema }),
  session: "acct_123-renewal",
});

if (result.status === "completed") {
  await reports.create({
    accountId: "acct_123",
    body: result.output,
    traceId: result.trace.id,
  });
}

Run A Chat Transcript To Completion

const result = await generateHarness({
  harness,
  messages,
  session: chatId,
  output: Output.text(),
});

if (result.status !== "completed") {
  return Response.json({ status: result.status }, { status: 202 });
}
return Response.json({
  text: result.text,
  artifacts: result.artifacts,
});

Handle Persistence Failure

const result = await generateHarness({
  harness,
  type: "project.update_notes",
  input,
  session: projectId,
});

if (result.status === "completed" && result.persistence.status === "failed") {
  await retryQueue.enqueue(result.persistence.failedCommits);
}

The model should not automatically receive persistence failures in its normal context. The host app can retry, notify the user, or inspect the trace.

On this page