Skip to content

Commit 0822463

Browse files
authored
Merge pull request #9 from AgentWorkforce/feature/issue-7-schema-enhancements
feat: implement Agent Trace integration and Issue #7 schema enhancements
2 parents c9ce280 + e3fe03b commit 0822463

18 files changed

Lines changed: 1111 additions & 11 deletions
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"id": "traj_cuuwpd2q5rr4",
3+
"version": 1,
4+
"task": {
5+
"title": "Implement Agent Trace integration (PR #8)"
6+
},
7+
"status": "completed",
8+
"startedAt": "2026-01-30T11:11:11.822Z",
9+
"agents": [
10+
{
11+
"name": "Multi-agent swarm",
12+
"role": "lead",
13+
"joinedAt": "2026-01-30T11:11:11.850Z"
14+
}
15+
],
16+
"chapters": [
17+
{
18+
"id": "chap_hi1s7ri5sozc",
19+
"title": "Initial work",
20+
"agentName": "Multi-agent swarm",
21+
"startedAt": "2026-01-30T11:11:11.850Z",
22+
"events": [],
23+
"endedAt": "2026-01-30T11:12:26.359Z"
24+
}
25+
],
26+
"commits": [],
27+
"filesChanged": [],
28+
"projectId": "/Users/khaliqgant/Projects/agent-workforce/trajectories",
29+
"tags": [],
30+
"_trace": {
31+
"startRef": "57465dbebfe80a229023baaa50484b8c70a14457",
32+
"endRef": "9ef63dc773f6f597bd4d92340e0543e41b16528e",
33+
"traceId": "trace_imxymv6f8x4b"
34+
},
35+
"completedAt": "2026-01-30T11:12:26.359Z",
36+
"retrospective": {
37+
"summary": "Implemented Agent Trace integration for PR #8. Added TraceRecord types, git state capture, trace generation logic, and CLI integration for start/complete/show commands. Updated README with Agent Trace section.",
38+
"approach": "Standard approach",
39+
"confidence": 0.9
40+
}
41+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Trajectory: Implement Agent Trace integration (PR #8)
2+
3+
> **Status:** ✅ Completed
4+
> **Confidence:** 90%
5+
> **Started:** January 30, 2026 at 12:11 PM
6+
> **Completed:** January 30, 2026 at 12:12 PM
7+
8+
---
9+
10+
## Summary
11+
12+
Implemented Agent Trace integration for PR #8. Added TraceRecord types, git state capture, trace generation logic, and CLI integration for start/complete/show commands. Updated README with Agent Trace section.
13+
14+
**Approach:** Standard approach
15+
16+
---
17+
18+
## Chapters
19+
20+
### 1. Initial work
21+
*Agent: Multi-agent swarm*
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"version": 1,
3+
"id": "trace_imxymv6f8x4b",
4+
"timestamp": "2026-01-30T11:12:26.413Z",
5+
"trajectory": "traj_cuuwpd2q5rr4",
6+
"files": [
7+
{
8+
"path": "README.md",
9+
"conversations": [
10+
{
11+
"contributor": {
12+
"type": "agent",
13+
"model": "unknown"
14+
},
15+
"ranges": [
16+
{
17+
"start_line": 33,
18+
"end_line": 62,
19+
"revision": "9ef63dc773f6f597bd4d92340e0543e41b16528e"
20+
}
21+
]
22+
}
23+
]
24+
}
25+
]
26+
}

.trajectories/index.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": 1,
3+
"lastUpdated": "2026-01-30T11:12:26.445Z",
4+
"trajectories": {
5+
"traj_cuuwpd2q5rr4": {
6+
"title": "Implement Agent Trace integration (PR #8)",
7+
"status": "completed",
8+
"startedAt": "2026-01-30T11:11:11.822Z",
9+
"completedAt": "2026-01-30T11:12:26.359Z",
10+
"path": "/Users/khaliqgant/Projects/agent-workforce/trajectories/.trajectories/completed/2026-01/traj_cuuwpd2q5rr4.json"
11+
}
12+
}
13+
}

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,30 @@ Works with any task system: Beads, Linear, Jira, GitHub Issues, or standalone. T
3333
### Integration Ready
3434
- Complements [claude-mem](https://github.com/thedotmack/claude-mem) for observation-level memory
3535
- Integrates with [agent-relay](https://github.com/khaliqgant/agent-relay) for multi-agent messaging
36+
- **Agent Trace integration** - Automatic code attribution following [agent-trace.dev](https://agent-trace.dev) spec
37+
38+
### Code Attribution (Agent Trace)
39+
40+
Trajectories automatically generate [Agent Trace](https://agent-trace.dev) records that attribute code changes to AI agents:
41+
42+
```bash
43+
trail start "Implement auth module"
44+
# ... agent writes code, makes commits ...
45+
trail complete --summary "Added JWT auth" --confidence 0.85
46+
47+
# View trace attribution
48+
trail show traj_abc123 --trace
49+
```
50+
51+
**What you get:**
52+
- `.trace.json` files saved alongside each trajectory
53+
- Line-level attribution of which code was AI-generated
54+
- Model identification (Claude, GPT, etc.)
55+
- Git revision tracking for change history
56+
57+
**Zero configuration required** - traces are generated automatically when completing trajectories in a git repository.
58+
59+
See the full [Agent Trace Integration Spec](./docs/specs/agent-trace-integration.md) for details.
3660

3761
## Use Cases
3862

biome.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
"*.md",
2929
".beads",
3030
".openskills",
31-
".claude"
31+
".claude",
32+
".agent-relay",
33+
".trajectories"
3234
]
3335
}
3436
}

src/cli/commands/complete.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,43 @@
22
* trail complete command
33
*/
44

5+
import { existsSync } from "node:fs";
6+
import { mkdir, writeFile } from "node:fs/promises";
7+
import { join } from "node:path";
58
import type { Command } from "commander";
9+
import { generateTrace, getGitHead } from "../../core/trace.js";
610
import { completeTrajectory } from "../../core/trajectory.js";
11+
import type { TraceRecord, Trajectory } from "../../core/types.js";
712
import { FileStorage } from "../../storage/file.js";
813

14+
/**
15+
* Save trace file alongside the trajectory
16+
*/
17+
async function saveTraceFile(
18+
trajectory: Trajectory,
19+
trace: TraceRecord,
20+
): Promise<void> {
21+
// Determine trajectory file location based on status
22+
const dataDir = process.env.TRAJECTORIES_DATA_DIR;
23+
const baseDir = dataDir ? dataDir : join(process.cwd(), ".trajectories");
24+
const completedDir = join(baseDir, "completed");
25+
26+
const date = new Date(trajectory.completedAt ?? trajectory.startedAt);
27+
const monthDir = join(
28+
completedDir,
29+
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`,
30+
);
31+
32+
// Ensure directory exists
33+
if (!existsSync(monthDir)) {
34+
await mkdir(monthDir, { recursive: true });
35+
}
36+
37+
// Save trace file with .trace.json extension
38+
const tracePath = join(monthDir, `${trajectory.id}.trace.json`);
39+
await writeFile(tracePath, JSON.stringify(trace, null, 2), "utf-8");
40+
}
41+
942
export function registerCompleteCommand(program: Command): void {
1043
program
1144
.command("complete")
@@ -36,16 +69,43 @@ export function registerCompleteCommand(program: Command): void {
3669
throw new Error("Invalid confidence");
3770
}
3871

39-
const completed = completeTrajectory(active, {
72+
let completed = completeTrajectory(active, {
4073
summary: options.summary,
4174
approach: options.approach || "Standard approach",
4275
confidence,
4376
});
4477

78+
// Generate trace if we have a start reference
79+
let trace: TraceRecord | null = null;
80+
if (active._trace?.startRef) {
81+
trace = generateTrace(completed, active._trace.startRef);
82+
if (trace) {
83+
// Update trajectory with final trace reference
84+
const endRef = getGitHead();
85+
completed = {
86+
...completed,
87+
_trace: {
88+
...completed._trace,
89+
startRef: active._trace.startRef,
90+
endRef: endRef ?? undefined,
91+
traceId: trace.id,
92+
},
93+
};
94+
}
95+
}
96+
4597
await storage.save(completed);
4698

99+
// Save trace file alongside trajectory if generated
100+
if (trace) {
101+
await saveTraceFile(completed, trace);
102+
}
103+
47104
console.log(`✓ Trajectory completed: ${completed.id}`);
48105
console.log(` Summary: ${options.summary}`);
49106
console.log(` Confidence: ${Math.round(confidence * 100)}%`);
107+
if (trace) {
108+
console.log(` Trace: ${trace.id} (${trace.files.length} files)`);
109+
}
50110
});
51111
}

src/cli/commands/decision.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ export function registerDecisionCommand(program: Command): void {
3030
}
3131

3232
const alternatives = options.alternatives
33-
? options.alternatives.split(",").map((s: string) => s.trim())
33+
? options.alternatives
34+
.split(",")
35+
.map((s: string) => ({ option: s.trim(), reason: "" }))
3436
: [];
3537

3638
const reasoning = options.reasoning || "";
@@ -49,7 +51,10 @@ export function registerDecisionCommand(program: Command): void {
4951
console.log(` Reasoning: ${reasoning}`);
5052
}
5153
if (alternatives.length > 0) {
52-
console.log(` Alternatives: ${alternatives.join(", ")}`);
54+
const altStrings = alternatives.map(
55+
(a: { option: string }) => a.option,
56+
);
57+
console.log(` Alternatives: ${altStrings.join(", ")}`);
5358
}
5459
});
5560
}

src/cli/commands/show.ts

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@
33
*/
44

55
import { existsSync } from "node:fs";
6+
import { readFile } from "node:fs/promises";
7+
import { join } from "node:path";
68
import type { Command } from "commander";
7-
import type { Decision, Trajectory } from "../../core/types.js";
9+
import type {
10+
Decision,
11+
TraceConversation,
12+
TraceRecord,
13+
Trajectory,
14+
} from "../../core/types.js";
815
import { FileStorage, getSearchPaths } from "../../storage/file.js";
916

1017
/**
@@ -44,11 +51,47 @@ async function findTrajectory(id: string): Promise<Trajectory | null> {
4451
return null;
4552
}
4653

54+
/**
55+
* Find and load the trace file for a trajectory
56+
*/
57+
async function findTraceFile(id: string): Promise<TraceRecord | null> {
58+
const searchPaths = getSearchPaths();
59+
60+
for (const searchPath of searchPaths) {
61+
if (!existsSync(searchPath)) {
62+
continue;
63+
}
64+
65+
const completedDir = join(searchPath, "completed");
66+
if (!existsSync(completedDir)) {
67+
continue;
68+
}
69+
70+
// Search through month directories
71+
try {
72+
const { readdirSync } = await import("node:fs");
73+
const months = readdirSync(completedDir);
74+
for (const month of months) {
75+
const tracePath = join(completedDir, month, `${id}.trace.json`);
76+
if (existsSync(tracePath)) {
77+
const content = await readFile(tracePath, "utf-8");
78+
return JSON.parse(content) as TraceRecord;
79+
}
80+
}
81+
} catch {
82+
// Continue searching
83+
}
84+
}
85+
86+
return null;
87+
}
88+
4789
export function registerShowCommand(program: Command): void {
4890
program
4991
.command("show <id>")
5092
.description("Show trajectory details")
5193
.option("-d, --decisions", "Show decisions only")
94+
.option("-t, --trace", "Show trace information")
5295
.action(async (id: string, options) => {
5396
const trajectory = await findTrajectory(id);
5497

@@ -57,6 +100,55 @@ export function registerShowCommand(program: Command): void {
57100
throw new Error("Trajectory not found");
58101
}
59102

103+
if (options.trace) {
104+
// Show trace information
105+
console.log(`Trace for ${trajectory.task.title}:\n`);
106+
107+
// Show embedded trace reference
108+
if (trajectory._trace) {
109+
console.log("Trace Reference:");
110+
console.log(` Start Ref: ${trajectory._trace.startRef}`);
111+
if (trajectory._trace.endRef) {
112+
console.log(` End Ref: ${trajectory._trace.endRef}`);
113+
}
114+
if (trajectory._trace.traceId) {
115+
console.log(` Trace ID: ${trajectory._trace.traceId}`);
116+
}
117+
console.log("");
118+
}
119+
120+
// Load and display trace file
121+
const trace = await findTraceFile(id);
122+
if (trace) {
123+
console.log("Trace Details:");
124+
console.log(` ID: ${trace.id}`);
125+
console.log(` Timestamp: ${trace.timestamp}`);
126+
console.log(` Files: ${trace.files.length}`);
127+
console.log("");
128+
129+
if (trace.files.length > 0) {
130+
console.log("Modified Files:");
131+
for (const file of trace.files) {
132+
const rangeCount = file.conversations.reduce(
133+
(sum: number, conv: TraceConversation) =>
134+
sum + conv.ranges.length,
135+
0,
136+
);
137+
const model =
138+
file.conversations[0]?.contributor.model ?? "unknown";
139+
console.log(` • ${file.path}`);
140+
console.log(` Ranges: ${rangeCount}, Model: ${model}`);
141+
}
142+
}
143+
} else if (!trajectory._trace) {
144+
console.log("No trace information available");
145+
console.log(
146+
"Trace is captured when starting a trajectory in a git repo",
147+
);
148+
}
149+
return;
150+
}
151+
60152
if (options.decisions) {
61153
// Show decisions only
62154
const decisions = extractDecisions(trajectory);
@@ -72,7 +164,10 @@ export function registerShowCommand(program: Command): void {
72164
console.log(` Chose: ${decision.chosen}`);
73165
console.log(` Reasoning: ${decision.reasoning}`);
74166
if (decision.alternatives.length > 0) {
75-
console.log(` Alternatives: ${decision.alternatives.join(", ")}`);
167+
const altStrings = decision.alternatives.map((a) =>
168+
typeof a === "string" ? a : a.option,
169+
);
170+
console.log(` Alternatives: ${altStrings.join(", ")}`);
76171
}
77172
console.log("");
78173
}

0 commit comments

Comments
 (0)