aaif-goose/goose · error · anyhow::Error
state machine conversation has no kickoff message
Error message
state machine conversation has no kickoff message
What it means
anyhow error from messages_since_kickoff (crates/goose-agent/src/operation.rs) when the conversation contains no message with role User that is user-visible and not a tool response (searched from the end via rposition). The state machine defines the turn as everything since that kickoff user message, so a conversation without one has no definable turn.
Source
Thrown at crates/goose-agent/src/operation.rs:29
use rmcp::model::Tool;
pub type OperationFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub struct SlashCommand<'a> {
pub command: &'a str,
pub params_str: &'a str,
}
pub fn messages_since_kickoff(conversation: &Conversation) -> Result<&[Message]> {
let messages = conversation.messages();
let start = messages
.iter()
.rposition(|message| {
message.role == rmcp::model::Role::User
&& message.is_user_visible()
&& !message.is_tool_response()
})
.ok_or_else(|| anyhow!("state machine conversation has no kickoff message"))?;
Ok(&messages[start..])
}
pub fn trailing_error(conversation: &Conversation) -> Option<MessageErrorKind> {
conversation.last().and_then(Message::error_kind)
}
pub fn last_effective_role(messages: &[Message]) -> Result<EffectiveRole> {
messages
.last()
.map(effective_role)
.ok_or_else(|| anyhow!("cannot determine the role of an empty conversation"))
}
pub fn assistant_turn_count(messages: &[Message]) -> u32 {
let mut turns = 0;
let mut in_assistant_block = false;
for message in messages.iter().rev() {View on GitHub (pinned to 3810898a74)
Solutions
- Resume from a session that began with a normal user message, or start a new session
- If context management trimmed the kickoff, keep at least one visible user message in the retained window
- When building conversations programmatically, insert the kickoff user message before stepping the machine
- If this reproduces on a normal interactive session, file it — the loader may be dropping user messages
Example fix
// before — stepping a conversation built only from tool responses
machine.step(&session, &emit).await?;
// after — ensure a visible, non-tool-response user message exists first
if messages_since_kickoff(conversation).is_err() {
conversation.push(Message::user("kickoff"));
}
machine.step(&session, &emit).await?; Defensive patterns
Strategy: validation
Validate before calling
let ok = conversation.messages().iter().any(|m| m.role == rmcp::model::Role::User && m.is_user_visible() && !m.is_tool_response());
if !ok { /* push kickoff or refuse to step */ } Type guard
fn has_kickoff(conversation: &Conversation) -> bool {
conversation.messages().iter().any(|m| {
m.role == rmcp::model::Role::User && m.is_user_visible() && !m.is_tool_response()
})
} Try / catch
match messages_since_kickoff(conversation) {
Ok(slice) => step_with(slice),
Err(_) => { conversation.push(Message::user("kickoff")); retry_or_report() }
} Prevention
- Always create sessions with an initial user message
- Ensure context trimming keeps the kickoff message
- Do not hand the machine conversations built solely from tool responses
When it happens
Trigger: Running a state-machine step on a conversation whose messages are only assistant messages and/or user-role tool responses (all tool responses or all filtered as hidden), or an effectively empty message list.
Common situations: Resuming a session that only recorded tool traffic; replaying a machine-generated conversation with no human kickoff; a truncation/context-management step removed the kickoff user message.
Related errors
- state-machine session loaded without conversation
- cannot determine the role of an empty conversation
- Download is being cancelled; wait for it to finish before re
- Session has no conversation after history modification
- Session {} has no conversation
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/187334f604f3a066.
Report an issue: GitHub.