Skip to content

Commit 5ee9c30

Browse files
kulvirgitclaude
andcommitted
feat: add tracing for fingerprint detection and skill selection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7afaff9 commit 5ee9c30

6 files changed

Lines changed: 89 additions & 1 deletion

File tree

packages/opencode/src/altimate/fingerprint/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Filesystem } from "../../util/filesystem"
22
import { Glob } from "../../util/glob"
33
import { Log } from "../../util/log"
4+
import { Tracer } from "../observability/tracing"
45
import path from "path"
56

67
const log = Log.create({ service: "fingerprint" })
@@ -27,6 +28,7 @@ export namespace Fingerprint {
2728
export async function detect(cwd: string, root?: string): Promise<Result> {
2829
if (cached && cached.cwd === cwd) return cached
2930

31+
const startTime = Date.now()
3032
const timer = log.time("detect", { cwd, root })
3133
const tags: string[] = []
3234

@@ -48,6 +50,15 @@ export namespace Fingerprint {
4850
cached = result
4951
timer.stop()
5052
log.info("detected", { tags: unique.join(","), cwd })
53+
54+
Tracer.active?.logSpan({
55+
name: "fingerprint",
56+
startTime,
57+
endTime: Date.now(),
58+
input: { cwd, root },
59+
output: { tags: unique },
60+
})
61+
5162
return result
5263
}
5364

packages/opencode/src/altimate/observability/tracing.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export interface TraceSpan {
4141
spanId: string
4242
parentSpanId: string | null
4343
name: string
44-
kind: "session" | "generation" | "tool" | "text"
44+
kind: "session" | "generation" | "tool" | "text" | "span"
4545
startTime: number
4646
endTime?: number
4747
status: "ok" | "error"
@@ -246,6 +246,11 @@ interface TracerOptions {
246246
}
247247

248248
export class Tracer {
249+
// Global active tracer — set when a session starts, cleared on end.
250+
private static _active: Tracer | null = null
251+
static get active(): Tracer | null { return Tracer._active }
252+
static setActive(tracer: Tracer | null) { Tracer._active = tracer }
253+
249254
private traceId: string
250255
private sessionId: string | undefined
251256
private rootSpanId: string | undefined
@@ -561,6 +566,39 @@ export class Tracer {
561566
if (part.text != null) this.generationText.push(String(part.text))
562567
}
563568

569+
/**
570+
* Log a custom span (e.g., fingerprint detection, skill selection).
571+
* Used for internal operations that aren't LLM generations or tool calls.
572+
*/
573+
logSpan(span: {
574+
name: string
575+
startTime: number
576+
endTime: number
577+
status?: "ok" | "error"
578+
input?: unknown
579+
output?: unknown
580+
attributes?: Record<string, unknown>
581+
}) {
582+
if (!this.rootSpanId) return
583+
try {
584+
this.spans.push({
585+
spanId: randomUUIDv7(),
586+
parentSpanId: this.rootSpanId,
587+
name: span.name,
588+
kind: "span",
589+
startTime: span.startTime,
590+
endTime: span.endTime,
591+
status: span.status ?? "ok",
592+
input: span.input,
593+
output: span.output,
594+
attributes: span.attributes,
595+
})
596+
this.snapshot()
597+
} catch {
598+
// best-effort
599+
}
600+
}
601+
564602
/**
565603
* Build a TraceFile snapshot of the current state (in-progress or complete).
566604
* Used for incremental writes and live viewing.

packages/opencode/src/altimate/skill-selector.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Provider } from "../provider/provider"
66
import { Log } from "../util/log"
77
import type { Skill } from "../skill"
88
import type { Fingerprint } from "./fingerprint"
9+
import { Tracer } from "./observability/tracing"
910

1011
const log = Log.create({ service: "skill-selector" })
1112

@@ -58,12 +59,21 @@ export async function selectSkillsWithLLM(
5859
fingerprint: Fingerprint.Result | undefined,
5960
deps?: SkillSelectorDeps,
6061
): Promise<Skill.Info[]> {
62+
const startTime = Date.now()
63+
6164
// Return cached result if cwd hasn't changed (0ms)
6265
const cwd = fingerprint?.cwd
6366
if (cachedResult && cwd === cachedCwd) {
6467
log.info("returning cached skill selection", {
6568
count: cachedResult.length,
6669
})
70+
Tracer.active?.logSpan({
71+
name: "skill-selection",
72+
startTime,
73+
endTime: Date.now(),
74+
input: { fingerprint: fingerprint?.tags, source: "cache" },
75+
output: { count: cachedResult.length, skills: cachedResult.map((s) => s.name) },
76+
})
6777
return cachedResult
6878
}
6979

@@ -147,11 +157,26 @@ export async function selectSkillsWithLLM(
147157
count: matched.length,
148158
names: matched.map((s) => s.name),
149159
})
160+
Tracer.active?.logSpan({
161+
name: "skill-selection",
162+
startTime,
163+
endTime: Date.now(),
164+
input: { fingerprint: fingerprint?.tags, totalSkills: skills.length, source: "llm" },
165+
output: { count: matched.length, skills: matched.map((s) => s.name) },
166+
})
150167
return cache(matched)
151168
} catch (e) {
152169
log.info("skill selection failed, returning all skills", {
153170
error: e instanceof Error ? e.message : String(e),
154171
})
172+
Tracer.active?.logSpan({
173+
name: "skill-selection",
174+
startTime,
175+
endTime: Date.now(),
176+
status: "error",
177+
input: { fingerprint: fingerprint?.tags, source: "fallback" },
178+
output: { count: skills.length, error: e instanceof Error ? e.message : String(e) },
179+
})
155180
return cache(skills)
156181
}
157182
}

packages/opencode/src/cli/cmd/run.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ You are speaking to a non-technical business executive. Follow these rules stric
720720
variant: args.variant,
721721
prompt: message,
722722
})
723+
if (tracer) Tracer.setActive(tracer)
723724

724725
// Register crash handlers to flush the trace on unexpected exit
725726
const onSigint = () => { tracer?.flushSync("Process interrupted"); process.exit(130) }
@@ -766,6 +767,7 @@ You are speaking to a non-technical business executive. Follow these rules stric
766767

767768
// Finalize trace and save to disk
768769
if (tracer) {
770+
Tracer.setActive(null)
769771
const tracePath = await tracer.endTrace(error)
770772
if (tracePath) {
771773
emit("trace_saved", { path: tracePath })

packages/opencode/src/cli/cmd/tui/worker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ function getOrCreateTracer(sessionID: string): Tracer | null {
9393
? Tracer.withExporters([...tracingExporters], { maxFiles: tracingMaxFiles })
9494
: Tracer.create()
9595
tracer.startTrace(sessionID, {})
96+
Tracer.setActive(tracer)
9697
sessionTracers.set(sessionID, tracer)
9798
return tracer
9899
} catch {

packages/opencode/src/session/prompt.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { decodeDataUrl } from "@/util/data-url"
5353
// altimate_change start - import fingerprint for env-based skill selection
5454
import { Fingerprint } from "../altimate/fingerprint"
5555
import { Config } from "../config/config"
56+
import { Tracer } from "../altimate/observability/tracing"
5657
// altimate_change end
5758
import { Telemetry } from "@/telemetry" // altimate_change — session telemetry
5859

@@ -727,6 +728,16 @@ export namespace SessionPrompt {
727728
system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
728729
}
729730

731+
// altimate_change start - trace system prompt
732+
Tracer.active?.logSpan({
733+
name: "system-prompt",
734+
startTime: Date.now(),
735+
endTime: Date.now(),
736+
input: { agent: agent.name, step },
737+
output: { parts: system.length, content: system.join("\n\n") },
738+
})
739+
// altimate_change end
740+
730741
const result = await processor.process({
731742
user: lastUser,
732743
agent,

0 commit comments

Comments
 (0)