Skip to main content
Run one piece of work as one bounded engine turn, and get an honest record of it: what was requested, what ran, what came back, and whether token usage was reported or unknown. Use this page when you bind engines to nodes yourself or write an engine plugin. For where these records live, read Events and artifacts; for what a resumed run repeats, The record. An engine-backed node attempt is one fresh engine call. Command adapters use one fresh CLI process, and data-only attempts use no engine process. The graph executor connects attempts to graph dispatch. The request names the work and its bounds:
examples/safe-node-attempt.ts (excerpt)
A request needs:
  • prompt and model, what to ask and which model to ask.
  • tools, allowedTools and workspaceMode, the access the process may have.
  • timeoutMs, maxOutputBytes and maxMemoryBytes, the bounds one turn runs inside.

Run an adapter

Each adapter runs one attempt and returns a result the public validator checks before its parts are used:
examples/safe-node-attempt.ts (excerpt)
The example runs the Grok and OpenCode adapters against local scripted executables, with no model and no network. Grok returns a native structured result; OpenCode returns one marked text part, which the job’s own parser reads. OpenCode’s fixture leaves out a valid usage receipt on purpose, so the runtime keeps its usage as unknown instead of zero. Run it with npx tsx safe-node-attempt.ts:
Output
The report prints the stub executable paths relative to the temporary directory; the exported attemptReport keeps the absolute paths. Both requested and effective identities are recorded. If a CLI reports a different model, the two records stay separate and the change stays visible. Each identity records the adapter, adapter version, provider, model family, model, capabilities and executable: the absolute path a command adapter selected, or null for an engine with no child process. When the engine runs with an effort, each identity also records it. A host that writes an engine’s selection itself names the effort in the selection, the same way it names the model. The runtime passes that effort to the engine with each request, preflight checks included. An effort set on the engine itself still applies to a request that names none, so an engine built with its own effort under a selection that names a different one, or none, reports a different requested identity, and the attempt fails. Name the effort in one place: the selection. The path identifies the selected wrapper, not its resolved target or file digest. When no Claude or Codex binary is configured, those plugins search the inherited PATH once and record the absolute path they select.

Declare access

tools lists the built-in capabilities the CLI may expose. allowedTools holds the narrower permission rules approved by the host. An adapter that can’t express the declared access refuses the request before it starts the process, so no model runs. The workspace mode is a ceiling. An approval never adds a capability the mode withholds, and a bypass never goes past it. A reviewer with no way to read the work is refused rather than run, so a review rests only on material the seat could open. The example uses workspaceMode: 'none' and exposes no tools. An evidence-only job can use workspaceMode: 'read' with declared read tools. A writing job must name its write access and gets a separate scratch directory for temporary data. An action decision is made before an effect: allow runs it, wait pauses it, and deny records a refusal without running it.

Read results and usage

A successful result has ordered parts and exactly one final part. A stopped or truncated turn can keep the parts it produced, including no parts at all, but it’s still a failed attempt. Usage has two states. reported means the engine supplied a valid receipt. unknown means it didn’t. Unknown usage never becomes a fake zero. Command adapters set three environment variables before they start a process, and children inherit them unless they replace their environment:
  • OBVERSA_HEADLESS=1 tells hooks and child tools that the attempt is unattended, so they must not open interactive or desktop prompts.
  • OBVERSA_RUN_ID identifies the run that owns the process.
  • OBVERSA_ATTEMPT_ID identifies the exact attempt and lets cleanup find descendants that detach from their parent process.

Failure

  • Fallback. One attempt can use one declared fallback after a model becomes unavailable. The failed model stays in the attempt record. Both lanes share one clock, so a fallback receives only the time left by the first lane. The graph executor uses durable events to keep later graph work away from that model.
  • TIMEOUT. timeoutMs is the work deadline; at that point a command adapter starts stopping its process tree. timeoutGraceMs is teardown time: the adapter asks the processes to stop, waits that long, then force-stops any that remain. The grace doesn’t start more model work. A marked final result returned inside the deadline stays separate from a later transport failure. If cleanup makes the adapter return after the deadline, the attempt fails with TIMEOUT and its parsed result stays null, while the record keeps the returned parts, reported usage, effective engine and transport failure as evidence. The runtime waits up to seven more seconds for that cleanup evidence, and the wait can’t turn the attempt into a success.
  • Cleanup. The command-adapter kit cleans its process tree on normal completion, timeout, abort and other handled exits, and the runtime waits for that cleanup before it records the attempt. temporaryDirectoryRemoved in the output covers only the example’s fixture files; the package test suite checks child-process cleanup separately.

Limits

  • Hosts own two things. Recovery after the supervising process dies, and checks against the CLIs installed on the machine. The supervised runner does both.
examples/safe-node-attempt.ts

Next steps

  • Supervised local runs: the watchdog that checks engines before the first step and restarts a killed worker.
  • Events and artifacts: the stores an attempt’s events and parts land in.
  • API: the engine contract every adapter implements.