Run MassGen experiments and analyze logs using automation mode, logfire tracing, and SQL queries. Use this skill for performance analysis, debugging agent behavior, evaluating coordination patterns, and improving the logging structure, or whenever an ANALYSIS_REPORT.md is needed in a log directory.
This skill provides a structured workflow for running MassGen experiments and analyzing the resulting traces and logs using Logfire.
The log-analyzer skill helps you:
The massgen logs CLI provides quick access to log analysis:
uv run massgen logs list # Show all recent logs with analysis status
uv run massgen logs list --analyzed # Only logs with ANALYSIS_REPORT.md
uv run massgen logs list --unanalyzed # Only logs needing analysis
uv run massgen logs list --limit 20 # Show more logs
# Run from within your coding CLI (e.g., Claude Code) so it sees output
uv run massgen logs analyze # Analyze latest turn of latest log
uv run massgen logs analyze --log-dir PATH # Analyze specific log
uv run massgen logs analyze --turn 1 # Analyze specific turn
The prompt output tells your coding CLI to use this skill on the specified log directory.
uv run massgen logs analyze --mode self # Run 3-agent analysis team (prompts if report exists)
uv run massgen logs analyze --mode self --force # Overwrite existing report without prompting
uv run massgen logs analyze --mode self --turn 2 # Analyze specific turn
uv run massgen logs analyze --mode self --config PATH # Use custom config
Self-analysis mode runs MassGen with multiple agents to analyze logs from different perspectives (correctness, efficiency, behavior) and produces a combined ANALYSIS_REPORT.md.
MassGen log directories support multiple turns (coordination sessions). Each turn has its own turn_N/ directory with attempts inside:
log_YYYYMMDD_HHMMSS/
├── turn_1/ # First coordination session
│ ├── ANALYSIS_REPORT.md # Report for turn 1
│ ├── attempt_1/ # First attempt
│ └── attempt_2/ # Retry if orchestration restarted
├── turn_2/ # Second coordination session (if multi-turn)
│ ├── ANALYSIS_REPORT.md # Report for turn 2
│ └── attempt_1/
When analyzing, the --turn flag specifies which turn to analyze. Without it, the latest turn is analyzed.
Use Local Log Files When:
streaming_debug.log)coordination_events.json)agent_*/*/vote.json)coordination_table.txt)metrics_summary.json)Use Logfire When:
Rate Limiting: If Logfire returns a rate limit error, wait up to 60 seconds and retry rather than falling back to local logs. The rate limit resets quickly and Logfire data is worth waiting for when timing/hierarchy analysis is needed.
Key Local Log Files:
| File | Contains |
|------|----------|
| status.json | Real-time status with agent reliability metrics (enforcement events, buffer loss) |
| metrics_summary.json | Cost, tokens, tool stats, round history |
| coordination_events.json | Full event timeline with tool calls |
| coordination_table.txt | Human-readable coordination flow |
| streaming_debug.log | Raw streaming data including command strings |
| agent_*/*/vote.json | Vote reasoning and context |
| agent_*/*/execution_trace.md | Full tool calls, arguments, results, and reasoning - invaluable for debugging |
| execution_metadata.yaml | Config and session metadata |
Execution Traces (execution_trace.md):
These are the most detailed debug artifacts. Each agent snapshot includes an execution trace with:
Use execution traces when you need to understand exactly what an agent did and why - they capture everything the agent saw and produced during that answer/vote iteration.
Enforcement Reliability (status.json):
The status.json file includes per-agent reliability metrics that track workflow enforcement events:
{
"agents": {
"agent_a": {
"reliability": {
"enforcement_attempts": [
{
"round": 0,
"attempt": 1,
"max_attempts": 3,
"reason": "no_workflow_tool",
"tool_calls": ["search", "read_file"],
"error_message": "Must use workflow tools",
"buffer_preview": "First 500 chars of lost content...",
"buffer_chars": 1500,
"timestamp": 1736683468.123
}
],
"by_round": {"0": {"count": 2, "reasons": ["no_workflow_tool", "invalid_vote_id"]}},
"unknown_tools": ["execute_command"],
"workflow_errors": ["invalid_vote_id"],
"total_enforcement_retries": 2,
"total_buffer_chars_lost": 3000,
"outcome": "ok"
}
}
}
}
Enforcement Reason Codes:
| Reason | Description |
|--------|-------------|
| no_workflow_tool | Agent called tools but none were vote or new_answer |
| no_tool_calls | Agent provided text-only response, no tools called |
| invalid_vote_id | Agent voted for non-existent agent ID |
| vote_no_answers | Agent tried to vote when no answers exist |
| vote_and_answer | Agent used both vote and new_answer in same response |
| answer_limit | Agent hit max answer count limit |
| answer_novelty | Answer too similar to existing answers |
| answer_duplicate | Exact duplicate of existing answer |
| api_error | API/streaming error (e.g., "peer closed connection") |
| connection_recovery | API stream ended early, recovered with preserved context |
| mcp_disconnected | MCP server disconnected mid-session (e.g., "Server 'X' not connected") |
This data is invaluable for understanding why agents needed retries and how much content was lost due to enforcement restarts.
Before using this skill, you need to set up Logfire for observability.
pip install "massgen[observability]"
# Or with uv
uv pip install "massgen[observability]"
Go to https://logfire.pydantic.dev/ and create a free account.
# This creates ~/.logfire/credentials.json
uv run logfire auth
# Or set the token directly as an environment variable
export LOGFIRE_TOKEN=your_token_here
claude mcp add logfire -e LOGFIRE_READ_TOKEN="your-read-token-here" -- uvx logfire-mcp@latest
Then restart Claude Code and re-invoke this skill.
Logfire MCP Server (Optional but Recommended):
The Logfire MCP server provides enhanced analysis with precise timing data and cross-session queries. If LOGFIRE_READ_TOKEN is not set, self-analysis mode will automatically disable the Logfire MCP and fall back to local log files only.
When configured, the MCP server provides these tools:
mcp__logfire__arbitrary_query - Run SQL queries against logfire datamcp__logfire__schema_reference - Get the database schemamcp__logfire__find_exceptions_in_file - Find exceptions in a filemcp__logfire__logfire_link - Create links to traces in the UIRequired Flags:
--automation - Clean output for programmatic parsing -- see massgen-develops-massgen skill for more info on this flag--logfire - Enable Logfire tracing (optional, but required to populate Logfire data)uv run massgen --automation --logfire --config [config_file] "[question]"
Use run_in_background: true (or however you run tasks in the background) to run experiments asynchronously so you can monitor progress and end early if needed.
Expected Output (first lines):
LOG_DIR: .massgen/massgen_logs/log_YYYYMMDD_HHMMSS_ffffff
STATUS: .massgen/massgen_logs/log_YYYYMMDD_HHMMSS_ffffff/turn_1/attempt_1/status.json
QUESTION: Your task here
[Coordination in progress - monitor status.json for real-time updates]
Parse the LOG_DIR - you'll need this for file-based analysis!
status.json updates every 2 seconds; use that to track progress.
cat [log_dir]/turn_1/attempt_1/status.json
Key fields to monitor:
coordination.completion_percentage (0-100)coordination.phase - "initial_answer", "enforcement", "presentation"results.winner - null while running, agent_id when completeagents[].status - "waiting", "streaming", "answered", "voted", "error"agents[].error - null if ok, error details if failedAfter completion (exit code 0):
# Read the final answer
cat [log_dir]/turn_1/attempt_1/final/[winner]/answer.txt
Other useful files:
execution_metadata.yaml - Full config and execution detailscoordination_events.json - Complete event logcoordination_table.txt - Human-readable coordination summaryThe main table is records with these key columns:
| Column | Description |
|--------|-------------|
| span_name | Name of the span (e.g., "agent.agent_a.round_0") |
| span_id | Unique identifier for this span |
| parent_span_id | ID of the parent span (null for root) |
| trace_id | Groups all spans in a single trace |
| duration | Time in seconds |
| start_timestamp | When the span started |
| end_timestamp | When the span ended |
| attributes | JSON blob with custom attributes |
| message | Log message |
| is_exception | Boolean for errors |
| exception_type | Type of exception if any |
| exception_message | Exception message |
MassGen creates hierarchical spans:
coordination.session (root)
├── Coordination event: coordination_started
├── agent.agent_a.round_0
│ ├── llm.openrouter.stream
│ ├── mcp.filesystem.write_file
│ └── Tool execution: mcp__filesystem__write_file
├── agent.agent_b.round_0
├── Agent answer: agent1.1
├── agent.agent_a.round_1 (voting round)
├── Agent vote: agent_a -> agent1.1
├── Coordination event: winner_selected
└── agent.agent_a.presentation
├── Winner selected: agent1.1
├── llm.openrouter.stream
└── Final answer from agent_a
MassGen spans include these custom attributes (access via attributes->'key'):
| Attribute | Description |
|-----------|-------------|
| massgen.agent_id | Agent identifier (agent_a, agent_b) |
| massgen.iteration | Current iteration number |
| massgen.round | Round number for this agent |
| massgen.round_type | "initial_answer", "voting", or "presentation" |
| massgen.backend | Backend provider name |
| massgen.num_context_answers | Number of answers in context |
| massgen.is_winner | True for presentation spans |
| massgen.outcome | "vote", "answer", or "error" (set after round completes) |
| massgen.voted_for | Agent ID voted for (only set for votes) |
| massgen.voted_for_label | Answer label voted for (e.g., "agent1.1", only set for votes) |
| massgen.answer_label | Answer label assigned (e.g., "agent1.1", only set for answers) |
| massgen.error_message | Error message (only set when outcome is "error") |
| massgen.usage.input | Input token count |
| massgen.usage.output | Output token count |
| massgen.usage.reasoning | Reasoning token count |
| massgen.usage.cached_input | Cached input token count |
| massgen.usage.cost | Estimated cost in USD |
SELECT span_name, span_id, parent_span_id, duration, start_timestamp
FROM records
WHERE trace_id = '[YOUR_TRACE_ID]'
ORDER BY start_timestamp
LIMIT 50
SELECT span_name, trace_id, duration, start_timestamp
FROM records
WHERE span_name = 'coordination.session'
ORDER BY start_timestamp DESC
LIMIT 10
SELECT
span_name,
duration,
attributes->>'massgen.agent_id' as agent_id,
attributes->>'massgen.round' as round,
attributes->>'massgen.round_type' as round_type
FROM records
WHERE span_name LIKE 'agent.%'
ORDER BY start_timestamp DESC
LIMIT 20
SELECT
span_name,
duration,
parent_span_id,
start_timestamp
FROM records
WHERE span_name LIKE 'mcp.%' OR span_name LIKE 'Tool execution:%'
ORDER BY start_timestamp DESC
LIMIT 30
SELECT
span_name,
exception_type,
exception_message,
trace_id,
start_timestamp
FROM records
WHERE is_exception = true
ORDER BY start_timestamp DESC
LIMIT 20
SELECT
span_name,
duration,
attributes->>'gen_ai.request.model' as model,
start_timestamp
FROM records
WHERE span_name LIKE 'llm.%'
ORDER BY start_timestamp DESC
LIMIT 30
SELECT
CASE
WHEN parent_span_id IS NULL THEN span_name
ELSE ' └─ ' || span_name
END as hierarchy,
duration,
span_id,
parent_span_id
FROM records
WHERE trace_id = '[YOUR_TRACE_ID]'
ORDER BY start_timestamp
SELECT span_name, message, duration, start_timestamp
FROM records
WHERE span_name LIKE 'Coordination event:%'
OR span_name LIKE 'Agent answer:%'
OR span_name LIKE 'Agent vote:%'
OR span_name LIKE 'Winner selected:%'
ORDER BY start_timestamp DESC
LIMIT 30
uv run massgen --automation --logfire --config [config] "[prompt]" 2>&1
Query for recent sessions:
SELECT trace_id, duration, start_timestamp
FROM records
WHERE span_name = 'coordination.session'
ORDER BY start_timestamp DESC
LIMIT 5
Get full trace structure:
SELECT span_name, span_id, parent_span_id, duration
FROM records
WHERE trace_id = '[trace_id_from_step_2]'
ORDER BY start_timestamp
Slow tool calls:
SELECT span_name, duration, parent_span_id
FROM records
WHERE trace_id = '[trace_id]' AND span_name LIKE 'mcp.%'
ORDER BY duration DESC
Agent comparison:
SELECT
attributes->>'massgen.agent_id' as agent,
COUNT(*) as rounds,
SUM(duration) as total_time,
AVG(duration) as avg_round_time
FROM records
WHERE trace_id = '[trace_id]' AND span_name LIKE 'agent.%'
GROUP BY attributes->>'massgen.agent_id'
Use the MCP tool to create a viewable link:
mcp__logfire__logfire_link(trace_id="[your_trace_id]")
| Span Pattern | Source | Description |
|--------------|--------|-------------|
| coordination.session | coordination_tracker.py | Root session span |
| agent.{id}.round_{n} | orchestrator.py | Agent execution round |
| agent.{id}.presentation | orchestrator.py | Winner's final presentation |
| mcp.{server}.{tool} | mcp_tools/client.py | MCP tool execution |
| llm.{provider}.stream | backends | LLM streaming call |
| Tool execution: {name} | base_with_custom_tool.py | Tool wrapper |
| Coordination event: * | coordination_tracker.py | Coordination events |
| Agent answer: {label} | coordination_tracker.py | Answer submission |
| Agent vote: {from} -> {to} | coordination_tracker.py | Vote cast |
Use the tracer from structured_logging:
from massgen.structured_logging import get_tracer
tracer = get_tracer()
with tracer.span("my_operation", attributes={
"massgen.custom_key": "value",
}):
do_work()
Known limitation: When multiple agents run concurrently via asyncio.create_task, child spans may not nest correctly under agent round spans. This is an OpenTelemetry context propagation issue with concurrent async code. The presentation phase works correctly because only one agent runs.
Workaround: For accurate nesting in concurrent scenarios, explicit context passing with contextvars.copy_context() would be needed.
Main Documentation: https://logfire.pydantic.dev/docs/
| Topic | URL | Description |
|-------|-----|-------------|
| Getting Started | /docs/ | Overview, setup, and core concepts |
| Manual Tracing | /docs/guides/onboarding-checklist/add-manual-tracing/ | Creating spans, adding attributes |
| SQL Explorer | /docs/guides/web-ui/explore/ | Writing SQL queries in the UI |
| Live View | /docs/guides/web-ui/live/ | Real-time trace monitoring |
| Query API | /docs/how-to-guides/query-api/ | Programmatic access to data |
| OpenAI Integration | /docs/integrations/llms/openai/ | LLM call instrumentation |
Spans vs Logs:
with logfire.span():)logfire.info(), logfire.error(), etc.)Span Names vs Messages:
span_name = the first argument (used for filtering, keep low-cardinality)message = formatted result with attribute values interpolatedlogfire.info('Hello {name}', name='Alice') → span_name="Hello {name}", message="Hello Alice"Attributes:
attributes->>'key' or attributes->'key'(attributes->'cost')::floatThe Logfire Live View UI (https://logfire.pydantic.dev/) provides:
/ to open) with auto-completeThe Explore page uses Apache DataFusion SQL syntax (similar to Postgres):
-- Subqueries and CTEs work
WITH recent AS (
SELECT * FROM records
WHERE start_timestamp > now() - interval '1 hour'
)
SELECT * FROM recent WHERE is_exception;
-- Access nested JSON
SELECT attributes->>'massgen.agent_id' as agent FROM records;
-- Cast JSON values
SELECT (attributes->'token_count')::int as tokens FROM records;
-- Time filtering is efficient
WHERE start_timestamp > now() - interval '30 minutes'
Logfire auto-instruments OpenAI calls when configured:
MassGen's backends use this for llm.{provider}.stream spans.
Logfire:
mcp__logfire__schema_reference toolMassGen:
AI_USAGE.mddocs/source/reference/status_file.rstmassgen/structured_logging.py--automation --logfire togetherNote that you may get an error like so:
Error: Error executing tool arbitrary_query: b'{"detail":"Rate limit exceeded for organization xxx: per minute
limit reached."}'
In this case, please sleep (for up to a minute) and try again.
When asked to analyze a MassGen log run, generate a markdown report saved to [log_dir]/turn_N/ANALYSIS_REPORT.md where N is the turn being analyzed. Each turn (coordination session) gets its own analysis report as a sibling to the attempt directories. The report must cover the Standard Analysis Questions below.
CRITICAL: Do not assume any agent's answer is "correct" unless the user explicitly provides ground truth.
Every analysis report MUST answer these questions:
submit_checklist startsubmit_checklist end -> first draft_approach startdraft_approach end -> round endsubmit_checklist end -> round end (if no propose happened)Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer