Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Transaction Log Format

Nia maintains a machine-readable transaction log in JSON Lines (JSONL) format at .nia/work/<job_id>/logs/transaction.jsonl. Each workflow execution is logged for enterprise reporting, cost tracking, and usage analytics.

File Location

.nia/work/
└── <job_id>/
    └── logs/
        └── transaction.jsonl

Example: .nia/work/job_399/logs/transaction.jsonl

Format

Each line in the transaction log is a JSON object representing a single event. Events are written as they occur during workflow execution.

Event Types

Workflow Event

Logs the execution of a workflow command (e.g., nia issue draft, nia code implement).

{
  "event_type": "workflow",
  "command": "issue draft",
  "start_time": "2026-04-20T23:34:22.753Z",
  "start_commit_sha": "dd014a85",
  "end_time": "2026-04-20T23:35:45.123Z",
  "end_commit_sha": "98cec87b",
  "trace_file": "traces/20260420_233418_issue.trace.md",
  "success": true,
  "model": "claude-sonnet-4.5",
  "role": "product_manager",
  "custom_agent": "none",
  "token_usage": {
    "input_tokens": 608900,
    "cached_tokens": 556700,
    "output_tokens": 5100
  }
}

Fields

FieldTypeRequiredDescription
event_typestringYesAlways "workflow" for workflow events
commandstringYesThe workflow command executed (e.g., "issue draft")
start_timestringYesISO 8601 timestamp when workflow started
start_commit_shastringNoGit commit SHA at workflow start (omitted if not in git repo)
end_timestringNoISO 8601 timestamp when workflow completed
end_commit_shastringNoGit commit SHA at workflow completion
trace_filestringNoPath to trace file (relative to job directory)
successbooleanNoWhether the workflow completed successfully
error_messagestringNoError message if workflow failed
token_usageobjectNoToken consumption statistics (see below)
modelstringYesAI model used (e.g., "claude-sonnet-4.5"). Value is "not set" for start events or when unavailable
rolestringYesRole prompt used (e.g., "product_manager"). Value is "none" when custom agent is used, "not set" for start events
custom_agentstringYesCustom agent name (e.g., "python-expert"). Value is "none" when not configured, "not set" for start events

Note on Agent Configuration Fields: The model, role, and custom_agent fields are always present in workflow events. When a value is not applicable, the field contains a descriptive sentinel string ("not set" or "none") rather than being omitted or set to null. This ensures a consistent schema for reporting and analytics.

  • Use "not set" sentinel to indicate the value wasn’t available at event time (e.g., start events)
  • Use "none" sentinel to indicate intentional absence (e.g., no custom agent configured)
  • role and custom_agent are mutually exclusive in completion events: when custom_agent has a value, role will be "none"

Token Usage Object

The token_usage field captures AI agent token consumption for cost tracking. This field is optional and only present when the agent supports token reporting.

FieldTypeDescription
input_tokensintegerNumber of tokens sent to the model (prompt size)
cached_tokensintegerNumber of tokens served from cache (reduces cost)
output_tokensintegerNumber of tokens generated by the model (completion size)
reasoning_tokensintegerNumber of reasoning tokens used (optional, when available)

Supported Agents:

  • ✅ GitHub Copilot CLI (parses token usage from stderr or stdout via STDERR:-prefixed lines)
  • ✅ Claude Code CLI (parses token usage from stream-json events)
  • ✅ OpenCode (parses token usage from step_finish events)
  • ❌ Gemini CLI (returns None, field is omitted from JSON)

Token Units: All token counts are in individual tokens, not thousands.

Example Calculation:

{
  "input_tokens": 608900,    // 608.9k tokens input
  "cached_tokens": 556700,   // 556.7k tokens cached (not charged)
  "output_tokens": 5100      // 5.1k tokens output
}

To calculate billable tokens:

billable_input = input_tokens - cached_tokens
billable_input = 608900 - 556700 = 52200 tokens (52.2k)
billable_output = 5100 tokens (5.1k)

Agent Configuration Fields

The model, role, and custom_agent fields capture the effective AI agent configuration used for workflow execution. These fields enable cost tracking, performance analysis, and audit trails.

Example with Standard Role:

{
  "event_type": "workflow",
  "command": "issue draft",
  "model": "claude-sonnet-4.5",
  "role": "product_manager",
  "custom_agent": "none",
  "success": true
}

Example with Custom Agent:

{
  "event_type": "workflow",
  "command": "code implement",
  "model": "gpt-5.4",
  "role": "none",
  "custom_agent": "python-expert",
  "success": true
}

Example Start Event:

{
  "event_type": "workflow",
  "command": "issue draft",
  "start_time": "2026-04-20T23:34:22.753Z",
  "model": "not set",
  "role": "not set",
  "custom_agent": "not set"
}

Sentinel Values:

  • "not set": Value wasn’t available at event time (used in start events before configuration resolution)
  • "none": Intentional absence (e.g., no custom agent configured, or role not used because custom agent was used)

Use Cases:

  • Cost Tracking: Group workflows by model to calculate usage costs per model tier
  • Performance Analysis: Compare workflow success rates and durations across different models
  • Troubleshooting: Identify which model and configuration were used for problematic workflows
  • Audit Trail: Track which roles and custom agents were used for compliance and review purposes

Utility Event

Logs utility command execution (e.g., nia config show, nia guide). In addition to the local JSONL log, this event is forwarded to App Insights (consent-gated), the same way workflow events are.

{
  "event_type": "utility",
  "command": "config show",
  "timestamp": "2026-04-20T23:30:15.000Z",
  "success": true
}

Example Log File

{"event_type":"workflow","command":"issue draft","start_time":"2026-04-20T23:34:22.753Z","start_commit_sha":"dd014a85","end_time":"2026-04-20T23:35:45.123Z","end_commit_sha":"98cec87b","trace_file":"traces/20260420_233418_issue.trace.md","success":true,"model":"claude-sonnet-4.5","role":"product_manager","custom_agent":"none","token_usage":{"input_tokens":608900,"cached_tokens":556700,"output_tokens":5100}}
{"event_type":"workflow","command":"code implement","start_time":"2026-04-20T23:40:10.000Z","start_commit_sha":"98cec87b","end_time":"2026-04-20T23:45:30.500Z","end_commit_sha":"a1b2c3d4","trace_file":"traces/20260420_234010_code.trace.md","success":true,"model":"gpt-5.4","role":"none","custom_agent":"python-expert","token_usage":{"input_tokens":420000,"cached_tokens":380000,"output_tokens":8500}}
{"event_type":"utility","command":"config show","timestamp":"2026-04-20T23:50:00.000Z","success":true}

Use Cases

1. Cost Tracking

Extract token usage for billing and cost analysis:

# Total tokens consumed
jq -s 'map(select(.token_usage != null)) |
       map(.token_usage | .input_tokens + .output_tokens) |
       add' transaction.jsonl

2. Usage Analytics

Count successful vs. failed workflows:

# Success rate
jq -s 'group_by(.success) |
       map({success: .[0].success, count: length})' transaction.jsonl

3. Performance Metrics

Calculate average workflow duration:

# Average duration in seconds
jq -s 'map(select(.end_time != null)) |
       map(((.end_time | fromdateiso8601) -
            (.start_time | fromdateiso8601))) |
       add / length' transaction.jsonl

4. Command Usage

Most frequently used workflows:

# Top 5 commands
jq -s 'group_by(.command) |
       map({command: .[0].command, count: length}) |
       sort_by(.count) |
       reverse |
       .[0:5]' transaction.jsonl

5. Model Usage Analysis

Track which AI models are being used and their success rates:

# Count workflows by model (excluding sentinel values)
jq -s 'map(select(.model != null and .model != "not set")) |
       group_by(.model) |
       map({model: .[0].model, count: length})' transaction.jsonl

# Success rate by model
jq -s 'map(select(.model != null and .model != "not set" and .success != null)) |
       group_by(.model) |
       map({
         model: .[0].model,
         total: length,
         successful: map(select(.success == true)) | length
       }) |
       map({
         model: .model,
         success_rate: ((.successful / .total) * 100 | round)
       })' transaction.jsonl

6. Custom Agent Usage

Identify which custom agents are most frequently used:

# Custom agent usage (excluding sentinel values)
jq -s 'map(select(.custom_agent != null and .custom_agent != "none" and .custom_agent != "not set")) |
       group_by(.custom_agent) |
       map({agent: .[0].custom_agent, count: length}) |
       sort_by(.count) |
       reverse' transaction.jsonl

7. Cost Tracking by Model

Calculate token usage per model for cost analysis:

# Token usage by model
jq -s 'map(select(.model != null and .model != "not set" and .token_usage != null)) |
       group_by(.model) |
       map({
         model: .[0].model,
         total_input: map(.token_usage.input_tokens) | add,
         total_output: map(.token_usage.output_tokens) | add
       })' transaction.jsonl

Backward Compatibility

The transaction log format is designed for backward compatibility:

  • New fields are added with default behavior for missing values
  • Existing fields maintain their schema and semantics
  • Old log parsers continue to work (they ignore unknown fields)
  • Sentinel values (since v2.12.0): The model, role, and custom_agent fields use sentinel strings ("not set", "none") instead of omitting fields or using null. Old logs without these fields can still be read.

Agent Configuration Fields (since v2.12.0):

  • Old logs (before v2.12.0) don’t have model, role, or custom_agent fields
  • New logs always include these fields with either real values or sentinel strings
  • When parsing old logs, treat missing fields as if they contain "not set"
  • When querying logs, filter out sentinel values ("not set", "none") to get real data

Example: The token_usage field was added in version 2.11.0. Logs from older versions don’t have this field, and parsers handle its absence gracefully. Similarly, agent configuration fields are optional when deserializing but always present when serializing.

Best Practices

  1. Parse line-by-line: JSONL files can be very large. Process them as a stream rather than loading all into memory.

  2. Handle missing fields: Always check for field existence before accessing:

    if (event.token_usage) {
      const tokens = event.token_usage.input_tokens;
    }
    
  3. Filter by event type: Use event_type field to process only relevant events:

    jq 'select(.event_type == "workflow")' transaction.jsonl
    
  4. Aggregate across jobs: For organization-wide analytics, collect transaction logs from multiple job directories.

See Also