tx trace
Execution tracing for debugging agent run failures
Purpose
tx trace provides execution tracing for debugging agent run failures. It combines two primitives:
- IO Capture -- File paths to transcripts, stderr, and stdout stored on run records
- Metrics Events -- Operational spans and metrics written to the
eventstable
Traces help answer "why did this run fail?" by correlating internal service timing with agent tool calls.
List Recent Runs
tx trace list [options]Options
| Option | Description |
|---|---|
--hours <n> | Time window in hours (default: 24) |
--limit <n> | Maximum number of results (default: 20) |
--json | Output as JSON |
Examples
# List recent runs
tx trace list
# Output:
# Recent Runs (last 24h)
# ───────────────────────────────────────────────────────────────────────────
# ID Agent Task Status Spans Time
# run-abc12345 tx-implementer tx-6407952c failed 23 2h ago
# run-def67890 tx-implementer tx-8a4be920 success 45 3h ago
# Show last 48 hours
tx trace list --hours 48
# JSON output
tx trace list --jsonimport { TxClient } from '@jamesaphoenix/tx-agent-sdk'
const tx = new TxClient({ apiUrl: 'http://localhost:3456' })
// List runs via the REST API
const { runs, hasMore, nextCursor } = await tx.runs.list({
limit: 20,
status: 'failed'
})
// Get a specific run with transcript messages
const { run, messages } = await tx.runs.get('run-abc12345')Trace MCP tools are planned for a future release. Use the CLI to inspect traces:
tx trace list --json
tx trace show run-abc12345 --jsonGET /api/runs?limit=20&status=failedQuery Parameters
| Parameter | Description |
|---|---|
limit | Max results (default: 20) |
cursor | Pagination cursor |
status | Filter by status (comma-separated) |
taskId | Filter by task ID |
agent | Filter by agent name |
Example
curl http://localhost:3456/api/runs?limit=10&status=failedResponse
{
"runs": [
{
"id": "run-abc12345",
"taskId": "tx-6407952c",
"agent": "tx-implementer",
"status": "failed",
"startedAt": "2025-01-15T14:00:00.000Z",
"endedAt": "2025-01-15T14:27:00.000Z",
"exitCode": 1,
"errorMessage": "ValidationError: Cannot mark blocked task as done"
}
],
"nextCursor": null,
"hasMore": false,
"total": 1
}Show Run Details
tx trace show <run-id> [options]Arguments
| Argument | Required | Description |
|---|---|---|
<run-id> | Yes | Run ID (e.g., run-abc12345) |
Options
| Option | Description |
|---|---|
--full | Combined timeline with tool calls from transcript |
--json | Output as JSON |
Examples
# Show metrics events for a run
tx trace show run-abc12345
# Output:
# Run: run-abc12345
# Agent: tx-implementer
# Task: tx-6407952c
# Status: failed
# Transcript: runs/run-abc12345.jsonl
# Stderr: runs/run-abc12345.stderr
#
# Metrics Events:
# ───────────────────────────────────────────────────────────────────────────
# 14:23:45 [span] TaskService.show 12ms ok
# 14:23:46 [span] ReadyService.getReady 45ms ok
# 14:24:12 [span] TaskService.update 8ms ok
# 14:27:09 [span] TaskService.done 156ms error
# └─ ValidationError: Cannot mark blocked task as done
# Combined timeline (metrics + transcript tool calls)
tx trace show run-abc12345 --full
# Output:
# Combined Timeline:
# ───────────────────────────────────────────────────────────────────────────
# 14:23:45.100 [span] TaskService.show 12ms ok
# 14:23:45.200 [tool] Bash: tx show tx-6407952c
# 14:23:46.000 [span] ReadyService.getReady 45ms ok
# 14:23:46.100 [tool] Read: /path/to/file.ts
# 14:27:09.000 [span] TaskService.done 156ms error
# └─ ValidationError: Cannot mark blocked task as doneimport { TxClient } from '@jamesaphoenix/tx-agent-sdk'
const tx = new TxClient({ apiUrl: 'http://localhost:3456' })
// Get run details with parsed transcript messages
const { run, messages } = await tx.runs.get('run-abc12345')
// messages includes tool_use, tool_result, text, and thinking entries
for (const msg of messages) {
if (msg.type === 'tool_use') {
console.log(`Tool: ${msg.tool_name}`)
}
}Trace MCP tools are planned for a future release. Use the CLI:
tx trace show run-abc12345 --full --jsonGET /api/runs/:idExample
curl http://localhost:3456/api/runs/run-abc12345Response
{
"run": {
"id": "run-abc12345",
"taskId": "tx-6407952c",
"agent": "tx-implementer",
"status": "failed",
"startedAt": "2025-01-15T14:00:00.000Z",
"endedAt": "2025-01-15T14:27:00.000Z",
"transcriptPath": "runs/run-abc12345.jsonl",
"stderrPath": "runs/run-abc12345.stderr",
"exitCode": 1,
"errorMessage": "ValidationError: Cannot mark blocked task as done"
},
"messages": [
{
"role": "assistant",
"content": "I'll read the task details...",
"type": "text",
"timestamp": "2025-01-15T14:23:45.000Z"
}
]
}View Transcript
tx trace transcript <run-id>Outputs raw JSONL content from the transcript file. Designed to be piped to jq for filtering:
# View full transcript
tx trace transcript run-abc12345
# Filter to tool calls only
tx trace transcript run-abc12345 | jq 'select(.type == "tool_use")'
# Filter to assistant messages
tx trace transcript run-abc12345 | jq 'select(.type == "assistant")'import { TxClient } from '@jamesaphoenix/tx-agent-sdk'
const tx = new TxClient({ apiUrl: 'http://localhost:3456' })
// Transcript messages are included in the run detail response
const { run, messages } = await tx.runs.get('run-abc12345')Use the CLI to access transcripts:
tx trace transcript run-abc12345Transcript messages are returned as part of the run detail endpoint:
GET /api/runs/:idThe messages array in the response contains parsed transcript entries.
View Stderr
tx trace stderr <run-id>Outputs raw stderr content. Useful for debugging crashes and runtime errors:
tx trace stderr run-abc12345
# Output:
# Error: SQLITE_BUSY: database is locked
# at Database.exec (/path/to/better-sqlite3.js:...)import { TxClient } from '@jamesaphoenix/tx-agent-sdk'
const tx = new TxClient({ apiUrl: 'http://localhost:3456' })
// Access stderr via REST API (HTTP mode)
// The SDK wraps the /api/runs/:id/stderr endpointUse the CLI:
tx trace stderr run-abc12345GET /api/runs/:id/stderrQuery Parameters
| Parameter | Description |
|---|---|
tail | Number of lines from the end (default: all) |
Example
curl http://localhost:3456/api/runs/run-abc12345/stderr?tail=50Response
{
"content": "Error: SQLITE_BUSY: database is locked\n at Database.exec ...",
"truncated": false
}Show Recent Errors
tx trace errors [options]Options
| Option | Description |
|---|---|
--hours <n> | Time window in hours (default: 24) |
--limit <n> | Maximum number of results (default: 20) |
--json | Output as JSON |
Example
tx trace errors
# Output:
# Recent Errors (last 24h)
# ────────────────────────────────────────────────────────────────────────────────
# 14:27:09 [span] run-abc12345 tx-implementer
# Name: TaskService.done
# Error: ValidationError: Cannot mark blocked task as done
# Task: tx-6407952c
# Duration: 156ms
#
# 13:15:22 [run] run-xyz98765 tx-planner
# Name: Run failed
# Error: Process exited with code 1Aggregates errors from three sources:
- Failed runs -- runs with
status = 'failed' - Error spans -- spans with
status = 'error'in metadata - Error events -- events with
event_type = 'error'
import { TxClient } from '@jamesaphoenix/tx-agent-sdk'
const tx = new TxClient({ apiUrl: 'http://localhost:3456' })
// List failed runs
const { runs } = await tx.runs.list({ status: 'failed', limit: 10 })Use the CLI:
tx trace errors --json# List failed runs
GET /api/runs?status=failed&limit=10TracingService
The TracingService provides programmatic tracing within Effect-TS service code.
withSpan
Wraps an Effect with a named span that records duration and status to the events table:
import { TracingService } from '@jamesaphoenix/tx-core'
import { Effect } from 'effect'
const program = Effect.gen(function* () {
const tracing = yield* TracingService
// Wrap an operation with a span
const result = yield* tracing.withSpan(
'MyService.processTask',
{ attributes: { taskId: 'tx-abc123' } },
Effect.gen(function* () {
// ... your operation here
return 'done'
})
)
// Records: event_type='span', content='MyService.processTask',
// duration_ms=<elapsed>, metadata={status:'ok', attributes:{...}}
})recordMetric
Records a point-in-time metric value:
const program = Effect.gen(function* () {
const tracing = yield* TracingService
yield* tracing.recordMetric('queue_depth', 42, { agent: 'tx-implementer' })
// Records: event_type='metric', content='queue_depth',
// duration_ms=42, metadata={agent:'tx-implementer'}
})withRunContext
Scopes a run ID to all nested spans using Effect's FiberRef:
const program = Effect.gen(function* () {
const tracing = yield* TracingService
yield* tracing.withRunContext('run-abc12345',
Effect.gen(function* () {
// All spans within this scope will have run_id = 'run-abc12345'
yield* tracing.withSpan('nested.operation', {},
Effect.succeed('value')
)
})
)
})Noop Implementation
When tracing is disabled, TracingServiceNoop is used. It passes effects through unchanged with zero overhead:
import { TracingServiceNoop } from '@jamesaphoenix/tx-core'
// Zero-overhead: withSpan returns the effect unchanged
// recordMetric returns Effect.void
// withRunContext returns the effect unchangedIO Capture Architecture
tx stores file paths, not file contents. The orchestrator decides how and where to capture IO:
.tx/
├── tasks.db
└── runs/
├── run-abc12345.jsonl # Claude transcript (stream-json output)
├── run-abc12345.stderr # Stderr capture
├── run-abc12345.stdout # Stdout capture (optional)
└── run-def67890.jsonlRun Record Columns
| Column | Description |
|---|---|
transcript_path | Path to JSONL transcript file |
stderr_path | Path to stderr capture file |
stdout_path | Path to stdout capture file |
Orchestrator Example
#!/bin/bash
RUN_ID="run-$(openssl rand -hex 4)"
RUN_DIR=".tx/runs"
mkdir -p "$RUN_DIR"
# Create run record
curl -X POST http://localhost:3456/api/runs \
-H "Content-Type: application/json" \
-d "{\"agent\": \"tx-implementer\", \"taskId\": \"$TASK_ID\"}"
# Run agent with IO capture
claude --print --output-format stream-json "$PROMPT" \
> "$RUN_DIR/$RUN_ID.jsonl" \
2> "$RUN_DIR/$RUN_ID.stderr"
# Update run record with paths and status
curl -X PATCH "http://localhost:3456/api/runs/$RUN_ID" \
-H "Content-Type: application/json" \
-d "{\"status\": \"completed\", \"transcriptPath\": \"runs/$RUN_ID.jsonl\"}"Transcript Adapters
Different LLM tools produce different transcript formats. tx includes adapters for parsing:
- ClaudeCodeAdapter -- Parses Claude's
--output-format stream-jsonJSONL - GenericJSONLAdapter -- Fallback for other JSONL-producing tools
The adapter is selected automatically based on the agent type stored in the run record.
Related Commands
tx ready-- List tasks available to work ontx done-- Complete a task (ends the logical work unit)tx attempts-- Record what approaches were triedtx sync-- Export run data to git-friendly JSONL