earendil-works/pi · error · Error

Agent is already processing. Wait for completion before cont

Error message

Agent is already processing. Wait for completion before continuing.

What it means

Agent.continue() resumes an idle agent from its transcript; like prompt() and reset() it is an idle-only operation and rejects while activeRun exists. The single-run design means a continuation cannot be started on top of a run that is still streaming or still settling its agent_end listeners. steer() and followUp() are the concurrency-safe ways to add input while a run is active.

Source

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

	}

	/** 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");
		}

		if (lastMessage.role === "assistant") {
			const queuedSteering = this.steeringQueue.drain();
			if (queuedSteering.length > 0) {
				await this.runPromptMessages(queuedSteering, { skipInitialSteeringPoll: true });
				return;
			}

			const queuedFollowUps = this.followUpQueue.drain();
			if (queuedFollowUps.length > 0) {
				await this.runPromptMessages(queuedFollowUps);
				return;

View on GitHub (pinned to 4af9d21d3b)

Solutions

  1. await agent.waitForIdle() before calling continue().
  2. If you wanted to inject input mid-run, use steer()/followUp() - continue() is only for resuming an idle agent.
  3. Await each prompt()/continue() fully (including listener settlement) before scheduling the next.

Example fix

// before
void agent.prompt("hi");
await agent.continue(); // rejects: previous run still active

// after
await agent.prompt("hi");
await agent.waitForIdle();
await agent.continue();
Defensive patterns

Strategy: validation

Validate before calling

await agent.waitForIdle(); // no-op when idle
await agent.continue();

Try / catch

try {
  await agent.continue();
} catch (err) {
  if (err instanceof Error && err.message.includes("before continuing")) {
    await agent.waitForIdle();
    await agent.continue(); // retry once the run settled
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling agent.continue() while a prompt()/continue() run is in flight; auto-continue loops that fire on a timer without checking idle; calling continue() from an event listener that executes before the run's agent_end listeners settle.

Common situations: Orchestrators that continue after each turn in a while-loop without awaiting; retry schedulers; UI resume buttons racing an in-progress run.

Related errors


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