loop: don't panic when LLM API call fails

And refactor a touch.
diff --git a/loop/agent.go b/loop/agent.go
index 88354e3..f7e7033 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -136,6 +136,19 @@
 	Idx int `json:"idx"`
 }
 
+// SetConvo sets m.ConversationID and m.ParentConversationID based on convo.
+func (m *AgentMessage) SetConvo(convo *ant.Convo) {
+	if convo == nil {
+		m.ConversationID = ""
+		m.ParentConversationID = nil
+		return
+	}
+	m.ConversationID = convo.ID
+	if convo.Parent != nil {
+		m.ParentConversationID = &convo.Parent.ID
+	}
+}
+
 // GitCommit represents a single git commit for a commit message
 type GitCommit struct {
 	Hash         string `json:"hash"`                    // Full commit hash
@@ -390,10 +403,7 @@
 		m.Elapsed = &elapsed
 	}
 
-	m.ConversationID = convo.ID
-	if convo.Parent != nil {
-		m.ParentConversationID = &convo.Parent.ID
-	}
+	m.SetConvo(convo)
 	a.pushToOutbox(ctx, m)
 }
 
@@ -415,6 +425,17 @@
 	delete(a.outstandingLLMCalls, id)
 	a.mu.Unlock()
 
+	if resp == nil {
+		// LLM API call failed
+		m := AgentMessage{
+			Type:    ErrorMessageType,
+			Content: "API call failed, type 'continue' to try again",
+		}
+		m.SetConvo(convo)
+		a.pushToOutbox(ctx, m)
+		return
+	}
+
 	endOfTurn := false
 	if resp.StopReason != ant.StopReasonToolUse {
 		endOfTurn = true
@@ -449,10 +470,7 @@
 		m.Elapsed = &elapsed
 	}
 
-	m.ConversationID = convo.ID
-	if convo.Parent != nil {
-		m.ParentConversationID = &convo.Parent.ID
-	}
+	m.SetConvo(convo)
 	a.pushToOutbox(ctx, m)
 }