# Agent harness attempt runtime

> Runtime helpers a selected harness calls during and after an attempt: injection, middleware, outcome classification, usage, and agent-end side effects

- 网址：https://funcoding.ai/agents/openclaw/plugins/sdk-agent-harness/attempt-runtime/
- 来源：OpenClaw 官方文档原文（英文），MIT 许可，同步于 2026-10-11
- 官方原文：https://docs.openclaw.ai/zh-CN/plugins/sdk-agent-harness/attempt-runtime

---
The helpers a selected harness calls while an attempt is running and as it finalizes: guarded input injection, tool-result middleware, terminal outcome classification, live token usage, and agent-end side effects. Part of the [Agent harness plugins](https://funcoding.ai/agents/openclaw/plugins/sdk-agent-harness/) reference.

## Guarded active-run injection

Backends that accept source-bound controls advertise `messageInjectionV2` on
their active-run handle. The capability is contextually typed by
`setActiveEmbeddedRun` from `openclaw/plugin-sdk/agent-harness-runtime`; its type
can also be derived from that function's handle parameter. It requires
`version: 2`, `isAvailable()`, and
`queueMessage(text, options, assertCurrent, authorityKind)`.
The required third argument is a host-owned assertion for that individual
injection, not a run ID, fingerprint, or diagnostic identity. The required
`authorityKind` is `"run"` for ordinary input or `"source-bound"` for input
whose source lifetime also constrains dispatch. Both retain the backing-run
check; a source-bound input must never be relabeled as ordinary input.

Invoke `assertCurrent()` alongside the backend's own live-run check after
awaited preparation and immediately before queue mutation or provider dispatch.
The host normalizes false or throwing source authority into rejection and keeps
that injection revoked even if the source later appears current again. Plugins
invoke the supplied assertion; they do not reconstruct its authority. Batched
backends retain and revalidate each item's assertion, including before retries;
omit revoked items without cancelling independently accepted work or poisoning
later authorized controls.

When supplied, call `options.onQueueSettled()` once after that input commits,
is canceled, or is terminally rejected. `onQueueAccepted(true)` only reports
admission. A backend that returns early for `waitForTranscriptCommit: false`
retains the settlement callback until its exact input finishes; core uses it
to release the selected sender's retained source authority.

Optional V2 `claimPendingUserInputAnswer(text, options, assertCurrent, authorityKind)`
and `cancelPendingUserInput(resolvedBy, assertCurrent, authorityKind)` methods
require the same assertion and authority kind. Carry it through question registration and persistence to the final
claim or cancellation boundary. Do not implement V2 by checking only before
calling an SDK method that itself awaits before dispatch. If the sink cannot
enforce the assertion, leave V2 unsupported.

The V1 `messageInjection`, queue options, `queueAgentHarnessMessage`, and
`setActiveEmbeddedRun` signatures shipped in v2026.8.1 remain source-compatible.
Pass the resolved agent ID as the fifth `setActiveEmbeddedRun` argument so raw
`global` and `unknown` keys retain their owner. Legacy calls inside a matching
live host binding inherit its validated agent; an ambient caller alone does not
supply ownership. Outside that binding, omitted ownership uses the qualified
session key or the configured default agent for session activity.
Unscoped V1 injection retains its existing behavior. Source-bound controls
require V2 and reject visibly before queue or I/O when only V1 is available;
they never fall back to an unchecked V1 callback. Existing deprecation windows
are unchanged.

Copilot remains V1-only: `@github/copilot-sdk` 1.0.11 awaits trace-context and
JSON-RPC writer preparation after `send` entry without a final-dispatch guard.
Scoped steering therefore fails before its queue, question claim, or provider
I/O; ordinary unscoped injection is unchanged. Check status, cancel the run, or
start a new explicit request instead. Update the runtime when guarded injection
is supported. Once upstream supplies a final-dispatch assertion, migrate
Copilot to V2 and remove this internal V1 reliance; do not add an unchecked
fallback or shorten the shipped API's deprecation window.

## Tool-result middleware

Bundled plugins and explicitly enabled installed plugins with matching
manifest contracts can attach runtime-neutral tool-result middleware through
`api.registerAgentToolResultMiddleware(...)` when their manifest declares the
targeted runtime ids in `contracts.agentToolResultMiddleware`. This trusted
seam is for async tool-result transforms that must run before the selected
harness feeds tool output back into the model. Supported runtime ids are
`agentsapi`, `codex`, and `openclaw`.

Middleware options may combine `runtimes` with a `matcher` tool-name list.
Each registration keeps that pair intact, so registering the same handler for
different runtimes does not broaden either matcher. Matchers use non-empty
canonical OpenClaw tool ids; omit `matcher` to match all tools.

Omitting `runtimes` uses every supported runtime declared in the plugin's
`contracts.agentToolResultMiddleware`, including `agentsapi` when declared.
Supply `runtimes` only to select a subset of that declaration. Registration
rejects an empty runtime list or any targeted runtime missing from the manifest.

Legacy bundled plugins can still use
`api.registerCodexAppServerExtensionFactory(...)` for Codex app-server-only
middleware, but new result transforms should use the runtime-neutral API. The
embedded-runner-only `api.registerEmbeddedExtensionFactory(...)` hook has been
removed; embedded tool-result transforms must use runtime-neutral middleware.

Retain `details.messageDelivery.sourceReplyDelivered` from the host message tool
before middleware transforms its result, and carry it into the attempt result.
This confirms a final external source reply and does not depend on destination
arguments or transcript mirrors.

Use `extractMessagingToolSourceReplyPayload(result)` from the same runtime
subpath to retain internal source-reply payloads through presentation changes.
For a confirmed messaging delivery, `collectMessagingMediaUrlsFromRecord(args)`
collects its attachment references for delivery deduplication. Neither helper
establishes delivery or grants local-file trust.

## Reply attachments from a remote workspace

A harness whose files are remote can call the optional
`params.hostCapabilities.prepareReplyMedia` before closing its file transport.
The host applies the existing sender read policy and channel/account byte limit,
then saves authorized attachment bytes for delivery.

| Request                      | Result             | Harness action                                                                                            |
| ---------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------- |
| `kind: "attempt"`, `attempt` | `preparedMedia`    | Set `attempt.preparedReplyMedia` on the same result object. Core applies it after final answer selection. |
| `kind: "payload"`, `payload` | Prepared `payload` | Deliver this copy through `onBlockReply`; keep the persisted message unchanged.                           |

Both requests supply `readWorkspaceFile(relativePath, { maxBytes, signal })`.
The reader must enforce the byte limit and workspace boundary and honor
cancellation. It receives only paths authorized under the host's captured
policy. Supply `workspaceRoot` when the remote workspace has a different
absolute path; the host maps that alias to the logical workspace before checking
policy. Supply `assertCurrent` when native session or transport ownership can be
revoked independently of the host attempt. The host retains this additional check
through the final media write and publication. Keep the reader alive until preparation finishes.

Raw file paths in these replies belong to the remote workspace. Paths outside
that workspace fail with a labeled remote-file notice, even if a Gateway-local
file exists at the same path. HTTP references and securely validated Gateway
managed media, including `media://` attachments, remain available.

For artifacts whose bytes the provider has already admitted, use `kind: "artifact"`
with `buffer`, `fileName`, `assertCurrent`, and an optional `signal`. The host
stages those exact bytes under the captured channel/account byte limit and returns
a prepared `payload`. This request grants no filesystem reads and applies no
image transformation or host-file MIME allowlist. The harness owns validation of
the provider artifact's session, turn, environment, and path before downloading it;
the host owns the outbound destination and retains live authority through publication.

Missing, denied, and oversized attachments produce the usual delivery failure
notice; preparation does not fall back to a stale Gateway workspace file.
Prepared facts contain file locations and failures, never a live reader. Do not
rewrite assistant text or transcript messages to insert Gateway file paths.
When the capability is absent, this remote attachment preparation is unavailable.

## Shared attempt mechanics

Official native harnesses use `buildCurrentInboundPrompt` from the private
`openclaw/plugin-sdk/agent-harness-attempt-runtime` to combine the prepared
`currentInboundContext` with the current prompt using the channel's joiner.
Submit this context with each message, including resumed sessions. Steering
receives its own `options.currentInboundContext`; do not reuse the initial
turn's context. Keep context out of the original user transcript and pending
question answer text. Conversation fields are model context, not tool authority.

`resolveAgentHarnessBeforePromptBuildResult` from
`openclaw/plugin-sdk/agent-harness-runtime` runs prompt hooks with prepared history
and tool authority. Pass the admitted message as `currentUserMessage`; the helper
extracts its text parts and `idempotencyKey` for ordinary and authorized hooks.
String input and a separate `currentUserMessageId` remain supported. The harness
owns the fallback when no admitted message exists.

Supply `messages` as an array or an async loader, which runs only when a `before_prompt_build`
hook needs history. Heartbeat-only contributions do not read conversation history.
The production-private `resolveAgentHarnessHistoryLimits` helper applies the shared
Codex and Agents API transcript read budget.

Agents API retains the first successfully prepared, bounded hook history for
retries of the same logical run and native session. Prompt hooks still run on
each attempt with the current input and live host authority; their history stays
at the original before-turn snapshot. Accepted steering therefore does not force
a new transcript read through the original message's now-stale admission.
This snapshot is data, not renewed transcript-read permission: later transcript
changes are observed by the next logical run, and a changed recorder, session,
run identity, or history budget requires a fresh read. Session reset and run
cancellation retain their existing authority checks.

The `developerInstructions.build` callback receives `toolsAllow` and
`hasToolRestrictions`. Omitted policy or a trimmed `*` entry is unrestricted;
an empty list or a list without `*` is restrictive. Backends enforcing per-turn
restrictions apply or reject them inside that callback, before authorized recall
runs. Agents API continues with hook context but does not enforce hook tool lists.

Official harnesses use the JavaScript-only private
`openclaw/plugin-sdk/agent-harness-attempt-runtime` for deadlines, cancellation,
and lifecycle/event publication; it is not a third-party Plugin SDK contract.
Codex and AgentsAPI also use `shouldIncludeAgentHarnessRuntimeContext` to exclude
runtime prompt additions from lightweight cron inputs, and
`resolveAgentWorkspaceMemoryRouting` to select admitted memory tools and check
that they reach the prompt workspace. Backends retain workspace selection, tool
name normalization, and native prompt rendering.

`createAgentHarnessAttemptDeadlineController` takes the original `startedAtMs`,
execution `timeoutMs`, backend `settlementTimeoutMs`, abort `signal`, and timeout
callback. The first `beginSettlement(receivedAtMs)` starts an absolute settlement
deadline; repeated calls do not extend it. Abort or `dispose()` closes it.
`createAgentHarnessAttemptCancellation` retains explicit cancellation reasons
and freezes admission at the terminal boundary. `emitAgentHarnessAttemptEvent`
isolates observer failures, and `createAgentHarnessAttemptLifecycle` gates
lifecycle events and deduplicates execution phases. Native interruption,
completion decisions, output flushing, and cleanup remain backend-owned.

Assistant event `data.itemId` identifies the native text item for cumulative
snapshots and delta merging. A harness that persists part of an item while it
continues streaming also supplies `data.occurrenceId`, an opaque string identifying
the exact live interval. Freeze that occurrence when capturing the transcript
candidate, before awaiting persistence, and allocate a new occurrence for later
bytes without changing the native item ID or delta text. Successful transcript
publication supplies the captured IDs in `assistantItemIds`; failed writes do not
retire them. Retries retain every occurrence actually included in the candidate.
Without `occurrenceId`, retirement uses `itemId` or the host's source receipt.
Text without either identity stays live until terminal settlement; matching
durable text alone never proves ownership.

The private `openclaw/plugin-sdk/agent-harness-tool-runtime` provides correlated
execution promises and argument/start snapshots through
`createAgentHarnessToolExecutionRegistry` and
`createAgentHarnessToolExecutionBoundaryRegistry`. Consumed snapshots cannot be
republished by late completion. Core tool guards and `observeToolTerminal`
remain authoritative; native decoding and result encoding stay with the harness.

## Shared host-tool result facts

Official harnesses use the private JavaScript-only
`openclaw/plugin-sdk/agent-harness-tool-runtime` to execute host tools and record portable tool facts.
`runAgentHarnessToolInvocation` owns argument preparation, validation at the
existing execution boundary, monotonic execution snapshots, middleware, and
cleanup. Its result and failure callbacks carry those facts to native adapters
without taking over their receipt or timeout owner.
`recordAgentHarnessToolResultTelemetry` collects host-tool delivery, media, TTS,
cron, and heartbeat facts using the caller's prepared source-reply projection.
The invocation preserves execution failures when presentation middleware
rewrites a result. `recordAgentHarnessMessagingDelivery`
records an already-confirmed messaging delivery, and
`recordAgentHarnessToolResultMedia` collects and trust-filters presented media.
Callers retain their native receipt, routing, cancellation, and result-encoding
contracts; these helpers do not establish delivery or grant execution authority.

## Workspace-staged attachments

Admitted attachment facts can refer to files staged under the prepared workspace
instead of the managed media store. Use `root(workspaceDir)` and
`createStagedInputPathMatcher(root)` from `openclaw/plugin-sdk/file-access-runtime`
to verify staging ownership before a bounded `root.read(relativePath, { maxBytes })`.
Match the fact's workspace to the attempt's prepared workspace, retain the host's
current-run assertion through awaited reads, and use admitted media facts rather
than paths extracted from user or model text. The matcher shares the staging
owner's exact marker contract and caches results only for that capture.

## Final tool-argument validation

Official native harness adapters can call
`runWithToolExecutionValidation(callId, validate, execute)` from
the private `openclaw/plugin-sdk/agent-harness-tool-runtime` around the host-bound tool's
`execute` call. The validator receives the final arguments after policy and
before-call hooks have adjusted them, at the existing tool execution boundary.
Use the shared schema validation helpers for the declared tool schema. Do not
copy private validation markers or run a second before-call hook. Validation is
scoped to the tool call and remains isolated from concurrent calls.

Retain accepted background work before middleware: `isAsyncStartedToolResult`
and `readAsyncStartedTaskIds` from `openclaw/plugin-sdk/agent-harness-tool-runtime`
expose its task metadata. `normalizeAcceptedSessionSpawnResult` from
`openclaw/plugin-sdk/agent-harness-tool-runtime` preserves a child session's
completion ownership. Carry their recorded facts into the attempt result so
recovery cannot replay accepted work.

## Terminal outcome classification

Native harnesses that own their own protocol projection can use
`classifyAgentHarnessTerminalOutcome(...)` from
`openclaw/plugin-sdk/agent-harness-runtime` when a completed turn produced no
visible assistant text. The helper returns `empty`, `reasoning-only`, or
`planning-only` so OpenClaw's fallback policy can decide whether to retry on a
different model. `planning-only` requires the harness's explicit `planText`
field; OpenClaw does not infer it from assistant prose. The helper
intentionally leaves prompt errors, in-flight turns, and intentional silent
replies such as `NO_REPLY` unclassified.

## Live output-token usage

Call `params.hostCapabilities.reportOutputTokens?.(outputTokens)` once per
completed model response. Pass that response's output tokens, not a
thread-lifetime or cumulative attempt total. Deduplicate native response
notifications before calling it.

The host binds this callback to the admitted run, adds the response to its
lifecycle-scoped total, and publishes the cumulative `usage` event globally and
through `params.onAgentEvent`. Do not emit a second usage event. Retries share
the same run total; run cleanup releases it. A closed or superseded capability
rejects reporting. Invalid or nonpositive counts do not emit an event.

The capability is optional for compatibility with older hosts; when absent,
live output-token reporting is unavailable. Keep last-response context
snapshots and persisted billing usage separate from this live counter.

## Agent-end side effects

Native harnesses must await `runAgentEndSideEffectsAsync(...)` from
`openclaw/plugin-sdk/agent-harness-runtime` after they finalize an attempt. It
prepares transcript anchors through the history reader before releasing the
turn lease, then starts the portable `agent_end` hook without waiting for it.
Use `awaitAgentEndSideEffects(...)` for local, non-interactive runs that must
also wait for plugin hooks. The synchronous `runAgentEndSideEffects(...)`
remains deprecated until the next Plugin SDK major. These helpers accept the
same `{ event, ctx }` payload as
`runAgentHarnessAgentEndHook(...)`; their failures do not alter the completed
attempt result.

Pass `ctx.foregroundPromptContext` built with
`buildEmbeddedForegroundPromptContext(params, agentDir)` from the same
`EmbeddedRunAttemptParams` the attempt ran with. The detached Skill Workshop
experience review rebuilds its system prompt and tool catalog from that
context, so the review shares the foreground turn's prompt-cache prefix.
Omit it only for runs that have no foreground prompt, such as CLI hook
contexts; the review is skipped for those.
