computeAgenticFraction classifies tool sequences into reasoning vs. data-gathering turns and returns the ratio of reasoning turns to total turns. Reasoning turns are those where the agent makes decisions, writes output, or uses multiple tool types. Data-gathering turns use only read-oriented tools.
(processedRun ProcessedRun)
| 462 | // Reasoning turns are those where the agent makes decisions, writes output, or uses |
| 463 | // multiple tool types. Data-gathering turns use only read-oriented tools. |
| 464 | func computeAgenticFraction(processedRun ProcessedRun) float64 { |
| 465 | run := processedRun.Run |
| 466 | if run.Turns <= 0 { |
| 467 | auditAgenticLog.Printf("Computing agentic fraction: run_id=%d turns=0, returning 0.0", processedRun.Run.DatabaseID) |
| 468 | return 0.0 |
| 469 | } |
| 470 | auditAgenticLog.Printf("Computing agentic fraction: run_id=%d turns=%d safe_items=%d", processedRun.Run.DatabaseID, run.Turns, run.SafeItemsCount) |
| 471 | |
| 472 | // If no tool sequence data, estimate from write actions |
| 473 | writeCount := run.SafeItemsCount |
| 474 | if writeCount > 0 && run.Turns > 0 { |
| 475 | // At minimum, the fraction of turns that produced write actions are agentic |
| 476 | // and non-write turns doing data gathering are deterministic-reducible |
| 477 | agenticTurns := writeCount |
| 478 | // Add reasoning turns: at least 1 turn of reasoning per write action, |
| 479 | // plus initial planning turn |
| 480 | reasoningTurns := min(writeCount+1, run.Turns) |
| 481 | agenticTurns = max(agenticTurns, reasoningTurns) |
| 482 | agenticTurns = min(agenticTurns, run.Turns) |
| 483 | return float64(agenticTurns) / float64(run.Turns) |
| 484 | } |
| 485 | |
| 486 | // No write actions: if the run is read-only with few turns, nearly all |
| 487 | // turns are reasoning (the AI is analyzing, not just gathering data) |
| 488 | if run.Turns <= 3 { |
| 489 | return 1.0 |
| 490 | } |
| 491 | |
| 492 | // For longer read-only runs, assume early turns gather context and |
| 493 | // later turns do analysis. Heuristic: half the turns are reasoning. |
| 494 | return 0.5 |
| 495 | } |
| 496 | |
| 497 | func containsAny(value string, terms ...string) bool { |
| 498 | for _, term := range terms { |