Skip to content

AI simulation cannot be triggered via API — human Railway operator required, agents are permanently read-only #3734

Description

@tg12

Summary

Simulation runs — described in WorldMonitor's product as AI-driven scenario forecasting — can only be triggered by a human operator running node scripts/process-simulation-tasks.mjs --once directly in the Railway environment. No HTTP endpoint exists. Agents and API consumers can read simulation outcomes but cannot initiate a simulation run. For a feature marketed as agent-native AI analysis, the AI cannot actually initiate the analysis.

The gap

enqueueSimulationTask(runId) and runSimulationWorker are exported from scripts/seed-forecasts.mjs and are callable from Node processes only. The HTTP API has no surface to trigger them.

Capability matrix:

Action Human operator Agent / API caller
Read latest simulation outcome
Read outcome pointer from Redis
Trigger a new simulation run ✓ (Railway CLI) ✗ — no endpoint
Check if a run is in progress ✓ (logs)
Verify a specific runId completed ✗ — runId filter is a no-op

Related: the runId filter in getSimulationOutcome is also documented as a no-op (todo #29), meaning even if an agent could trigger a run, it cannot verify its specific run completed.

Impact on the product claim

WorldMonitor's AI Market Implications panel and agent-native MCP tools are built around the premise that an AI agent can analyze a developing situation and run forward simulations. With no HTTP trigger, the workflow is:

  1. Agent detects an event
  2. Agent reads the latest (stale) simulation outcome
  3. Agent cannot trigger a fresh simulation

The simulation system is read-only for every consumer except a human with Railway terminal access.

Proposed fix

Add POST /api/forecast/v1/trigger-simulation following the existing RPC handler pattern:

// server/worldmonitor/forecast/v1/trigger-simulation.ts
export default async function handler(ctx) {
  const isPremium = await isCallerPremium(ctx.request);
  if (!isPremium) return errorResponse('Premium required', 403);

  // Rate limit: 1 trigger per 5 minutes per identity
  const limiter = Ratelimit.slidingWindow(1, '5 m');
  const { success } = await limiter.limit(getCallerIdentity(ctx.request));
  if (!success) return errorResponse('Rate limited', 429);

  const latest = await redis.get(SIMULATION_PACKAGE_LATEST_KEY);
  if (!latest) return jsonResponse({ queued: false, reason: 'no_package' });

  const runId = (latest as any).runId ?? crypto.randomUUID();
  await enqueueSimulationTask(runId);
  return jsonResponse({ queued: true, runId });
}

The actual execution remains Railway-side (existing poll loop picks it up). This endpoint only enqueues the task and returns the runId for polling.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions