Files
composio-skills-reference/composio-sdk/rules/tr-session-lifecycle.md
sohamganatra b8b711dff6 Add Composio SDK skill with rules and agent config
Adds composio-sdk/ with SKILL.md, AGENTS.md, and 18 rule files
covering Tool Router, direct execution, triggers, and auth patterns.

Source: composiohq/skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 22:54:21 -08:00

10 KiB

title, impact, description, tags
title impact description tags
Treat Sessions as Short-Lived and Disposable CRITICAL Create new sessions frequently for better logging, debugging, and configuration management
tool-router
session
lifecycle
best-practices
logging

Treat Sessions as Short-Lived and Disposable

Tool Router sessions should be short-lived and disposable. Create new sessions frequently rather than caching or reusing them across different contexts.

Incorrect

// DON'T: Cache and reuse sessions across messages
class AgentService {
  private sessionCache = new Map<string, ToolRouterSession>();

  async handleMessage(userId: string, message: string) {
    // BAD: Reusing cached session
    let session = this.sessionCache.get(userId);

    if (!session) {
      session = await composio.create(userId, {
        toolkits: ['gmail', 'slack']
      });
      this.sessionCache.set(userId, session);
    }

    // ❌ Configuration changes won't be reflected
    // ❌ Logs mixed across different conversations
    // ❌ Stale toolkit connections
    const tools = await session.tools();
  }
}
# DON'T: Cache and reuse sessions across messages
class AgentService:
    def __init__(self):
        self.session_cache = {}

    async def handle_message(self, user_id: str, message: str):
        # BAD: Reusing cached session
        if user_id not in self.session_cache:
            session = composio.tool_router.create(
                user_id=user_id,
                toolkits=["gmail", "slack"]
            )
            self.session_cache[user_id] = session

        session = self.session_cache[user_id]

        # ❌ Configuration changes won't be reflected
        # ❌ Logs mixed across different conversations
        # ❌ Stale toolkit connections
        tools = session.tools()

Correct - Create New Session Per Message

// DO: Create fresh session for each message
import { Composio } from '@composio/core';
import { VercelProvider } from '@composio/vercel';

const composio = new Composio({
  provider: new VercelProvider()
});

async function handleUserMessage(
  userId: string,
  message: string,
  config: { toolkits: string[] }
) {
  // Create new session for this message
  const session = await composio.create(userId, {
    toolkits: config.toolkits,
    manageConnections: true
  });

  const tools = await session.tools();

  // Use tools with agent...
  const response = await runAgent(message, tools);

  // ✅ Fresh configuration
  // ✅ Clean logs grouped by session
  // ✅ Latest connection states
  return response;
}

// Each message gets a new session
await handleUserMessage('user_123', 'Check my emails', { toolkits: ['gmail'] });
await handleUserMessage('user_123', 'Send a slack message', { toolkits: ['slack'] });
# DO: Create fresh session for each message
from composio import Composio
from composio_openai import OpenAIProvider

composio = Composio(provider=OpenAIProvider())

async def handle_user_message(
    user_id: str,
    message: str,
    config: dict
):
    # Create new session for this message
    session = composio.tool_router.create(
        user_id=user_id,
        toolkits=config["toolkits"],
        manage_connections=True
    )

    tools = session.tools()

    # Use tools with agent...
    response = await run_agent(message, tools)

    # ✅ Fresh configuration
    # ✅ Clean logs grouped by session
    # ✅ Latest connection states
    return response

# Each message gets a new session
await handle_user_message("user_123", "Check my emails", {"toolkits": ["gmail"]})
await handle_user_message("user_123", "Send a slack message", {"toolkits": ["slack"]})

Correct - Single Session Per Conversation (When Config Stable)

// DO: Use one session for entire conversation if config doesn't change
async function handleConversation(
  userId: string,
  conversationId: string,
  config: { toolkits: string[] }
) {
  // Create ONE session for this conversation/thread
  const session = await composio.create(userId, {
    toolkits: config.toolkits,
    manageConnections: true
  });

  const tools = await session.tools();

  console.log(`Session ${session.sessionId} for conversation ${conversationId}`);

  // Use the same session for all messages in this conversation
  for await (const message of conversationStream) {
    const response = await runAgent(message, tools);

    // ✅ All tool executions logged under same session
    // ✅ Easy to debug entire conversation flow
    // ✅ Grouped logs in monitoring tools
  }
}
# DO: Use one session for entire conversation if config doesn't change
async def handle_conversation(
    user_id: str,
    conversation_id: str,
    config: dict
):
    # Create ONE session for this conversation/thread
    session = composio.tool_router.create(
        user_id=user_id,
        toolkits=config["toolkits"],
        manage_connections=True
    )

    tools = session.tools()

    print(f"Session {session.session_id} for conversation {conversation_id}")

    # Use the same session for all messages in this conversation
    async for message in conversation_stream:
        response = await run_agent(message, tools)

        # ✅ All tool executions logged under same session
        # ✅ Easy to debug entire conversation flow
        # ✅ Grouped logs in monitoring tools

When to Create New Sessions

Always Create New Session When:

  1. Configuration Changes

    // User connects new toolkit
    if (userConnectedSlack) {
      // Create new session with updated toolkits
      const session = await composio.create(userId, {
        toolkits: ['gmail', 'slack'] // Added slack
      });
    }
    
  2. Connected Accounts Change

    // User disconnected and reconnected Gmail
    const session = await composio.create(userId, {
      toolkits: ['gmail'],
      // Will use latest connection
    });
    
  3. Different Toolkit Requirements

    // Message needs different toolkits
    const emailSession = await composio.create(userId, {
      toolkits: ['gmail']
    });
    
    const codeSession = await composio.create(userId, {
      toolkits: ['github', 'linear']
    });
    
  4. New Conversation/Thread

    // Starting a new conversation thread
    const session = await composio.create(userId, {
      toolkits: config.toolkits,
      // Fresh session for clean log grouping
    });
    

Can Reuse Session When:

  1. Same conversation/thread
  2. Configuration unchanged
  3. No toolkit connections changed
  4. Actively ongoing interaction

Benefits of Short-Lived Sessions

1. Clean Log Grouping

// All tool executions in one session are grouped together
const session = await composio.create(userId, {
  toolkits: ['gmail', 'slack']
});

// These executions are grouped under session.sessionId
await agent.run('Check emails'); // Logs: session_abc123
await agent.run('Send slack message'); // Logs: session_abc123

// Easy to trace entire conversation flow in monitoring
console.log(`View logs: /sessions/${session.sessionId}`);

2. Fresh Configuration

// Always get latest toolkit connections and auth states
const session = await composio.create(userId, {
  toolkits: ['gmail']
});

// ✅ Uses current connected account
// ✅ Reflects any new connections user made
// ✅ Picks up toolkit updates

3. Easier Debugging

// Session ID becomes your debug trace ID
console.log(`Processing message in session ${session.sessionId}`);

// All logs tagged with session ID:
// [session_abc123] Executing GMAIL_FETCH_EMAILS
// [session_abc123] Executed GMAIL_FETCH_EMAILS
// [session_abc123] Executing SLACK_SEND_MESSAGE

// Filter all logs for this specific interaction

4. Simplified Error Tracking

try {
  const session = await composio.create(userId, config);
  const result = await runAgent(message, session);
} catch (error) {
  // Session ID in error context
  logger.error('Agent failed', {
    sessionId: session.sessionId,
    userId,
    error
  });
}

Pattern: Per-Message Sessions

// Recommended pattern for most applications
export async function handleAgentRequest(
  userId: string,
  message: string,
  toolkits: string[]
) {
  // 1. Create fresh session
  const session = await composio.create(userId, {
    toolkits,
    manageConnections: true
  });

  // 2. Log session start
  logger.info('Session started', {
    sessionId: session.sessionId,
    userId,
    toolkits
  });

  try {
    // 3. Get tools and run agent
    const tools = await session.tools();
    const response = await agent.run(message, tools);

    // 4. Log session completion
    logger.info('Session completed', {
      sessionId: session.sessionId
    });

    return response;
  } catch (error) {
    // 5. Log session error
    logger.error('Session failed', {
      sessionId: session.sessionId,
      error
    });
    throw error;
  }
}

Pattern: Per-Conversation Sessions

// For long-running conversations with stable config
export class ConversationSession {
  private session: ToolRouterSession;

  async start(userId: string, config: SessionConfig) {
    // Create session once for conversation
    this.session = await composio.create(userId, config);

    logger.info('Conversation session started', {
      sessionId: this.session.sessionId
    });
  }

  async handleMessage(message: string) {
    // Reuse session for all messages
    const tools = await this.session.tools();
    return await agent.run(message, tools);
  }

  async end() {
    logger.info('Conversation session ended', {
      sessionId: this.session.sessionId
    });
  }
}

Key Principles

  1. Don't cache sessions - Create new ones as needed
  2. Session = Unit of work - One session per task or conversation
  3. Short-lived is better - Fresh state, clean logs, easier debugging
  4. Session ID = Trace ID - Use for log correlation and debugging
  5. Create on demand - No need to pre-create or warm up sessions

Reference