Skip to main content
Glama

solve_schedule

Read-onlyIdempotent

Assign tasks to time slots by matching their energy requirements with slot energy levels to maximize productivity. Ideal for deep-work scheduling, shift planning, and exercise scheduling.

Instructions

Assign tasks to time slots maximizing productivity, matching task energy requirements with slot energy levels. Use specifically for task→slot assignment with energy matching (deep-work scheduling, shift planning, exercise scheduling). For general resource allocation with arbitrary linear constraints, use solve_constraints. For sequence/route problems, use plan_pathfind. Deterministic.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
tasksYes
slotsYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
assignmentsYes
unassignedTasksNoTask IDs that did not fit.
totalScoreNo

Implementation Reference

  • The main handler function `optimizeSchedule` that implements the schedule optimization logic. Takes tasks and time slots, formulates a binary integer programming problem maximizing priority-weighted completion with energy matching, solves it via HiGHS, and returns assignments with total score.
    export async function optimizeSchedule(
      tasks: Task[],
      slots: TimeSlot[],
    ): Promise<ScheduleResult> {
      const energyMatch: Record<string, Record<string, number>> = {
        high: { high: 1.0, medium: 0.5, low: 0.2 },
        medium: { high: 0.8, medium: 1.0, low: 0.6 },
        low: { high: 0.4, medium: 0.7, low: 1.0 },
      };
    
      const variables: OptimizationVariable[] = [];
      const objective: Record<string, number> = {};
      const constraints: Constraint[] = [];
    
      // Sanitize IDs for LP format (no hyphens, spaces, or special chars)
      const sanitize = (s: string) => s.replace(/[^a-zA-Z0-9]/g, "");
      const taskIdMap = new Map(tasks.map((t) => [t.id, sanitize(t.id)]));
      const slotIdMap = new Map(slots.map((s) => [s.id, sanitize(s.id)]));
    
      // Binary variable x_i_j = 1 if task i assigned to slot j
      for (const task of tasks) {
        for (const slot of slots) {
          if (slot.durationMinutes >= task.durationMinutes) {
            const varName = `x${taskIdMap.get(task.id)}${slotIdMap.get(slot.id)}`;
            variables.push({ name: varName, type: "binary" });
    
            // Objective: priority × energy match
            const match = energyMatch[task.energyRequired]?.[slot.energyLevel] ?? 0.5;
            objective[varName] = task.priority * match;
          }
        }
      }
    
      // Each task assigned at most once
      for (const task of tasks) {
        const coefficients: Record<string, number> = {};
        for (const slot of slots) {
          const varName = `x${taskIdMap.get(task.id)}${slotIdMap.get(slot.id)}`;
          if (variables.some((v) => v.name === varName)) {
            coefficients[varName] = 1;
          }
        }
        if (Object.keys(coefficients).length > 0) {
          constraints.push({ name: `task${taskIdMap.get(task.id)}`, coefficients, upper: 1 });
        }
      }
    
      // Each slot used at most once
      for (const slot of slots) {
        const coefficients: Record<string, number> = {};
        for (const task of tasks) {
          const varName = `x${taskIdMap.get(task.id)}${slotIdMap.get(slot.id)}`;
          if (variables.some((v) => v.name === varName)) {
            coefficients[varName] = 1;
          }
        }
        if (Object.keys(coefficients).length > 0) {
          constraints.push({ name: `slot${slotIdMap.get(slot.id)}`, coefficients, upper: 1 });
        }
      }
    
      if (variables.length === 0) {
        return { assignments: [], unscheduled: tasks.map((t) => t.id), totalScore: 0 };
      }
    
      const result = await solve({
        direction: "maximize",
        objective,
        variables,
        constraints,
      });
    
      // Build reverse maps: sanitized → original ID
      const reverseTaskMap = new Map(tasks.map((t) => [sanitize(t.id), t.id]));
      const reverseSlotMap = new Map(slots.map((s) => [sanitize(s.id), s.id]));
    
      const assignments: Array<{ taskId: string; slotId: string; score: number }> = [];
      const scheduledTasks = new Set<string>();
    
      if (result.status === "optimal") {
        for (const [varName, value] of Object.entries(result.solution)) {
          if (value > 0.5 && varName.startsWith("x")) {
            // Find which task+slot this variable represents
            const suffix = varName.slice(1); // remove leading "x"
            for (const [sanTask, origTask] of reverseTaskMap) {
              if (suffix.startsWith(sanTask)) {
                const sanSlot = suffix.slice(sanTask.length);
                const origSlot = reverseSlotMap.get(sanSlot);
                if (origSlot) {
                  scheduledTasks.add(origTask);
                  assignments.push({
                    taskId: origTask,
                    slotId: origSlot,
                    score: objective[varName] ?? 0,
                  });
                  break;
                }
              }
            }
          }
        }
      }
    
      const unscheduled = tasks.filter((t) => !scheduledTasks.has(t.id)).map((t) => t.id);
    
      return {
        assignments,
        unscheduled,
        totalScore: result.objectiveValue,
      };
    }
  • Type definitions: Task, TimeSlot, and ScheduleResult interfaces used by optimizeSchedule.
    export interface Task {
      id: string;
      name: string;
      durationMinutes: number;
      priority: number; // Higher = more important
      deadline?: number; // Unix timestamp
      energyRequired: "high" | "medium" | "low";
      category?: string;
    }
    
    export interface TimeSlot {
      id: string;
      startTime: number; // Unix timestamp
      durationMinutes: number;
      energyLevel: "high" | "medium" | "low";
    }
    
    export interface ScheduleResult {
      assignments: Array<{ taskId: string; slotId: string; score: number }>;
      unscheduled: string[];
      totalScore: number;
    }
  • The MCP tool handler case that dispatches 'solve_schedule' requests, extracts tasks and slots from args, and calls optimizeSchedule.
    case "solve_schedule": {
      const { tasks, slots } = args as { tasks: Parameters<typeof optimizeSchedule>[0]; slots: Parameters<typeof optimizeSchedule>[1] };
      const result = await optimizeSchedule(tasks, slots);
      return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
    }
  • Input schema for the 'solve_schedule' tool with tasks (id, name, durationMinutes, priority, energyRequired) and slots (id, startTime, durationMinutes, energyLevel) properties.
    {
      name: "solve_schedule",
      description:
        "Optimal task scheduling. Given tasks (with priority, duration, energy requirement) and time slots (with available energy), finds the assignment that maximizes priority-weighted completion with energy matching. Use for daily planning, sprint planning, or resource scheduling.",
      inputSchema: {
        type: "object" as const,
        properties: {
          tasks: {
            type: "array",
            items: {
              type: "object",
              properties: {
                id: { type: "string" },
                name: { type: "string" },
                durationMinutes: { type: "number" },
                priority: { type: "number", description: "Higher = more important" },
                energyRequired: { type: "string", enum: ["high", "medium", "low"] },
              },
              required: ["id", "name", "durationMinutes", "priority", "energyRequired"],
            },
          },
          slots: {
            type: "array",
            items: {
              type: "object",
              properties: {
                id: { type: "string" },
                startTime: { type: "number" },
                durationMinutes: { type: "number" },
                energyLevel: { type: "string", enum: ["high", "medium", "low"] },
              },
              required: ["id", "startTime", "durationMinutes", "energyLevel"],
            },
          },
        },
        required: ["tasks", "slots"],
      },
  • Import of the optimizeSchedule function from constraintOptimizer into the MCP server.
      optimizeSchedule,
    } from "../../apps/api/src/services/oracle/algorithms/constraintOptimizer.js";
Behavior4/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, etc. The description adds 'Deterministic' behavior, which is beyond annotations. It does not contradict annotations. It could detail edge cases but is sufficient given annotation coverage.

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?

The description is three sentences, efficient and front-loaded with the main action and key concepts. No unnecessary words or repetition.

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 the tool's moderate complexity (two array parameters with nested objects), annotations covering safety and idempotency, and an output schema (present), the description provides sufficient context: purpose, selection criteria, and behavioral note (deterministic). It does not need to explain return values due to output schema.

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

Parameters3/5

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

Schema description coverage is 0%, so the description should compensate. It adds the key semantic of 'matching task energy requirements with slot energy levels' which explains the 'energyRequired' and 'energyLevel' fields. However, it doesn't elaborate on task 'priority', slot 'duration', or other properties beyond what names and schema describe.

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?

The description clearly states the verb (Assign tasks to time slots) and resource (schedule), and explicitly distinguishes from siblings 'solve_constraints' and 'plan_pathfind' by naming them and specifying different use cases (general resource allocation, sequence/route problems).

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

Usage Guidelines5/5

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

The description provides explicit when-to-use guidance ('Use specifically for task→slot assignment with energy matching') and when-not-to-use by naming alternatives for different problem types. It also lists concrete applications (deep-work scheduling, shift planning, exercise scheduling).

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/Whatsonyourmind/oraclaw'

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