@apo-ai/sdk 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,18 +8,18 @@ import {
8
8
  resetApoTracing,
9
9
  withApoRun,
10
10
  withApoRunSync
11
- } from "../chunk-GETY34VI.js";
11
+ } from "../chunk-IPQ6IIEG.js";
12
12
  import {
13
13
  createApoTracer
14
- } from "../chunk-QHRWPVPZ.js";
15
- import "../chunk-HWZVL6TS.js";
14
+ } from "../chunk-CKB3SUDO.js";
15
+ import "../chunk-HLRXQAZ5.js";
16
16
  import {
17
17
  createApoOpenAI
18
- } from "../chunk-6ZRW5IW6.js";
18
+ } from "../chunk-3B3L2H4Y.js";
19
19
  import {
20
20
  createApoAnthropic
21
- } from "../chunk-SZDSIPXC.js";
22
- import "../chunk-GDB6SDGN.js";
21
+ } from "../chunk-43OI6GP7.js";
22
+ import "../chunk-3OQ3JBFJ.js";
23
23
 
24
24
  // src/agent-task/trace-projection/view.ts
25
25
  function invocationOrderKey(obs) {
@@ -314,6 +314,12 @@ async function callSecondJudge(args) {
314
314
  }
315
315
 
316
316
  // src/agent-task/checks/judge.ts
317
+ var JudgeUnavailableError = class extends Error {
318
+ constructor(message) {
319
+ super(message);
320
+ this.name = "JudgeUnavailableError";
321
+ }
322
+ };
317
323
  var VERDICT_FIRST_CONTRACT = 'Respond with ONLY a JSON object: {"pass": true/false, "reasoning": "your reasoning"}';
318
324
  var REASONING_FIRST_CONTRACT = 'Respond with ONLY a JSON object: {"reasoning": "your reasoning", "pass": true/false}';
319
325
  function isJudgeVerdictFirstOverrideEnabled() {
@@ -389,7 +395,92 @@ function parseJudgeJson(raw) {
389
395
  reasoning: "Judge response could not be parsed as JSON \u2014 the verdict is unknown, so this check is treated as a failure. The model's raw response is available in the judge metadata."
390
396
  };
391
397
  }
392
- var JUDGE_TIMEOUT_MS = 12e4;
398
+ var JUDGE_TIMEOUT_MS = 3e5;
399
+ var JUDGE_IDLE_TIMEOUT_MS = 9e4;
400
+ var JUDGE_RETRY_DELAY_MS = 1e3;
401
+ var JUDGE_RETRY_AFTER_CAP_MS = 2e4;
402
+ var JUDGE_MIN_RETRY_BUDGET_MS = 6e4;
403
+ function isRetryableStatus(status) {
404
+ return status === 429 || status >= 500;
405
+ }
406
+ function retryDelayMs(retryAfter) {
407
+ if (!retryAfter) return JUDGE_RETRY_DELAY_MS;
408
+ const seconds = Number(retryAfter);
409
+ const ms = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(retryAfter) - Date.now();
410
+ if (!Number.isFinite(ms)) return JUDGE_RETRY_DELAY_MS;
411
+ return Math.min(Math.max(ms, JUDGE_RETRY_DELAY_MS), JUDGE_RETRY_AFTER_CAP_MS);
412
+ }
413
+ async function readCompletion(response, onData) {
414
+ const contentType = response.headers.get("content-type") ?? "";
415
+ if (!contentType.includes("text/event-stream")) {
416
+ const data = await response.json();
417
+ return {
418
+ text: data.choices?.[0]?.message?.content ?? "",
419
+ usage: data.usage,
420
+ finishReason: data.choices?.[0]?.finish_reason ?? void 0
421
+ };
422
+ }
423
+ let text = "";
424
+ let usage;
425
+ let finishReason;
426
+ let eventType = "message";
427
+ let dataLines = [];
428
+ let finished = false;
429
+ const dispatch = () => {
430
+ const payload = dataLines.join("\n").trim();
431
+ const type = eventType;
432
+ eventType = "message";
433
+ dataLines = [];
434
+ if (payload === "[DONE]") finished = true;
435
+ if (!payload || finished) return;
436
+ onData();
437
+ let chunk;
438
+ try {
439
+ chunk = JSON.parse(payload);
440
+ } catch {
441
+ chunk = void 0;
442
+ }
443
+ if (type === "error" || chunk?.error) {
444
+ const error = chunk?.error;
445
+ const message = typeof error === "string" ? error : error?.message ?? (chunk ? void 0 : payload);
446
+ throw new JudgeUnavailableError(`Judge stream error: ${message ?? "unknown"}`);
447
+ }
448
+ if (!chunk) return;
449
+ const choice = chunk.choices?.[0];
450
+ text += choice?.delta?.content ?? "";
451
+ if (choice?.finish_reason) finishReason = choice.finish_reason;
452
+ if (chunk.usage) usage = chunk.usage;
453
+ };
454
+ const handleLine = (line) => {
455
+ if (line === "") return dispatch();
456
+ if (line.startsWith(":")) return;
457
+ const colon = line.indexOf(":");
458
+ const field = colon === -1 ? line : line.slice(0, colon);
459
+ let value = colon === -1 ? "" : line.slice(colon + 1);
460
+ if (value.startsWith(" ")) value = value.slice(1);
461
+ if (field === "data") dataLines.push(value);
462
+ else if (field === "event") eventType = value;
463
+ };
464
+ const reader = response.body?.getReader();
465
+ if (!reader) return { text, usage, finishReason };
466
+ const decoder = new TextDecoder();
467
+ let buffer = "";
468
+ for (; ; ) {
469
+ const { done, value } = await reader.read();
470
+ if (done) break;
471
+ buffer += decoder.decode(value, { stream: true });
472
+ const lines = buffer.split(/\r\n|\r(?!$)|\n/);
473
+ buffer = lines.pop() ?? "";
474
+ for (const line of lines) {
475
+ handleLine(line);
476
+ if (finished) return { text, usage, finishReason };
477
+ }
478
+ }
479
+ buffer += decoder.decode();
480
+ for (const line of buffer.split(/\r\n|\r|\n/)) if (line) handleLine(line);
481
+ dispatch();
482
+ return { text, usage, finishReason };
483
+ }
393
484
  var prefixQueues = /* @__PURE__ */ new Map();
394
485
  function runWithSharedPrefix(key, task2) {
395
486
  const prev = prefixQueues.get(key) ?? Promise.resolve();
@@ -422,75 +513,155 @@ ${instructionText}`,
422
513
  apiKey: resolveSecondJudgeAPIKey(apiKey)
423
514
  }) : void 0;
424
515
  const cacheKey = `${args.model}\0${briefingText}\0${deliverableText}`;
425
- return runWithSharedPrefix(cacheKey, async () => {
426
- const startedAt = Date.now();
427
- const response = await fetch(`${baseURL}/chat/completions`, {
428
- method: "POST",
429
- signal: AbortSignal.timeout(JUDGE_TIMEOUT_MS),
430
- headers: {
431
- "Content-Type": "application/json",
432
- ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
433
- },
434
- body: JSON.stringify({
435
- model: args.model,
436
- messages: [
516
+ const requestBody = (stream) => JSON.stringify({
517
+ model: args.model,
518
+ messages: [
519
+ {
520
+ role: "system",
521
+ content: [
522
+ { type: "text", text: briefingText },
437
523
  {
438
- role: "system",
439
- content: [
440
- { type: "text", text: briefingText },
441
- {
442
- type: "text",
443
- text: deliverableText,
444
- cache_control: { type: "ephemeral" }
445
- }
446
- ]
524
+ type: "text",
525
+ text: deliverableText,
526
+ cache_control: { type: "ephemeral" }
527
+ }
528
+ ]
529
+ },
530
+ { role: "user", content: instructionText }
531
+ ],
532
+ temperature: 0,
533
+ response_format: { type: "json_object" },
534
+ ...stream ? { stream: true, stream_options: { include_usage: true } } : {}
535
+ });
536
+ const attempt = async (stream, deadline) => {
537
+ const controller = new AbortController();
538
+ const expire = (reason, name) => () => controller.abort(new DOMException(reason, name));
539
+ const total = setTimeout(
540
+ expire(`no complete response within ${JUDGE_TIMEOUT_MS / 1e3}s`, "TimeoutError"),
541
+ Math.max(deadline - Date.now(), 0)
542
+ );
543
+ const stalled = expire(`no data for ${JUDGE_IDLE_TIMEOUT_MS / 1e3}s`, "IdleTimeoutError");
544
+ let idle;
545
+ const onData = () => {
546
+ clearTimeout(idle);
547
+ idle = setTimeout(stalled, JUDGE_IDLE_TIMEOUT_MS);
548
+ };
549
+ try {
550
+ let response;
551
+ try {
552
+ response = await fetch(`${baseURL}/chat/completions`, {
553
+ method: "POST",
554
+ signal: controller.signal,
555
+ headers: {
556
+ "Content-Type": "application/json",
557
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
447
558
  },
448
- { role: "user", content: instructionText }
449
- ],
450
- temperature: 0,
451
- response_format: { type: "json_object" }
452
- })
453
- });
454
- if (!response.ok) {
455
- const body = await response.text().catch(() => "");
456
- throw new Error(`Judge API ${response.status}: ${body.slice(0, 200)}`);
559
+ body: requestBody(stream)
560
+ });
561
+ } catch (error) {
562
+ return transportFailure("Judge request failed", controller.signal.reason ?? error);
563
+ }
564
+ if (!response.ok) {
565
+ const body = await response.text().catch(() => "");
566
+ return {
567
+ kind: "unavailable",
568
+ error: new JudgeUnavailableError(`Judge API ${response.status}: ${body.slice(0, 200)}`),
569
+ retryable: isRetryableStatus(response.status),
570
+ ...isRetryableStatus(response.status) ? { retryDelayMs: retryDelayMs(response.headers.get("retry-after")) } : {},
571
+ // Name the field, not the word: gateways echo request params in
572
+ // unrelated 400 bodies.
573
+ ...stream && response.status === 400 && /stream_options|["'`]stream["'`]/.test(body) ? { streamRejected: true } : {}
574
+ };
575
+ }
576
+ try {
577
+ return { kind: "completion", completion: await readCompletion(response, onData) };
578
+ } catch (error) {
579
+ return transportFailure("Judge response failed", controller.signal.reason ?? error);
580
+ }
581
+ } finally {
582
+ clearTimeout(total);
583
+ clearTimeout(idle);
584
+ controller.abort();
457
585
  }
458
- const data = await response.json();
459
- const text = data.choices?.[0]?.message?.content ?? "";
460
- const outputTokens = data.usage?.completion_tokens;
461
- const isEmpty = !text.trim();
462
- const reportedZeroTokens = data.usage !== void 0 && outputTokens === 0;
463
- if (isEmpty || reportedZeroTokens) {
586
+ };
587
+ const isTruncated = ({ text, usage, finishReason }) => {
588
+ if (!text.trim()) return true;
589
+ if (usage !== void 0 && usage.completion_tokens === 0) return true;
590
+ if (finishReason !== "length" && finishReason !== "content_filter" && finishReason !== "error") {
591
+ return false;
592
+ }
593
+ try {
594
+ JSON.parse(text);
595
+ return false;
596
+ } catch {
597
+ return true;
598
+ }
599
+ };
600
+ return runWithSharedPrefix(cacheKey, async () => {
601
+ const startedAt = Date.now();
602
+ const deadline = startedAt + JUDGE_TIMEOUT_MS;
603
+ let stream = true;
604
+ let result = await attempt(stream, deadline);
605
+ const truncated = result.kind === "completion" && isTruncated(result.completion);
606
+ const streamRejected = result.kind === "unavailable" && result.streamRejected === true;
607
+ const retryable = result.kind === "unavailable" && result.retryable;
608
+ if (truncated || streamRejected || retryable) {
609
+ const delay = result.kind === "unavailable" && result.retryDelayMs !== void 0 ? result.retryDelayMs : streamRejected ? 0 : JUDGE_RETRY_DELAY_MS;
610
+ if (deadline - Date.now() - delay >= JUDGE_MIN_RETRY_BUDGET_MS) {
611
+ if (streamRejected) stream = false;
612
+ await new Promise((resolve4) => setTimeout(resolve4, delay));
613
+ const first = result;
614
+ result = await attempt(stream, deadline);
615
+ if (result.kind === "unavailable" && first.kind === "unavailable") {
616
+ result = {
617
+ ...result,
618
+ error: new JudgeUnavailableError(
619
+ `${first.error.message} (retry: ${result.error.message})`
620
+ )
621
+ };
622
+ }
623
+ }
624
+ }
625
+ if (result.kind === "unavailable") throw result.error;
626
+ const { text, usage } = result.completion;
627
+ const judge = {
628
+ model: args.model,
629
+ contract: judgeContractInUse(),
630
+ prompt: { system: systemPromptText, user: instructionText },
631
+ response: text,
632
+ tokens: parseJudgeUsage(usage),
633
+ latency_ms: Date.now() - startedAt,
634
+ secondJudge: await secondJudgePromise
635
+ };
636
+ if (isTruncated(result.completion)) {
464
637
  return {
465
638
  pass: false,
466
- reasoning: "Judge returned an empty or truncated response \u2014 likely a transient provider failure. The verdict is unknown, so this check is treated as a failure.",
467
- judge: {
468
- model: args.model,
469
- contract: judgeContractInUse(),
470
- prompt: { system: systemPromptText, user: instructionText },
471
- response: text,
472
- tokens: parseJudgeUsage(data.usage),
473
- latency_ms: Date.now() - startedAt,
474
- secondJudge: await secondJudgePromise
475
- }
639
+ reasoning: "Judge returned an empty or truncated response \u2014 likely a transient provider failure. The verdict is unknown, so this check is recorded as a judge error, not a verdict.",
640
+ judge,
641
+ unavailable: true
476
642
  };
477
643
  }
478
644
  const parsed = parseJudgeJson(text);
479
645
  return {
480
646
  pass: parsed.pass === true,
481
647
  reasoning: typeof parsed.reasoning === "string" ? parsed.reasoning : "",
482
- judge: {
483
- model: args.model,
484
- contract: judgeContractInUse(),
485
- prompt: { system: systemPromptText, user: instructionText },
486
- response: text,
487
- tokens: parseJudgeUsage(data.usage),
488
- latency_ms: Date.now() - startedAt,
489
- secondJudge: await secondJudgePromise
490
- }
648
+ judge
491
649
  };
492
650
  });
493
651
  }
652
+ function transportFailure(prefix, error) {
653
+ if (error instanceof JudgeUnavailableError) {
654
+ return { kind: "unavailable", error, retryable: true };
655
+ }
656
+ const timedOut = error instanceof Error && error.name === "TimeoutError";
657
+ return {
658
+ kind: "unavailable",
659
+ error: new JudgeUnavailableError(
660
+ `${prefix}: ${error instanceof Error ? error.message : String(error)}`
661
+ ),
662
+ retryable: !timedOut
663
+ };
664
+ }
494
665
  function assembleBriefing(args) {
495
666
  if (!args.prompt) {
496
667
  return {
@@ -521,6 +692,53 @@ ${args.instruction}`
521
692
 
522
693
  // src/agent-task/checks/agent-session.ts
523
694
  import { createHash } from "crypto";
695
+
696
+ // src/agent-task/tracing.ts
697
+ function createNoopAgentTaskTraceContext() {
698
+ const noopFn = async (fn) => fn();
699
+ return {
700
+ runId: "agent-task-untraced",
701
+ rootSpanId: "agent-task-untraced-root",
702
+ async step(options, fn) {
703
+ return fn(options.id ?? "agent-task-untraced-step");
704
+ },
705
+ recordEvent(options) {
706
+ return options.id ?? "agent-task-untraced-event";
707
+ },
708
+ endRoot() {
709
+ },
710
+ traceTool(_name, _params, fn) {
711
+ return noopFn(fn);
712
+ },
713
+ traceRetriever(_query, fn) {
714
+ return noopFn(fn);
715
+ },
716
+ traceChain(_name, fn) {
717
+ return noopFn(fn);
718
+ },
719
+ traceAgent(_name, fn) {
720
+ return noopFn(fn);
721
+ },
722
+ traceGuardrail(_name, fn) {
723
+ return noopFn(fn);
724
+ },
725
+ traceEmbedding(_model, _input, fn) {
726
+ return noopFn(fn);
727
+ },
728
+ async score(_params) {
729
+ },
730
+ createSpan(_options) {
731
+ return "agent-task-untraced-span";
732
+ },
733
+ endSpan(_spanId, _params) {
734
+ }
735
+ };
736
+ }
737
+ function isTraceableSpanId(spanId) {
738
+ return spanId !== void 0 && !spanId.startsWith("agent-task-untraced");
739
+ }
740
+
741
+ // src/agent-task/checks/agent-session.ts
524
742
  var DEFAULT_BUDGET = {
525
743
  maxTurns: 12,
526
744
  maxToolCalls: 24,
@@ -581,7 +799,7 @@ function extractVerdict(steps) {
581
799
  return void 0;
582
800
  }
583
801
  function buildEvidenceTools(args) {
584
- const { z, tool, evidence, scope, budget, ledger, stepIndexOf, includeTrace } = args;
802
+ const { z, tool, evidence, scope, budget, ledger, stepIndexOf, includeTrace, includeHistory, tracer } = args;
585
803
  const budgetGuard = () => {
586
804
  ledger.toolCallsUsed += 1;
587
805
  if (ledger.toolCallsUsed > budget.maxToolCalls) {
@@ -600,6 +818,7 @@ function buildEvidenceTools(args) {
600
818
  });
601
819
  return ledger.readBytesUsed > budget.maxReadBytes ? overflow() : null;
602
820
  };
821
+ const span = (name, input, fn) => tracer ? tracer.traceTool(name, input, fn) : fn();
603
822
  const tools = {
604
823
  read_deliverable: tool({
605
824
  description: `Read one of this run's deliverables by name, with optional offset/limit for pagination (max ${MAX_READ_WINDOW} bytes per call).`,
@@ -610,21 +829,23 @@ function buildEvidenceTools(args) {
610
829
  }),
611
830
  execute: async (input) => {
612
831
  const { name, offset, limit } = input;
613
- const guard = budgetGuard();
614
- if (guard) return { error: guard };
615
- if (!Object.prototype.hasOwnProperty.call(evidence.deliverables, name)) {
616
- return { error: `unknown deliverable: ${name}` };
617
- }
618
- const content = renderValue(evidence.deliverables[name]);
619
- const slice = content.slice(offset, offset + limit);
620
- const overflow = accountRead(
621
- "read_deliverable",
622
- JSON.stringify({ name, offset, limit }),
623
- slice,
624
- () => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
625
- );
626
- if (overflow) return { error: overflow };
627
- return { name, total_bytes: content.length, offset, returned: slice.length, content: slice };
832
+ return span("read_deliverable", input, async () => {
833
+ const guard = budgetGuard();
834
+ if (guard) return { error: guard };
835
+ if (!Object.prototype.hasOwnProperty.call(evidence.deliverables, name)) {
836
+ return { error: `unknown deliverable: ${name}` };
837
+ }
838
+ const content = renderValue(evidence.deliverables[name]);
839
+ const slice = content.slice(offset, offset + limit);
840
+ const overflow = accountRead(
841
+ "read_deliverable",
842
+ JSON.stringify({ name, offset, limit }),
843
+ slice,
844
+ () => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
845
+ );
846
+ if (overflow) return { error: overflow };
847
+ return { name, total_bytes: content.length, offset, returned: slice.length, content: slice };
848
+ });
628
849
  }
629
850
  }),
630
851
  search_deliverable: tool({
@@ -632,49 +853,53 @@ function buildEvidenceTools(args) {
632
853
  inputSchema: z.object({ name: z.string(), pattern: z.string() }),
633
854
  execute: async (input) => {
634
855
  const { name, pattern } = input;
635
- const guard = budgetGuard();
636
- if (guard) return { error: guard };
637
- if (!Object.prototype.hasOwnProperty.call(evidence.deliverables, name)) {
638
- return { error: `unknown deliverable: ${name}` };
639
- }
640
- if (pattern.length > SEARCH_MAX_PATTERN) {
641
- return { error: `pattern too long (max ${SEARCH_MAX_PATTERN} chars)` };
642
- }
643
- let re;
644
- try {
645
- re = new RegExp(pattern, "gi");
646
- } catch (error) {
647
- return { error: `invalid regex: ${error instanceof Error ? error.message : String(error)}` };
648
- }
649
- const content = renderValue(evidence.deliverables[name]).slice(0, SEARCH_SCAN_LIMIT);
650
- const hits = [];
651
- let m;
652
- let execs = 0;
653
- while ((m = re.exec(content)) && hits.length < SEARCH_HITS) {
654
- if (++execs > SEARCH_MAX_EXEC) break;
655
- const start = Math.max(0, m.index - SEARCH_CONTEXT);
656
- hits.push({ at_byte: m.index, context: content.slice(start, m.index + m[0].length + SEARCH_CONTEXT) });
657
- if (m.index === re.lastIndex) re.lastIndex++;
658
- }
659
- const served = JSON.stringify({ total_bytes: content.length, match_count: hits.length, matches: hits });
660
- const overflow = accountRead(
661
- "search_deliverable",
662
- JSON.stringify({ name, pattern }),
663
- served,
664
- () => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
665
- );
666
- if (overflow) return { error: overflow };
667
- return { total_bytes: content.length, match_count: hits.length, matches: hits };
856
+ return span("search_deliverable", input, async () => {
857
+ const guard = budgetGuard();
858
+ if (guard) return { error: guard };
859
+ if (!Object.prototype.hasOwnProperty.call(evidence.deliverables, name)) {
860
+ return { error: `unknown deliverable: ${name}` };
861
+ }
862
+ if (pattern.length > SEARCH_MAX_PATTERN) {
863
+ return { error: `pattern too long (max ${SEARCH_MAX_PATTERN} chars)` };
864
+ }
865
+ let re;
866
+ try {
867
+ re = new RegExp(pattern, "gi");
868
+ } catch (error) {
869
+ return { error: `invalid regex: ${error instanceof Error ? error.message : String(error)}` };
870
+ }
871
+ const content = renderValue(evidence.deliverables[name]).slice(0, SEARCH_SCAN_LIMIT);
872
+ const hits = [];
873
+ let m;
874
+ let execs = 0;
875
+ while ((m = re.exec(content)) && hits.length < SEARCH_HITS) {
876
+ if (++execs > SEARCH_MAX_EXEC) break;
877
+ const start = Math.max(0, m.index - SEARCH_CONTEXT);
878
+ hits.push({ at_byte: m.index, context: content.slice(start, m.index + m[0].length + SEARCH_CONTEXT) });
879
+ if (m.index === re.lastIndex) re.lastIndex++;
880
+ }
881
+ const served = JSON.stringify({ total_bytes: content.length, match_count: hits.length, matches: hits });
882
+ const overflow = accountRead(
883
+ "search_deliverable",
884
+ JSON.stringify({ name, pattern }),
885
+ served,
886
+ () => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
887
+ );
888
+ if (overflow) return { error: overflow };
889
+ return { total_bytes: content.length, match_count: hits.length, matches: hits };
890
+ });
668
891
  }
669
892
  }),
670
- ...evidence.history ? {
893
+ ...includeHistory && evidence.history ? {
671
894
  list_runs: tool({
672
895
  description: "List runs of this task \u2014 id, status, pass/fail, model, check counts. The run under judgment is flagged; use get_run for a prior run's full check report.",
673
896
  inputSchema: z.object({}),
674
897
  execute: async () => {
675
- const guard = budgetGuard();
676
- if (guard) return { error: guard };
677
- return evidence.history.runs;
898
+ return span("list_runs", {}, async () => {
899
+ const guard = budgetGuard();
900
+ if (guard) return { error: guard };
901
+ return evidence.history.runs;
902
+ });
678
903
  }
679
904
  }),
680
905
  get_run: tool({
@@ -682,18 +907,20 @@ function buildEvidenceTools(args) {
682
907
  inputSchema: z.object({ run_id: z.string() }),
683
908
  execute: async (input) => {
684
909
  const { run_id } = input;
685
- const guard = budgetGuard();
686
- if (guard) return { error: guard };
687
- const detail = await evidence.history.getRun(run_id);
688
- const served = JSON.stringify(detail);
689
- const overflow = accountRead(
690
- "get_run",
691
- JSON.stringify({ run_id }),
692
- served,
693
- () => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
694
- );
695
- if (overflow) return { error: overflow };
696
- return detail;
910
+ return span("get_run", input, async () => {
911
+ const guard = budgetGuard();
912
+ if (guard) return { error: guard };
913
+ const detail = await evidence.history.getRun(run_id);
914
+ const served = JSON.stringify(detail);
915
+ const overflow = accountRead(
916
+ "get_run",
917
+ JSON.stringify({ run_id }),
918
+ served,
919
+ () => `read budget exhausted (${budget.maxReadBytes} bytes); call finish_verdict now`
920
+ );
921
+ if (overflow) return { error: overflow };
922
+ return detail;
923
+ });
697
924
  }
698
925
  })
699
926
  } : {},
@@ -701,13 +928,15 @@ function buildEvidenceTools(args) {
701
928
  description: "The task this run executed: id, description, deliverable names.",
702
929
  inputSchema: z.object({}),
703
930
  execute: async () => {
704
- const guard = budgetGuard();
705
- if (guard) return { error: guard };
706
- return {
707
- task_id: scope?.taskId,
708
- description: scope?.taskDescription ?? "(unavailable)",
709
- deliverables: Object.keys(evidence.deliverables)
710
- };
931
+ return span("get_task_definition", {}, async () => {
932
+ const guard = budgetGuard();
933
+ if (guard) return { error: guard };
934
+ return {
935
+ task_id: scope?.taskId,
936
+ description: scope?.taskDescription ?? "(unavailable)",
937
+ deliverables: Object.keys(evidence.deliverables)
938
+ };
939
+ });
711
940
  }
712
941
  })
713
942
  };
@@ -717,21 +946,23 @@ function buildEvidenceTools(args) {
717
946
  inputSchema: z.object({ query: z.string().describe("substring filter on tool names; empty = all") }),
718
947
  execute: async (input) => {
719
948
  const { query } = input;
720
- const guard = budgetGuard();
721
- if (guard) return { error: guard };
722
- const view = evidence.view;
723
- if (!view) {
724
- return {
725
- status: "unsupported",
726
- detail: "no trace projection exists for this run \u2014 judge on deliverables instead"
727
- };
728
- }
729
- if (view.requireCapability("tools") !== "available") {
730
- return { status: "unsupported", detail: "tool-call evidence is unavailable in this trace projection" };
731
- }
732
- const calls = view.toolCalls.filter((c) => query ? c.name.includes(query) : true).slice(0, 30).map((c) => ({ name: c.name, input: renderValue(c.input).slice(0, 200) }));
733
- const reply = view.requireCapability("messages") === "available" && view.reply ? view.reply.slice(0, 1500) : void 0;
734
- return { turns: view.turnCount, tool_calls: calls, ...reply !== void 0 ? { final_reply: reply } : {} };
949
+ return span("get_trace", input, async () => {
950
+ const guard = budgetGuard();
951
+ if (guard) return { error: guard };
952
+ const view = evidence.view;
953
+ if (!view) {
954
+ return {
955
+ status: "unsupported",
956
+ detail: "no trace projection exists for this run \u2014 judge on deliverables instead"
957
+ };
958
+ }
959
+ if (view.requireCapability("tools") !== "available") {
960
+ return { status: "unsupported", detail: "tool-call evidence is unavailable in this trace projection" };
961
+ }
962
+ const calls = view.toolCalls.filter((c) => query ? c.name.includes(query) : true).slice(0, 30).map((c) => ({ name: c.name, input: renderValue(c.input).slice(0, 200) }));
963
+ const reply = view.requireCapability("messages") === "available" && view.reply ? view.reply.slice(0, 1500) : void 0;
964
+ return { turns: view.turnCount, tool_calls: calls, ...reply !== void 0 ? { final_reply: reply } : {} };
965
+ });
735
966
  }
736
967
  });
737
968
  }
@@ -858,7 +1089,9 @@ async function runAgentSession(spec) {
858
1089
  budget,
859
1090
  ledger,
860
1091
  stepIndexOf: () => liveSteps.length,
861
- includeTrace: spec.tools?.trace !== false
1092
+ includeTrace: spec.tools?.trace !== false,
1093
+ includeHistory: spec.tools?.history !== false,
1094
+ tracer: spec.tracer
862
1095
  });
863
1096
  const system = buildBriefing(spec.scope, spec.evidence, budget);
864
1097
  const exhibitsBlock = spec.exhibits?.length ? "\n\nValues submitted for judgment:\n" + spec.exhibits.map((v, i) => ` [${i + 1}] ${renderValue(v).slice(0, RECORD_RESULT_LIMIT)}`).join("\n") : "";
@@ -918,7 +1151,7 @@ async function runAgentSession(spec) {
918
1151
  latency_ms: Date.now() - started
919
1152
  };
920
1153
  }
921
- function createAgentMethod(rec, judgeConfig, judgeScope, evidence) {
1154
+ function createAgentMethod(rec, judgeConfig, judgeScope, evidence, judgeTracer) {
922
1155
  return async (instruction, opts) => {
923
1156
  const label = opts?.label ?? "agent";
924
1157
  const location = rec.captureLocation();
@@ -934,18 +1167,54 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence) {
934
1167
  );
935
1168
  return;
936
1169
  }
1170
+ const run = () => runAgentSession({
1171
+ instruction,
1172
+ model: effective.model,
1173
+ baseURL: effective.baseURL,
1174
+ apiKey: effective.apiKey,
1175
+ evidence: evidence ?? { deliverables: {} },
1176
+ scope: judgeScope,
1177
+ exhibits,
1178
+ tools: opts?.tools,
1179
+ budget: opts?.budget,
1180
+ tracer: judgeTracer
1181
+ });
937
1182
  try {
938
- const result = await runAgentSession({
939
- instruction,
940
- model: effective.model,
941
- baseURL: effective.baseURL,
942
- apiKey: effective.apiKey,
943
- evidence: evidence ?? { deliverables: {} },
944
- scope: judgeScope,
945
- exhibits,
946
- tools: opts?.tools,
947
- budget: opts?.budget
948
- });
1183
+ let sessionSpanId;
1184
+ const result = await (judgeTracer ? judgeTracer.step(
1185
+ {
1186
+ step_name: `t.agent:${judgeScope?.checkName ?? label}`,
1187
+ observation_type: "AGENT",
1188
+ // The span carries the model + tokens so the backend prices the
1189
+ // judge session and trace rollups count its spend (issue #288).
1190
+ model: effective.model,
1191
+ input: { model: effective.model, instruction },
1192
+ usage: (r) => {
1193
+ const usage = r?.usage;
1194
+ return usage ? {
1195
+ prompt_tokens: usage.input_tokens,
1196
+ completion_tokens: usage.output_tokens
1197
+ } : void 0;
1198
+ },
1199
+ summarize: (r) => {
1200
+ const res = r;
1201
+ const reasoning = res?.verdict?.reasoning?.slice(0, 2e3) ?? `session ended without a verdict (${res?.outcome ?? "unknown"})`;
1202
+ return {
1203
+ text: reasoning,
1204
+ // reasoning-first, matching the judge response
1205
+ // contract's default order (#163).
1206
+ verdict: {
1207
+ reasoning,
1208
+ pass: res?.verdict?.pass ?? null
1209
+ }
1210
+ };
1211
+ }
1212
+ },
1213
+ async (spanId) => {
1214
+ sessionSpanId = spanId;
1215
+ return run();
1216
+ }
1217
+ ) : run());
949
1218
  const judge = {
950
1219
  model: effective.model,
951
1220
  temperature: 0,
@@ -956,7 +1225,8 @@ function createAgentMethod(rec, judgeConfig, judgeScope, evidence) {
956
1225
  },
957
1226
  ...result.cost !== void 0 ? { cost: result.cost } : {},
958
1227
  latency_ms: result.latency_ms,
959
- session: result.session
1228
+ session: result.session,
1229
+ ...isTraceableSpanId(sessionSpanId) ? { span_id: sessionSpanId } : {}
960
1230
  };
961
1231
  const detail = result.outcome === "error" ? `agentic judge session errored before verdicting: ${result.error ?? "unknown error"}` : `agentic judge session ended without a verdict (budget exhausted after ${result.session.usage?.steps ?? "?"} steps; last tool: ${result.session.steps?.at(-1)?.tool_calls?.map((c) => c.name).join(", ") ?? "none"})`;
962
1232
  rec.record(label, result.verdict?.pass ?? false, result.verdict?.reasoning ?? detail, {
@@ -1016,7 +1286,14 @@ function matchName(name, m) {
1016
1286
  if (m instanceof RegExp) return m.test(name);
1017
1287
  return m(name);
1018
1288
  }
1019
- function createJudgeMethod(rec, judgeConfig, judgeScope) {
1289
+ function trackPending(rec, method) {
1290
+ return ((...args) => {
1291
+ const promise = method(...args);
1292
+ rec.track(promise);
1293
+ return promise;
1294
+ });
1295
+ }
1296
+ function createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer) {
1020
1297
  return async (values, instruction, opts) => {
1021
1298
  const label = opts?.label ?? "judge";
1022
1299
  const location = rec.captureLocation();
@@ -1032,16 +1309,49 @@ function createJudgeMethod(rec, judgeConfig, judgeScope) {
1032
1309
  return;
1033
1310
  }
1034
1311
  const context2 = judgeScopeToContext(judgeScope);
1035
- try {
1036
- const { pass, reasoning, judge } = await callJudge({
1037
- values: valueArray,
1038
- instruction,
1312
+ const call = () => callJudge({
1313
+ values: valueArray,
1314
+ instruction,
1315
+ model: effective.model,
1316
+ baseURL: effective.baseURL,
1317
+ apiKey: effective.apiKey,
1318
+ prompt: effective.prompt,
1319
+ ...context2 ? { context: context2 } : {}
1320
+ });
1321
+ let judgeSpanId;
1322
+ const traced = judgeTracer ? judgeTracer.step(
1323
+ {
1324
+ step_name: `judge:${judgeScope?.checkName ?? label}`,
1325
+ // A single-shot judge call is an LLM generation — project it
1326
+ // with the same observation semantics as the main agent's.
1327
+ observation_type: "GENERATION",
1328
+ // The span carries the model + tokens so the backend prices the
1329
+ // judge call and trace rollups count its spend (issue #288).
1039
1330
  model: effective.model,
1040
- baseURL: effective.baseURL,
1041
- apiKey: effective.apiKey,
1042
- prompt: effective.prompt,
1043
- ...context2 ? { context: context2 } : {}
1044
- });
1331
+ input: { model: effective.model, instruction },
1332
+ summarize: (r) => {
1333
+ const res = r;
1334
+ return {
1335
+ text: res?.reasoning?.slice(0, 2e3) ?? "",
1336
+ verdict: {
1337
+ reasoning: res?.reasoning?.slice(0, 2e3) ?? null,
1338
+ pass: res?.pass ?? null
1339
+ }
1340
+ };
1341
+ },
1342
+ usage: (r) => {
1343
+ const tokens = r?.judge?.tokens;
1344
+ return tokens ? { prompt_tokens: tokens.input, completion_tokens: tokens.output } : void 0;
1345
+ }
1346
+ },
1347
+ async (spanId) => {
1348
+ judgeSpanId = spanId;
1349
+ return call();
1350
+ }
1351
+ ) : call();
1352
+ try {
1353
+ const { pass, reasoning, judge, unavailable } = await traced;
1354
+ if (isTraceableSpanId(judgeSpanId)) judge.span_id = judgeSpanId;
1045
1355
  rec.record(label, pass, reasoning, {
1046
1356
  evaluator_type: "llm",
1047
1357
  judge,
@@ -1050,7 +1360,8 @@ function createJudgeMethod(rec, judgeConfig, judgeScope) {
1050
1360
  // dashboard can render it with the right viewer — Markdown for
1051
1361
  // prose, a JSON tree for structured objects.
1052
1362
  received: valueArray.length === 1 ? valueArray[0] : valueArray,
1053
- location
1363
+ location,
1364
+ ...unavailable ? { outcome: "error" } : {}
1054
1365
  });
1055
1366
  } catch (error) {
1056
1367
  rec.record(
@@ -1061,7 +1372,8 @@ function createJudgeMethod(rec, judgeConfig, judgeScope) {
1061
1372
  evaluator_type: "llm",
1062
1373
  expected: instruction,
1063
1374
  received: valueArray.length === 1 ? valueArray[0] : valueArray,
1064
- location
1375
+ location,
1376
+ outcome: "error"
1065
1377
  }
1066
1378
  );
1067
1379
  }
@@ -1085,7 +1397,7 @@ var CAPABILITY_LABELS = {
1085
1397
  skills: "skill",
1086
1398
  subagents: "subagent"
1087
1399
  };
1088
- function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence) {
1400
+ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidence, judgeTracer) {
1089
1401
  const unsupported = (id, capability) => {
1090
1402
  const label = CAPABILITY_LABELS[capability];
1091
1403
  rec.record(id, false, `${label} evidence is unavailable in this trace projection`, {
@@ -1271,8 +1583,8 @@ function createTraceTestContext(view, rec, judgeConfig, judgeScope, agentEvidenc
1271
1583
  },
1272
1584
  // judge does not consult trace capabilities; the scope carries the
1273
1585
  // task/check frame for prompt builders (#161).
1274
- judge: createJudgeMethod(rec, judgeConfig, judgeScope),
1275
- agent: createAgentMethod(rec, judgeConfig, judgeScope, agentEvidence ?? { deliverables: {}, view })
1586
+ judge: trackPending(rec, createJudgeMethod(rec, judgeConfig, judgeScope, judgeTracer)),
1587
+ agent: trackPending(rec, createAgentMethod(rec, judgeConfig, judgeScope, agentEvidence ?? { deliverables: {}, view }, judgeTracer))
1276
1588
  };
1277
1589
  }
1278
1590
  function matchesTraceToolCall(call, name, opts) {
@@ -1293,16 +1605,9 @@ function matchesTraceToolCall(call, name, opts) {
1293
1605
  var Recorder = class {
1294
1606
  constructor(locate) {
1295
1607
  this.records = [];
1608
+ this.pending = [];
1296
1609
  this.locate = locate;
1297
1610
  }
1298
- /**
1299
- * Record an assertion.
1300
- *
1301
- * - ``extra.location`` overrides auto-capture (used by the runner for thrown
1302
- * errors, whose relevant stack is the error's own, not the call site).
1303
- * - ``extra.expected`` / ``extra.received`` carry the structured values for
1304
- * testing-framework-style display.
1305
- */
1306
1611
  /**
1307
1612
  * Capture the call site location synchronously. Use this when a record will
1308
1613
  * happen AFTER an `await` (e.g. `t.judge`): once an async function resumes,
@@ -1314,6 +1619,28 @@ var Recorder = class {
1314
1619
  captureLocation() {
1315
1620
  return this.locate ? this.locate(new Error().stack ?? "") : void 0;
1316
1621
  }
1622
+ /**
1623
+ * Register an async evaluation (``t.judge`` / ``t.agent``) started by this
1624
+ * check. If the check returns without awaiting it, the runner still waits
1625
+ * for its record via ``settlePending`` — a dropped await must not let the
1626
+ * check vacuously pass ("no assertions recorded") while the verdict is
1627
+ * still in flight.
1628
+ */
1629
+ track(promise) {
1630
+ this.pending.push(promise);
1631
+ }
1632
+ /** Wait until every tracked evaluation has settled (and recorded). */
1633
+ async settlePending() {
1634
+ await Promise.allSettled(this.pending);
1635
+ }
1636
+ /**
1637
+ * Record an assertion.
1638
+ *
1639
+ * - ``extra.location`` overrides auto-capture (used by the runner for thrown
1640
+ * errors, whose relevant stack is the error's own, not the call site).
1641
+ * - ``extra.expected`` / ``extra.received`` carry the structured values for
1642
+ * testing-framework-style display.
1643
+ */
1317
1644
  record(id, pass, reasoning, extra) {
1318
1645
  const location = extra?.location ?? (this.locate ? this.locate(new Error().stack ?? "") : void 0);
1319
1646
  this.records.push({
@@ -1425,8 +1752,10 @@ async function runTraceChecks(args) {
1425
1752
  {
1426
1753
  deliverables: args.deliverables,
1427
1754
  view,
1428
- ...args.historyPlane ? { history: args.historyPlane } : {}
1429
- }
1755
+ ...args.historyPlane ? { history: args.historyPlane } : {},
1756
+ ...args.historyUnavailableReason ? { historyUnavailable: args.historyUnavailableReason } : {}
1757
+ },
1758
+ args.judgeTracer
1430
1759
  );
1431
1760
  let thrownLocation;
1432
1761
  try {
@@ -1441,8 +1770,10 @@ async function runTraceChecks(args) {
1441
1770
  location: thrownLocation
1442
1771
  });
1443
1772
  }
1773
+ await rec.settlePending();
1444
1774
  const failed = rec.all.filter((r) => !r.pass);
1445
1775
  const pass = failed.length === 0;
1776
+ const outcome = rollUpCheckOutcome(failed);
1446
1777
  const reasoning = failed.length > 0 ? failed.map((r) => r.reasoning || r.id).join("; ") : rec.all.length > 0 ? "passed" : "no assertions recorded";
1447
1778
  const location = failed.find((r) => r.location)?.location;
1448
1779
  const judge = rec.all.find((r) => r.judge)?.judge;
@@ -1451,6 +1782,7 @@ async function runTraceChecks(args) {
1451
1782
  pass,
1452
1783
  reasoning,
1453
1784
  evaluator_type: "code",
1785
+ ...outcome ? { outcome } : {},
1454
1786
  ...judge ? { judge } : {},
1455
1787
  ...location ? { location } : {},
1456
1788
  ...args.displayFile ? { source_file: args.displayFile } : {},
@@ -1465,6 +1797,14 @@ async function runTraceChecks(args) {
1465
1797
  );
1466
1798
  return results;
1467
1799
  }
1800
+ function rollUpCheckOutcome(failed) {
1801
+ if (failed.length === 0) return void 0;
1802
+ const verdictless = failed.filter(
1803
+ (r) => r.outcome === "error" || r.outcome === "unsupported"
1804
+ );
1805
+ if (verdictless.length < failed.length) return void 0;
1806
+ return verdictless.some((r) => r.outcome === "error") ? "error" : "unsupported";
1807
+ }
1468
1808
  async function loadAndRunFlowChecks(checksPath, args, brokenDeliverables = {}) {
1469
1809
  if (!checksPath) return [];
1470
1810
  resetFlowChecks();
@@ -1477,6 +1817,7 @@ async function loadAndRunFlowChecks(checksPath, args, brokenDeliverables = {}) {
1477
1817
  task: args.task,
1478
1818
  judgeConfig: args.judgeConfig,
1479
1819
  ...args.historyPlane ? { historyPlane: args.historyPlane } : {},
1820
+ ...args.judgeTracer ? { judgeTracer: args.judgeTracer } : {},
1480
1821
  moduleUrl,
1481
1822
  displayFile: basename(checksPath)
1482
1823
  });
@@ -2528,7 +2869,29 @@ function createProjectionTee(real) {
2528
2869
  }
2529
2870
  },
2530
2871
  async traceTool(name, params, fn) {
2531
- return real.traceTool(name, params, fn);
2872
+ const obsSpanId = recordStart({
2873
+ step_name: `tool ${name}`,
2874
+ observation_type: "TOOL",
2875
+ tool_name: name,
2876
+ input: params
2877
+ });
2878
+ const start = monotonicNowMs();
2879
+ try {
2880
+ const result = await real.traceTool(name, params, fn);
2881
+ complete(obsSpanId, {
2882
+ latency_ms: round3(monotonicNowMs() - start),
2883
+ output: result
2884
+ });
2885
+ return result;
2886
+ } catch (error) {
2887
+ const message = error instanceof Error ? error.message : String(error);
2888
+ complete(obsSpanId, {
2889
+ latency_ms: round3(monotonicNowMs() - start),
2890
+ status_message: message,
2891
+ level: "ERROR"
2892
+ });
2893
+ throw error;
2894
+ }
2532
2895
  },
2533
2896
  async traceRetriever(query, fn) {
2534
2897
  return real.traceRetriever(query, fn);
@@ -2719,48 +3082,6 @@ function aggregateResult(checksResults) {
2719
3082
  return { checks: checksResults, pass };
2720
3083
  }
2721
3084
 
2722
- // src/agent-task/tracing.ts
2723
- function createNoopAgentTaskTraceContext() {
2724
- const noopFn = async (fn) => fn();
2725
- return {
2726
- runId: "agent-task-untraced",
2727
- rootSpanId: "agent-task-untraced-root",
2728
- async step(options, fn) {
2729
- return fn(options.id ?? "agent-task-untraced-step");
2730
- },
2731
- recordEvent(options) {
2732
- return options.id ?? "agent-task-untraced-event";
2733
- },
2734
- endRoot() {
2735
- },
2736
- traceTool(_name, _params, fn) {
2737
- return noopFn(fn);
2738
- },
2739
- traceRetriever(_query, fn) {
2740
- return noopFn(fn);
2741
- },
2742
- traceChain(_name, fn) {
2743
- return noopFn(fn);
2744
- },
2745
- traceAgent(_name, fn) {
2746
- return noopFn(fn);
2747
- },
2748
- traceGuardrail(_name, fn) {
2749
- return noopFn(fn);
2750
- },
2751
- traceEmbedding(_model, _input, fn) {
2752
- return noopFn(fn);
2753
- },
2754
- async score(_params) {
2755
- },
2756
- createSpan(_options) {
2757
- return "agent-task-untraced-span";
2758
- },
2759
- endSpan(_spanId, _params) {
2760
- }
2761
- };
2762
- }
2763
-
2764
3085
  // src/agent-task/run/runTask.ts
2765
3086
  var AgentTaskRunError = class extends Error {
2766
3087
  constructor(message, options) {
@@ -2787,13 +3108,15 @@ async function runTask(taskDir, options) {
2787
3108
  void 0
2788
3109
  );
2789
3110
  }
2790
- const phase1 = await trace2.client.traceRun(
3111
+ return trace2.client.traceRun(
2791
3112
  buildTraceRunOptions(loaded, trace2),
2792
- async (traceContext) => captureExecution(loaded, options, traceContext)
3113
+ async (traceContext) => {
3114
+ const phase1 = await captureExecution(loaded, options, traceContext);
3115
+ const canonical = await readCanonicalSnapshot(trace2);
3116
+ if (canonical) phase1.snapshot = canonical;
3117
+ return evaluate(loaded, options, phase1, traceContext);
3118
+ }
2793
3119
  );
2794
- const canonical = await readCanonicalSnapshot(trace2);
2795
- if (canonical) phase1.snapshot = canonical;
2796
- return evaluate(loaded, options, phase1);
2797
3120
  }
2798
3121
  async function readCanonicalSnapshot(trace2) {
2799
3122
  if (!trace2.taskRunId) return null;
@@ -2991,6 +3314,7 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
2991
3314
  files,
2992
3315
  task: task2,
2993
3316
  ...judgeConfig ? { judgeConfig } : {},
3317
+ judgeTracer: trace2,
2994
3318
  ...historyPlane ? { historyPlane } : {}
2995
3319
  },
2996
3320
  validationResults.brokenDeliverables
@@ -3005,6 +3329,7 @@ async function executeLoadedTask(loaded, options, rawTrace, traceRunId) {
3005
3329
  files,
3006
3330
  task: task2,
3007
3331
  ...judgeConfig ? { judgeConfig } : {},
3332
+ judgeTracer: trace2,
3008
3333
  ...historyPlane ? { historyPlane } : {},
3009
3334
  moduleUrl,
3010
3335
  displayFile: evalFileName
@@ -3033,7 +3358,7 @@ async function captureExecution(loaded, options, rawTrace) {
3033
3358
  snapshot: outcome.getSnapshot()
3034
3359
  }));
3035
3360
  }
3036
- async function evaluate(loaded, options, phase1) {
3361
+ async function evaluate(loaded, options, phase1, judgeTracer) {
3037
3362
  const {
3038
3363
  task: task2,
3039
3364
  adapter,
@@ -3051,12 +3376,13 @@ async function evaluate(loaded, options, phase1) {
3051
3376
  );
3052
3377
  const judgeConfig = resolveJudgeConfig(options?.judge, task2.judge);
3053
3378
  const historyPlane = await freezeHistoryPlaneFromEnv(task2.id);
3054
- const checksResults = await (inlineChecks ? runTraceChecks({
3379
+ const runChecks = async () => inlineChecks ? runTraceChecks({
3055
3380
  snapshot: phase1.snapshot,
3056
3381
  deliverables,
3057
3382
  files,
3058
3383
  task: task2,
3059
3384
  ...judgeConfig ? { judgeConfig } : {},
3385
+ ...judgeTracer ? { judgeTracer } : {},
3060
3386
  ...historyPlane ? { historyPlane } : {},
3061
3387
  moduleUrl,
3062
3388
  displayFile: evalFileName
@@ -3068,10 +3394,22 @@ async function evaluate(loaded, options, phase1) {
3068
3394
  files,
3069
3395
  task: task2,
3070
3396
  ...judgeConfig ? { judgeConfig } : {},
3397
+ ...judgeTracer ? { judgeTracer } : {},
3071
3398
  ...historyPlane ? { historyPlane } : {}
3072
3399
  },
3073
3400
  validationResults.brokenDeliverables
3074
- ));
3401
+ );
3402
+ const hasChecksModule = inlineChecks || checksPath !== null;
3403
+ const checksResults = await (judgeTracer && hasChecksModule ? judgeTracer.step(
3404
+ {
3405
+ step_name: "checks.run",
3406
+ observation_type: "CHAIN",
3407
+ input: { sourceFile: inlineChecks ? evalFileName : checksPath },
3408
+ metadata: { sourceFile: inlineChecks ? evalFileName : checksPath },
3409
+ summarize: (result2) => summarizeEvaluationResults(result2)
3410
+ },
3411
+ runChecks
3412
+ ) : runChecks());
3075
3413
  const result = aggregateResult(checksResults);
3076
3414
  return {
3077
3415
  task: task2,
@@ -3169,17 +3507,29 @@ async function cleanupAdapter(loaded, trace2, state, session) {
3169
3507
  }
3170
3508
  function summarizeEvaluationResults(results) {
3171
3509
  const passCount = results.filter((r) => r.pass).length;
3510
+ const noVerdictCount = results.filter(
3511
+ (r) => !r.pass && (r.outcome === "error" || r.outcome === "unsupported")
3512
+ ).length;
3513
+ const failCount = results.length - passCount - noVerdictCount;
3514
+ const suffix = noVerdictCount > 0 ? ` (${noVerdictCount} no-verdict)` : "";
3172
3515
  return {
3173
- total: results.length,
3174
- passCount,
3175
- failCount: results.length - passCount,
3176
- results: results.map((r) => ({
3177
- id: r.id,
3178
- pass: r.pass,
3179
- reasoning: r.reasoning,
3180
- evaluator_type: r.evaluator_type,
3181
- judge_model: r.judge?.model
3182
- }))
3516
+ text: `${passCount}/${results.length} checks passed${suffix}`,
3517
+ verdict: {
3518
+ total: results.length,
3519
+ passCount,
3520
+ failCount,
3521
+ noVerdictCount,
3522
+ results: results.map((r) => ({
3523
+ id: r.id,
3524
+ pass: r.pass,
3525
+ reasoning: r.reasoning?.slice(0, 2e3),
3526
+ evaluator_type: r.evaluator_type,
3527
+ judge_model: r.judge?.model,
3528
+ ...r.outcome ? { outcome: r.outcome } : {},
3529
+ ...r.group_id ? { group_id: r.group_id } : {},
3530
+ ...r.group_name ? { group_name: r.group_name } : {}
3531
+ }))
3532
+ }
3183
3533
  };
3184
3534
  }
3185
3535
  function buildTraceRunOptions(loaded, tracing) {
@@ -3307,6 +3657,7 @@ async function rejudgeTaskRun(runId, endpoints, options = {}) {
3307
3657
  );
3308
3658
  const proxied = proxyBrokenDeliverables(deliverables, validation.brokenDeliverables);
3309
3659
  const passCounts = /* @__PURE__ */ new Map();
3660
+ const errorCounts = /* @__PURE__ */ new Map();
3310
3661
  let primary = [];
3311
3662
  const judgeConfig = resolveJudgeConfig(options.judge, loaded.task.judge);
3312
3663
  for (let sample = 0; sample < samples; sample++) {
@@ -3315,12 +3666,16 @@ async function rejudgeTaskRun(runId, endpoints, options = {}) {
3315
3666
  if (sample === 0) primary = results;
3316
3667
  for (const result of results) {
3317
3668
  passCounts.set(result.id, (passCounts.get(result.id) ?? 0) + (result.pass ? 1 : 0));
3669
+ if (!result.pass && result.outcome === "error") {
3670
+ errorCounts.set(result.id, (errorCounts.get(result.id) ?? 0) + 1);
3671
+ }
3318
3672
  }
3319
3673
  }
3320
3674
  const stability = primary.map((result) => ({
3321
3675
  check_id: result.id,
3322
3676
  passes: passCounts.get(result.id) ?? 0,
3323
- samples
3677
+ samples,
3678
+ ...errorCounts.get(result.id) ? { errored: errorCounts.get(result.id) } : {}
3324
3679
  }));
3325
3680
  const aggregate = aggregateResult(primary);
3326
3681
  return {
@@ -3544,6 +3899,7 @@ function createOtelAgentTaskTraceClient(config) {
3544
3899
  activeSpans.set(spanId, {
3545
3900
  span,
3546
3901
  startedAt: monotonicNowMs2(),
3902
+ startWallMs: Date.now(),
3547
3903
  model: params.model || "unknown",
3548
3904
  observationType: params.observation_type
3549
3905
  });
@@ -3561,6 +3917,13 @@ function createOtelAgentTaskTraceClient(config) {
3561
3917
  const payload = params.output && typeof params.output === "object" ? params.output : { value: params.output };
3562
3918
  active.span.setAttribute("gen_ai.tool.call.result", JSON.stringify(payload));
3563
3919
  } else if (params.output.text) {
3920
+ active.span.setAttribute("gen_ai.response.text", String(params.output.text));
3921
+ if (params.output.verdict !== void 0) {
3922
+ active.span.setAttribute(
3923
+ "gen_ai.tool.call.result",
3924
+ JSON.stringify(params.output.verdict)
3925
+ );
3926
+ }
3564
3927
  active.span.setAttribute("gen_ai.output.messages", JSON.stringify([{
3565
3928
  role: "assistant",
3566
3929
  parts: [{ type: "text", content: String(params.output.text) }]
@@ -3585,6 +3948,12 @@ function createOtelAgentTaskTraceClient(config) {
3585
3948
  if (params.completion_tokens !== void 0) {
3586
3949
  active.span.setAttribute("gen_ai.usage.output_tokens", params.completion_tokens);
3587
3950
  }
3951
+ if (params.reasoning_tokens !== void 0) {
3952
+ active.span.setAttribute(
3953
+ "gen_ai.usage.reasoning.output_tokens",
3954
+ params.reasoning_tokens
3955
+ );
3956
+ }
3588
3957
  if (params.status_message) {
3589
3958
  active.span.setAttribute("apo.status_message", params.status_message);
3590
3959
  }
@@ -3594,7 +3963,7 @@ function createOtelAgentTaskTraceClient(config) {
3594
3963
  if (params.metadata) {
3595
3964
  active.span.setAttribute("apo.metadata", JSON.stringify(params.metadata));
3596
3965
  }
3597
- active.span.end();
3966
+ active.span.end(new Date(active.startWallMs + latency));
3598
3967
  activeSpans.delete(params.id);
3599
3968
  }
3600
3969
  return {
@@ -3628,6 +3997,7 @@ function createOtelAgentTaskTraceClient(config) {
3628
3997
  activeSpans.set(rootSpanId, {
3629
3998
  span: rootSpan,
3630
3999
  startedAt: monotonicNowMs2(),
4000
+ startWallMs: Date.now(),
3631
4001
  model: "trace"
3632
4002
  });
3633
4003
  let callCount = 1;
@@ -3653,7 +4023,8 @@ function createOtelAgentTaskTraceClient(config) {
3653
4023
  id: spanId,
3654
4024
  latency_ms: round32(monotonicNowMs2() - startedAt),
3655
4025
  output: options.summarize?.(result),
3656
- metadata: options.metadata
4026
+ metadata: options.metadata,
4027
+ ...options.usage?.(result)
3657
4028
  });
3658
4029
  callCount += 1;
3659
4030
  return result;