earendil-works/pi · error · Error

Agent is already processing a prompt. Use steer() or followU

Error message

Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.

What it means

Agent.prompt() starts a new run, and the Agent executes one run at a time (a single activeRun slot). Calling prompt() again while a run is in flight makes the async method reject with this error to make the race visible; the message names the two supported alternatives - steer() to inject a message into the current run, or followUp() to queue a message that runs after the agent would otherwise stop. Nothing is queued or lost when the error throws.

Source

Thrown at packages/agent/src/agent.ts:352

		if (this.activeRun) {
			throw new Error("Agent is already processing. Wait for completion before resetting.");
		}

		this._state.messages = [];
		this._state.isStreaming = false;
		this._state.streamingMessage = undefined;
		this._state.pendingToolCalls = new Set<string>();
		this._state.errorMessage = undefined;
		this.clearFollowUpQueue();
		this.clearSteeringQueue();
	}

	/** Start a new prompt from text, a single message, or a batch of messages. */
	async prompt(message: AgentMessage | AgentMessage[]): Promise<void>;
	async prompt(input: string, images?: ImageContent[]): Promise<void>;
	async prompt(input: string | AgentMessage | AgentMessage[], images?: ImageContent[]): Promise<void> {
		if (this.activeRun) {
			throw new Error(
				"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.",
			);
		}
		const messages = this.normalizePromptInput(input, images);
		await this.runPromptMessages(messages);
	}

	/** Continue from the current transcript. The last message must be a user or tool-result message. */
	async continue(): Promise<void> {
		if (this.activeRun) {
			throw new Error("Agent is already processing. Wait for completion before continuing.");
		}

		const lastMessage = this._state.messages[this._state.messages.length - 1];
		if (!lastMessage) {
			throw new Error("No messages to continue from");
		}

View on GitHub (pinned to 4af9d21d3b)

Solutions

  1. If the input belongs to the current conversation while the agent is mid-run, use agent.steer(message) or agent.followUp(message) instead of prompt().
  2. Otherwise serialize: await agent.waitForIdle() (or await the earlier prompt()) before the next prompt().
  3. Route all user input through one dispatcher that prompts when idle and steers/follows up when busy.

Example fix

// before
void agent.prompt("first");   // not awaited
await agent.prompt("second"); // rejects: already processing

// after
const first = agent.prompt("first");
agent.steer({ role: "user", content: [{ type: "text", text: "second" }], timestamp: Date.now() });
await first;
Defensive patterns

Strategy: validation

Validate before calling

if (agent.state.isStreaming) {
  agent.followUp(message); // or steer(message) to inject into the current run
} else {
  await agent.prompt(message);
}

Try / catch

try {
  await agent.prompt(text);
} catch (err) {
  if (err instanceof Error && err.message.includes("already processing a prompt")) {
    agent.steer({ role: "user", content: [{ type: "text", text }], timestamp: Date.now() });
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: A second prompt() call before the first run's agent_end listeners settle; issuing prompts from unawaited event handlers; awaiting a first prompt() that an event listener keeps active past stream close; concurrent test prompts on a shared Agent.

Common situations: Chat front-ends where every user message calls prompt() without awaiting idle; timers/background jobs enqueueing prompts; sequentially written code where one prompt() was accidentally fire-and-forget.

Related errors


AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24). Data as JSON: /api/errors/964bc9e0a7c54790. Report an issue: GitHub.