loop: add todo checklist

This should improve Sketch's executive function and user communication.
diff --git a/loop/server/loophttp.go b/loop/server/loophttp.go
index 0e0d930..e62947e 100644
--- a/loop/server/loophttp.go
+++ b/loop/server/loophttp.go
@@ -55,6 +55,18 @@
 	SessionID string `json:"sessionId"`
 }
 
+// TodoItem represents a single todo item for task management
+type TodoItem struct {
+	ID     string `json:"id"`
+	Task   string `json:"task"`
+	Status string `json:"status"` // queued, in-progress, completed
+}
+
+// TodoList represents a collection of todo items
+type TodoList struct {
+	Items []TodoItem `json:"items"`
+}
+
 type State struct {
 	// null or 1: "old"
 	// 2: supports SSE for message updates
@@ -82,6 +94,7 @@
 	InsideOS             string                        `json:"inside_os,omitempty"`
 	OutsideWorkingDir    string                        `json:"outside_working_dir,omitempty"`
 	InsideWorkingDir     string                        `json:"inside_working_dir,omitempty"`
+	TodoContent          string                        `json:"todo_content,omitempty"` // Contains todo list JSON data
 }
 
 type InitRequest struct {
@@ -1197,6 +1210,7 @@
 		InContainer:          s.agent.IsInContainer(),
 		FirstMessageIndex:    s.agent.FirstMessageIndex(),
 		AgentState:           s.agent.CurrentStateName(),
+		TodoContent:          s.agent.CurrentTodoContent(),
 	}
 }
 
diff --git a/loop/server/loophttp_test.go b/loop/server/loophttp_test.go
index 58d8ad0..cedf6aa 100644
--- a/loop/server/loophttp_test.go
+++ b/loop/server/loophttp_test.go
@@ -28,6 +28,7 @@
 	title                    string
 	branchName               string
 	workingDir               string
+	sessionID                string
 }
 
 func (m *mockAgent) NewIterator(ctx context.Context, nextMessageIdx int) loop.MessageIterator {
@@ -229,7 +230,8 @@
 func (m *mockAgent) RepoRoot() string                            { return m.workingDir }
 func (m *mockAgent) Diff(commit *string) (string, error)         { return "", nil }
 func (m *mockAgent) OS() string                                  { return "linux" }
-func (m *mockAgent) SessionID() string                           { return "test-session" }
+func (m *mockAgent) SessionID() string                           { return m.sessionID }
+func (m *mockAgent) CurrentTodoContent() string                  { return "" } // Mock returns empty for simplicity
 func (m *mockAgent) OutstandingLLMCallCount() int                { return 0 }
 func (m *mockAgent) OutstandingToolCalls() []string              { return nil }
 func (m *mockAgent) OutsideOS() string                           { return "linux" }