ruvnet/ruflo · error

Trajectory ${params.trajectoryId} not found

Error message

Trajectory ${params.trajectoryId} not found

What it means

SONAAdapter.recordTrajectoryStep looks up params.trajectoryId in the in-memory activeTrajectories map and throws on a miss. The map is populated only by startTrajectory, and entries are removed by endTrajectory — steps are accepted only for live, started trajectories on the same adapter instance.

Source

Thrown at v3/@claude-flow/integration/src/sona-adapter.ts:273

    return trajectoryId;
  }

  /**
   * Record a step in an active trajectory
   */
  async recordTrajectoryStep(params: {
    trajectoryId: string;
    stepId?: string;
    action: string;
    observation: string;
    reward: number;
    embedding?: number[];
  }): Promise<void> {
    this.ensureInitialized();

    const trajectory = this.activeTrajectories.get(params.trajectoryId);
    if (!trajectory) {
      throw new Error(`Trajectory ${params.trajectoryId} not found`);
    }

    const step: SONATrajectoryStep = {
      stepId: params.stepId || this.generateId('step'),
      action: params.action,
      observation: params.observation,
      reward: params.reward,
      timestamp: Date.now(),
      embedding: params.embedding,
    };

    trajectory.steps.push(step);
    trajectory.totalReward += params.reward;

    this.emit('trajectory-step-recorded', {
      trajectoryId: params.trajectoryId,
      step
    });

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Create the trajectory first and use exactly the id returned by startTrajectory for every subsequent step
  2. Stop recording after endTrajectory — the trajectory is closed by design
  3. Keep start/step/end on the same adapter instance and process; after a restart, start a new trajectory rather than resuming the old id

Example fix

// before
adapter.recordTrajectoryStep({ trajectoryId: 'traj-42', action, observation, reward }); // never started

// after
const trajectoryId = await adapter.startTrajectory({ /* ... */ });
adapter.recordTrajectoryStep({ trajectoryId, action, observation, reward });
Defensive patterns

Strategy: validation

Validate before calling

// Track live ids on the caller side (activeTrajectories is private to the adapter)
const live = new Set<string>();
const trajectoryId = await adapter.startTrajectory(params);
live.add(trajectoryId);
function recordStep(p: StepParams) {
  if (!live.has(p.trajectoryId)) {
    throw new Error(`trajectory not live: ${p.trajectoryId}`);
  }
  return adapter.recordTrajectoryStep(p);
}

Type guard

const isLiveTrajectory = (id: string, live: Set<string>): boolean => live.has(id);

Try / catch

try {
  await adapter.recordTrajectoryStep(step);
} catch (e) {
  if (e instanceof Error && e.message.includes('not found')) {
    // start a new trajectory (or drop the step) — never fabricate a resume of a dead id
  }
  throw e;
}

Prevention

When it happens

Trigger: Recording a step for an id that was never started, for a trajectory already ended (endTrajectory deletes it from the map), or for a trajectory started on a different adapter instance (the map is per-instance and memory-only).

Common situations: Continuing to log steps after calling endTrajectory; a process restart mid-trajectory wiping the in-memory map; splitting start/step/end across serverless invocations or worker processes.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/15cdeb2b84b938d0. Report an issue: GitHub.