Skip to main content
Sessions in Goose represent individual conversations with the AI agent. Each session maintains its own state, history, configuration, and context, enabling you to work on multiple tasks independently and resume work later.

What is a Session?

A session encapsulates:
  • Conversation history: All messages exchanged with the agent
  • Working directory: Where the agent operates on your filesystem
  • Extension state: Which tools are enabled and their data
  • Configuration: Model, provider, and recipe being used
  • Metadata: Creation time, token usage, session name
// From crates/goose/src/session/session_manager.rs
pub struct Session {
    pub id: String,                           // Unique identifier
    pub working_dir: PathBuf,                 // Agent's workspace
    pub name: String,                         // Human-readable name
    pub session_type: SessionType,            // User/SubAgent/Scheduled/etc
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub extension_data: ExtensionData,        // Extension state
    pub conversation: Option<Conversation>,   // Message history
    pub recipe: Option<Recipe>,               // Active recipe
    pub provider_name: Option<String>,        // AI provider
    pub model_config: Option<ModelConfig>,    // Model settings
    pub total_tokens: Option<i32>,            // Token counts
    // ...
}

Session Types

Goose supports different session types for different purposes:
pub enum SessionType {
    User,        // Regular user sessions
    SubAgent,    // Subagent task execution
    Scheduled,   // Scheduled/automated tasks
    Hidden,      // Internal/system sessions
    Terminal,    // Terminal integration sessions
    Gateway,     // Gateway/proxy sessions
}

User Sessions

Standard interactive sessions:
# Create a new session
goose session create --name "Refactor auth module"

# Resume existing session
goose session resume abc123

# List sessions
goose session list

SubAgent Sessions

Created automatically when spawning subagents:
// Subagent gets its own isolated session
let subagent_session = session_manager.create_session(
    SessionType::SubAgent,
    recipe,
    parent_session_id,
).await?;
Subagent sessions are:
  • Isolated from parent session
  • Have their own conversation history
  • Can have different extensions/settings
  • Automatically cleaned up when complete

Session Lifecycle

Creating Sessions

// Via SessionManager
let session = session_manager.create_session(
    SessionType::User,
    working_dir,
    name,
).await?;

// With recipe
let session = session_manager.create_session_with_recipe(
    SessionType::User,
    recipe,
    recipe_values,  // Parameter values
).await?;

Updating Sessions

Goose uses a builder pattern for updates:
session_manager
    .update_session(session_id)
    .name("New session name")
    .extension_data(updated_data)
    .total_tokens(Some(1500))
    .apply()
    .await?;

Querying Sessions

// Get session by ID
let session = session_manager.get_session(&session_id).await?;

// List all user sessions
let sessions = session_manager.list_sessions(
    SessionType::User,
    None,  // limit
    None,  // offset
).await?;

// Search sessions
let sessions = session_manager.search_sessions("authentication").await?;

Session Storage

Sessions are persisted to SQLite:
~/.config/goose/
└── sessions/
    ├── sessions.db          # SQLite database
    └── [session-id]/
        ├── conversation.json  # Message history
        └── metadata.json      # Session metadata

Database Schema

CREATE TABLE sessions (
    id TEXT PRIMARY KEY,
    working_dir TEXT NOT NULL,
    name TEXT NOT NULL,
    user_set_name INTEGER DEFAULT 0,
    session_type TEXT DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    total_tokens INTEGER,
    input_tokens INTEGER,
    output_tokens INTEGER,
    schedule_id TEXT,
    recipe TEXT,  -- JSON
    user_recipe_values TEXT,  -- JSON
    provider_name TEXT,
    model_config TEXT  -- JSON
);

CREATE TABLE messages (
    id TEXT PRIMARY KEY,
    session_id TEXT NOT NULL,
    role TEXT NOT NULL,  -- 'user' or 'assistant'
    content TEXT NOT NULL,  -- JSON
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (session_id) REFERENCES sessions(id)
);

Conversation Management

Each session maintains a conversation object:
pub struct Conversation {
    messages: Vec<Message>,
}

impl Conversation {
    // Add a user message
    pub fn add_user_message(&mut self, text: String) {
        self.messages.push(Message::user(text));
    }
    
    // Add assistant response
    pub fn add_assistant_message(&mut self, text: String) {
        self.messages.push(Message::assistant(text));
    }
    
    // Add tool result
    pub fn add_tool_result(&mut self, tool_id: String, result: ToolResult) {
        self.messages.push(Message::tool_result(tool_id, result));
    }
    
    // Get all messages
    pub fn messages(&self) -> &[Message] {
        &self.messages
    }
}

Message Types

pub enum MessageContent {
    Text(TextContent),           // Plain text
    ToolRequest(ToolRequest),    // Tool invocation from AI
    ToolResponse(ToolResponse),  // Tool result
    Image(ImageContent),         // Image data (vision models)
    Thinking(ThinkingContent),   // Model reasoning (if available)
}

pub struct Message {
    pub role: Role,              // User or Assistant
    pub content: Vec<MessageContent>,
    pub metadata: ProviderMetadata,  // Token counts, etc.
}

Context Management

Goose automatically manages context to stay within model limits:

Context Compaction

// From crates/goose/src/context_mgmt/
const DEFAULT_COMPACTION_THRESHOLD: f64 = 0.75;

pub fn check_if_compaction_needed(
    messages: &[Message],
    context_limit: usize,
) -> bool {
    let token_count = count_tokens(messages);
    token_count > (context_limit as f64 * COMPACTION_THRESHOLD) as usize
}

pub fn compact_messages(
    messages: Vec<Message>,
    context_limit: usize,
) -> Vec<Message> {
    // Strategy:
    // 1. Keep system prompt
    // 2. Keep recent messages (last N turns)
    // 3. Summarize middle section
    // 4. Preserve important context (errors, key decisions)
    
    let recent_threshold = 10;  // Keep last 10 message pairs
    let mut compacted = Vec::new();
    
    // System prompt (always keep)
    if let Some(system_msg) = messages.first() {
        compacted.push(system_msg.clone());
    }
    
    // Middle section (summarize)
    if messages.len() > recent_threshold {
        let summary = summarize_messages(
            &messages[1..messages.len() - recent_threshold]
        );
        compacted.push(Message::assistant(summary));
    }
    
    // Recent messages (keep all)
    let recent_start = messages.len().saturating_sub(recent_threshold);
    compacted.extend_from_slice(&messages[recent_start..]);
    
    compacted
}
Compaction is triggered automatically:
// Before sending to provider
if check_if_compaction_needed(&conversation.messages, model_context_limit) {
    // Show user what's happening
    yield AgentEvent::Thinking("Compacting conversation history...");
    
    conversation.messages = compact_messages(
        conversation.messages,
        model_context_limit,
    );
}

Extension Data

Sessions store extension-specific data:
pub struct ExtensionData {
    enabled_extensions: EnabledExtensionsState,  // Which extensions are active
    extension_state: HashMap<String, Value>,     // Extension-specific data
}
Extensions can persist state across messages:
# In an MCP server
@server.tool()
async def save_preference(key: str, value: str) -> list[TextContent]:
    # Store in session extension data
    session_state[key] = value
    return [TextContent(type="text", text=f"Saved {key} = {value}")]

@server.tool()
async def get_preference(key: str) -> list[TextContent]:
    # Retrieve from session extension data
    value = session_state.get(key, "not set")
    return [TextContent(type="text", text=f"{key} = {value}")]

Session Naming

Goose automatically generates descriptive session names:
// After first few messages, use AI to generate name
if conversation.messages.len() == MSG_COUNT_FOR_SESSION_NAME_GENERATION {
    if !session.user_set_name {
        let name = provider.generate_session_name(
            &conversation.messages
        ).await?;
        
        session_manager
            .update_session(&session.id)
            .name(name)
            .apply()
            .await?;
    }
}
The AI analyzes the conversation and suggests names like:
  • “Refactoring authentication module”
  • “Debugging WebSocket connection issues”
  • “Setting up CI/CD pipeline”
Users can override:
goose session rename abc123 "My custom name"

Token Tracking

Sessions track token usage for cost estimation:
pub struct Session {
    // Current session totals
    pub total_tokens: Option<i32>,
    pub input_tokens: Option<i32>,
    pub output_tokens: Option<i32>,
    
    // Accumulated across all time (even after compaction)
    pub accumulated_total_tokens: Option<i32>,
    pub accumulated_input_tokens: Option<i32>,
    pub accumulated_output_tokens: Option<i32>,
}
After each model call:
let usage = provider_response.usage;

session_manager
    .update_session(&session.id)
    .total_tokens(Some(usage.total_tokens))
    .input_tokens(Some(usage.input_tokens))
    .output_tokens(Some(usage.output_tokens))
    .accumulated_total_tokens(Some(
        session.accumulated_total_tokens.unwrap_or(0) + usage.total_tokens
    ))
    .apply()
    .await?;

Session Isolation

Sessions are isolated for security and organization:

Working Directory Isolation

Each session operates in its own working directory:
// Session A
Session {
    id: "abc123",
    working_dir: "/home/user/project-a",
    // ...
}

// Session B  
Session {
    id: "def456",
    working_dir: "/home/user/project-b",
    // ...
}
File operations are scoped to the working directory:
# Agent in session A can only access /home/user/project-a
read_file("config.yaml")  # Reads /home/user/project-a/config.yaml

Extension Isolation

Extensions are session-specific:
// Session A has developer + github extensions
// Session B has developer + database extensions
// They don't interfere with each other

Conversation Isolation

Conversation history doesn’t leak between sessions:
// Session A's conversation
Session A: ["How do I authenticate?", "Use OAuth2..."]

// Session B's conversation (separate)
Session B: ["Deploy to production", "Running deployment..."]

Multi-Session Workflows

You can work with multiple sessions simultaneously:
# Terminal 1: Work on feature A
goose session resume feature-a

# Terminal 2: Work on bugfix B (different session)
goose session resume bugfix-b

# Terminal 3: Code review (yet another session)
goose run --recipe code-review.yaml
Each session maintains independent:
  • Conversation history
  • Working directory
  • Extension state
  • Model/provider settings

Session Management API

REST API

// Create session
POST /sessions
{
  "workingDirectory": "/path/to/project",
  "name": "My Task",
  "recipe": { /* recipe object */ },
  "recipeValues": { /* parameter values */ }
}

// List sessions
GET /sessions?type=user&limit=10&offset=0

// Get session
GET /sessions/{id}

// Update session
PATCH /sessions/{id}
{
  "name": "Updated Name"
}

// Delete session
DELETE /sessions/{id}

// Get session messages
GET /sessions/{id}/messages

Agent Client Protocol (ACP)

# Create session
response = client.send_request("session/new", {
    "cwd": "/path/to/project",
    "recipe": recipe_config
})
session_id = response["result"]["sessionId"]

# Load existing session
response = client.send_request("session/load", {
    "sessionId": session_id
})

# Send prompt to session
response = client.send_request("session/prompt", {
    "sessionId": session_id,
    "prompt": [{"type": "text", "text": "Analyze the codebase"}]
})

Session Insights

Get aggregated statistics:
pub struct SessionInsights {
    pub total_sessions: usize,
    pub total_tokens: i64,
}

// Via API
GET /sessions/insights
{
  "total_sessions": 42,
  "total_tokens": 1_234_567
}

Best Practices

# Good: Descriptive names
goose session create --name "Implement OAuth2 authentication"
goose session create --name "Debug production memory leak"

# Bad: Vague names
goose session create --name "Work"
goose session create --name "Session 1"
# Create session in project root
cd /path/to/my-project
goose session create

# Agent has full context of project structure
# Can access all files, run tests, etc.
# List sessions
goose session list

# Delete completed sessions
goose session delete abc123

# Or archive for later reference
goose session archive abc123
# Check token usage
goose session info abc123

# Session: Implement authentication
# Total tokens: 12,543
# Input tokens: 8,234
# Output tokens: 4,309
# Estimated cost: $0.15
// User sessions for interactive work
SessionType::User

// Subagent sessions for delegated tasks (auto-managed)
SessionType::SubAgent

// Scheduled sessions for automation
SessionType::Scheduled

Troubleshooting

Session Not Found

# List all sessions
goose session list --all

# Search by name
goose session list --search "authentication"

Session State Corruption

# Validate session
goose session validate abc123

# Repair if needed
goose session repair abc123

High Token Usage

# Enable aggressive compaction
goose session configure abc123 --compact-threshold 0.5

# Or limit conversation length
goose session configure abc123 --max-turns 20

Lost Session Data

# Sessions backed up to
ls ~/.config/goose/sessions/backups/

# Restore from backup
goose session restore --from-backup 2024-03-04-abc123.db

Next Steps

Agents

Learn about agent orchestration

Recipes

Configure sessions with recipes

CLI Reference

Session management commands

API Reference

Session management API

Build docs developers (and LLMs) love