earendil-works/pi · error · Error

Agent is already processing. Wait for completion before rese

Error message

Agent is already processing. Wait for completion before resetting.

What it means

Agent.reset() clears transcript state, runtime state, and both message queues in one step; it refuses to run while activeRun exists because the in-flight loop holds a snapshot of the context and would keep emitting events into wiped state. The guard is a synchronous throw, so when it fires nothing has been cleared. The agent stays 'active' until the run and all awaited agent_end listeners have settled, not merely until the stream closes.

Source

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

	/** Abort the current run, if one is active. */
	abort(): void {
		this.activeRun?.abortController.abort();
	}

	/**
	 * Resolve when the current run and all awaited event listeners have finished.
	 *
	 * This resolves after `agent_end` listeners settle.
	 */
	waitForIdle(): Promise<void> {
		return this.activeRun?.promise ?? Promise.resolve();
	}

	/** Clear transcript state, runtime state, and queued messages. */
	reset(): void {
		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.",

View on GitHub (pinned to 4af9d21d3b)

Solutions

  1. await agent.waitForIdle() before reset() - it resolves immediately when idle and after agent_end listeners settle when busy.
  2. If the run should be stopped, call agent.abort() first, then await agent.waitForIdle(), then reset().
  3. In UIs, disable or defer the reset action while agent.state.isStreaming is true.

Example fix

// before
agent.abort();
agent.reset(); // throws: run still active

// after
agent.abort();
await agent.waitForIdle();
agent.reset();
Defensive patterns

Strategy: validation

Validate before calling

if (agent.state.isStreaming) {
  await agent.waitForIdle(); // resolves after agent_end listeners settle
}
agent.reset();

Try / catch

try {
  agent.reset(); // synchronous throw, nothing is cleared on failure
} catch (err) {
  if (err instanceof Error && err.message.includes("before resetting")) {
    agent.abort();
    await agent.waitForIdle();
    agent.reset(); // safe now
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling agent.reset() while prompt() or continue() is still in flight; a UI 'new conversation' handler racing an in-progress turn; resetting in a listener that runs before agent_end listeners settle; calling abort() then reset() immediately without waiting for the abort to finish.

Common situations: Chat apps wiring a new-chat button to reset without awaiting the previous turn; tests resetting in afterEach while a slow stream drains; fire-and-forget prompt() calls that leave activeRun set longer than expected.

Related errors


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