SDK

Mid-run steering

Redirect the agent while it is generating — without aborting and re-prompting. Available in the interactive REPL and over headless stream-json.

Steering lets you correct or add to a request during a run. Your message is queued while the agent generates and injected at the next turn boundary, so the agent picks it up cleanly instead of being interrupted. It works in two layers: the interactive REPL and the headless --input-format stream-json path.

Interactive REPL

While the agent is working, the prompt stays live — keep typing.

Queue while generating

Pressing mid-run queues the message rather than starting a new prompt. It renders as a dimmed line just above the input and is notyet part of the agent's context:

Text
⋯ Actually, keep the public API unchanged · esc to edit

Commit on injection

When the agent reaches the next turn boundary, the queued message is injected and commits to the scrollback as a normal user message — exactly as though you had sent it at that point.

Esc-to-edit

  • Empty prompt. esc peels the most recently queued message back into the input for editing and re-syncs the queue — instead of interrupting the run.
  • Non-empty draft. Your in-progress text is never clobbered; there, esc still interrupts the run as usual.

Undelivered messages

If a run ends — aborted, errored, or max-turns reached — while messages are still queued (never injected), they're surfaced as a faint "not delivered — resend if still needed" note and then cleared, so nothing is lost silently.

Headless: --input-format stream-json

For SDK and automation use, harnext -p --input-format stream-json keeps stdin open and reads newline-delimited JSON (NDJSON) user messages incrementally, rather than draining stdin into a single prompt.

Shell
harnext -p --input-format stream-json --output-format stream-json

Message shape

One JSON object per line:

JSON
{"type":"user","message":{"role":"user","content":"Refactor the auth module to use async/await"}}

First vs. steer vs. idle

What a line does depends on when it arrives:

  • First message — starts the run.
  • Arrives while the agent is generating — injected as a steering message, delivered at the next turn boundary.
  • Arrives while idle — continues the session as a new turn.

Output envelopes

Output is per-run for text, json, and stream-json. The stream-json form emits one init envelope, then a stream of assistant, user, and result envelopes.

Steering from the SDK (Node)

Programmatic steering is this same stream-jsonsurface, driven from your own process: spawn the headless agent, write the first NDJSON line to start the run, then write another line mid-run to steer. Parse the output envelopes to know when the run is underway and when it's done.

TypeScript
import { spawn } from 'node:child_process';

// Headless agent: line-delimited JSON in, line-delimited JSON out.
const child = spawn(
  'harnext',
  ['-p', '--input-format', 'stream-json', '--output-format', 'stream-json'],
  { stdio: ['pipe', 'pipe', 'inherit'] },
);

const send = (content: string) =>
  child.stdin.write(
    JSON.stringify({ type: 'user', message: { role: 'user', content } }) + '\n',
  );

// 1. The first line starts the run.
send('Refactor the auth module to use async/await');

// 2. Read the output envelopes and steer once the run is underway.
let steered = false;
let buf = '';
child.stdout.on('data', (chunk) => {
  buf += chunk;
  for (let nl; (nl = buf.indexOf('\n')) !== -1; ) {
    const line = buf.slice(0, nl).trim();
    buf = buf.slice(nl + 1);
    if (!line) continue;
    const evt = JSON.parse(line); // 'init' | 'assistant' | 'user' | 'result'

    // 3. Mid-run steer — delivered at the next turn boundary.
    if (evt.type === 'assistant' && !steered) {
      steered = true;
      send('Actually, keep the public API unchanged');
    }

    // 4. The run is finished.
    if (evt.type === 'result') child.stdin.end();
  }
});

Sending a line while the agent is idle (after a result) continues the session as a new turn instead of steering — so the same loop drives a multi-turn conversation. A complete, runnable version lives at examples/steering-client.mjs.

Source
Mid-run steering shipped in QualityUnit/harnext#50, mirroring Claude Code's steering model. See the announcement post for a walkthrough.