{"record":{"id":"964bc9e0a7c54790","repo":"earendil-works/pi","slug":"agent-is-already-processing-a-prompt-use-steer","errorCode":null,"errorMessage":"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.","messagePattern":"Agent is already processing a prompt\\. Use steer\\(\\) or followUp\\(\\) to queue messages, or wait for completion\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/agent.ts","lineNumber":352,"sourceCode":"\t\tif (this.activeRun) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for completion before resetting.\");\n\t\t}\n\n\t\tthis._state.messages = [];\n\t\tthis._state.isStreaming = false;\n\t\tthis._state.streamingMessage = undefined;\n\t\tthis._state.pendingToolCalls = new Set<string>();\n\t\tthis._state.errorMessage = undefined;\n\t\tthis.clearFollowUpQueue();\n\t\tthis.clearSteeringQueue();\n\t}\n\n\t/** Start a new prompt from text, a single message, or a batch of messages. */\n\tasync prompt(message: AgentMessage | AgentMessage[]): Promise<void>;\n\tasync prompt(input: string, images?: ImageContent[]): Promise<void>;\n\tasync prompt(input: string | AgentMessage | AgentMessage[], images?: ImageContent[]): Promise<void> {\n\t\tif (this.activeRun) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.\",\n\t\t\t);\n\t\t}\n\t\tconst messages = this.normalizePromptInput(input, images);\n\t\tawait this.runPromptMessages(messages);\n\t}\n\n\t/** Continue from the current transcript. The last message must be a user or tool-result message. */\n\tasync continue(): Promise<void> {\n\t\tif (this.activeRun) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for completion before continuing.\");\n\t\t}\n\n\t\tconst lastMessage = this._state.messages[this._state.messages.length - 1];\n\t\tif (!lastMessage) {\n\t\t\tthrow new Error(\"No messages to continue from\");\n\t\t}\n","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/earendil-works/pi/blob/4af9d21d3b4d664e4a29fcabfec85171077248e3/packages/agent/src/agent.ts#L334-L370","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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().","Otherwise serialize: await agent.waitForIdle() (or await the earlier prompt()) before the next prompt().","Route all user input through one dispatcher that prompts when idle and steers/follows up when busy."],"exampleFix":"// before\nvoid agent.prompt(\"first\");   // not awaited\nawait agent.prompt(\"second\"); // rejects: already processing\n\n// after\nconst first = agent.prompt(\"first\");\nagent.steer({ role: \"user\", content: [{ type: \"text\", text: \"second\" }], timestamp: Date.now() });\nawait first;","handlingStrategy":"validation","validationCode":"if (agent.state.isStreaming) {\n  agent.followUp(message); // or steer(message) to inject into the current run\n} else {\n  await agent.prompt(message);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await agent.prompt(text);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"already processing a prompt\")) {\n    agent.steer({ role: \"user\", content: [{ type: \"text\", text }], timestamp: Date.now() });\n    return;\n  }\n  throw err;\n}","preventionTips":["Route all user input through a dispatcher that prompts when idle and steers/follows up when busy.","Always await prompt() or keep and await its promise before the next turn.","Treat prompt/continue/reset as idle-only operations; the queues are the concurrency-safe path."],"tags":["agent","prompt","concurrency","queueing","race-condition"],"backgroundTag":"agent-busy-concurrent-call","analyzedSha":"4af9d21d3b4d664e4a29fcabfec85171077248e3","analyzedAt":"2026-08-24T13:07:14.692Z","schemaVersion":2},"datasetVersion":"2026-08-24T17:17:21.512Z"}