`activeTrajectories` is a bare in-process `Map` set at `trajectory-start` and only ever persisted at `trajectory-end` — a pair split across MCP server processes (a respawn, a second client) always returns "Trajectory not found"
- Dominant language
- TypeScript
- Stars
- 72.7k
- Forks
- 8.6k
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 83
Description
# `activeTrajectories` is a bare in-process `Map` set at `trajectory-start` and only ever persisted at `trajectory-end` — a pair split across MCP server processes (a respawn, a second client) always returns "Trajectory not found"
**Filing note:** this issue is adjacent to, but distinct from, the already-drafted `STATS-sona-block-per-process-drift.md` in this same directory, which covers the same `activeTrajectories` map as one of three folded findings. That draft focuses on the resulting stats-reporting confusion (`sona.trajectoriesTotal` reads low next to a large persisted `global` count). This draft focuses on the specific failure mode of a `trajectory-start`/`trajectory-end` pair being split across two different MCP server processes — a real, observed, non-hypothetical event on this install (ps-confirmed respawns) — and proposes the fix as a design choice rather than restating the stats-drift framing. If the maintainer prefers one issue, these two drafts can be merged; left separate here because the specimens and the proposed remedies differ.
**Environment**
- ruflo 3.38.19
- node v24.6.0
- macOS 25.6
- agentdb 3.0.0-alpha.20
- plugin ruflo-core 0.2.6
- plugin ruflo-metaharness 0.1.1
**Steps to reproduce**
1. Call `hooks_intelligence_trajectory-start` from one MCP client/session; note the returned `trajectoryId`.
2. Let that MCP server process end (client disconnects, session respawns, or a new `ruflo mcp start` process is launched for a different window) before calling `trajectory-end`.
3. Call `hooks_intelligence_trajectory-end` with the same `trajectoryId` from the new process.
4. Observe the response.
5. Separately: call `hooks_intelligence-reset` mid-session and observe its effect on any trajectories that were started but not yet ended in that same process.
**Observed**
`trajectory-end` returns `{ persisted: false, note: "Trajectory not found" }` (and no learning update) whenever the `trajectoryId` it is given was not set by a `trajectory-start` call in the *same* process's in-memory `activeTrajectories` `Map`. This is not a hypothetical race: on this install, new `ruflo mcp start` processes were observed spawning mid-session (each new MCP client connection / lazy-mcp reconnect) with distinct process-start timestamps (`ps -axo pid,lstart,command` specimens at 10:05:58, 10:34:14, and 11:04:04 on 2026-09-05) — any `trajectory-start`/`trajectory-end` pair split across one of these respawns loses its trajectory. Live specimens: a team lead's door started `traj-1788566620421-gnzpdu` via `trajectory-start`; a different seat's session later called `trajectory-end` with that same id and received `{trajectoryId:'traj-1788566620421-gnzpdu', success:true, ended:'2026-09-05T01:04:17.231Z', persisted:false, trajectory:null, note:'Trajectory not found'}` (`task/task-1788566146255-6q61bc/receipts`, field `learning.trajectoryEnd`). A second, independent specimen: `task/task-1788569777385-gxmfqg/receipts` records `trajectory-start` returning `traj-1788569846875-wn1keh`, then `trajectory-end` on that same id in what the receipt describes as the same logical task but across a window where "an apparent mid-session ruflo server restart" is evidenced by an immediately-preceding `memory_store` timeout/crash — result again `{success:true, persisted:false, note:'Trajectory not found'}`.
Additionally, `hooks_intelligence-reset` unconditionally clears the entire in-process `activeTrajectories` map (`cleared.trajectories = activeTrajectories.size; activeTrajectories.clear();`) — any trajectory that was started but not yet ended at the moment `-reset` is called is lost the same way, with no per-trajectory warning.
**Expected**
A `trajectoryId` returned by `trajectory-start` should either (a) be resolvable from any MCP client/process of the same project — i.e. persisted at start, not only at end — so a `trajectory-start`/`trajectory-end` pair split across a respawn still completes, or (b) the tool's own docs/response shape should say plainly that trajectory ids are single-process-only and must never be handed to, or expected to survive into, a different session. `hooks_intelligence-reset` should either warn when it is about to drop in-flight (started-not-ended) trajectories, or exclude them from the clear.
**Root cause**
`activeTrajectories` is declared as a bare in-memory `Map`, populated by `trajectory-start`, read (and only there persisted downstream) by `trajectory-end`, and unconditionally wiped by `hooks_intelligence-reset` — there is no persistence layer backing the map itself; only the *outcome* of a successful `trajectory-end` call gets written through to the memory bridge.
Upstream at pinned sha `db4991967c45c6f72133dff0bb80b0a492960fc1`, `v3/@claude-flow/cli/src/mcp-tools/hooks-tools.ts`:
```
473:const activeTrajectories = new Map();
...
2749: name: 'hooks_intelligence-reset',
...
2805: cleared.trajectories = activeTrajectories.size;
2806: activeTrajectories.clear();
...
2851: activeTrajectories.set(trajectoryId, trajectory); // trajectory-start
...
2914: const trajectory = activeTrajectories.get(trajectoryId); // trajectory-step
...
2985: const trajectory = activeTrajectories.get(trajectoryId); // trajectory-end
...
3015: activeTrajectories.delete(trajectoryId);
...
3236: : (persistResult.success ? 'Trajectory persisted for future learning' : (persistResult.error || 'Trajectory not found')),
```
Installed dist (ruflo 3.38.19), identical logic — `@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js`:
```
391: const activeTrajectories = new Map();
...
2601: name: 'hooks_intelligence-reset',
...
2656: cleared.trajectories = activeTrajectories.size;
2657: activeTrajectories.clear();
...
2708: activeTrajectories.set(trajectoryId, trajectory); // trajectory-start
...
2775: const trajectory = activeTrajectories.get(trajectoryId); // trajectory-step
...
2845: const trajectory = activeTrajectories.get(trajectoryId); // trajectory-end
...
2872: activeTrajectories.delete(trajectoryId);
...
3086: : (persistResult.success ? 'Trajectory persisted for future learning' : (persistResult.error || 'Trajectory not found')),
```
**Suggested fix — offered as options, this is a design choice, not a decided answer**
1. **Persist at start.** Have `trajectory-start` write a lightweight pending-trajectory record through the same memory bridge `trajectory-end` already uses to persist a completed one, so `trajectory-end` in a different process can look up and complete it instead of relying solely on the in-memory `Map`.
2. **Create-and-close form.** Add a variant of `trajectory-end` (or a new tool) that accepts `task`+`agent`+`success` directly and creates-and-closes a trajectory in one call, for callers who cannot guarantee the same process will see both ends of the pair — this sidesteps the cross-process lookup problem entirely rather than fixing it.
3. **At minimum**, have `hooks_intelligence-reset` report which in-flight trajectory ids it is about to drop (not just a count) so a caller mid-pair gets a chance to notice before losing the pairing.
**Related**
- `STATS-sona-block-per-process-drift.md` (this directory) — the stats-reporting consequence of the same `activeTrajectories` object being per-process; see filing note above for how the two drafts divide the same underlying mechanism.
- #3025 (closed) — "hooks_intelligence_unified-stats reads models.json but reports patterns.json — consistency block emits a false diagnosis" — same general shape (a tool's own consistency-check machinery papering over a real object mismatch) in a different subsystem, cited only as triage precedent.
- Searched `gh api search/issues -f q='repo:ruvnet/ruflo trajectory not found'` (43 results, none matching this specific cross-process pairing failure) — filing fresh.
**Evidence / estate provenance**
`task/task-1788569777385-gxmfqg/receipts` (ns `final`) — `learning` block: `trajectory-start` id `traj-1788569846875-wn1keh`, `trajectory-end` result `{persisted:false, note:'Trajectory not found'}` "apparent mid-session ruflo server restart." `task/task-1788566146255-6q61bc/receipts` (ns `final`) — `learning.trajectoryEnd` block, cross-process specimen quoted above verbatim. `task/task-1788563164755-ticrih/recon-c5-sona-hnsw` (ns `final`) — the `hooks_intelligence-reset` clear-on-reset citation (`:2601` name registration, `:2655-2657` clear call, quoted in that row) and the "estate gap ... nobody fires trajectory-end" framing this draft narrows to the specific cross-process failure mode. All three re-opened and quoted directly this session; the `ps lstart` respawn timestamps (10:05:58, 10:34:14, 11:04:04) are carried forward from the recon row's own prior measurement, not independently re-measured by this drafting pass (this pass ran no process listing — read-only against the evidence rows and the two code trees only, per this task's own bar).
**Deferrals**
The choice between "persist at start" and "create-and-close form" (suggested-fix options 1 and 2) is a genuine design decision for the maintainer, not resolved by this draft — flagged as options, not a recommendation, per this task's own package instruction.
Contributor guide
Research direction
Start with v3/@claude-flow/cli/src/mcp-tools/hooks-tools.ts, especially the activeTrajectories declaration and the trajectory-start, trajectory-end, and hooks_intelligence-reset handlers; compare the installed dist version if needed. Reproduce the cross-process and reset cases described in the issue. Done means the maintainer-approved behavior is implemented and a split start/end pair no longer silently loses the trajectory, or the single-process contract and reset behavior are explicitly documented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- api, backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100