Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# ADR-65163: Report Dispatch Coordinator Operations in Audit and Logs

**Date**: 2026-10-03
**Status**: Draft
**Deciders**: gh-aw maintainers (review pending)

---

### Context

The dispatcher work coordinator already records evidence across activation snapshots, agent finish-intent files, and workflow job logs, but that evidence is split across artifacts and is difficult to inspect from a single `gh aw` report. This pull request extends both `gh aw logs` and `gh aw audit` so operators can investigate queue publication and worker reconciliation without manually correlating multiple downloaded files. The change must preserve the existing distinction between activation-time queue facts and run-time coordinator operations, keep compact `logs` defaults unchanged, and work with cached summaries so older downloads can be refreshed when coordinator evidence is incomplete.

### Decision

We will introduce a first-class `work_queue` report section backed by a `DispatchCoordinatorReport` extracted from activation artifacts, agent finish-intent artifacts, and workflow logs. We will add a dedicated `work-queue` artifact set for `gh aw logs`, include the same evidence in default `gh aw audit`, and persist the resulting coordinator data through audit and cached-logs schema updates so cached reports can be regenerated or backfilled when workflow logs are missing. We will treat activation snapshot transactions as historical queue facts, `finish_intent` as an unverified agent request, and workflow-log operations as separate publication and reconciliation diagnostics with source-file provenance.

Artifact selection is preserved explicitly rather than inferred from expanded artifact names: `github-api` and `work-queue` download the same artifact names but have different reporting intent. Usage-only logs do not expose previously cached work-queue evidence. Coordinator operations are read only from compiler-owned step log paths, excluding agent execution and whole-job copies. Workflow logs are staged and published with a completion marker after successful extraction; incomplete directories are not accepted as evidence. Required snapshot fields are checked for presence even when nullable, and text rendering escapes control characters without changing JSON values.

### Alternatives Considered

#### Alternative 1: Keep coordinator evidence only in raw artifacts

This would avoid new report fields, artifact-set plumbing, and cache-schema changes. It was not chosen because the PR's stated goal is to make queue publication and worker reconciliation investigable from a single logs or audit report, which raw artifact spelunking does not provide.

#### Alternative 2: Add coordinator reporting only to `audit`

Limiting the feature to `gh aw audit` would reduce the surface area of the change and avoid adding a new `logs` artifact set. It was not chosen because the PR explicitly adds `gh aw logs <run-id> --artifacts work-queue --json` support and updates cached logs output, indicating that operators need the same evidence in logs workflows as well as in audit workflows.

### Consequences

#### Positive
- Operators can inspect queue snapshots, assigned worker identifiers, finish intents, and timestamped coordinator operations from one structured `work_queue` section.
- `gh aw logs` gains an explicit `work-queue` download mode while `gh aw audit` includes the evidence by default, improving discoverability without changing compact logs defaults.
- Cached audit and logs summaries retain coordinator evidence and can backfill partial older reports when artifacts or workflow logs were previously missing.

#### Negative
- The implementation increases cache schema versions and adds more coupling between artifact download logic, workflow-log availability, and report rendering.
- Coordinator reporting now depends on parsing and validating multiple evidence sources, including workflow log text patterns that may require maintenance if runtime messages change.
- Downloading coordinator evidence for logs requires workflow logs in addition to activation and agent artifacts, which can increase report generation cost compared with usage-only logs.

#### Neutral
- The decision extends the existing dispatch-work-coordinator architecture rather than changing queue authority or protocol semantics.
- The report intentionally separates historical queue facts (`snapshot.transactions`) from run-specific operational diagnostics (`operations`), preserving the existing mental model for coordinator evidence.

---

*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.*
11 changes: 11 additions & 0 deletions docs/src/content/docs/reference/audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ The Friction section attributes execution-friction costs by driver and source. I

The Gateway Steering Events section reports `token_steering` and `timeout_steering` warnings emitted when a run approaches its AI Credits or time limit. JSON output includes each event's type, message, and timestamp when available.

The Work Queue section reports the activation-time queue snapshot, the assigned worker's work and claim IDs, the agent's finish intent, and timestamped coordinator operations from workflow job logs. In JSON, `work_queue.snapshot.transactions` contains the queue facts captured at activation, while `work_queue.operations` contains this run's publication and reconciliation diagnostics with their source files. A `finish_intent` is a request, not evidence that completion was verified. The field is omitted when no coordinator evidence is available.

Operations are accepted only from the compiler-owned snapshot, finish-intent-copy, and claim-reconciliation step logs, not from agent stdout or whole-job log copies. Incomplete workflow-log downloads are retried before their operations are reported. Text reports escape control characters in stored identifiers.

`audit` downloads this evidence by default. For `logs`, which defaults to compact usage downloads, select `work-queue` (activation and agent artifacts plus workflow logs) or `all`. Both commands include `work_queue` in their JSON reports, per run for `logs`, and preserve it in cached summaries. Usage-only logs omit the section even when a previous request cached work-queue evidence:

```bash
gh aw logs 1234567890 --artifacts work-queue --json
gh aw audit 1234567890 --artifacts work-queue --json
```

The Observability Insights section includes `skill_activations` when skill-invocation evidence is found. Each entry reports the skill name, `status` (`invoked`), the detection `source` (`agent_output` or `log_parse`), and provenance fields in JSON output. This makes it possible to distinguish skills that were merely restored or installed from skills that were actually invoked during the run.

The Graders section is present when the run recorded deterministic grader results (`graders` declared in the workflow frontmatter). The `graders` object in JSON output lists each grader (`id`, `name`, `status`, `value`, `unit`, `passed`, and, when declared in the grader manifest, `direction` and `threshold`) plus aggregate counts: `total`, `passed`, `failed`, `error_count`, and `unavailable_count`. Grader results are read from the compact `usage` artifact (mirrored there by the conclusion job), the unified `agent` artifact, or the `agent-output-fallback` artifact, so they are available even when `--artifacts usage` narrows the download. The same `graders` object is included per run in `gh aw logs --json` output.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/setup/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ gh aw audit 1234567890 --runtime cloud-hypervisor # Skip run unless sandb

The `--repo` flag accepts `owner/repo` format and is required when passing a bare numeric run ID without a full URL, allowing the command to locate the correct repository.

The `--artifacts` flag selects which artifact sets to download (default: `all`). Valid sets include `activation`, `agent`, `all`, `detection`, `evals`, `experiment`, `firewall`, `github-api`, `graders`, `info`, `mcp`, and `usage`. Use `all` to download the full artifact set. Unlike `gh aw logs`, which defaults to `usage`, `audit` defaults to `all` for comprehensive analysis. The `--experiment` flag filters to runs that include the named experiment; `--variant` further restricts to a specific variant value and requires `--experiment` to be set. The `--output/-o` flag overrides the output directory.
The `--artifacts` flag selects which artifact sets to download (default: `all`). Valid sets include `activation`, `agent`, `all`, `detection`, `evals`, `experiment`, `firewall`, `github-api`, `graders`, `info`, `mcp`, `usage`, and `work-queue`. The `work-queue` set downloads queue snapshots, worker finish intents, and workflow logs for coordinator operation reporting. Use `all` to download the full artifact set. Unlike `gh aw logs`, which defaults to `usage`, `audit` defaults to `all` for comprehensive analysis. The `--experiment` flag filters to runs that include the named experiment; `--variant` further restricts to a specific variant value and requires `--experiment` to be set. The `--output/-o` flag overrides the output directory.

Logs are saved to `.github/aw/logs/run-{id}/` with filenames indicating the extraction level. Pre-agent failures (integrity filtering, missing secrets, binary install) surface the actual error in `failure_analysis.error_summary`. Invalid run IDs return a human-readable error.

Expand Down
2 changes: 2 additions & 0 deletions pkg/cli/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type auditRunConfig struct {
jobID int64
stepNumber int
artifactFilter []string
includeWorkQueue bool
experimentFilter string
variantFilter string
runtimeFilter string
Expand Down Expand Up @@ -79,6 +80,7 @@ type auditAnalysisResults struct {
tokenUsageSummary *TokenUsageSummary
workingSet *WorkingSetMetrics
ledger *LedgerActivity
dispatchCoordinator *DispatchCoordinatorReport
friction *FrictionCostSummary
redactedDomainsAnalysis *RedactedDomainsAnalysis
rateLimitUsage *GitHubRateLimitUsage
Expand Down
5 changes: 5 additions & 0 deletions pkg/cli/audit_analysis_fanout.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ func launchFirewallAnalysis(g *errgroup.Group, gctx context.Context, results *au

// launchSupplementalAuditAnalyses exclusively writes redactedDomainsAnalysis, rateLimitUsage, artifacts, and safeItemsCount.
func launchSupplementalAuditAnalyses(g *errgroup.Group, gctx context.Context, results *auditAnalysisResults, runOutputDir string, verbose bool) {
runAuditAnalysis(g, gctx, true, "extractDispatchCoordinatorReport", "Failed to extract dispatch coordinator operations", func(v *DispatchCoordinatorReport) {
results.dispatchCoordinator = v
}, func() (*DispatchCoordinatorReport, error) {
return extractDispatchCoordinatorReport(runOutputDir)
})
runAuditAnalysis(g, gctx, verbose, "analyzeRedactedDomains", "Failed to analyze redacted domains", func(v *RedactedDomainsAnalysis) {
results.redactedDomainsAnalysis = v
}, func() (*RedactedDomainsAnalysis, error) {
Expand Down
8 changes: 7 additions & 1 deletion pkg/cli/audit_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

type auditCacheSource string

const auditSchemaVersion = 2
const auditSchemaVersion = 5

const (
auditCacheSourceFull auditCacheSource = "full"
Expand Down Expand Up @@ -99,6 +99,9 @@ func writeLogsAuditFile(processedRun ProcessedRun, processedRuns []ProcessedRun,
auditData, _ = buildLocalAuditData(processedRun, metrics, processedRun.MCPToolUsage)
auditData.CacheSource = auditCacheSourceLogs
}
if processedRun.DispatchCoordinator != nil {
auditData.DispatchCoordinator = processedRun.DispatchCoordinator
}
hydratedProcessedRuns := hydrateProcessedRunsWithCachedAudit(processedRuns)
auditData.Comparison = buildAuditComparisonForProcessedRuns(hydrateProcessedRunWithCachedAudit(processedRun), hydratedProcessedRuns)
if err := writeAuditData(runOutputDir, auditData); err != nil {
Expand Down Expand Up @@ -141,5 +144,8 @@ func hydrateProcessedRunWithCachedAudit(processedRun ProcessedRun) ProcessedRun
if len(processedRun.GatewaySteeringEvents) == 0 {
processedRun.GatewaySteeringEvents = audit.GatewaySteeringEvents
}
if processedRun.DispatchCoordinator == nil {
processedRun.DispatchCoordinator = audit.DispatchCoordinator
}
return processedRun
}
1 change: 1 addition & 0 deletions pkg/cli/audit_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ func writeCachedAuditRunFixture(t *testing.T, outputDir string, runID int64, fin
},
}
require.NoError(t, saveRunSummary(runOutputDir, summary, false))
markWorkflowLogsComplete(t, runOutputDir)

auditData := AuditData{
SchemaVersion: auditSchemaVersion,
Expand Down
7 changes: 7 additions & 0 deletions pkg/cli/audit_render_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ func renderAuditReport(ctx context.Context, processedRun ProcessedRun, metrics L
if auditData.Ledger == nil {
auditData.Ledger = processedRun.Ledger
}
// NoBaseline runs avoid writing audit-cache changes, including coordinator backfills.
if mergeDispatchCoordinatorReport(&auditData.DispatchCoordinator, processedRun.DispatchCoordinator) && !opts.NoBaseline {
Comment thread
pelikhan marked this conversation as resolved.
Resolved
if err := writeAuditData(runOutputDir, auditData); err != nil {
return err
}
}
if opts.Group {
parseAuditLogsIfRequested(runID, runOutputDir, opts)
return nil
Expand All @@ -64,6 +70,7 @@ func buildRenderedAuditDataFromCache(ctx context.Context, processedRun Processed
if auditData.Ledger == nil {
auditData.Ledger = processedRun.Ledger
}
mergeDispatchCoordinatorReport(&auditData.DispatchCoordinator, processedRun.DispatchCoordinator)
createdItems := resolveCreatedItems(runOutputDir, processedRun.SafeOutputs)
addAuditOutcomeSummary(ctx, &auditData, createdItems)
auditData.Comparison = buildRenderedAuditComparison(ctx, processedRun, createdItems, runOutputDir, opts)
Expand Down
Loading