Events
HarnessEvent envelope and trace event vocabulary for sessions, models, tools, artifacts, and commits.
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
The event names below are the alpha trace vocabulary. The names are observable
from local traces and onEvent callbacks, while metadata details may continue
to evolve during alpha.
The trace vocabulary on this page is separate from the durable session-log contract described under HarnessEvent Envelope.
UNRELEASED: the durable validators
normalizePersistedDurableHarnessEventandcanonicalHarnessEventRequestDigest, and the dedicated@little-stack/harness/eventsand@little-stack/harness/remote-sessionentry points, exist only in the current source. No npm version includes them yet. In the current source these two entries are the only home of the durable event contract (HarnessSessionLog,DurableHarnessEventInput,createHarnessAppendIdempotencyKey,emitHarnessOccurrence, …) and of the session-log helpers; the root entry no longer re-exports them.Published
0.2.0-alpha.0has neither the validators nor an./eventsor./remote-sessionpackage export. It exports the older durable and remote helpers from the root@little-stack/harnessentry:emitHarnessOccurrence,remoteSessionLog,startSessionLogServer,createFileSessionLogandcreateInMemorySessionLog(renamedfileSessionLogandinMemorySessionLogin the current source). See Small subpaths.
// Published 0.2.0-alpha.0 (the `little-harness` package): older helpers from the root entry.
import { createFileSessionLog, remoteSessionLog } from "little-harness";
// UNRELEASED: the session logs and the durable event contract have their own entries.
import { fileSessionLog, remoteSessionLog } from "@little-stack/harness/remote-session";
import {
canonicalHarnessEventRequestDigest,
normalizePersistedDurableHarnessEvent,
type HarnessSessionLog,
} from "@little-stack/harness/events";HarnessEvent Envelope
HarnessEvent is the versioned trace/callback event object emitted by
onEvent and written to Local Host trace.ndjson files. Every persisted Local
Host trace event uses the same envelope:
type HarnessEvent<TEventType extends string = HarnessEventType> = {
schemaVersion?: "lh.trace.v2";
eventId?: string;
sequence?: number;
occurrenceId?: string;
type: TEventType;
sessionId: string;
turnId?: string;
stepId?: string;
parentEventId?: string;
timestamp: string;
metadata?: Record<string, unknown>;
};Most consumers should key off type, sessionId, turnId, and
occurrenceId, then read type-specific details from metadata.
| Field | Meaning |
|---|---|
schemaVersion | Trace schema version. Local traces currently use lh.trace.v2. |
eventId | Unique id for this emitted trace/callback event. |
sequence | Monotonic sequence number within one trace.ndjson file. |
occurrenceId | Correlation id for one model call, tool call, runtime command, or other logical occurrence. |
type | Dotted event name such as harness.session.started, harness.model.called, or harness.tool_call.failed. |
sessionId | Harness session id. |
turnId | Turn id for the current generateHarness() or streamHarness() call. |
stepId | Optional model step id for multi-step model/tool loops. |
parentEventId | Optional parent event id for nested event relationships. |
timestamp | ISO timestamp for when the event was emitted. |
metadata | Event-specific details after trace redaction. Defaults to {} in validated local traces. |
Example trace event:
{
"schemaVersion": "lh.trace.v2",
"eventId": "evt_abc123",
"sequence": 3,
"type": "harness.model.called",
"sessionId": "chat_123",
"turnId": "turn_abc123",
"occurrenceId": "evt_model_call",
"stepId": "step_1",
"timestamp": "2026-06-10T12:00:00.000Z",
"metadata": {
"model": { "provider": "openai", "modelId": "gpt-5" }
}
}HarnessEvent is not the same object as a durable replay event. Durable replay
stores payloads. The shape differs by version:
| Version | DurableHarnessEventInput | Persisted event adds |
|---|---|---|
Published 0.2.0-alpha.0 | type, runId, optional occurrenceId, payload (a JSON object) | eventId, sequence, recordedAt |
| UNRELEASED source (no npm version yet) | The type below: a required appendIdempotencyKey, optional correlation ids, and either payload or payloadArtifactId | eventId, sequence, recordedAt, canonicalRequestDigest |
The type below and the three paragraphs after it describe the UNRELEASED
durable contract, including the Little Compute adapter, which is also
unpublished. None of it is in 0.2.0-alpha.0; see
Alpha Migrations.
// UNRELEASED durable event input.
type DurableHarnessEventInput<TEventType extends string = HarnessEventType> = {
type: TEventType;
runId: string;
appendIdempotencyKey: string;
occurrenceId?: string;
operationId?: string;
taskId?: string;
attemptId?: string;
correlationId?: string;
causationId?: string;
} & (
| { payload: Record<string, unknown>; payloadArtifactId?: never }
| { payload?: never; payloadArtifactId: string }
);appendIdempotencyKey is mandatory and is chosen by the logical emitter before
transport. Retrying the same (runId, appendIdempotencyKey) and canonical
request returns the original eventId and sequence; reusing the key with
different content fails. occurrenceId is a separate, optional causal group:
called/responded or started/succeeded lifecycle events normally share it while
using different append keys. Use createHarnessAppendIdempotencyKey() to derive
an opaque key from a stable session, turn, call, or step identity.
Persisted events add eventId, a per-run sequence, canonical UTC
recordedAt, and canonicalRequestDigest. Inline payloads are normalized to
wire JSON before that digest is computed. Large payloads should be stored as a
content-addressed Artifact and referenced with payloadArtifactId. When Little
Harness runs under Little Compute, its Artifact-spooling Session-log adapter
does this automatically before the event append and can verify/reconstruct the
payload after the trusted runner moves.
In a self-hosted Little Compute installation, that adapter uses an exact
little_compute_runner database credential. It can select and append Session
events, advance the per-run sequence head, and insert an Artifact idempotency
record, but it cannot update/delete event history or delete a run head. API,
scheduler, reconciler, GC, provider-gateway, and time-sampler credentials have
no Session-log access.
The rest of this page returns to the trace vocabulary. Large strings and JSON values use a bounded content record:
type TraceContentRef = {
captured: boolean;
preview?: string;
truncated?: boolean;
contentRef?: string;
bytes?: number;
sha256?: string;
mediaType?: string;
redacted?: boolean;
redactionReason?: string;
};When content is too large to inline, contentRef points to
/artifacts/trace/.... The trace still records byte counts and hashes so a
developer can verify what changed.
Session Events
harness.session.started
harness.session.completed
harness.session.failedSession events identify the runtime profile, root trace id, session status, and
any app-defined extraBody fields allowed by the trace policy.
Model Events
harness.model.called
harness.model.responded
harness.model.failedModel events track each AI SDK model-loop step.
harness.model.called is emitted before a provider call. Metadata should include the
step number, provider and model id when available, prompt hash, bounded message
previews, tool names, tool schema hashes, and output mode.
harness.model.responded is emitted after the step completes. Metadata should include
finish reason, usage, warnings, provider metadata, bounded text output,
provider-exposed reasoning when available, tool calls, sources, and generated
file metadata.
harness.model.failed includes the model, step number, duration, retry metadata when
available, and a structured error envelope. Provider-hidden reasoning is not
traced.
Tool Events
harness.tool_call.started
harness.tool_call.succeeded
harness.tool_call.failedLarge tool outputs should point to artifacts. Automatically spooled tool results
also emit normal harness.file.created, harness.file.written_by_tool, and
harness.artifact.created events for the /artifacts/tool-results/... file.
Tool events include a caller value:
| Caller | Meaning |
|---|---|
model | The provider returned a tool call in an agent-loop step. |
runtime | just-bash JavaScript called a configured tool through tools.<name>(args). |
code | Workflow code invoked a configured tool through a code-run proxy. |
harness.tool_call.started records the tool name, tool call id, step id, and bounded input
metadata when the trace policy allows tool input capture.
harness.tool_call.succeeded records duration, bounded output metadata, artifact refs, and
spooled output refs. If the model receives a compact file-reference response,
the trace should include the full artifact path and hash.
harness.tool_call.failed records duration and an error envelope. Tool implementation source
code and closure internals are never traced.
Runtime Events
harness.runtime.command.started
harness.runtime.command.succeeded
harness.runtime.command.failed
harness.runtime.command.denied
harness.runtime.tier.escalated
harness.runtime.tier.unavailable
harness.runtime.usage
harness.runtime.error
harness.runtime.dispose.failedRuntime events are for the execution substrate, such as the built-in bash tool.
harness.runtime.command.* events record commands, exit codes, duration, bounded
stdout and stderr previews, and file changes discovered by the runtime. Every
sandbox emits them identically.
harness.runtime.tier.escalated, harness.runtime.tier.unavailable, and
harness.runtime.command.denied appear only under tieredSandbox.
They record why a turn switched execution tier, why a needed tier could not be
used, or why a policy refused a command — with the classification reasons — and
never reach the model's prompt. See
Execution Tiers.
harness.runtime.usage is emitted once per turn that had a shell sandbox, when
the sandbox is disposed and before the turn's terminal session event, so a
LittleDB trace of the turn includes it. It records which execution tier the turn
reached and what the sandbox used, for cost accounting; it never reaches the
model. Its metadata: tier ("tier-0" for the emulated shell over the session
folder, "next-tier" for a real execution environment), commands (tier0,
nextTier and denied counts), escalations, and sandbox, the
HarnessSandboxUsage the sandbox reported: provider, instanceType, vcpu,
memoryGiB, diskGB, activeMs (milliseconds commands were running),
awakeMs (what the vendor billed as provisioned, when known) and costUsd (the
provider's estimate at list price, when it knows the rates). The built-in
just-bash sandboxes report provider: "just-bash" and their active time; a
custom sandbox reports its own through HarnessSandbox.usage(), and one that
reports nothing emits no usage event. See
Execution Tiers.
harness.runtime.dispose.failed records a sandbox whose teardown
threw, with the error envelope under metadata.error. It never fails the turn,
but little-harness doctor counts it as a failure.
File Events
harness.file.created
harness.file.updated
harness.file.deleted
harness.file.staged_from_message
harness.file.staged_from_host
harness.file.written_by_tool
harness.artifact.created
harness.filesystem.mountedThe goal is not to log every byte of every file. The trace should explain what changed and where the data is stored.
harness.filesystem.mounted records roots that were made available to the turn:
{
"root": "skills",
"harnessDir": ".agents/skills",
"source": "skill",
"writable": false,
"fileCount": 3,
"files": [
{
"path": ".agents/skills/customer-research/SKILL.md",
"kind": "file",
"bytes": 1842
}
]
}File change events should include path, root, source, before/after byte
and hash metadata, and a diff record when the changed content is text:
type TraceFileDiff =
| {
available: true;
format: "unified";
preview?: string;
truncated: boolean;
contentRef?: string;
bytes?: number;
sha256?: string;
}
| {
available: false;
reason:
| "disabled"
| "non_text"
| "too_large"
| "redacted"
| "content_unavailable";
};Deleted file events should record metadata and path, not deleted file contents.
When a deleted FileRef includes byte/hash fields, they must match the before
version; created and updated references must match after.
UNRELEASED:
createHarnessFileMutationEvents()exists only in the current source. Published0.2.0-alpha.0has no such symbol, and no npm version includes it yet.
Trusted local and remote adapters share createHarnessFileMutationEvents().
Its caller must allocate one stable occurrenceId at the file-mutation
boundary, before transport; every file/tool/artifact projection of that one
mutation shares the ID. Repeated byte-identical writes are separate mutations
and therefore receive different occurrence IDs instead of collapsing during
durable append replay.
The serializer rejects noncanonical paths and version contradictions, accessors,
symbols, non-JSON values, JSON deeper than 64 levels or larger than 10,000
nodes/256 KiB, strings larger than 64 KiB, keys larger than 1 KiB, paths larger
than 4 KiB, and source/media/artifact labels larger than 1 KiB. These are event
metadata limits, independent of Artifact and File Snapshot content limits.
Persistent Dir Events
harness.persistent_dir.loaded
harness.persistent_dir.commit.started
harness.persistent_dir.commit.succeeded
harness.persistent_dir.commit.failedCommit failures should be host-facing. They should not be automatically inserted into the agent's normal context.
Persistent Dir metadata should include harnessDir, commit policy, file counts,
change counts, storage adapter metadata allowed by policy, duration, and error
details for failed commits.
Outcome Events
outcome.reportedoutcome.reported is a side-channel event: a verdict appended to a session's
trace after a run by reportHarnessOutcome, createHarnessOutcomeReporter, or a
Chat SDK reaction. It is a valid trace event but deliberately not part of the
durable session log — an outcome is never an input to the run it describes.
Metadata carries status ("success" | "failure" | "partial") and source,
plus optional retracted, score, detail, reporter (always a pseudonym),
reportKey, stepPath, promptHash, and caller-supplied extras. See
Outcome Capture.
Subagent Events
Subagent trace events are deferred for the alpha release. If they are added in a later version, they should link back to the parent session and use the same content-free metadata rules as the rest of the trace vocabulary.
What Is Not Traced
Little Harness traces should not capture:
- API keys, auth headers, cookies, bearer tokens, or provider credentials
- raw mounted file contents
- raw binary payloads such as PDFs, images, archives, audio, or video
- hidden model reasoning not exposed by the provider
- deleted file contents after deletion
- Persistent Dir backing-store credentials
- tool implementation source code or closure internals
- anything matched by the configured redaction policy