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.

Execution Identity Fields

Every uploaded document, regardless of event type, carries two fields that identify the execution that emitted it:

FieldTypeDescription
instance_idstringGlobally unique id of the emitting execution, <type>-<uuid> (e.g. workflow-3f2a9c81-...). Omitted only for synthetic contexts that emit nothing.
callersarray of stringsAncestor instance ids, root first. Omitted for a root execution (a command typed directly by a user); callers[0] is the root, the last element is the direct parent, and the length is the depth.

These two fields are not written to the local file. The local JSONL records the raw event; the identity is attached when the event is turned into a document for OpenSearch and OTEL. Reconstruct call trees from the backend, not from transaction.jsonl. Locally, .nia/work/<job_id>/ already scopes events to one job.

These are the fields to use when reconstructing or querying a call tree (which command triggered which workflow triggered which step). Do not use invocation_source for this — it is a low-cardinality manual/flow label derived from callers for the App Insights sink only, and OpenSearch has no explicit mapping for it.

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-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-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 (cache reads, reduces cost)
cache_write_tokensintegerNumber of tokens written to cache this turn (cache creation, priced separately from reads; omitted when the agent doesn’t report it)
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-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 (when telemetry is enabled), the same way workflow events are.

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

Workflow Engine Events

nia workflow run writes six further event types as the state machine advances. They are routed through the same pipeline as workflow and utility events, so they also reach a self-hosted OpenSearch/OTEL backend when one is configured. They are not forwarded to App Insights, which only accepts workflow and utility events.

event_typeWritten whenKey fields
workflow_startedA workflow execution beginsworkflow_type, workflow_id, execution_id, initial_state
workflow_state_transitionThe state machine changes statefrom_state, to_state, outcome, execution_id, command
branch_evaluatedA branch condition is evaluatedfrom_state, to_state, condition_desc, result
step_executionA step (shell, builtin or agent) finishesstep_id, step_type, phase, outcome, duration_ms
check_evaluationA check finishescheck_id, check_type, phase, result, action
approvalAn approval gate is released or bypassedgate_id, step, approver_email, approval_code, method
{
  "event_type": "approval",
  "timestamp": "2026-04-20T23:38:02.000Z",
  "workflow_id": "392",
  "workflow_type": "issue-to-pr",
  "execution_id": "20260420_233418",
  "gate_id": "code_review_gate",
  "step": "code_review",
  "approver_email": "dev@example.com",
  "approval_code": "LGTM",
  "method": "manual",
  "success": true
}

Privacy. approval events are the only transaction events carrying an approver identity. When uploaded, approver_email is hashed if [privacy] strict_privacy = true is set in telemetry.toml; approval_code is uploaded verbatim. Do not use secrets as approval codes. Deliberately, this data reaches only your own OpenSearch/OTEL backend — never Progress Analytics. See Security Model.

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-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