Skip to main content
Glama

Simulation Status

simulation_status
Read-only

Check the progress of a running or completed simulation. Long-polls up to 50 seconds for state changes and includes a full prediction report upon completion.

Instructions

Check the progress of a running or completed simulation. Long-polls by default — blocks up to 50s waiting for a state change (phase transition, new round, new actions, completion). When state=COMPLETED, includes the full prediction report inline.

Lifecycle: CREATED → GRAPH_BUILDING → GENERATING_PROFILES → READY → SIMULATING → COMPLETED/FAILED/CANCELLED/INTERRUPTED.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
simulation_idYesThe simulation ID returned by create_simulation
detailedNoInclude recent agent actions with content in the response
waitNoLong-poll: block up to 50s waiting for the next state change. Default true. Set false for immediate snapshot.

Implementation Reference

  • Registration of the 'simulation_status' tool with the MCP server, including the async handler that supports both immediate snapshots and long-polling (up to 50s) via SSE events. Also contains helper functions waitForChange and formatRichStatus.
    export function registerSimulationStatus(
      server: McpServer,
      client: MirofishClient,
    ): void {
      server.registerTool(
        "simulation_status",
        {
          title: "Simulation Status",
          description:
            "Check the progress of a running or completed simulation. Long-polls by default " +
            "— blocks up to 50s waiting for a state change (phase transition, new round, new " +
            "actions, completion). When state=COMPLETED, includes the full prediction report " +
            "inline.\n\n" +
            "Lifecycle: CREATED → GRAPH_BUILDING → GENERATING_PROFILES → READY → SIMULATING → " +
            "COMPLETED/FAILED/CANCELLED/INTERRUPTED.",
          inputSchema,
          annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
        },
        async (args) => {
          try {
            const wait = args.wait !== false;
            const detailed = args.detailed ?? false;
    
            // Immediate snapshot path
            if (!wait) {
              const snapshot = await client.getStatus(args.simulation_id);
              const rich = await formatRichStatus(client, snapshot, detailed);
              return {
                content: [{ type: "text" as const, text: JSON.stringify(rich, null, 2) }],
              };
            }
    
            // Long-poll path: snapshot → wait for next event (up to 50s).
            const initial = await client.getStatus(args.simulation_id);
            if (isTerminal(initial.state)) {
              const rich = await formatRichStatus(client, initial, detailed);
              return {
                content: [{ type: "text" as const, text: JSON.stringify(rich, null, 2) }],
              };
            }
    
            // Open SSE; wait for STATE_CHANGED / ROUND_END / terminal, or 50s timeout.
            const stream: MirofishEventStream = client.subscribeEvents(
              args.simulation_id,
              initial.last_event_id,
            );
            const changedSnapshot = await waitForChange(stream, initial, 50_000);
            stream.close();
    
            const final = changedSnapshot ?? initial;
            const rich = await formatRichStatus(client, final, detailed);
            return {
              content: [{ type: "text" as const, text: JSON.stringify(rich, null, 2) }],
            };
          } catch (err) {
            throw toMcpError(err);
          }
        },
      );
    }
    
    /**
     * Wait for the next meaningful event on the SSE stream (or timeout).
     * Returns a fresh snapshot if the sim state changed, else null.
     */
    async function waitForChange(
      stream: MirofishEventStream,
      initial: SimSnapshot,
      timeoutMs: number,
    ): Promise<SimSnapshot | null> {
      return new Promise<SimSnapshot | null>((resolve) => {
        const timeout = setTimeout(() => resolve(null), timeoutMs);
        let settled = false;
    
        const settle = (result: SimSnapshot | null) => {
          if (settled) return;
          settled = true;
          clearTimeout(timeout);
          resolve(result);
        };
    
        // STATE_CHANGED → fetch fresh snapshot and return
        stream.on("STATE_CHANGED", async (evt: any) => {
          try {
            const sim_id = evt?.sim_id ?? initial.simulation_id;
            const { MirofishClient } = await import("../client/mirofish-client.js");
            // We don't have client here; use the embedded sim_id via re-fetch
            // through the same endpoint. (The actual client is passed via closure
            // in the outer handler — simpler to re-open.)
            // Emit sentinel to let outer handler re-fetch.
            settle({ ...initial, _changed: true } as unknown as SimSnapshot);
          } catch {
            settle(null);
          }
        });
    
        // terminal or error → same treatment
        stream.on("terminal", () => settle({ ...initial, _changed: true } as unknown as SimSnapshot));
        stream.on("ERROR", () => settle({ ...initial, _changed: true } as unknown as SimSnapshot));
        stream.on("ROUND_END", () => settle({ ...initial, _changed: true } as unknown as SimSnapshot));
        stream.on("close", () => settle(null));
      });
    }
    
    /**
     * Build the RichSimulationStatus response from a SimSnapshot.
     * Pulls recent_posts for narration material and (on COMPLETED) fetches the
     * report markdown so Claude Desktop can render it as an artifact.
     */
    async function formatRichStatus(
      client: MirofishClient,
      snapshot: SimSnapshot,
      detailed: boolean,
    ): Promise<RichSimulationStatus> {
      // If we got a sentinel from waitForChange, re-fetch a fresh snapshot.
      const anySnap = snapshot as unknown as { _changed?: boolean; simulation_id: string };
      let fresh = snapshot;
      if (anySnap._changed) {
        try {
          fresh = await client.getStatus(snapshot.simulation_id);
        } catch {
          // keep original
        }
      }
    
      const totalActions = fresh.twitter_actions_count + fresh.reddit_actions_count;
      const phaseLabel = PHASE_DISPLAY[fresh.state] ?? fresh.state;
      const roundLine =
        fresh.total_rounds > 0
          ? `Round ${fresh.current_round}/${fresh.total_rounds} — ${totalActions} actions so far.`
          : `${phaseLabel}.`;
    
      // recent_posts for narration
      const posts = (fresh.recent_posts ?? [])
        .filter((p) => p.action_args?.content)
        .slice(0, 8)
        .map((p) => ({
          agent: p.agent_name ?? `Agent ${p.agent_id ?? "?"}`,
          content: String(p.action_args?.content ?? ""),
          platform: p.platform,
          round: p.round_num,
        }));
    
      const rich: RichSimulationStatus = {
        simulation_id: fresh.simulation_id,
        state: fresh.state,
        phase: fresh.phase ?? fresh.state.toLowerCase(),
        progress_percent: fresh.progress_percent ?? 0,
        current_round: fresh.current_round,
        total_rounds: fresh.total_rounds,
        twitter_actions: fresh.twitter_actions_count,
        reddit_actions: fresh.reddit_actions_count,
        total_actions: totalActions,
        message: roundLine,
        error: fresh.error,
      };
    
      if (posts.length > 0) {
        rich.recent_posts = posts;
        rich.narration_hint = fresh.state === "SIMULATING" ? NARRATION_HINT_ACTIVE : undefined;
      } else if (fresh.state === "SIMULATING") {
        rich.narration_hint = NARRATION_HINT_WARMUP;
      }
    
      // On COMPLETED, fetch and embed the report so Claude Desktop can render it.
      if (fresh.state === "COMPLETED") {
        try {
          const report = await client.getOrGenerateReport(fresh.simulation_id);
          rich.report_markdown = report.markdown_content;
          rich.report_summary = report.outline?.summary;
          if (rich.report_markdown) {
            rich.display_instructions =
              "The full prediction report is included below as markdown. Output the markdown " +
              "directly to the user — Claude Desktop will render it as an artifact in the side panel. " +
              "Do not summarize or truncate.";
          }
        } catch {
          // Report still generating — leave field empty, Claude will poll again.
        }
      }
    
      return rich;
    }
  • Output type definition for the simulation_status tool response, including state, round info, recent posts for narration, and optional report markdown on completion.
    export interface RichSimulationStatus {
      simulation_id: string;
      state: SimState;
      phase: string;
      progress_percent: number;
      current_round: number;
      total_rounds: number;
      twitter_actions: number;
      reddit_actions: number;
      total_actions: number;
      message: string;
    
      recent_posts?: Array<{
        agent: string;
        content: string;
        platform?: string;
        likes?: number;
        round?: number;
      }>;
      narration_hint?: string;
    
      report_markdown?: string;
      report_summary?: string;
      display_instructions?: string;
    
      error?: string | null;
    }
  • Input schema (Zod) for the simulation_status tool: simulation_id (required), detailed (optional boolean), wait (optional boolean, default true).
    const inputSchema = {
      simulation_id: z.string().describe("The simulation ID returned by create_simulation"),
      detailed: z
        .coerce.boolean()
        .optional()
        .describe("Include recent agent actions with content in the response"),
      wait: z
        .coerce.boolean()
        .optional()
        .describe(
          "Long-poll: block up to 50s waiting for the next state change. Default true. Set false for immediate snapshot.",
        ),
    };
  • Tool registration entry point — registerSimulationStatus is imported and called in registerAllTools.
    import { registerSimulationStatus } from "./simulation-status.js";
    import { registerGetReport } from "./get-report.js";
    import { registerInterviewAgent } from "./interview-agent.js";
    import { registerListSimulations } from "./list-simulations.js";
    import { registerSearchSimulations } from "./search-simulations.js";
    import { registerUploadDocument } from "./upload-document.js";
    import { registerSimulationData } from "./simulation-data.js";
    import { registerCancelSimulation } from "./cancel-simulation.js";
    
    export function registerAllTools(server: McpServer, client: MirofishClient): void {
      registerCreateSimulation(server, client);
      registerSimulationStatus(server, client);
      registerGetReport(server, client);
      registerInterviewAgent(server, client);
      registerListSimulations(server, client);
      registerSearchSimulations(server, client);
      registerUploadDocument(server, client);
      registerSimulationData(server, client);
      registerCancelSimulation(server, client);
    }
  • SimSnapshot interface — the raw data from the backend that formatRichStatus transforms into RichSimulationStatus.
    export interface SimSnapshot {
      simulation_id: string;
      project_id: string;
      graph_id?: string | null;
      state: SimState;
    
      // Round tracking
      current_round: number;
      total_rounds: number;
      simulated_hours: number;
      total_simulation_hours: number;
      twitter_current_round: number;
      reddit_current_round: number;
      twitter_simulated_hours: number;
      reddit_simulated_hours: number;
      twitter_running: boolean;
      reddit_running: boolean;
      twitter_actions_count: number;
      reddit_actions_count: number;
      twitter_completed: boolean;
      reddit_completed: boolean;
    
      enable_twitter: boolean;
      enable_reddit: boolean;
    
      process_pid?: number | null;
      entities_count: number;
      profiles_count: number;
      config_generated: boolean;
      config_reasoning: string;
    
      started_at?: string | null;
      updated_at: string;
      completed_at?: string | null;
      error?: string | null;
    
      recent_actions: AgentActionRecord[];
    
      // Derived fields added by the /status endpoint
      phase: string;
      progress_percent: number;
      is_terminal: boolean;
      last_event_id?: number;
      recent_posts?: Array<{
        round_num?: number;
        timestamp?: string;
        platform?: string;
        agent_id?: number;
        agent_name?: string;
        action_type?: string;
        action_args?: { content?: string };
      }>;
    }
Behavior5/5

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

Beyond annotations (readOnlyHint, destructiveHint), the description discloses long-polling timeout (50s), lifecycle states, and that completed status includes the full prediction report. No contradictions with annotations.

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?

Two concise sentences plus a lifecycle bullet list. Front-loaded with purpose, no wasted words. The structure is clear and scannable.

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

Completeness4/5

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

With no output schema, the description adequately explains what to expect (status, report on completion, polling mechanics). It covers lifecycle and key behaviors, though error handling or exact response fields are omitted.

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% (baseline 3). The description adds context: 'detailed' includes agent actions with content, 'wait' defaults to true and blocks up to 50s, and simulation_id references create_simulation. This provides value beyond the 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?

The description clearly states the tool checks simulation progress, with specific verb 'Check' and resource 'progress of a running or completed simulation'. It distinguishes from sibling tools like create_simulation and cancel_simulation by focusing on status and polling behavior.

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?

The description explains long-polling behavior and lifecycle states, implying use for monitoring state changes or waiting for completion. However, it does not explicitly contrast with alternative tools like get_report or simulation_data for retrieving full results or historical data.

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/kakarot-dev/deepmiro'

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