On this page
On this page
Plugin SDK reference
Plugin runtime background work
Start agent work in the background: hook-dispatched turns for external content and subagent runs. Part of the Plugin runtime helpers reference.
Requester-bound native children
Use optional api.runtime.subagent.native inside an eligible reply_dispatch handler when a plugin controller needs the same native children as sessions_spawn, rather than a separate agent turn. The Gateway supplies the actual current request; plugins cannot choose a parent session, requester, credentials, or permission scope.
spawn returns native accepted IDs and always uses collector completion. Reuse a nonempty requestId within the same plugin and parent turn to replay the same request. Reusing it with different arguments fails instead of creating another child. Normal tool hooks still run; incompatible rewrites of the native runtime, run mode, collector completion, or replay identity are rejected before creating a child.
collect({ ids, timeoutSeconds: 0 }) reads current status. A bounded wait returns when a result is available or the timeout expires. collect({ ids, awaitResults: true }) joins all requested results without polling; do not combine it with timeoutSeconds. Pending IDs are not failures. Completion states are done, failed, killed, or timeout, with native result, structured output, and error fields when available.
model, thinking, and fastMode select native child overrides, subject to the existing model and requester policies. context selects fork or isolated; native defaults apply when omitted. No ACP runtime is exposed by this facade.
Supported ingress is the Gateway's normal reply-dispatch path with an existing admitted requester session, current operator authority, and the host's user-turn transcript recorder. Before spawning, the host adopts the turn when necessary and commits its actual input through that recorder. Existing restricted-ingress hook exclusions remain unchanged. Direct embedded/CLI before_agent_reply hooks, direct ACP dispatch, services, timers, and detached callbacks do not acquire this capability merely by holding its object. Custom runtimes may omit native; absence or an unsupported invocation fails explicitly.
Both calls recheck the invoking plugin's full-runtime lifetime and request authority after asynchronous preparation and at effect. The requester's effective native tool policy must allow both sessions_spawn and a callable native agents_wait for spawn, and agents_wait for collection; disabled Swarm/collector support, denied tools, revoked authority, or a retired hook cannot fall back to an administrator or system actor. Collection checks the exact parent session incarnation and native ownership; unrelated or reset parents cannot read a result.
A handler owns its calls only until it settles or times out. Accepted children transfer to native execution custody and do not automatically send a chat reply. A controller can await results while its handler remains active, or retain accepted IDs in its existing plugin store and collect during a later eligible invocation for the same parent incarnation. Persist controller decisions in the plugin's own store; this facade does not add a task engine, scheduler, or model-memory protocol. Existing subagent.run, complete, and waitForRun behavior is unchanged.
Recover plugin-owned native children
Hosts advertising the string capability native-subagent-list-v1 support optional
native.list({ status? }) on both eligible reply-dispatch and command runtimes.
Probe the optional method before calling it; older hosts and custom runtimes may
omit it. An older record without host-captured ownership is not inferred to belong
to a plugin, even if its label, request key, or parent looks familiar.
Listing recovers native acceptance when the plugin did not persist its receipt.
It returns at most 200 entries, newest first (the returned run ID, in code-unit order, breaks equal-time ties):
{ runId, childSessionKey, requestId, label?, status }. The status filter is
applied before the bound, and an unknown status value is rejected. There is no pagination or completeness claim beyond
that bounded window. A known collector completion has status done, failed,
killed, or timeout; otherwise the native execution state is queued,
running, interrupted, or terminal. Terminal execution alone is not a
completed result; use collect for result/error data.
Only children accepted through this facade by the exact current plugin,
requester’s profile, and parent session incarnation are returned. Plugins cannot
supply those identities. A reset parent, another requester, or another plugin
cannot enumerate them. Listing rechecks request/plugin lifetime, native currency,
and the current agents_wait tool policy; revocation never falls back to an
administrator. This uses existing native custody and retention, not a separate
plugin index. If the runtime config changes during preparation or reading, listing
rejects with a policy-changed error; retry from the still-active invocation. Spawn,
collection, and reply dispatch signatures remain unchanged.
Command-scoped native children
A registered slash command can request the same native spawn/collect runtime
without registering a reply_dispatch hook. Declare
contracts.commandRuntimeCapabilities: ["subagents"] in the manifest, then:
The host advertises command-native-subagents-v1 in the optional
api.runtime.capabilities array. Probe ctx.runtimeContext?.subagents at
invocation time: undeclared commands, unsupported hosts, denied native tool
surfaces, and non-requester command paths see undefined. Both
sessions_spawn and agents_wait must be allowed. Subsequent spawn denial
throws an explicit native error; it never launches an administrator fallback.
Only an authorized sender’s actual external command with current operator
authority is eligible. Quoted text, foreign commands, internal suggestions,
services, and timers cannot obtain the capability. Before exposing it, the host
adopts and commits the actual command text as a user turn and admits its current
requester under auto-reply.plugin-command. Children inherit that requester’s
lineage and effective tool ceiling. No new hook is registered, so Goal
start/resume and restart-safe chat admission keep their existing rules.
Channel messages without explicit provenance use the channel’s ordinary sender
authorization; an authorized non-owner can spend child-model budget only under
that requester’s tool ceiling. A native slash surface targeting a different
CommandTargetSessionKey than the current dispatch session gets no capability.
The deprecated direct command registrar cannot grant runtime capabilities; use
the managed api.registerCommand path with its reviewed manifest declaration.
The facade expires when the command settles; retained references fail closed.
/stop aborts in-flight command calls. Accepted children retain native custody;
store their IDs and collect in a later eligible invocation for the same parent
incarnation. After reset, old children return not_owner. Bounded waits and
resume/handoff behavior belong to the plugin, not a new host task engine.
Acceptance and completed collection results may include optional
selection: { requested, resolved?, observed? }. requested describes
explicit spawn controls (it can be empty on later collection). resolved
comes from the exact child’s host session controls, not the requested model or
a tool-result echo. observed is absent without independent host execution
evidence. Unknown facts remain absent; these receipts do not prove that a
provider honored a control.
Requester-admitted scheduled commands
An eligible external command can request bounded, unattended command turns using
optional ctx.runtimeContext?.scheduledTurns. This is not a timer or a general
Gateway capability: registration, services, and retained invocation references
cannot create these schedules.
Declare both manifest contracts:
The scheduling command must set requireAuth: true and
runtimeCapabilities: ["subagents", "scheduledTurns"]. The target command must
belong to the same plugin, require authentication, support the current channel,
and declare subagents.
Feature-detect twice. Before registering the command, check that
api.runtime.capabilities includes command-scheduled-turns-v1; hosts without
it reject a command that requests scheduledTurns, so register a variant
without it instead. At invocation time, probe ctx.runtimeContext?.scheduledTurns.
An absent capability must not fall back to a system or administrator schedule.
Use an at schedule with an ISO at value for a single occurrence, or
every with everyMs >= 1000. Every occurrence must precede the deadline,
which can be at most seven days from invocation preparation. maxRuns is an
integer from 1 to 100. An activated native launch spends an attempt even if
subsequent authorization fails or execution is interrupted; queued preparation
and capacity deferral do not. Restart recovery never refunds attempts.
The schedule is retired (disabled, with no next run) in the same scheduler transaction that launches its final allowed attempt, or that finds its deadline passed, its attempts exhausted, or its Gateway process gone. A retired schedule never wakes again and stays visible for inspection. One invocation can create at most 4 schedules, and each loaded plugin instance can keep at most 8 active schedules per parent session. Only enabled schedules created by the same plugin instance in the current Gateway process count toward that cap. Schedules left by an earlier Gateway process or a replaced plugin instance can never run plugin code again, so they do not block re-enrollment; each one retires at its next occurrence.
The stored ceiling is the requester's whole native tool surface, at most 256 plain tool names. A larger surface refuses scheduling with an explicit error.
Identical calls with one requestId within the invocation join the same native
job. Changed input refuses rather than replacing or rearming it. A refused call
can be retried with the same requestId; if an earlier attempt was committed,
the retry returns that job rather than creating another. The native cron store
owns persistence, receipts, and recovery. Normal authorized cron management can
disable or remove a schedule; editing or rearming it is not supported. Disabling
also refuses manual force runs, so a disabled schedule cannot spend attempts.
Each run restores the original requester and revalidates current operator, provider-account, session, plugin, and native tool authority. It reuses the command-scoped native-child eligibility producer. Reset, deletion, revocation, expired budgets, and unloaded plugins fail closed. Stored source facts are not execution authority. No caller can choose a different parent or delivery route.
Schedules bind the exact managed plugin instance, not just its ID, version, or source path. Registry adoption of that same instance preserves the binding. Plugin replacement/reload or a Gateway process restart cannot inherit old schedules; a fresh eligible requester command must enroll a new schedule. Persisted jobs do not establish instance continuity or restore execution authority.
Schedules are therefore restart-scoped. After a Gateway restart or update, each old schedule is retired at its next occurrence without spending an attempt. A plugin that needs to continue across restarts must re-enroll from a later eligible requester command, such as an explicit resume command. After an in-process plugin reload, the first occurrence that finds another live instance of the plugin is refused without running plugin code and retires the schedule; it does not wait for generic repeated-failure auto-disable. That refusal spends one attempt. A transient gap, such as a registry swap that keeps the same instance or a plugin that is still unloading, is an ordinary refusal that retries at the next occurrence.
Schedules are stored under a dedicated pluginCommand payload kind. An older
Gateway that does not support scheduled command turns treats that row as an
invalid job and never runs it as an ordinary model prompt.
Scheduled invocations expose native spawn/collect, not another scheduling
capability. Direct command llm.complete and continueAgent are refused:
model work must use admitted native children. Text/media output is recorded only
in the exact original parent transcript, without channel announcement. Retained
runtime callbacks and late results cannot create effects after their invocation
closes. This does not sandbox trusted in-process plugin code.
Background work namespaces
api.runtime.hooks
Dispatch isolated agent turns for untrusted external-content triggers, such
as an email watcher. Unlike api.runtime.subagent.run(...), hook dispatch
wraps external content, serializes runs for the same session, and reports
completion through the Gateway. Plugin turns share the cron execution
budget without requiring the HTTP hooks endpoint. When HTTP hooks are
enabled, one slot in that shared budget remains reserved for HTTP work.
agentId is required, and sessionKey must begin with hook: and contain
no whitespace or control characters. externalContentSource currently
accepts only "email"; external-content wrapping cannot be disabled. Set
deliver to false to record completion without announcing it. Successful
admission returns { ok: true, runId }; rejected admission returns
{ ok: false, reason }.
This capability is available only to bundled plugins and trusted official plugin installations. It does not require enabling or configuring the HTTP hooks endpoint.
api.runtime.subagent
Launch and manage background subagent runs.
For a tool-free completion that needs no retained session or reply delivery,
use complete(...):
agentId and message are required. extraSystemPrompt, model,
timeoutMs, and signal are optional. The selected agent supplies its
configured default model and credential owner when model is omitted.
The result is { text: string }; no session creation, message polling,
deletion, or completion delivery is needed. The configured runtime must
support fresh, tool-free isolated inference; unsupported runtimes fail
before inference.
Completions use the subagent queue at
background priority, sharing agents.defaults.subagents.maxConcurrent
across plugins and workspaces. Set this existing limit to 1 to serialize them.
Cancellation removes queued work immediately. Running work keeps its slot
until underlying runtime cleanup finishes, then rejects; late output is not
returned after cancellation, timeout, or runtime retirement. Calls require
a live Gateway binding and plugin identity. Request-scoped calls retain the
caller's operator scopes and agent access; completions started inside an
operator tool invocation are cancelled when that invocation ends.
Model overrides retain the
existing subagent authorization and allowedModels policy below.
Use run(...) when you need a session or an agent tool surface:
Gateway-backed runs return the normalized accepted sessionKey alongside runId. The field is optional in the TypeScript result only so explicit custom runtimes remain compatible.
waitForRun(...) returns the standard Gateway wait result. status is "ok", "error", "timeout", or "pending"; pending is a normal nonterminal observation, not an exception. Optional error, startedAt, endedAt, stopReason, livenessState, yielded, pendingError, timeoutPhase, providerStarted, and terminalReply metadata is preserved so callers can distinguish observation timeouts from terminal outcomes. timeoutMs bounds the wait call; it does not cancel the run.
plugins.entries.<id>.subagent.allowedModels can restrict overrides to
normalized provider/model targets. The same policy applies to complete;
request-scoped calls retain their authenticated client's override authority.
The check uses the destination agent's model configuration, including exact
configured model IDs, and applies to the plugin's initial override. Configured
defaults, operator-installed model routing hooks, and automatic model fallbacks
retain their own selection policies.
toolsAlsoAllow adds exact, uniquely owned tools registered by the calling plugin to the worker's normal tool surface. The runtime rejects core tools and names shared with another plugin. Profiles and operator tool policies still apply, including explicit allowlists and denies.
Owner-authorized command launches can pass their captured assertion as
subagent.run({ ..., assertCurrent }); the Gateway applies it at run
admission. Managed worktrees.create({ ..., commitGuard }) accepts the same
assertion through its existing creation owner. Revocation prevents pending
launches or worktree writes, while accepted work retains its cleanup and
completion responsibilities.
promptMode: "minimal" selects the bounded subagent prompt instead of the full conversation prompt. The plugin runtime exposes only this mode; omission keeps the full prompt. Use disableTools: true as well when the run must have an exact empty tool surface.
completionDelivery: "current-requester" is default-off and is only available while a before_dispatch hook is handling an authenticated inbound request. OpenClaw captures the resolved requester session and delivery route before invoking the plugin, then delivers the subagent completion through the normal announce path. Plugins cannot provide or override requester lineage or destination fields. Calls outside that requester-bound hook context are rejected.
deleteSession(...) can delete sessions created by the same plugin through api.runtime.subagent.run(...). Deleting arbitrary user or operator sessions still requires an admin-scoped Gateway request.
Native harness continuation retry settings
AgentHarnessHostCapabilities.prepareContinuationRetryCount?.({ cwd }) prepares
the host's continuation retry count from the native owner's admitted Gateway-local
execution directory. Call it after permission and workspace resolution, before tools
or inference, inside the attempt's cleanup owner. Do not pass a remote or container
path, infer a Gateway mirror, or call it from isolated finalization.
The host freezes retry.provider.maxRetries, including absence, and applies an
explicit count over the harness result's providerRetryMaxRetries. Repeated calls
with the same cwd reuse that prepared value; changing cwd throws. Retained callbacks
reject after their host or admission closes. Native request retries, delay policy,
and timeouts remain harness-owned.
The capability is additive and optional. Harnesses that do not participate retain their result budget/default. Local Codex, Copilot, and Muse participate; ACP and remote/provider-hosted settings projection require a separate owner contract.
Native harness completion delivery
Bundled harnesses use openclaw/plugin-sdk/agent-harness-completion to route
native child results through the existing requester completion-delivery owner.
deliverAgentHarnessCompletion requires a host-issued AgentHarnessCompletionScope
and a live isSourceSessionAdmissionAllowed callback. Keep the callback bound
to the exact native assignment and its current parent. Requester identity and
admission are rechecked after awaited routing and before new effects.
Native runtime history, cancellation, and submission receipts remain owned by
the harness; there is no generic task registry or managed-flow API.
For detached native work, await captureAgentHarnessCompletionCustody(scope)
during the admitting parent registration, before publishing the registration or
starting native child work. Preparation retains the original requester lifecycle
and rejects replacement or revocation before returning custody. Each accepted child assignment retains
its own hold with retain() and passes it as completionCustody when delivering
its result. Release each hold when its registration or assignment ends. The hold
preserves the original operator ceiling and requester lifecycle; it does not
grant general tool access or survive revocation or Gateway closure.
Bind createAgentHarnessCompletionEventSink(...) to the same completion custody
and an isSourceCurrent callback for the exact native assignment. After native
terminal persistence and the first completion handoff settle, call
completionCustody.settleExecution() before sleeping delivery retries. This
releases the execution drain obligation while retaining completion delivery
authority. After a restart, recovery must capture fresh custody from a live
registration and validate its requester; stored history never grants authority.