earendil-works/pi · error · Error
Cannot continue from message role: assistant
Error message
Cannot continue from message role: assistant
What it means
agentLoopContinue() requires the last message in context to convert to a user or toolResult message via convertToLlm, because LLM providers reject conversations that end with the assistant's own message (there is nothing new to answer). Since convertToLlm only runs once per turn, the loop pre-checks the recorded role and throws synchronously when it is 'assistant'. This is a guard against double-continuing: the model already produced the final assistant turn for the current input.
Source
Thrown at packages/agent/src/agent-loop.ts:75
* Continue an agent loop from the current context without adding a new message.
* Used for retries - context already has user message or tool results.
*
* **Important:** The last message in context must convert to a `user` or `toolResult` message
* via `convertToLlm`. If it doesn't, the LLM provider will reject the request.
* This cannot be validated here since `convertToLlm` is only called once per turn.
*/
export function agentLoopContinue(
context: AgentContext,
config: AgentLoopConfig,
signal: AbortSignal | undefined,
streamFn: StreamFn,
): EventStream<AgentEvent, AgentMessage[]> {
if (context.messages.length === 0) {
throw new Error("Cannot continue: no messages in context");
}
if (context.messages[context.messages.length - 1].role === "assistant") {
throw new Error("Cannot continue from message role: assistant");
}
const stream = createAgentStream();
void runAgentLoopContinue(
context,
config,
async (event) => {
stream.push(event);
},
signal,
streamFn,
).then((messages) => {
stream.end(messages);
});
return stream;
}View on GitHub (pinned to 4af9d21d3b)
Solutions
- Append a user or toolResult message to the context before continuing, or use agentLoop([newUserMessage], ...) to add one properly.
- If you meant 'make the model say more', send a short follow-up user instruction instead of resuming from the assistant turn.
- In retry code, check the last message role and skip the retry when it is 'assistant'.
Example fix
// before
const stream = agentLoopContinue(context, config, signal, streamFn); // last message is assistant -> throws
// after
context.messages.push({ role: "user", content: [{ type: "text", text: "continue" }], timestamp: Date.now() });
const stream = agentLoopContinue(context, config, signal, streamFn); Defensive patterns
Strategy: validation
Validate before calling
const last = context.messages[context.messages.length - 1];
if (!last || last.role === "assistant") {
context.messages.push(newUserMessage); // or call agentLoop with a prompt
}
const stream = agentLoopContinue(context, config, signal, streamFn); Type guard
function endsOnContinuableRole(messages: AgentMessage[]): boolean {
const last = messages[messages.length - 1];
return last !== undefined && last.role !== "assistant";
} Try / catch
try {
const stream = agentLoopContinue(context, config, signal, streamFn);
} catch (err) {
if (err instanceof Error && err.message.includes("role: assistant")) {
// append a user/toolResult message, or prompt instead of continuing
} else {
throw err;
}
} Prevention
- Map regenerate/continue UI actions to a new user prompt, not a resume from an assistant turn.
- Check the last message role before every continue call.
- Remember custom AgentMessage roles must map to user/toolResult via convertToLlm - the loop can only validate the recorded role.
When it happens
Trigger: Calling agentLoopContinue when the transcript's last message is an assistant message: after a completed run where the model answered without tool calls, after manually appending an assistant message, or from retry logic that continues a turn that ended on stopReason 'error' or 'aborted' with an assistant message already streamed.
Common situations: Chat UIs mapping a regenerate/continue button to a continue call after the agent already replied; retry-after-error wrappers; tests seeding transcripts that end with an assistant message.
Related errors
- Cannot continue: no messages in context
- No messages to continue from
- Cannot continue from message role: assistant
- No default stream function configured. Pass streamFn explici
AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24).
Data as JSON: /api/errors/91f4554361d87871.
Report an issue: GitHub.