# Reporting

> Generate comprehensive multi-tenant security and operational reports from LimaCharlie. Provides billing summaries, usage roll-ups, detection trends, sensor health monitoring, and configuration audits across multiple organizations. Supports both per-tenant detailed breakdowns and cross-tenant aggregated roll-ups. Built with strict data accuracy guardrails to prevent fabricated metrics. Supports partial report generation when some organizations fail, with transparent error documentation. Time windows always displayed, detection limits clearly flagged, zero cost calculations.

- Skill: `refractionpoint/reporting` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add refractionpoint/reporting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/refractionpoint/reporting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: refractionpoint (https://skillmd.com/u/refractionpoint)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/refractionpoint/reporting

---


# LimaCharlie Reporting Skill

---

## LimaCharlie Integration

> **Prerequisites**: Run `/init-lc` to initialize LimaCharlie context.

### LimaCharlie CLI Access

All LimaCharlie operations use the `limacharlie` CLI directly:

```bash
limacharlie <noun> <verb> --oid <oid> --output yaml [flags]
```

For command help and discovery: `limacharlie <command> --ai-help`

### Critical Rules

| Rule | Wrong | Right |
|------|-------|-------|
| **CLI Access** | Call MCP tools or spawn api-executor | Use `Bash("limacharlie ...")` directly |
| **Output Format** | `--output json` | `--output yaml` (more token-efficient) |
| **Filter Output** | Pipe to jq/yq | Use `--filter JMESPATH` to select fields |
| **LCQL Queries** | Write query syntax manually | Use `limacharlie ai generate-query` first |
| **Timestamps** | Calculate epoch values | Use `date +%s` or `date -d '7 days ago' +%s` |
| **OID** | Use org name | Use UUID (call `limacharlie org list` if needed) |

---

## Overview

This skill enables AI-assisted generation of comprehensive security and operational reports across LimaCharlie organizations. It provides structured access to billing data, usage statistics, detection summaries, sensor health, and configuration audits. Supports both per-tenant detailed reports and cross-tenant aggregated roll-ups.

**Core Philosophy**: Accuracy over completeness. This skill prioritizes data accuracy with strict guardrails that make fabricated metrics impossible. Reports clearly document what data is available, what failed, and what limits were applied.

## Purpose

- Generate multi-tenant reports across 50+ organizations
- Provide billing and usage summaries for customer invoicing
- Analyze security detection trends across customer base
- Monitor sensor health and deployment status
- Audit organizational configurations
- Track operational metrics for capacity planning
- Support partial report generation with clear error documentation

## When to Use This Skill

Use this skill when you need to:

### Multi-Tenant MSSP Reports
- **"Generate monthly report for all my customers"** - Comprehensive overview across all organizations
- **"Billing summary for November 2025"** - Usage and billing data for invoicing period
- **"Show me customer health dashboard"** - Sensor status and detection trends across clients
- **"Which customers had the most detections this month?"** - Security activity ranking
- **"Export usage data for all organizations"** - Bulk data extraction for analysis

### Single Organization Deep Dives
- **"Detailed report for Client ABC"** - Complete organizational analysis
- **"Security posture for organization XYZ"** - Detection and rule effectiveness
- **"Sensor health for customer PDQ"** - Endpoint deployment and status

### Billing and Usage Analysis
- **"Usage trends across all customers"** - Comparative analysis for capacity planning
- **"Which orgs are using the most data?"** - Resource consumption identification
- **"Show subscription status for all clients"** - Billing health check

### Operational Monitoring
- **"How many sensors are offline across all orgs?"** - Fleet health monitoring
- **"Detection volume trends this month"** - Security activity patterns
- **"Which customers need attention?"** - Issue identification and prioritization

## Report Templates

This skill supports structured JSON templates that define input schemas, output schemas, and data sources for each report type. Templates are located in `skills/reporting/templates/`.

| Template | Description | Scope |
|----------|-------------|-------|
| `billing-report.json` | Invoice-focused billing data with SKU breakdown | single / all |
| `mssp-executive-report.json` | High-level fleet health for MSSP leadership | single / all |
| `customer-health-report.json` | Comprehensive customer success tracking | single / all |
| `detection-analytics-report.json` | Detection volume, categories, and trends | single / all |

### Template Structure

Each template defines:
- **Input schema**: Required and optional parameters with types and validation
- **Output schema**: Expected JSON structure for structured data consumers
- **Data sources**: Which API calls populate each field

### Using Templates

1. **Read the template** to understand required inputs
2. **Validate user input** against the input schema
3. **Use the orchestration layer** (parallel subagents) for data collection
4. **Format output** to match the output schema
5. **Display results in the console** as formatted markdown/tables (default) - HTML output only when user explicitly requests it

Templates ensure consistency across reports and enable:
- Programmatic consumption of report data
- Validation of inputs and outputs
- Clear documentation of data sources
- Reproducible report generation

## Critical Prerequisites

### Authentication
Ensure you are authenticated to LimaCharlie with access to target organizations:
- User must have permissions across multiple organizations (MSSP/partner account)
- Billing data requires admin/owner role per organization
- Usage statistics accessible with standard read permissions

### Understanding Organization IDs (OIDs)
**⚠️ CRITICAL**: Organization ID (OID) is a **UUID** (like `c7e8f940-1234-5678-abcd-1234567890ab`), **NOT** the organization name.

- Use `limacharlie org list` to get OID from organization name
- All API calls require the UUID, not the friendly name
- OIDs are permanent identifiers (names can change)

### Time Range Requirements

**⚠️ MANDATORY: Prompt User for Time Range**

Before generating any report that requires detection or event data, you MUST ask the user to confirm or specify the time range using the `AskUserQuestion` tool:

```
AskUserQuestion(
  questions=[{
    "question": "What time range should I use for this report?",
    "header": "Time Range",
    "options": [
      {"label": "Last 24 hours", "description": "Most recent day of data"},
      {"label": "Last 7 days", "description": "Past week of activity"},
      {"label": "Last 30 days", "description": "Past month of activity"},
      {"label": "Custom range", "description": "I'll specify exact dates"}
    ],
    "multiSelect": false
  }]
)
```

If user selects "Custom range", follow up to get specific start/end dates.

**Core Requirements:**
- All reports MUST specify explicit time ranges
- Time windows MUST be displayed in every report section
- NEVER assume a default time range without user confirmation
- Maximum recommended range: 90 days (API limitations)

**⚠️ CRITICAL: Dynamic Timestamp Calculation**

**NEVER use hardcoded epoch values from examples or documentation!**

ALWAYS calculate timestamps dynamically using bash before making API calls:

```bash
# Get current Unix timestamp
NOW=$(date +%s)

# Calculate relative time ranges
HOURS_24_AGO=$((NOW - 86400))        # 24 hours = 86400 seconds
DAYS_7_AGO=$((NOW - 604800))          # 7 days = 604800 seconds
DAYS_30_AGO=$((NOW - 2592000))        # 30 days = 2592000 seconds
DAYS_90_AGO=$((NOW - 7776000))        # 90 days = 7776000 seconds

# For specific date ranges (user-provided)
START=$(date -d "2025-11-01 00:00:00 UTC" +%s)
END=$(date -d "2025-11-30 23:59:59 UTC" +%s)

# Display human-readable for confirmation
echo "Time range: $(date -d @$START) to $(date -d @$END)"
```

**Why This Matters:**
- The detection API (`get_historic_detections`) uses Unix epoch timestamps in SECONDS
- Using stale or example timestamps (like those in documentation) returns NO DATA
- The API only returns detections within the specified time window
- Incorrect timestamps = empty results = incorrect reports

**Validation Before API Call:**
```bash
# Verify timestamps are reasonable
if [ $START -gt $END ]; then
  echo "ERROR: Start time is after end time"
  exit 1
fi

if [ $END -gt $NOW ]; then
  echo "WARNING: End time is in the future, using current time"
  END=$NOW
fi
```

## Data Accuracy Guardrails

### Principle 1: NEVER Fabricate Data

**Absolute Rules:**
- ❌ NEVER estimate, infer, or extrapolate data not in API responses
- ❌ NEVER calculate costs (no pricing data in API)
- ❌ NEVER guess at missing fields
- ❌ NEVER assume "standard" values
- ❌ NEVER substitute placeholder data for errors

**Always:**
- ✅ Show "N/A" or "Data unavailable" for missing fields
- ✅ Display explicit warnings when limits reached
- ✅ Document all errors and failures prominently
- ✅ State "Retrieved X of potentially more" when truncated
- ✅ Link to invoice URLs for billing details

### Principle 2: Detection Limit Handling

**Default Limit**: 5,000 detections per organization

**Required Workflow:**
```
1. Query with limit=5000
2. Track retrieved_count
3. Check: limit_reached = (retrieved_count >= 5000)
4. If limit_reached:
   ⚠️ DISPLAY PROMINENT WARNING
   "DETECTION LIMIT REACHED
    Retrieved: 5,000 detections
    Actual count: May be significantly higher

    For complete data:
      - Narrow time range
      - Query specific date ranges
      - Filter by category or sensor"
```

**Never Say:**
- ❌ "Total detections: 5,000" (implies this is complete)
- ❌ "Approximately 5,000 detections" (ambiguous)

**Always Say:**
- ✅ "Retrieved 5,000 detections (limit reached - actual count may be higher)"
- ✅ "Detection sample: First 5,000 of potentially more"

### Principle 3: Pricing and Billing

**Absolute Rule: ZERO Cost Calculations**

**What You CAN Show:**
- ✅ Usage metrics: events, data output (GB), evaluations, peak sensors
- ✅ Billing metadata: plan name, status, next billing date
- ✅ Invoice links: get-org-invoice-url for actual costs

**What You CANNOT Do:**
- ❌ Calculate costs based on usage
- ❌ Estimate bills
- ❌ Multiply usage by assumed rates
- ❌ Project future costs
- ❌ Compare plan pricing

**Even if user provides rates:**
- ❌ Don't perform calculations
- ✅ Show usage metrics
- ✅ Provide invoice link
- ✅ State: "For billing details, see invoice: [URL]"

### Principle 4: Time Window Display

**MANDATORY in Every Report:**

```
Header (always visible):
  Generated: 2025-11-20 14:45:30 UTC
  Time Window: 2025-11-01 00:00:00 UTC to 2025-11-30 23:59:59 UTC (30 days)
  Organizations: 45 of 50 processed successfully

Per Section:
  ── Usage Statistics ──
  Data Retrieved: 2025-11-20 14:45:35 UTC
  Coverage Period: Nov 1-30, 2025 (30 days)
  Source: get-usage-stats API
  Data Freshness: Daily updates (24hr delay typical)
```

### Principle 5: Error Transparency

**Partial Reports Are Acceptable:**
- Generate reports even if some organizations fail
- Clearly document which organizations failed and why
- Never silently skip failed organizations
- Provide actionable remediation steps

**Error Documentation Template:**
```
⚠️ FAILED ORGANIZATIONS (3 of 50)

Client ABC (oid: c7e8f940-...)
  Status: ❌ Failed
  Error: 403 Forbidden
  Endpoint: get-billing-details
  Reason: Insufficient permissions
  Impact: Billing data unavailable
  Action: Grant billing:read permission
  Timestamp: 2025-11-20 14:32:15 UTC
```

## Available Data Sources

### 1. Multi-Tenant Discovery
**CLI**: `limacharlie org list`
- **Endpoint**: GET /v1/user/orgs
- **OID Required**: NO (user-level operation)
- **Returns**: List of accessible organizations with OIDs and names
- **Usage**: Starting point for all multi-tenant operations
- **Data Freshness**: Real-time

**Response Structure:**
```json
{
  "orgs": [
    {
      "oid": "c7e8f940-1234-5678-abcd-1234567890ab",
      "name": "Client ABC Production",
      "role": "owner"
    },
    {
      "oid": "c7e8f940-5678-1234-dcba-0987654321ab",
      "name": "Client XYZ Security",
      "role": "admin"
    }
  ],
  "total": 2
}
```

**Validation:**
- Check `orgs` array exists and not empty
- Verify each OID is valid UUID format
- Confirm role is "owner", "admin", or "user"

### 2. Organization Metadata
**CLI**: `limacharlie org info`
- **Endpoint**: GET /v1/orgs/{oid}
- **OID Required**: YES
- **Returns**: Organization details, creation date, settings
- **Data Freshness**: Real-time

**Response Structure:**
```json
{
  "oid": "c7e8f940-...",
  "name": "Client ABC",
  "created": 1672531200,
  "creator": "user@example.com"
}
```

### 3. Usage Statistics
**CLI**: `limacharlie org stats`
- **Endpoint**: GET /v1/usage/{oid}
- **OID Required**: YES
- **Returns**: Daily usage metrics (~90 days historical)
- **Data Freshness**: Daily aggregation, 24-hour delay typical

**Response Structure:**
```json
{
  "usage": {
    "2025-11-06": {
      "sensor_events": 131206,
      "output_bytes_tx": 500123456,
      "replay_num_evals": 435847,
      "peak_sensors": 4
    },
    "2025-11-07": {
      "sensor_events": 145821,
      "output_bytes_tx": 523456789,
      "replay_num_evals": 478932,
      "peak_sensors": 4
    }
  }
}
```

**Field Definitions:**
- `sensor_events`: Total events ingested from sensors
- `output_bytes_tx`: Data transmitted to outputs (in bytes)
- `replay_num_evals`: D&R rule evaluations performed
- `peak_sensors`: Maximum concurrent sensors online

**Critical Notes:**
- API returns ~90 days of data
- **MUST filter to requested time range** - don't use all 90 days
- Dates are in YYYY-MM-DD format
- `output_bytes_tx` is in BYTES - convert to GB: divide by 1,073,741,824
- ALWAYS show both: "450 GB (483,183,820,800 bytes)"

**Aggregation Rules:**
```
For time range Nov 1-30, 2025:
  1. Filter usage dict to only dates in range
  2. Sum daily values:
     total_events = sum(usage[date]['sensor_events'] for date in range)
  3. Document calculation:
     "Total Events: 1,250,432,100
      Calculation: Sum of daily sensor_events from Nov 1-30, 2025
      Source: get-usage-stats"
```

### 4. Billing Details
**CLI**: `limacharlie billing details`
- **Endpoint**: GET /orgs/{oid}/details (billing endpoint)
- **OID Required**: YES
- **Permissions**: Requires admin/owner role
- **Returns**: Subscription info, payment status, billing contact
- **Data Freshness**: Updated on changes, ~1hr delay

**Response Structure:**
```json
{
  "plan": "enterprise",
  "status": "active",
  "billing_email": "billing@example.com",
  "payment_method": "card",
  "last_four": "4242",
  "next_billing_date": 1672531200,
  "auto_renew": true
}
```

**Common Error**: 403 Forbidden (insufficient permissions)
- Expected for user-role accounts
- Document in failures section
- Continue with partial data

### 5. Invoice URLs
**CLI**: `limacharlie billing invoice-url`
- **Endpoint**: GET /orgs/{oid}/invoice
- **OID Required**: YES
- **Returns**: Direct URL to organization's invoice
- **Usage**: For actual billing amounts (no cost calculations in reports)

**Response Structure:**
```json
{
  "url": "https://billing.limacharlie.io/invoice/..."
}
```

**Usage in Reports:**
```
For billing details and charges:
→ View Invoice: https://billing.limacharlie.io/invoice/...
```

### 6. Sensor Inventory
**CLI**: `limacharlie sensor list`
- **Endpoint**: GET /v1/sensors/{oid}
- **OID Required**: YES
- **Returns**: All sensors with metadata
- **Data Freshness**: Real-time snapshot
- **Large Result Handling**: May return `resource_link` if >100KB

**Response Structure (normal):**
```json
{
  "sensors": {
    "sensor-id-1": {
      "sid": "sensor-id-1",
      "hostname": "SERVER01",
      "plat": 268435456,
      "arch": 1,
      "enroll": "2024-01-15T10:30:00Z",
      "alive": "2024-11-20 14:22:13",
      "int_ip": "10.0.1.50",
      "ext_ip": "203.0.113.45",
      "oid": "c7e8f940-..."
    }
  },
  "continuation_token": ""
}
```

**Large Result Handling:**

For large result sets, pipe CLI output to a file:

```bash
# Save large results to file
limacharlie sensor list --oid <oid> --output yaml > /tmp/sensors.yaml

# Or use --filter to extract needed fields directly
limacharlie sensor list --oid <oid> --filter "length(sensors)" --output yaml  # Count
limacharlie sensor list --oid <oid> --filter "sensors.*.hostname" --output yaml  # Hostnames
```

**Field Validation - CRITICAL:**

**CORRECT Fields to Use:**
- ✅ `alive`: "2025-11-20 14:22:13" (datetime string for last seen)
- ✅ `plat`: Platform code (int or string)
- ✅ `hostname`: Sensor hostname
- ✅ `sid`: Sensor ID
- ✅ `int_ip`: Internal IP address
- ✅ `ext_ip`: External IP address

**INCORRECT Fields (Common Mistakes):**
- ❌ `last_seen`: Often 0 or missing - DO NOT USE
- ❌ Use `alive` field instead for offline detection

**Offline Sensor Detection:**
```python
# Parse alive field (datetime string format: "YYYY-MM-DD HH:MM:SS")
from datetime import datetime, timezone

alive_str = sensor_info.get('alive', '')
if alive_str:
    # Parse: "2025-10-01 17:08:10"
    alive_dt = datetime.strptime(alive_str, '%Y-%m-%d %H:%M:%S')
    alive_dt = alive_dt.replace(tzinfo=timezone.utc)
    last_seen_timestamp = alive_dt.timestamp()

    hours_offline = (current_time - last_seen_timestamp) / 3600

    # Categorize with explicit thresholds:
    if hours_offline < 24:
        category = "Recently offline (< 24 hours)"
    elif hours_offline < 168:  # 7 days
        category = "Offline short term (1-7 days)"
    elif hours_offline < 720:  # 30 days
        category = "Offline medium term (7-30 days)"
    else:
        category = "Offline long term (30+ days)"
```

**Platform Code Translation:**

Traditional OS platforms (strings):
- "windows" → Windows
- "linux" → Linux
- "macos" → macOS
- "chrome" → Chrome OS

Numeric platform codes (extensions/adapters):
- Use two-pass pattern analysis
- Collect hostname samples
- Match patterns: ext-, test-, slack-, office365-
- ALWAYS show sample hostnames in report

**Example:**
```
Platform: LimaCharlie Extensions (code: 2415919104)
Sample hostnames: ext-strelka-01, ext-hayabusa-02, ext-secureannex-01
Sensor count: 30
```

### 7. Online Sensors
**CLI**: `limacharlie sensor list --online`
- **Endpoint**: GET /v1/sensors/online/{oid}
- **OID Required**: YES
- **Returns**: List of currently online sensor IDs
- **Data Freshness**: Real-time

**Response Structure:**
```json
{
  "sensors": [
    "sensor-id-1",
    "sensor-id-2",
    "sensor-id-3"
  ]
}
```

**Usage:**
```python
# Convert to set for O(1) lookup
online_sids = set(response['sensors'])

# Check if sensor is online
is_online = sensor_id in online_sids

# Calculate offline count
total_sensors = 2500
online_count = len(online_sids)
offline_count = total_sensors - online_count
```

### 8. Historic Detections
**CLI**: `limacharlie detection list`
- **Endpoint**: GET /v1/insight/{oid}/detections
- **OID Required**: YES
- **Returns**: Security detections within time range
- **Data Freshness**: Near real-time (5-minute delay typical)
- **Default Limit**: 5,000 per query

**Query Parameters:**
- `start`: Unix epoch timestamp (seconds)
- `end`: Unix epoch timestamp (seconds)
- `limit`: Maximum detections to retrieve (default: 5000)
- `sid`: Filter by sensor ID (optional)
- `cat`: Filter by category (optional)

**Response Structure:**
```json
{
  "detects": [
    {
      "detect_id": "detect-uuid-123",
      "cat": "suspicious_process",
      "source_rule": "general.encoded-powershell",
      "namespace": "general",
      "ts": 1732108934567,
      "sid": "sensor-xyz-123",
      "detect": {
        "event": {
          "TIMESTAMP": 1732108934567,
          "COMMAND_LINE": "powershell.exe -encodedCommand ...",
          "FILE_PATH": "C:\\Windows\\System32\\..."
        },
        "routing": {
          "sid": "sensor-xyz-123",
          "hostname": "SERVER01"
        }
      }
    }
  ],
  "next_cursor": ""
}
```

**Field Validation - CRITICAL:**

**CORRECT Fields:**
- ✅ `source_rule`: "namespace.rule-name" (actual rule identifier)
- ✅ `cat`: Category name
- ✅ `ts`: Timestamp (MAY be seconds or milliseconds - normalize!)
- ✅ `sid`: Sensor ID (may be "N/A" for some detections)
- ✅ `detect_id`: Unique detection identifier

**INCORRECT Fields (Common Mistakes):**
- ❌ `rule_name`: Doesn't exist - use `source_rule` instead
- ❌ `severity`: NOT in detection records (only in D&R rule config)

**Timestamp Normalization (MANDATORY):**
```python
ts = detection.get('ts', 0)

# Check magnitude to determine units
if ts > 10000000000:
    # Milliseconds - convert to seconds
    ts = ts / 1000

# Sanity check result
if ts < 1577836800:  # Before 2020-01-01
    # Invalid timestamp
    display = "Invalid timestamp"
elif ts > time.time() + 86400:  # More than 1 day in future
    # Invalid timestamp
    display = "Invalid timestamp"
else:
    # Valid - format for display
    display = datetime.fromtimestamp(ts, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
```

**Detection Limit Tracking (MANDATORY):**
```python
retrieved_count = 0
for detection in detections:
    retrieved_count += 1

limit_reached = (retrieved_count >= query_limit)

if limit_reached:
    # MUST display prominent warning
    warning = f"""
    ⚠️ DETECTION LIMIT REACHED
    Retrieved: {retrieved_count:,} detections
    Actual count: May be significantly higher

    This organization has more detections than retrieved.
    For complete data:
      - Narrow time range (currently: {days} days)
      - Query specific date ranges separately
      - Filter by category or sensor
    """
```

### 9. D&R Rules Inventory
**CLI**: `limacharlie dr list`
- **Endpoint**: GET /v1/rules/{oid}?namespace=general
- **OID Required**: YES
- **Returns**: Custom D&R rules in general namespace
- **Data Freshness**: Real-time

**Response Structure:**
```json
{
  "custom-rule-1": {
    "name": "custom-rule-1",
    "namespace": "general",
    "detect": {
      "event": "NEW_PROCESS",
      "op": "contains",
      "path": "event/COMMAND_LINE",
      "value": "powershell"
    },
    "respond": [
      {
        "action": "report",
        "name": "suspicious_powershell"
      }
    ],
    "is_enabled": true
  }
}
```

**Usage:**
- Count total rules: Object.keys(response).length
- Count enabled: Filter where is_enabled === true
- List detection types: Extract event types from detect blocks

### 10. Outputs Configuration
**CLI**: `limacharlie output list`
- **Endpoint**: GET /v1/outputs/{oid}
- **OID Required**: YES
- **Returns**: Configured data outputs (SIEM, storage, webhooks)
- **Data Freshness**: Real-time

**Usage in Reports:**
- Count total outputs
- List destination types
- Note any disabled outputs

## Workflow Patterns

### Architecture Overview

This skill uses a **parallel subagent architecture** for efficient multi-tenant data collection:

```
┌─────────────────────────────────────────────────────────────┐
│  reporting (this skill)                                │
│  ├─ Phase 1: Discovery (list orgs via CLI)                   │
│  ├─ Phase 2: Time range validation                          │
│  ├─ Phase 3: Spawn parallel agents ────────────────────┐    │
│  ├─ Phase 4: Aggregate results                         │    │
│  └─ Phase 5: Generate report                           │    │
└────────────────────────────────────────────────────────┼────┘
                                                         │
    ┌────────────────────────────────────────────────────┘
    │
    │  Spawns ONE agent per organization (in parallel)
    │
    ▼
┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐
│org-       │  │org-       │  │org-       │  │org-       │
│reporter   │  │reporter   │  │reporter   │  │reporter   │
│  Org 1    │  │  Org 2    │  │  Org 3    │  │  Org N    │
└─────┬─────┘  └─────┬─────┘  └─────┬─────┘  └─────┬─────┘
      │              │              │              │
      │  Each agent collects ALL data for its org:
      │  - org info, usage, billing, sensors,
      │  - detections, rules, outputs
      │              │              │              │
      └──────────────┴──────┬───────┴──────────────┘
                            │
                            ▼
                    Structured JSON results
                    returned to parent skill
```

**Benefits of this architecture:**
- True parallelism across organizations
- Each agent handles its own error recovery
- Reduced context usage in main skill
- Scalable to 50+ organizations

### Output Format Requirements

**Default Output: Console (Formatted Markdown)**

By default, ALL report data MUST be displayed directly in the console as formatted markdown tables and text. This includes:
- Executive summary with key metrics
- Per-organization breakdowns
- Aggregate statistics and rollups
- All warnings, errors, and data limitations
- Methodology and data provenance sections

**Console Output Characteristics:**
- Formatted tables using markdown pipe syntax
- Status indicators as text badges: `[GREEN]`, `[YELLOW]`, `[RED]`
- Numeric formatting with thousand separators
- All data visible without requiring external files

**HTML Output: Only When Explicitly Requested**

HTML visualization should ONLY be generated when the user explicitly requests it using phrases like:
- "generate as HTML"
- "create HTML dashboard"
- "export to HTML"
- "visual report"
- "interactive dashboard"

If user requests HTML output:
1. First collect and display data in console (so user sees results immediately)
2. Then spawn `html-renderer` agent to create the HTML file
3. Save to `/tmp/{report-name}-{date}.html`
4. Open in browser

**NEVER automatically generate HTML** - console output is always the default.

### Pattern 1: Multi-Tenant MSSP Comprehensive Report

**Template Reference**: `templates/mssp-executive-report.json`

**User Request Examples:**
- "Generate monthly MSSP report for all my customers"
- "Show me comprehensive overview across all organizations"
- "Create security and billing summary for November 2025"

**Step-by-Step Execution:**

```
┌─ PHASE 1: DISCOVERY ──────────────────────────────┐
│ 1. Use CLI to get org list:                       │
│    limacharlie org list --output yaml              │
│                                                    │
│ 2. Validation:                                    │
│    ✓ Check orgs array exists and not empty        │
│    ✓ Validate each OID is UUID format             │
│    ✓ Count total organizations                    │
│                                                    │
│ 3. User Confirmation (if >20 orgs):              │
│    "Found 50 organizations. Generate report for   │
│     all 50? This may take a few minutes."         │
│                                                    │
│    Options to present:                            │
│    - Yes, process all 50                          │
│    - No, let me filter first                      │
│    - Show me the organization list                │
└────────────────────────────────────────────────────┘

┌─ PHASE 2: TIME RANGE - ASK USER & CALCULATE ──────┐
│                                                    │
│ 4. ⚠️ MANDATORY: Ask user for time range:         │
│                                                    │
│    Use AskUserQuestion tool:                      │
│    - "Last 24 hours"                              │
│    - "Last 7 days"                                │
│    - "Last 30 days"                               │
│    - "Custom range"                               │
│                                                    │
│    If "Custom range", ask for specific dates.     │
│    NEVER assume or default without asking!        │
│                                                    │
│ 5. ⚠️ CRITICAL: Calculate timestamps dynamically: │
│                                                    │
│    ```bash                                        │
│    NOW=$(date +%s)                                │
│    # Based on user selection:                     │
│    # 24h:  START=$((NOW - 86400))                 │
│    # 7d:   START=$((NOW - 604800))                │
│    # 30d:  START=$((NOW - 2592000))               │
│    END=$NOW                                       │
│    ```                                            │
│                                                    │
│    NEVER use hardcoded epoch values!              │
│    Stale timestamps = NO DATA returned!           │
│                                                    │
│ 6. Validation Checks:                             │
│    ✓ Start timestamp < End timestamp              │
│    ✓ End timestamp <= Current time                │
│    ✓ Range is reasonable (<= 90 days)            │
│    ✓ Timestamps are Unix epoch in SECONDS         │
│                                                    │
│ 7. Display for user confirmation:                 │
│    "Time Range:                                    │
│     - Start: [calculated date] UTC                │
│     - End: [calculated date] UTC                  │
│     - Duration: X days                            │
│     - Unix: [start_epoch] to [end_epoch]"         │
└────────────────────────────────────────────────────┘

┌─ PHASE 3: SPAWN PARALLEL AGENTS ──────────────────┐
│ 7. Spawn org-reporter agents IN PARALLEL:   │
│                                                    │
│    CRITICAL: Send ALL Task calls in a SINGLE      │
│    message to achieve true parallelism:           │
│                                                    │
│    Task(                                          │
│      subagent_type="lc-essentials:org-reporter",  │
│      prompt="Collect reporting data for org       │
│        'Client ABC' (OID: uuid-1)                 │
│        Time Range:                                │
│        - Start: 1730419200                        │
│        - End: 1733011199                          │
│        Detection Limit: 5000"                     │
│    )                                              │
│    Task(                                          │
│      subagent_type="lc-essentials:org-reporter",  │
│      prompt="Collect reporting data for org       │
│        'Client XYZ' (OID: uuid-2)..."             │
│    )                                              │
│    ... (one Task per organization)                │
│                                                    │
│ 8. Each agent returns structured JSON:           │
│    {                                              │
│      "org_name": "...",                           │
│      "oid": "...",                                │
│      "status": "success|partial|failed",          │
│      "data": { usage, billing, sensors, ... },    │
│      "errors": [...],                             │
│      "warnings": [...]                            │
│    }                                              │
│                                                    │
│ 9. Wait for all agents to complete               │
└────────────────────────────────────────────────────┘

┌─ PHASE 4: AGGREGATE RESULTS ──────────────────────┐
│ 10. Categorize agent results:                    │
│     success_orgs = [] (status == "success")       │
│     partial_orgs = [] (status == "partial")       │
│     failed_orgs = [] (status == "failed")         │
│                                                    │
│ 11. Multi-Org Aggregation:                       │
│     Aggregate across SUCCESS + PARTIAL orgs:      │
│     - Sum total_events (from usage)               │
│     - Sum total_output_bytes (convert to GB)      │
│     - Sum total_evaluations                       │
│     - Sum peak_sensors                            │
│     - Count total sensors                         │
│     - Count total detections (track limits)       │
│                                                    │
│     Document for each aggregate:                  │
│     - Formula used                                │
│     - Orgs included count                         │
│     - Orgs excluded (and why)                     │
│     - Time range covered                          │
└────────────────────────────────────────────────────┘

┌─ PHASE 5: REPORT GENERATION (CONSOLE OUTPUT) ─────┐
│ ⚠️ DEFAULT: Display ALL data in console as        │
│    formatted markdown. HTML only if requested.    │
│                                                    │
│ 17. Console Report Structure:                     │
│                                                    │
│     A. HEADER (mandatory metadata)                │
│        ═══════════════════════════════════════    │
│        MSSP Comprehensive Report                  │
│        Generated: 2025-11-20 14:45:30 UTC         │
│        Time Window: Nov 1-30, 2025 (30 days)      │
│        Organizations: 45 of 50 successful         │
│        ═══════════════════════════════════════    │
│                                                    │
│     B. EXECUTIVE SUMMARY                          │
│        - High-level metrics (successful orgs)     │
│        - Critical warnings and alerts             │
│        - Failed organization count                │
│        - Detection limit warnings                 │
│                                                    │
│     C. AGGREGATE METRICS                          │
│        Total Across 45 Organizations              │
│        (Excluded: 5 orgs - see failures section)  │
│                                                    │
│        - Total Sensor Events: 1,250,432,100       │
│          Calculation: Sum of daily sensor_events  │
│          from Nov 1-30, 2025                      │
│                                                    │
│        - Total Data Output: 3,847 GB              │
│          (4,128,394,752,000 bytes)                │
│          Calculation: Sum of daily output_bytes_tx│
│          ÷ 1,073,741,824                          │
│                                                    │
│        - Peak Sensors: 12,450                     │
│          Calculation: Sum of max peak_sensors     │
│                                                    │
│     D. PER-ORGANIZATION DETAILS                   │
│        For each successful organization:          │
│                                                    │
│        ── Client ABC ─────────────────────────    │
│        OID: c7e8f940-1234-5678-abcd-...           │
│        Data Retrieved: 2025-11-20 14:45:35 UTC    │
│                                                    │
│        Usage Statistics (Nov 1-30, 2025):         │
│          - Sensor Events: 42,150,000              │
│          - Data Output: 125 GB (134,217,728,000 B)│
│          - D&R Evaluations: 1,200,450             │
│          - Peak Sensors: 250                      │
│                                                    │
│        Sensor Inventory:                          │
│          - Total Sensors: 250                     │
│          - Online: 245 (98%)                      │
│          - Offline: 5 (2%)                        │
│          - Platforms: Windows (150), Linux (100)  │
│                                                    │
│        Detection Summary:                         │
│          Retrieved: 5,000 detections              │
│          ⚠️ LIMIT REACHED - actual count higher   │
│          Top Categories:                          │
│            - suspicious_process: 1,250            │
│            - network_threat: 890                  │
│            - malware: 450                         │
│                                                    │
│        Billing Status:                            │
│          - Plan: Enterprise                       │
│          - Status: Active ✓                       │
│          - Next Billing: Dec 1, 2025              │
│          - Invoice: [URL]                         │
│                                                    │
│     E. FAILED ORGANIZATIONS SECTION               │
│        ⚠️ FAILED ORGANIZATIONS (5 of 50)          │
│                                                    │
│        Client XYZ (oid: c7e8f940-...)             │
│          Status: ❌ Failed                        │
│          Error: 403 Forbidden                     │
│          Endpoint: get-billing-details            │
│          Reason: Insufficient billing permissions │
│          Impact: Billing data unavailable         │
│          Available: Usage stats, sensor inventory │
│          Action: Grant billing:read permission    │
│          Timestamp: 2025-11-20 14:32:15 UTC       │
│                                                    │
│     F. DETECTION LIMIT WARNINGS                   │
│        ⚠️ Organizations at Detection Limit:       │
│                                                    │
│        15 of 45 organizations exceeded the 5,000  │
│        detection limit. Actual counts are higher. │
│                                                    │
│        Organizations affected:                    │
│          - Client A: 5,000 retrieved ⚠️           │
│          - Client B: 5,000 retrieved ⚠️           │
│          - [... 13 more]                          │
│                                                    │
│        Recommendation: For complete detection     │
│        data, narrow time ranges or query specific │
│        date ranges for these organizations.       │
│                                                    │
│     G. METHODOLOGY SECTION                        │
│        Data Sources:                              │
│          - limacharlie org list: Organization discovery │
│          - limacharlie org stats: Daily metrics   │
│          - limacharlie billing details: Sub info  │
│          - limacharlie sensor list: Endpoint inv  │
│          - limacharlie sensor list --online: Live │
│          - limacharlie detection list: Security   │
│          - limacharlie dr list: Custom rules    │
│                                                    │
│        Query Parameters:                          │
│          - Detection limit: 5,000 per org         │
│          - Time range: Nov 1-30, 2025             │
│          - Date filtering: Applied to usage stats │
│                                                    │
│        Calculations:                              │
│          - Bytes to GB: value ÷ 1,073,741,824     │
│          - Aggregations: Sum across successful    │
│            organizations only                     │
│          - Timestamps: Normalized from mixed      │
│            seconds/milliseconds format            │
│                                                    │
│        Data Freshness:                            │
│          - Usage stats: Daily (24hr delay)        │
│          - Detections: Near real-time (5min)      │
│          - Sensor status: Real-time               │
│          - Billing: Updated on changes (~1hr)     │
│                                                    │
│     H. FOOTER                                     │
│        ═══════════════════════════════════════    │
│        Report completed: 2025-11-20 14:50:15 UTC  │
│        Execution time: 4 minutes 45 seconds       │
│                                                    │
│        For questions or issues:                   │
│        Contact: support@limacharlie.io            │
│                                                    │
│        Disclaimer: Usage metrics shown are from   │
│        LimaCharlie APIs. For billing and pricing, │
│        refer to individual organization invoices. │
│        ═══════════════════════════════════════    │
└────────────────────────────────────────────────────┘

Progress Reporting During Execution:
  Display progress as orgs are processed:

  "Generating MSSP Report for 50 Organizations...

   [1/50] Client ABC... ✓ Success (2.3s)
   [2/50] Client XYZ... ✓ Success (1.8s)
   [3/50] Client PDQ... ⚠️ Billing permission denied
   [4/50] Client RST... ✓ Success (2.1s)
   [5/50] Client MNO... ❌ Failed: 500 Server Error
   ...
   [50/50] Client ZZZ... ✓ Success (1.9s)

   Collection Complete:
     ✓ Successful: 45 organizations
     ⚠️ Partial: 2 organizations (some data unavailable)
     ❌ Failed: 3 organizations

   Generating report structure..."
```

**IMPORTANT: Console Output is Complete**

The above console output displays ALL collected data. Do NOT automatically proceed to HTML generation.

**Only generate HTML if user explicitly requests it** (e.g., "export as HTML", "create dashboard"). When HTML is requested:
1. T

…(truncated)
