earendil-works/pi · error · Error
Cannot continue: no messages in context
Error message
Cannot continue: no messages in context
What it means
agentLoopContinue() resumes an agent loop from an existing AgentContext without adding a new message; it exists for retries where the context already ends with a user prompt or tool results. It refuses to start when context.messages is empty because there would be nothing for the LLM to respond to and the provider request would be malformed. The check runs synchronously before the EventStream is created, so the function throws instead of emitting an error event. To start a conversation, use agentLoop() with prompt messages instead.
Source
Thrown at packages/agent/src/agent-loop.ts:71
return stream;
}
/**
* 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);View on GitHub (pinned to 4af9d21d3b)
Solutions
- Before calling agentLoopContinue, check context.messages.length > 0; when empty, start a new loop with agentLoop([userMessage], context, ...) instead.
- In retry logic, only continue when the context already contains the prompt you sent (inspect the last message role/timestamp).
- When restoring state from persistence, validate that the messages array is non-empty and fall back to a fresh prompt.
Example fix
// before
const stream = agentLoopContinue(context, config, signal, streamFn); // throws: no messages in context
// after
const userMsg: AgentMessage = { role: "user", content: [{ type: "text", text: "hello" }], timestamp: Date.now() };
const stream =
context.messages.length > 0
? agentLoopContinue(context, config, signal, streamFn)
: agentLoop([userMsg], context, config, signal, streamFn); Defensive patterns
Strategy: validation
Validate before calling
if (context.messages.length === 0) {
// nothing to continue from - start a new loop instead
const stream = agentLoop([userMessage], context, config, signal, streamFn);
} else {
const stream = agentLoopContinue(context, config, signal, streamFn);
} Type guard
function hasContextMessages(context: AgentContext): boolean {
return context.messages.length > 0;
} Try / catch
// agentLoopContinue throws synchronously (before the stream is created)
try {
const stream = agentLoopContinue(context, config, signal, streamFn);
} catch (err) {
if (err instanceof Error && err.message.includes("no messages in context")) {
// fall back to agentLoop with a fresh prompt
} else {
throw err;
}
} Prevention
- Treat agentLoopContinue as a retry API: only call it after a turn that already appended messages to the context.
- Build AgentContext in one place and assert messages.length > 0 there during development.
- Prefer the Agent class: Agent.continue() has its own empty-transcript error and drains steering/follow-up queues first.
When it happens
Trigger: Calling agentLoopContinue(context, config, signal, streamFn) on a freshly built AgentContext whose messages array is empty; retry helpers that fire after an aborted turn but before any message was committed; contexts assembled by hand where the user message was never appended; calling it after clearing/resetting transcript state.
Common situations: Automatic retry wrappers around failed or aborted turns that resume even when nothing was sent; hand-built AgentContext objects in tests; state restoration (e.g. initialState.messages loaded from persisted JSON) that silently yields an empty array.
Related errors
- Cannot continue from message role: assistant
- No messages to continue from
- No default stream function configured. Pass streamFn explici
AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24).
Data as JSON: /api/errors/bab878ba55424937.
Report an issue: GitHub.