aaif-goose/goose · error · anyhow::Error
state-machine session loaded without conversation
Error message
state-machine session loaded without conversation
What it means
anyhow error returned by StateMachine::step (crates/goose-agent/src/machine.rs) when session.conversation() yields None. The MachineSession trait exposes the conversation as Option<&Conversation>; a loaded session that carries no conversation cannot be stepped, so the machine refuses before running any operations.
Source
Thrown at crates/goose-agent/src/machine.rs:65
pub struct StateMachine<'a, S, E = ConversationEffect> {
steps: Vec<Step<'a, S, E>>,
cancel: CancellationToken,
}
impl<'a, S, E> StateMachine<'a, S, E>
where
S: MachineSession,
E: MachineEffect + Send + 'static,
{
pub fn new(steps: Vec<Step<'a, S, E>>, cancel: CancellationToken) -> Self {
Self { steps, cancel }
}
pub async fn step(&self, session: &S, emit: &Emitter) -> Result<Option<StepResult<E>>> {
let conversation = session
.conversation()
.ok_or_else(|| anyhow!("state-machine session loaded without conversation"))?;
for step in &self.steps {
let name = step.operation().name();
let result = if self.cancel.is_cancelled() {
OperationResult::NotApplicable
} else {
let step_fut: OperationFuture<'_, Result<OperationResult<E>>> = match step {
Step::Operation(operation) => operation.run(session, conversation, emit),
Step::Inference(inference) => {
let mut input = InferenceInput::default();
for operation in self.steps.iter().map(|step| step.operation()) {
input
.tools
.extend(operation.inference_tools(session).await?);
input
.prompt_parts
.extend(operation.prompt_parts(session, conversation).await?);
inputView on GitHub (pinned to 3810898a74)
Solutions
- Start from a fresh session (drop --resume / the session id) so a conversation exists
- Inspect the session file for a conversation field; if a migration dropped it, re-run migration or discard the session
- If you implement MachineSession yourself, make conversation() return Some for any session you intend to step
- Report/fix the loader if valid sessions are being persisted without conversation data
Example fix
// before
impl MachineSession for MySession {
fn conversation(&self) -> Option<&Conversation> { None }
}
// after
impl MachineSession for MySession {
fn conversation(&self) -> Option<&Conversation> { self.conv.as_ref() } // always Some for steppable sessions
} Defensive patterns
Strategy: type-guard
Validate before calling
if let Some(conv) = session.conversation() { /* safe to step */ } else { start_new_session().await?; } Type guard
fn has_conversation<S: MachineSession>(s: &S) -> bool {
s.conversation().is_some()
} Try / catch
match machine.step(&session, &emit).await {
Err(e) if e.to_string().contains("without conversation") => {
// session record unusable: start fresh rather than retry
start_new_session().await?
}
other => other?,
} Prevention
- Never persist a session record without a conversation
- In tests, always attach a Conversation to fake sessions
- Treat this error as data corruption: create a new session instead of retrying
When it happens
Trigger: Starting the state-machine agent path (GOOSE_STATE_MACHINE=1) against a session whose persisted data has no conversation section — an empty/newly created session record, a corrupted or truncated session file, or a session type that never stores conversations.
Common situations: Resuming a session file that was created but never received messages; hand-crafted or migrated session JSON missing the conversation key; tests constructing a bare MachineSession without attaching a Conversation.
Related errors
- state machine conversation has no kickoff message
- cannot determine the role of an empty conversation
- Session ID '{}' not found.
- Session with name '{}' not found.
- No sessions found.
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/3cdb78ec6b8a2696.
Report an issue: GitHub.