Skip to main content
Glama

board_create_session

Start a new work session on a project. Automatically abandons previous active sessions and returns a handoff with last session's progress, active tasks, and recent activity for context.

Instructions

Start a new work session on a project and get the previous session's handoff. Side effect: any currently-active sessions on the same project are automatically marked 'abandoned' with ended_at=now — there's only ever one active session per project. Call this at the start of every substantive session so the next one can pick up where you left off. The returned handoff includes: last_session (progress_summary + handoff_notes + context_artifacts from the previous run), active_tasks (priority-sorted non-done tasks), and recent_activity (last 20 activity_log entries). Returns { session_id, abandoned_sessions, handoff, message }.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idYesProject ID (from board_get_projects) the session operates on
session_typeNoSession type. 'solo' (default) = single agent, 'team' = coordinated multi-agent, 'background' = long-running async work like a Docker worker.
metadataNoOptional metadata (e.g., { worker_id: 'batch-123', hostname: 'mig-5' }). Stored on the session document verbatim.

Implementation Reference

  • The async handler function that executes the board_create_session tool logic: abandons stale active sessions, creates a new session, logs activity, and builds handoff context.
    async ({ project_id, session_type, metadata }) => {
      const now = Timestamp.now();
    
      // 1. Abandon stale active sessions
      const activeSessions = await db
        .collection("sessions")
        .where("project_id", "==", project_id)
        .where("status", "==", "active")
        .get();
    
      const batch = db.batch();
      activeSessions.docs.forEach((doc) => {
        batch.update(doc.ref, {
          status: "abandoned",
          ended_at: now,
          progress_summary: doc.data().progress_summary ?? "Session abandoned (new session started)",
        });
      });
    
      // 2. Create new session
      const sessionRef = db.collection("sessions").doc();
      batch.set(sessionRef, {
        project_id,
        session_type: session_type ?? "solo",
        status: "active",
        started_at: now,
        ended_at: null,
        progress_summary: null,
        handoff_notes: null,
        context_artifacts: {},
        metadata: metadata ?? {},
      });
    
      await batch.commit();
    
      // 3. Log activity
      await db.collection("activity_log").add({
        task_id: null,
        session_id: sessionRef.id,
        agent_name: "system",
        action: "session_started",
        details: `New ${session_type ?? "solo"} session started${activeSessions.size > 0 ? ` (${activeSessions.size} stale session(s) abandoned)` : ""}`,
        metadata: {},
        created_at: now,
      });
    
      // 4. Build handoff context
      const handoff = await buildHandoffContext(db, project_id);
    
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify(
              {
                session_id: sessionRef.id,
                abandoned_sessions: activeSessions.size,
                handoff: handoff,
                message: "Session started successfully",
              },
              null,
              2
            ),
          },
        ],
      };
    }
  • Input schema for board_create_session using Zod: project_id (required string), session_type (optional enum), metadata (optional record).
    {
      project_id: z.string().describe("Project ID (from board_get_projects) the session operates on"),
      session_type: z
        .enum(["solo", "team", "background"])
        .optional()
        .describe("Session type. 'solo' (default) = single agent, 'team' = coordinated multi-agent, 'background' = long-running async work like a Docker worker."),
      metadata: z
        .record(z.string(), z.unknown())
        .optional()
        .describe("Optional metadata (e.g., { worker_id: 'batch-123', hostname: 'mig-5' }). Stored on the session document verbatim."),
    },
  • The registerSessionTools function registers 'board_create_session' via server.tool() with name, description, schema, and handler.
    export function registerSessionTools(server: McpServer, db: Firestore) {
      server.tool(
        "board_create_session",
        "Start a new work session on a project and get the previous session's handoff. **Side effect**: any currently-active sessions on the same project are automatically marked 'abandoned' with ended_at=now — there's only ever one active session per project. Call this at the start of every substantive session so the next one can pick up where you left off. The returned handoff includes: last_session (progress_summary + handoff_notes + context_artifacts from the previous run), active_tasks (priority-sorted non-done tasks), and recent_activity (last 20 activity_log entries). Returns { session_id, abandoned_sessions, handoff, message }.",
        {
          project_id: z.string().describe("Project ID (from board_get_projects) the session operates on"),
          session_type: z
            .enum(["solo", "team", "background"])
            .optional()
            .describe("Session type. 'solo' (default) = single agent, 'team' = coordinated multi-agent, 'background' = long-running async work like a Docker worker."),
          metadata: z
            .record(z.string(), z.unknown())
            .optional()
            .describe("Optional metadata (e.g., { worker_id: 'batch-123', hostname: 'mig-5' }). Stored on the session document verbatim."),
        },
        async ({ project_id, session_type, metadata }) => {
          const now = Timestamp.now();
    
          // 1. Abandon stale active sessions
          const activeSessions = await db
            .collection("sessions")
            .where("project_id", "==", project_id)
            .where("status", "==", "active")
            .get();
    
          const batch = db.batch();
          activeSessions.docs.forEach((doc) => {
            batch.update(doc.ref, {
              status: "abandoned",
              ended_at: now,
              progress_summary: doc.data().progress_summary ?? "Session abandoned (new session started)",
            });
          });
    
          // 2. Create new session
          const sessionRef = db.collection("sessions").doc();
          batch.set(sessionRef, {
            project_id,
            session_type: session_type ?? "solo",
            status: "active",
            started_at: now,
            ended_at: null,
            progress_summary: null,
            handoff_notes: null,
            context_artifacts: {},
            metadata: metadata ?? {},
          });
    
          await batch.commit();
    
          // 3. Log activity
          await db.collection("activity_log").add({
            task_id: null,
            session_id: sessionRef.id,
            agent_name: "system",
            action: "session_started",
            details: `New ${session_type ?? "solo"} session started${activeSessions.size > 0 ? ` (${activeSessions.size} stale session(s) abandoned)` : ""}`,
            metadata: {},
            created_at: now,
          });
    
          // 4. Build handoff context
          const handoff = await buildHandoffContext(db, project_id);
    
          return {
            content: [
              {
                type: "text" as const,
                text: JSON.stringify(
                  {
                    session_id: sessionRef.id,
                    abandoned_sessions: activeSessions.size,
                    handoff: handoff,
                    message: "Session started successfully",
                  },
                  null,
                  2
                ),
              },
            ],
          };
        }
      );
  • buildHandoffContext helper function called by board_create_session handler - fetches project info, last session, active tasks (priority-sorted), and recent activity (last 20 entries).
    async function buildHandoffContext(db: Firestore, project_id: string) {
      // Get project info
      const projectSnap = await db.collection("projects").doc(project_id).get();
      const projectData = projectSnap.exists ? projectSnap.data() : null;
    
      // Get last completed or abandoned session
      const lastSessionSnap = await db
        .collection("sessions")
        .where("project_id", "==", project_id)
        .where("status", "in", ["completed", "abandoned"])
        .orderBy("ended_at", "desc")
        .limit(1)
        .get();
    
      const lastSession = lastSessionSnap.docs[0]
        ? (() => {
            const data = lastSessionSnap.docs[0].data();
            return {
              id: lastSessionSnap.docs[0].id,
              status: data.status,
              progress_summary: data.progress_summary,
              handoff_notes: data.handoff_notes,
              context_artifacts: data.context_artifacts,
              started_at: data.started_at?.toDate?.()?.toISOString() ?? null,
              ended_at: data.ended_at?.toDate?.()?.toISOString() ?? null,
            };
          })()
        : null;
    
      // Get all non-done tasks sorted by priority
      const tasksSnap = await db
        .collection("tasks")
        .where("project_id", "==", project_id)
        .where("status", "!=", "done")
        .get();
    
      const priorityOrder: Record<string, number> = {
        critical: 0,
        high: 1,
        medium: 2,
        low: 3,
      };
    
      const activeTasks = tasksSnap.docs
        .map((doc) => {
          const data = doc.data();
          return {
            id: doc.id,
            title: data.title,
            status: data.status,
            priority: data.priority,
            assigned_agent: data.assigned_agent,
            riper_mode: data.riper_mode,
            depends_on: data.depends_on,
          };
        })
        .sort(
          (a, b) =>
            (priorityOrder[a.priority] ?? 99) - (priorityOrder[b.priority] ?? 99)
        );
    
      // Get recent activity
      const activitySnap = await db
        .collection("activity_log")
        .orderBy("created_at", "desc")
        .limit(20)
        .get();
    
      // Filter to project-related activity (tasks in this project or sessions in this project)
      const taskIds = new Set(tasksSnap.docs.map((d) => d.id));
      const recentActivity = activitySnap.docs
        .map((doc) => {
          const data = doc.data();
          return {
            id: doc.id,
            action: data.action,
            agent_name: data.agent_name,
            details: data.details,
            task_id: data.task_id,
            session_id: data.session_id,
            created_at: data.created_at?.toDate?.()?.toISOString() ?? null,
          };
        });
    
      return {
        project: projectData
          ? {
              id: project_id,
              name: projectData.name,
              status: projectData.status,
              description: projectData.description,
            }
          : null,
        last_session: lastSession,
        active_tasks: activeTasks,
        active_task_count: activeTasks.length,
        recent_activity: recentActivity,
      };
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Description clearly discloses the side effect: any currently-active sessions on the same project are automatically marked 'abandoned'. Also explains return structure in detail (handoff contents, activity log). Since no annotations are provided, the description fully covers behavioral transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with main purpose and side effect. Every sentence adds unique information (side effect, return fields, usage instruction). No fluff. Efficiently structured for an agent to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, description explains the return object fields in detail (handoff sections, active_tasks, recent_activity). Fully covers what the agent needs to understand the tool's outcome and side effects.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% so baseline is 3. Description adds meaning: for 'session_type', it explains the three types ('solo', 'team', 'background') with brief context; for 'metadata', provides example and states it is stored verbatim. Adds value beyond schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states the tool starts a new work session and retrieves the previous handoff. Verbs are specific ('start', 'get') and resource identified ('session on a project'). Distinguishes from sibling tools like 'board_end_session' and 'board_get_handoff'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs to call at the start of every substantive session so the next one can pick up. Implies when to use but does not explicitly state when not to use or list alternatives. The side effect about aborting other sessions is clear context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HuntsDesk/ve-vibe-board'

If you have feedback or need assistance with the MCP directory API, please join our Discord server