Skip to content

fix(sdk): align WorkflowTrajectory writer with canonical layout + fields#730

Closed
khaliqgant wants to merge 1 commit intomainfrom
fix/sdk-trajectory-writer-canonical
Closed

fix(sdk): align WorkflowTrajectory writer with canonical layout + fields#730
khaliqgant wants to merge 1 commit intomainfrom
fix/sdk-trajectory-writer-canonical

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Apr 13, 2026

Summary

Aligns WorkflowTrajectory (the standalone SDK writer used by the workflow runner) with the canonical agent-trajectories layout and top-level field contract. Defense-in-depth follow-up to AgentWorkforce/trajectories#22, which makes the reader tolerant of the current SDK output.

Why

WorkflowTrajectory writes trajectory JSON directly — it never touches the agent-trajectories library. Over time its output diverged from what FileStorage.save() produces:

  1. Completed files landed in a flat completed/ root instead of completed/YYYY-MM/{id}.json. This is why workforce's .trajectories/completed/ is a flat pile of files — every one of them came through this writer.
  2. Top-level commits, filesChanged, and tags arrays were missing, even though the canonical schema declares them. The reader had to default them on load.

Neither is a correctness bug today (trajectories#22 made the reader tolerant), but the divergence forces the reader to carry legacy-layout fallbacks forever. Aligning the writer lets those fallbacks be removed in a future release.

Changes

  • moveToCompleted() — computes a YYYY-MM bucket from completedAt (falling back to startedAt) and writes the file to completed/<bucket>/{id}.json. Bucket directory is created with mkdir -p.
  • TrajectoryFile interface — declares commits: string[], filesChanged: string[], tags: string[].
  • start() — initializes all three to [] so the written trajectory is canonical-complete from the first flush().

Test plan

  • npm run build (SDK package)
  • npx vitest run src/__tests__/workflow-trajectory.test.ts — 29 passed (27 pre-existing + 2 new)
  • New test: completed file path has exactly one YYYY-MM intermediate directory
  • New test: commits / filesChanged / tags populated as [] after start()
  • Existing readCompletedTrajectoryFile helper now walks completed/ recursively so tests don't hardcode the bucket name

Unrelated full-suite failures in verification.test.ts, step-executor.test.ts, etc. are pre-existing on origin/main and out of scope for this change.

Reader companion

AgentWorkforce/trajectories#22 — relaxes the zod schema on the reader side so ids like traj_<ts>_<hex>, roles like workflow-runner/specialist, and missing commits/filesChanged/projectId/tags no longer fail validation. This SDK PR is the upstream "fix it at source" companion — both can ship independently; the reader PR unblocks the immediate bug (trail compact --all → "No trajectories found"), and this SDK PR makes new trajectories canonical so the reader can stop tolerating legacy shapes in a future release.

🤖 Generated with Claude Code


Open with Devin

The SDK's WorkflowTrajectory is a standalone writer that emits
trajectory JSON without using the `agent-trajectories` library. Its
output diverged from the canonical layout in two visible ways:

1. Completed files landed in a flat `completed/` root instead of
   `completed/YYYY-MM/{id}.json`, forcing downstream readers to grow
   legacy-layout fallbacks.
2. Top-level `commits`, `filesChanged`, and `tags` arrays were omitted,
   even though the canonical schema declares them. Readers had to
   default them at load time.

These are now aligned:

- `moveToCompleted()` computes a `YYYY-MM` bucket from
  `completedAt` (falling back to `startedAt`) and writes into
  `completed/<bucket>/{id}.json`.
- `TrajectoryFile` interface declares `commits: string[]`,
  `filesChanged: string[]`, `tags: string[]`, and `start()` initializes
  them to empty arrays.

Canonical output is defense-in-depth — `agent-trajectories` PR #22
(AgentWorkforce/trajectories#22) makes the reader tolerant of the prior
shapes, so this change is not urgent. It does let the reader shed those
fallbacks over time.

Test coverage:
- Existing `readCompletedTrajectoryFile` helper now walks completed/
  recursively so tests don't hardcode the bucket name.
- New: asserts the completed file path has exactly one `YYYY-MM`
  intermediate directory.
- New: asserts `commits`/`filesChanged`/`tags` are populated as [] on
  `start()`.

All 29 tests in `workflow-trajectory.test.ts` pass. Diff scoped to
two files; unrelated full-suite failures (verification.test.ts,
step-executor, etc.) are pre-existing and out of scope.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +787 to +793
const bucketSource = this.trajectory.completedAt ?? this.trajectory.startedAt;
const bucketDate = new Date(bucketSource);
const monthBucket = `${bucketDate.getUTCFullYear()}-${String(bucketDate.getUTCMonth() + 1).padStart(
2,
'0'
)}`;
const completedDir = path.join(this.dataDir, 'completed', monthBucket);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Completed trajectory path change breaks readCompletedTrajectoryFile in workflow-runner tests

The new moveToCompleted() writes files to completed/YYYY-MM/{id}.json instead of completed/{id}.json, but readCompletedTrajectoryFile in packages/sdk/src/__tests__/workflow-runner.test.ts:204-211 still does a flat readdirSync(completedDir) looking for .json files. It will only find the YYYY-MM subdirectory (not a .json file), return null, and the test at packages/sdk/src/__tests__/workflow-runner.test.ts:848 will crash with Cannot read properties of null (reading 'chapters'). The same pattern affects readLatestTrajectoryFile in tests/integration/broker/utils/workflow-harness.ts:278-302 (flat readdirSync) and the hardcoded flat path assertion at tests/integration/broker/trajectory.test.ts:95.

Prompt for agents
The moveToCompleted() method was changed to write completed trajectory files to completed/YYYY-MM/ subdirectories, but three existing readers of completed trajectories were not updated to walk subdirectories:

1. packages/sdk/src/__tests__/workflow-runner.test.ts (lines 204-211): readCompletedTrajectoryFile does a flat readdirSync on .trajectories/completed/ and filters for .json files. It needs to walk subdirectories like the updated findCompletedTrajectoryJson in workflow-trajectory.test.ts.

2. tests/integration/broker/utils/workflow-harness.ts (lines 278-302): readLatestTrajectoryFile does the same flat scan. When getTrajectory (line 208) passes .trajectories/completed/, it won't find files in YYYY-MM subdirectories.

3. tests/integration/broker/trajectory.test.ts (line 95): Constructs completedPath as .trajectories/completed/{id}.json without the YYYY-MM bucket, so the fs.existsSync check will always fail.

All three need to be updated to handle the new YYYY-MM subdirectory layout, similar to how findCompletedTrajectoryJson was implemented in workflow-trajectory.test.ts.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@khaliqgant
Copy link
Copy Markdown
Member Author

Closing in favor of a comprehensive unification approach.

This PR was a tactical alignment: make WorkflowTrajectory emit the canonical layout (completed/YYYY-MM/) and populate canonical top-level arrays. It fixes the symptom — writer output drifting from the reader's expected shape — without fixing the root cause: the SDK re-implements the trajectory data model instead of using agent-trajectories directly.

@agent-relay/sdk does not currently depend on agent-trajectories, despite the latter shipping a complete public SDK (TrajectoryClient, TrajectorySession, TrajectoryBuilder) at ./sdk. WorkflowTrajectory is a 781-line parallel implementation with its own types, event model, chapter logic, ID format, and file layout. Every change to the canonical schema requires a parallel change here, and drift is inevitable — this PR and AgentWorkforce/trajectories#22 are both manifestations of that drift.

The right fix is to replace WorkflowTrajectory's internals with a thin wrapper around TrajectoryClient / TrajectorySession, mapping workflow-specific events to canonical addChapter / addEvent / addDecision calls. Non-blocking behavior is preserved via try/catch around the delegated calls. Net effect: ~+250 / –700 lines, one source of truth for the trajectory model, drift becomes structurally impossible.

A follow-up PR implementing the unification will land shortly.

Companion reader-tolerance fix (ships independently): AgentWorkforce/trajectories#22

@khaliqgant khaliqgant closed this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant