Back to skills
SkillHub ClubShip Full StackFull Stack

context-assembler

Assembles relevant context for agent spawns with prioritized ranking. Ranks packages by relevance, enforces token budgets with graduated zones, captures error patterns for learning, and supports configurable per-agent retrieval limits.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
0
Hot score
74
Updated
March 20, 2026
Overall rating
C2.5
Composite score
2.5
Best-practice grade
C60.3

Install command

npx @skill-hub/cli install mehdic-artk-context-assembler

Repository

mehdic/ARTK

Skill path: .claude/skills/context-assembler

Assembles relevant context for agent spawns with prioritized ranking. Ranks packages by relevance, enforces token budgets with graduated zones, captures error patterns for learning, and supports configurable per-agent retrieval limits.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: mehdic.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install context-assembler into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/mehdic/ARTK before adding context-assembler to shared team environments
  • Use context-assembler for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: context-assembler
description: Assembles relevant context for agent spawns with prioritized ranking. Ranks packages by relevance, enforces token budgets with graduated zones, captures error patterns for learning, and supports configurable per-agent retrieval limits.
version: 1.5.3
allowed-tools: [Bash, Read]
---

# Context-Assembler Skill

You are the context-assembler skill. When invoked, you assemble relevant context packages for agent spawns, prioritizing by relevance and respecting token budgets.

## When to Invoke This Skill

**Invoke this skill when:**
- Orchestrator prepares to spawn an agent and needs relevant context
- Any agent mentions "assemble context", "get context packages", or "context-assembler"
- Preparing developer/QA/tech lead spawns with session context
- Need to check for relevant error patterns before agent spawn

**Do NOT invoke when:**
- No active orchestration session exists
- Manually reading specific files (use Read tool directly)
- Working outside BAZINGA orchestration

---

## Your Task

When invoked, execute these steps in order:

### Step 1: Determine Context Parameters

Extract from the calling request or infer from conversation:
- `session_id`: Current orchestration session (REQUIRED)
- `group_id`: Task group being processed (OPTIONAL - use empty string "" if not provided)
- `agent_type`: Target agent - developer/senior_software_engineer/qa_expert/tech_lead/investigator (REQUIRED)
- `model`: Model being used - haiku/sonnet/opus or full model ID (OPTIONAL, for token budgeting)
- `current_tokens`: Current token usage in conversation (OPTIONAL, for zone detection)
- `iteration`: Current iteration number (optional, default 0)
- `include_reasoning`: Whether to include prior agent reasoning for handoff (OPTIONAL)
  - **DEFAULT BEHAVIOR:** Automatically `true` when reasoning context is beneficial:
    - `qa_expert`, `tech_lead`: ALWAYS (handoff recipients)
    - `senior_software_engineer`: ALWAYS (escalation needs prior context)
    - `investigator`: ALWAYS (debugging needs full context)
    - `developer`: When `iteration > 0` (retry needs prior reasoning; first attempt has none)
  - Explicitly set to `false` to disable reasoning for any agent
- `reasoning_level`: Level of detail for reasoning retrieval (OPTIONAL)
  - `minimal`: 400 tokens - key decisions only
  - `medium`: 800 tokens - decisions + approach (DEFAULT)
  - `full`: 1200 tokens - complete reasoning chain

If `session_id` or `agent_type` are missing, check recent conversation context or ask the orchestrator.

### Step 2: Load Configuration and Check FTS5

**Step 2a: Load retrieval limit for this agent type:**

```bash
# Extract retrieval limit for the specific agent type
AGENT_TYPE="developer"  # Replace with actual agent_type

# Pass AGENT_TYPE via command-line argument (not string interpolation)
LIMIT=$(cat bazinga/skills_config.json 2>/dev/null | python3 -c "
import sys, json
agent = sys.argv[1] if len(sys.argv) > 1 else 'developer'
defaults = {'developer': 3, 'senior_software_engineer': 5, 'qa_expert': 5, 'tech_lead': 5, 'investigator': 5}
try:
    c = json.load(sys.stdin).get('context_engineering', {})
    limits = c.get('retrieval_limits', {})
    print(limits.get(agent, defaults.get(agent, 3)))
except:
    print(defaults.get(agent, 3))
" "$AGENT_TYPE" 2>/dev/null || echo 3)
echo "Retrieval limit for $AGENT_TYPE: $LIMIT"
```

Default limits: developer=3, senior_software_engineer=5, qa_expert=5, tech_lead=5, investigator=5

**Step 2b: FTS5 availability:**

FTS5 is assumed unavailable (requires special SQLite build). Always use heuristic fallback in Step 3b for ranking.

```bash
# FTS5 disabled by default - use heuristic ranking
FTS5_AVAILABLE="false"
echo "FTS5_AVAILABLE=$FTS5_AVAILABLE (heuristic fallback enabled)"
```

**Step 2c: Determine token zone and budget:**

```bash
# Token estimation with tiktoken (with fallback to character estimation)
# Input: MODEL, CURRENT_TOKENS (from Step 1)
MODEL="sonnet"  # or "haiku", "opus", or full model ID
CURRENT_TOKENS=0  # Current usage if known, else 0

# IMPORTANT: Use eval to capture output as shell variables
eval "$(python3 -c "
import sys, json

try:
    import tiktoken
    HAS_TIKTOKEN = True
except ImportError:
    HAS_TIKTOKEN = False

# Model context limits (conservative estimates)
MODEL_LIMITS = {
    'haiku': 200000, 'claude-3-5-haiku': 200000,
    'sonnet': 200000, 'claude-sonnet-4-20250514': 200000, 'claude-3-5-sonnet': 200000,
    'opus': 200000, 'claude-opus-4-20250514': 200000
}

# Read safety margin from config (default 15%)
try:
    with open('bazinga/skills_config.json') as f:
        cfg = json.load(f).get('context_engineering', {})
        SAFETY_MARGIN = cfg.get('token_safety_margin', 0.15)
except:
    SAFETY_MARGIN = 0.15

model = sys.argv[1] if len(sys.argv) > 1 else 'sonnet'
current = int(sys.argv[2]) if len(sys.argv) > 2 else 0

# Normalize model name (longest key first to avoid partial matches)
model_key = model.lower()
for key in sorted(MODEL_LIMITS.keys(), key=len, reverse=True):
    if key in model_key:
        model_key = key
        break

limit = MODEL_LIMITS.get(model_key, 200000)
effective_limit = int(limit * (1 - SAFETY_MARGIN))

# Calculate REMAINING budget (not total)
remaining_budget = max(0, effective_limit - current)
usage_pct = (current / effective_limit * 100) if effective_limit > 0 else 0

# Determine zone
if usage_pct >= 95:
    zone = 'Emergency'
elif usage_pct >= 85:
    zone = 'Wrap-up'
elif usage_pct >= 75:
    zone = 'Conservative'
elif usage_pct >= 60:
    zone = 'Soft_Warning'  # Underscore for shell variable safety
else:
    zone = 'Normal'

# Token cap logic (T042 Part C):
# - If orchestrator passes current_tokens (even 0 for first spawn), trust zone detection
# - Only apply conservative cap if invoked outside orchestrator context (safety fallback)
# The orchestrator now tracks: estimated_token_usage = total_spawns * 15000
# First spawn: 0 tokens, zone=Normal, full budget available - this is correct behavior

# Output as shell variable assignments (will be eval'd)
print(f'ZONE={zone}')
print(f'USAGE_PCT={usage_pct:.1f}')
print(f'EFFECTIVE_LIMIT={effective_limit}')
print(f'REMAINING_BUDGET={remaining_budget}')
print(f'HAS_TIKTOKEN={HAS_TIKTOKEN}')
" "$MODEL" "$CURRENT_TOKENS")"

# Now $ZONE, $USAGE_PCT, $EFFECTIVE_LIMIT, $REMAINING_BUDGET, $HAS_TIKTOKEN are set
echo "Zone: $ZONE, Usage: $USAGE_PCT%, Remaining: $REMAINING_BUDGET tokens"
```

**Token Zone Behaviors:**

| Zone | Usage % | Behavior |
|------|---------|----------|
| Normal | 0-60% | Full context with all packages |
| Soft Warning | 60-75% | Prefer summaries over full content |
| Conservative | 75-85% | Minimal context, critical packages only |
| Wrap-up | 85-95% | Essential info only, no new packages |
| Emergency | 95%+ | Return immediately, suggest checkpoint |

**Token Budget Allocation by Agent Type:**

| Agent | Task | Specialization | Context Pkgs | Errors |
|-------|------|----------------|--------------|--------|
| developer | 50% | 20% | 20% | 10% |
| senior_software_engineer | 40% | 20% | 25% | 15% |
| qa_expert | 40% | 15% | 30% | 15% |
| tech_lead | 30% | 15% | 40% | 15% |
| investigator | 35% | 15% | 35% | 15% |

**Note:** SSE and Investigator handle escalations/complex debugging, so they need more context and error budget.

### Step 3: Query Context Packages (Zone-Conditional)

**CRITICAL: Execute query based on zone from Step 2c**

The query behavior depends entirely on the zone. Use this conditional structure:

```bash
# Zone-conditional query execution
# Variables from previous steps: $ZONE, $SESSION_ID, $GROUP_ID, $AGENT_TYPE, $LIMIT, $REMAINING_BUDGET

# Initialize result variable
QUERY_RESULT=""

if [ "$ZONE" = "Emergency" ]; then
    # Emergency zone: Skip all queries, go directly to Step 5
    echo "ZONE=Emergency: Skipping context query, proceeding to emergency output"
    QUERY_RESULT='{"packages":[],"total_available":0,"zone_skip":true}'

elif [ "$ZONE" = "Wrap-up" ]; then
    # Wrap-up zone: Skip context packages, minimal output only
    echo "ZONE=Wrap-up: Skipping context packages"
    QUERY_RESULT='{"packages":[],"total_available":0,"zone_skip":true}'

elif [ "$ZONE" = "Conservative" ]; then
    # Conservative zone: Priority fallback with LIMIT items across buckets
    echo "ZONE=Conservative: Using priority fallback ladder via bazinga-db"

    # Use bazinga-db get-context-packages command for each priority level
    QUERY_RESULT=$(python3 -c "
import subprocess
import json
import sys
import time

session_id = sys.argv[1]
group_id = sys.argv[2]
limit = int(sys.argv[3])
agent_type = sys.argv[4] if len(sys.argv) > 4 else 'developer'

def db_cmd_with_retry(cmd_args, max_retries=3, backoff_ms=[100, 250, 500]):
    '''Execute bazinga-db command with retry on database busy.'''
    for attempt in range(max_retries + 1):
        result = subprocess.run(cmd_args, capture_output=True, text=True)
        if result.returncode == 0:
            try:
                return json.loads(result.stdout) if result.stdout.strip() else []
            except json.JSONDecodeError:
                # Surface error rather than silently returning empty
                sys.stderr.write(f'JSON decode error: {result.stdout[:100]}\\n')
                return []
        if 'database is locked' in result.stderr or 'SQLITE_BUSY' in result.stderr:
            if attempt < max_retries:
                time.sleep(backoff_ms[attempt] / 1000.0)
                continue
        # Surface command errors
        if result.stderr:
            sys.stderr.write(f'Command error: {result.stderr[:200]}\\n')
        return []
    return []

# Priority fallback: Use bazinga-db to fetch packages by priority
# The get-context-packages command handles priority ordering internally
collected = db_cmd_with_retry([
    'python3', '.claude/skills/bazinga-db/scripts/bazinga_db.py', '--quiet',
    'get-context-packages', session_id, group_id, agent_type, str(limit)
])

# Handle result format
if isinstance(collected, dict):
    packages = collected.get('packages', [])
    total_available = collected.get('total_available', len(packages))
elif isinstance(collected, list):
    packages = collected
    total_available = len(packages)
else:
    packages = []
    total_available = 0

print(json.dumps({'packages': packages, 'total_available': total_available}))
" "$SESSION_ID" "$GROUP_ID" "$LIMIT" "$AGENT_TYPE")

else
    # Normal or Soft_Warning zone: Standard query
    echo "ZONE=$ZONE: Standard query with LIMIT=$LIMIT"
    QUERY_RESULT=$(python3 -c "
import subprocess
import json
import sys
import time

session_id = sys.argv[1]
group_id = sys.argv[2]
agent_type = sys.argv[3]
limit = int(sys.argv[4])

def db_query_with_retry(cmd_args, max_retries=3, backoff_ms=[100, 250, 500]):
    for attempt in range(max_retries + 1):
        result = subprocess.run(cmd_args, capture_output=True, text=True)
        if result.returncode == 0:
            try:
                return json.loads(result.stdout) if result.stdout.strip() else []
            except json.JSONDecodeError:
                return []
        if 'SQLITE_BUSY' in result.stderr or 'database is locked' in result.stderr:
            if attempt < max_retries:
                time.sleep(backoff_ms[attempt] / 1000.0)
                continue
        return []
    return []

# Use bazinga-db get-context-packages (parameterized, safe)
result = db_query_with_retry([
    'python3', '.claude/skills/bazinga-db/scripts/bazinga_db.py', '--quiet',
    'get-context-packages', session_id, group_id, agent_type, str(limit)
])

# If result is dict with 'packages' key, use it; otherwise wrap
if isinstance(result, dict):
    print(json.dumps(result))
elif isinstance(result, list):
    print(json.dumps({'packages': result, 'total_available': len(result)}))
else:
    print(json.dumps({'packages': [], 'total_available': 0}))
" "$SESSION_ID" "$GROUP_ID" "$AGENT_TYPE" "$LIMIT")
fi

# Parse result for next steps (log count only - summaries may contain secrets before redaction)
echo "Query returned: $(echo "$QUERY_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'{len(d.get(\"packages\",[]))} packages, total_available={d.get(\"total_available\",0)}')" 2>/dev/null || echo 'parse error')"
```

**If query fails or returns empty, proceed to Step 3b (Heuristic Fallback).**

### Step 3b: Heuristic Fallback (Query Failed or FTS5 Unavailable)

**First, fetch raw context packages with consumer data:**

```bash
# Fetch packages with LEFT JOIN to get consumer info for agent_relevance calculation
SESSION_ID="bazinga_20250212_143530"
GROUP_ID="group_a"  # or empty string for session-wide
AGENT_TYPE="developer"

# Note: SESSION_ID is system-generated (not user input), but use shell variables for clarity
python3 .claude/skills/bazinga-db/scripts/bazinga_db.py --quiet query \
  "SELECT cp.id, cp.file_path, cp.priority, cp.summary, cp.group_id, cp.created_at,
          GROUP_CONCAT(cs.agent_type) as consumers
   FROM context_packages cp
   LEFT JOIN consumption_scope cs ON cp.id = cs.package_id AND cs.session_id = cp.session_id
   WHERE cp.session_id = '$SESSION_ID'
   GROUP BY cp.id"
```

**Then apply heuristic ranking:**

| Priority | Weight |
|----------|--------|
| critical | 4 |
| high | 3 |
| medium | 2 |
| low | 1 |

**Scoring Formula:**
```
score = (priority_weight * 4) + (same_group_boost * 2) + (agent_relevance * 1.5) + recency_factor

Where:
- same_group_boost = 1 if package.group_id == request.group_id, else 0
- agent_relevance = 1 if AGENT_TYPE appears in package.consumers (from JOIN), else 0
- recency_factor = 1 / (days_since_created + 1)
```

Sort packages by score DESC, then by `created_at DESC` (tie-breaker), take top N.
Calculate: `overflow_count = max(0, total_packages - limit)`

### Step 3c: Token Packing with Redaction

After Step 3 or 3b retrieves packages, apply redaction, truncation, and token packing in the correct order:

```bash
# Token packing with proper order: redact → truncate → estimate → pack
# Input: $QUERY_RESULT (JSON from Step 3), $ZONE, $AGENT_TYPE, $REMAINING_BUDGET

PACKED_RESULT=$(python3 -c "
import json
import sys
import re

# Inputs from command line
query_result = json.loads(sys.argv[1])
zone = sys.argv[2]
agent_type = sys.argv[3]
remaining_budget = int(sys.argv[4])

packages = query_result.get('packages', [])
total_available = query_result.get('total_available', len(packages))

# --- Redaction Patterns (apply FIRST) ---
REDACTION_PATTERNS = [
    (r'(?i)(api[_-]?key|apikey|access[_-]?token|auth[_-]?token|bearer)[\"\\s:=]+[\"\\']?([a-zA-Z0-9_\\-]{20,})[\"\\']?', r'\\1=[REDACTED]'),
    (r'(?i)(aws[_-]?(access|secret)[_-]?key[_-]?id?)[\"\\s:=]+[\"\\']?([A-Z0-9]{16,})[\"\\']?', r'\\1=[REDACTED]'),
    (r'(?i)(password|passwd|secret|private[_-]?key)[\"\\s:=]+[\"\\']?([^\\s\"\\'\n]{8,})[\"\\']?', r'\\1=[REDACTED]'),
    (r'(?i)(mongodb|postgres|mysql|redis|amqp)://[^\\s]+@', r'\\1://[REDACTED]@'),
    (r'eyJ[a-zA-Z0-9_-]*\\.eyJ[a-zA-Z0-9_-]*\\.[a-zA-Z0-9_-]*', '[JWT_REDACTED]'),
]

def redact_text(text):
    for pattern, replacement in REDACTION_PATTERNS:
        text = re.sub(pattern, replacement, text)
    return text

# --- Truncation limits per zone ---
SUMMARY_LIMITS = {
    'Normal': 400,
    'Soft_Warning': 200,
    'Conservative': 100,
    'Wrap-up': 60,
    'Emergency': 0
}

def truncate_summary(summary, zone):
    max_len = SUMMARY_LIMITS.get(zone, 400)
    if len(summary) <= max_len:
        return summary
    truncated = summary[:max_len].rsplit(' ', 1)[0]
    return truncated + '...'

# --- Token estimation ---
def estimate_tokens(text):
    # ~4 chars per token (conservative fallback)
    return len(text) // 4 + 1

# --- Budget allocation ---
CONTEXT_PCT = {
    'developer': 0.20,
    'senior_software_engineer': 0.25,
    'qa_expert': 0.30,
    'tech_lead': 0.40,
    'investigator': 0.35
}

pct = CONTEXT_PCT.get(agent_type, 0.20)
context_budget = int(remaining_budget * pct)  # Use REMAINING, not total

# --- Process packages: redact → truncate → estimate → pack ---
packed = []
used_tokens = 0
package_ids = []

for pkg in packages:
    raw_summary = pkg.get('summary', '')

    # 1. REDACT first
    redacted_summary = redact_text(raw_summary)

    # 2. TRUNCATE second
    truncated_summary = truncate_summary(redacted_summary, zone)

    # 3. ESTIMATE tokens
    pkg_text = f\"**[{pkg.get('priority', 'medium').upper()}]** {pkg.get('file_path', '')}\\n> {truncated_summary}\"
    pkg_tokens = estimate_tokens(pkg_text)

    # 4. PACK if within budget
    if used_tokens + pkg_tokens > context_budget:
        break

    packed.append({
        'id': pkg.get('id'),
        'file_path': pkg.get('file_path'),
        'priority': pkg.get('priority'),
        'summary': truncated_summary,
        'est_tokens': pkg_tokens
    })
    package_ids.append(pkg.get('id'))
    used_tokens += pkg_tokens

print(json.dumps({
    'packages': packed,
    'total_available': total_available,
    'used_tokens': used_tokens,
    'budget': context_budget,
    'package_ids': package_ids
}))
" "$QUERY_RESULT" "$ZONE" "$AGENT_TYPE" "$REMAINING_BUDGET")

# Extract package IDs for Step 5b consumption tracking (cast to strings to avoid TypeError)
PACKAGE_IDS=($(echo "$PACKED_RESULT" | python3 -c "import sys,json; ids=json.load(sys.stdin).get('package_ids',[]); print(' '.join(str(x) for x in ids))"))

echo "Packed: $(echo "$PACKED_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'{len(d.get(\"packages\",[]))} pkgs, {d.get(\"used_tokens\",0)}/{d.get(\"budget\",0)} tokens')")"
echo "Package IDs to mark consumed: ${PACKAGE_IDS[*]}"
```

**Key improvements:**
- Uses `REMAINING_BUDGET` (not total limit)
- Applies redaction BEFORE truncation
- Populates `PACKAGE_IDS` array for Step 5b
- Includes `investigator` in budget allocation

### Step 3.5: Prior Reasoning Retrieval (Automatic for Handoffs)

**When to include:**
- **AUTOMATIC** for `qa_expert` and `tech_lead` (handoff recipients in workflow)
- **OPTIONAL** for other agents (only if `Include Reasoning: true` is explicit)
- Can be **disabled** for any agent with `Include Reasoning: false`

**Purpose:** Retrieve prior agents' reasoning to provide continuity during handoffs (Developer→QA→Tech Lead).

**Reasoning Levels (Token Budgets):**

| Level | Tokens | Content | Use Case |
|-------|--------|---------|----------|
| `minimal` | 400 | Key decisions only | Quick handoff, simple tasks |
| `medium` | 800 | Decisions + approach (DEFAULT) | Standard handoffs |
| `full` | 1200 | Complete reasoning chain | Complex tasks, debugging |

**Priority Order:** completion > decisions > understanding (most actionable first).

**Variable Setup:** Determine reasoning inclusion based on agent type, iteration, and explicit overrides:
```bash
# Step 3.5 Variable Setup
# Automatic reasoning when context is beneficial

AGENT_TYPE="developer"  # From Step 1
ITERATION="${ITERATION:-0}"  # From Step 1 (default 0)

# Smart default: Enable reasoning when it provides value
# - qa_expert, tech_lead: ALWAYS (handoff recipients)
# - senior_software_engineer, investigator: ALWAYS (escalation/debugging needs context)
# - developer: Only on retry (iteration > 0); first attempt has no prior reasoning

case "$AGENT_TYPE" in
    qa_expert|tech_lead|senior_software_engineer|investigator)
        INCLUDE_REASONING="true"   # Always include for these agents
        ;;
    developer)
        if [ "$ITERATION" -gt 0 ]; then
            INCLUDE_REASONING="true"   # Retry needs prior reasoning
        else
            INCLUDE_REASONING="false"  # First attempt has no prior context
        fi
        ;;
    *)
        INCLUDE_REASONING="false"  # Unknown agents default off
        ;;
esac

# Check for explicit override in request (parse from Step 1)
# "Include Reasoning: false" -> disable even for QA/TL/SSE
# "Include Reasoning: true" -> enable even for developer first attempt
# "Reasoning Level: full" -> set REASONING_LEVEL

REASONING_LEVEL="medium"  # Default level
# If request contains "Reasoning Level: minimal" -> REASONING_LEVEL="minimal"
# If request contains "Reasoning Level: full" -> REASONING_LEVEL="full"
```

```bash
# Prior reasoning retrieval with level-based token budgets
# Variables: $SESSION_ID, $GROUP_ID, $AGENT_TYPE, $ITERATION, $INCLUDE_REASONING, $REASONING_LEVEL

# FIX 1: Validate iteration is a valid number (default to 0 if invalid)
validate_iteration() {
    local val="$1"
    if [[ "$val" =~ ^[0-9]+$ ]]; then
        echo "$val"
    else
        echo "0"  # Default to 0 for invalid input
    fi
}

ITERATION=$(validate_iteration "${ITERATION:-0}")

# Apply smart defaults if not explicitly set
if [ -z "$INCLUDE_REASONING" ]; then
    case "$AGENT_TYPE" in
        qa_expert|tech_lead|senior_software_engineer|investigator)
            INCLUDE_REASONING="true"
            ;;
        developer)
            if [ "$ITERATION" -gt 0 ]; then
                INCLUDE_REASONING="true"
            else
                INCLUDE_REASONING="false"
            fi
            ;;
        *)
            INCLUDE_REASONING="false"
            ;;
    esac
fi

REASONING_LEVEL="${REASONING_LEVEL:-medium}"

if [ "$INCLUDE_REASONING" = "true" ]; then
    echo "Retrieving prior reasoning for handoff context (level: $REASONING_LEVEL, iteration: $ITERATION)..."

    REASONING_DIGEST=$(python3 -c "
import sys
import json
import subprocess

session_id = sys.argv[1]
group_id = sys.argv[2] if len(sys.argv) > 2 else ''
reasoning_level = sys.argv[3] if len(sys.argv) > 3 else 'medium'
target_agent = sys.argv[4] if len(sys.argv) > 4 else 'unknown'

# Token budget based on reasoning level
LEVEL_BUDGETS = {
    'minimal': 400,
    'medium': 800,
    'full': 1200
}
max_tokens = LEVEL_BUDGETS.get(reasoning_level, 800)

# FIX 2: Relevance filtering - define which agents' reasoning is relevant for each target
# Workflow: Developer -> QA -> Tech Lead
# Escalation: Developer -> SSE, Developer -> Investigator
RELEVANT_AGENTS = {
    'qa_expert': ['developer', 'senior_software_engineer'],  # QA needs dev reasoning
    'tech_lead': ['developer', 'senior_software_engineer', 'qa_expert'],  # TL needs dev + QA
    'senior_software_engineer': ['developer'],  # SSE needs failed dev reasoning
    'investigator': ['developer', 'senior_software_engineer', 'qa_expert'],  # Investigator needs all
    'developer': ['developer', 'qa_expert', 'tech_lead'],  # Dev retry needs own + feedback
}
relevant_agents = RELEVANT_AGENTS.get(target_agent, [])

# FIX 3: Pruning limits for long retry chains
MAX_ENTRIES_PER_AGENT = 2  # Max 2 most recent entries per agent type
MAX_TOTAL_ENTRIES = 5  # Max 5 entries total regardless of agents

# Query reasoning from database via bazinga-db
# Priority order: completion > decisions > understanding (most actionable first)
PRIORITY_PHASES = ['completion', 'decisions', 'understanding']

try:
    # Get all reasoning for this session/group
    cmd = ['python3', '.claude/skills/bazinga-db/scripts/bazinga_db.py', '--quiet', 'get-reasoning', session_id]
    if group_id:
        cmd.extend(['--group_id', group_id])

    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(json.dumps({'error': 'query_failed', 'entries': [], 'used_tokens': 0}))
        sys.exit(0)

    entries = json.loads(result.stdout) if result.stdout.strip() else []
except Exception as e:
    print(json.dumps({'error': str(e), 'entries': [], 'used_tokens': 0}))
    sys.exit(0)

if not entries:
    print(json.dumps({'entries': [], 'used_tokens': 0, 'total_available': 0}))
    sys.exit(0)

# FIX 2: Filter to relevant agents only
if relevant_agents:
    entries = [e for e in entries if e.get('agent_type') in relevant_agents]

# FIX 3: Prune to MAX_ENTRIES_PER_AGENT per agent (most recent first)
# Group by agent, sort by timestamp desc, take top N per agent
from collections import defaultdict
agent_entries = defaultdict(list)
for entry in entries:
    agent_entries[entry.get('agent_type', 'unknown')].append(entry)

pruned_entries = []
for agent, agent_list in agent_entries.items():
    # Sort by timestamp descending (most recent first)
    agent_list.sort(key=lambda e: e.get('timestamp', ''), reverse=True)
    # Take only MAX_ENTRIES_PER_AGENT
    pruned_entries.extend(agent_list[:MAX_ENTRIES_PER_AGENT])

entries = pruned_entries

# Sort by priority phase, then by timestamp (most recent first within each phase)
def phase_priority(entry):
    phase = entry.get('phase', 'understanding')
    try:
        return PRIORITY_PHASES.index(phase)
    except ValueError:
        return len(PRIORITY_PHASES)  # Unknown phases last

# Two-pass sort: first by timestamp DESC, then stable sort by phase priority ASC
# This gives us most recent entries first within each phase
entries.sort(key=lambda e: e.get('timestamp', ''), reverse=True)  # timestamp DESC
entries.sort(key=phase_priority)  # phase priority ASC (stable sort preserves timestamp order)

# FIX 3: Apply total entry limit
entries = entries[:MAX_TOTAL_ENTRIES]

# Token estimation (~4 chars per token)
def estimate_tokens(text):
    return len(text) // 4 + 1 if text else 0

# Pack entries within budget
packed = []
used_tokens = 0

for entry in entries:
    content = entry.get('content', '')
    # Format: [agent] phase: content
    formatted = f\"[{entry.get('agent_type', 'unknown')}] {entry.get('phase', 'unknown')}: {content[:300]}\"
    entry_tokens = estimate_tokens(formatted)

    if used_tokens + entry_tokens > max_tokens:
        break

    packed.append({
        'agent_type': entry.get('agent_type'),
        'phase': entry.get('phase'),
        'content': content[:300] if len(content) > 300 else content,
        'confidence': entry.get('confidence_level'),
        'est_tokens': entry_tokens
    })
    used_tokens += entry_tokens

print(json.dumps({
    'entries': packed,
    'used_tokens': used_tokens,
    'budget': max_tokens,
    'level': reasoning_level,
    'total_available': len(entries),
    'relevant_agents': relevant_agents,
    'pruning': {'max_per_agent': MAX_ENTRIES_PER_AGENT, 'max_total': MAX_TOTAL_ENTRIES}
}))
" "$SESSION_ID" "$GROUP_ID" "$REASONING_LEVEL" "$AGENT_TYPE")

    echo "Reasoning digest: $(echo "$REASONING_DIGEST" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'{len(d.get(\"entries\",[]))} entries, {d.get(\"used_tokens\",0)}/{d.get(\"budget\",800)} tokens (level: {d.get(\"level\", \"medium\")})')" 2>/dev/null || echo 'parse error')"
else
    REASONING_DIGEST='{"entries":[],"used_tokens":0,"level":"none"}'
    echo "Skipping reasoning retrieval (include_reasoning=false for $AGENT_TYPE)"
fi
```

**Output Format for Step 5:**

If reasoning entries are found, include in output:

```markdown
### Prior Agent Reasoning ({count} entries)

**[developer] completion:** Successfully implemented authentication using JWT...
**[qa_expert] decisions:** Chose to focus on edge cases for token expiration...
```

Only include if `$INCLUDE_REASONING = true` AND entries exist.

### Step 4: Query Error Patterns (Optional)

If the agent previously failed or error patterns might be relevant:

**Step 4a: Get project_id from session:**
```bash
SESSION_ID="bazinga_20250212_143530"

# Retrieve project_id (defaults to 'default' if not set)
PROJECT_ID=$(python3 .claude/skills/bazinga-db/scripts/bazinga_db.py --quiet query \
  "SELECT COALESCE(json_extract(metadata, '\$.project_id'), 'default') as pid FROM sessions WHERE session_id = '$SESSION_ID'" \
  2>/dev/null | python3 -c "import sys,json; r=json.load(sys.stdin); print(r[0]['pid'] if r else 'default')" 2>/dev/null || echo "default")
```

**Step 4b: Query matching error patterns:**
```bash
# Filter by project_id and optionally session_id for more specific matches
python3 .claude/skills/bazinga-db/scripts/bazinga_db.py --quiet query \
  "SELECT signature_json, solution, confidence, occurrences FROM error_patterns WHERE project_id = '$PROJECT_ID' AND confidence > 0.7 ORDER BY confidence DESC, occurrences DESC LIMIT 3"
```

Only include patterns with confidence > 0.7 in the output.

### Step 5: Format Output

**Compute display values:**
- `count` = number of packages returned (up to limit)
- `available` = total_available from Step 3 response (or total from Step 3b query)
- `overflow_count` = max(0, available - count)
- `zone` = current token zone from Step 2c
- `usage_pct` = token usage percentage from Step 2c

**Micro-Summary Truncation:**

Apply zone-specific summary length limits for actual degradation:

| Zone | Max Summary Chars | Rationale |
|------|-------------------|-----------|
| Normal | 400 | Full detail |
| Soft Warning | 200 | Reduced detail |
| Conservative | 100 | Key points only |
| Wrap-up | 60 | Minimal hints |

```python
def truncate_summary(summary: str, zone: str) -> str:
    """Truncate summary based on zone-specific limits."""
    limits = {
        'Normal': 400,
        'Soft_Warning': 200,  # Underscore to match $ZONE variable
        'Conservative': 100,
        'Wrap-up': 60,
        'Emergency': 0  # No summaries in emergency
    }
    max_len = limits.get(zone, 400)
    if len(summary) <= max_len:
        return summary
    # Truncate at word boundary with ellipsis
    truncated = summary[:max_len].rsplit(' ', 1)[0]
    return truncated + '...'
```

Apply `truncate_summary()` to each package summary before rendering output.

**Summary Redaction (Security):**

Apply the same redaction patterns used for error_patterns to summaries before output:

```python
import re

# Redaction patterns for secrets (same as error_patterns redaction)
REDACTION_PATTERNS = [
    # API keys and tokens
    (r'(?i)(api[_-]?key|apikey|access[_-]?token|auth[_-]?token|bearer)["\s:=]+["\']?([a-zA-Z0-9_\-]{20,})["\']?', r'\1=[REDACTED]'),
    # AWS credentials
    (r'(?i)(aws[_-]?(access|secret)[_-]?key[_-]?id?)["\s:=]+["\']?([A-Z0-9]{16,})["\']?', r'\1=[REDACTED]'),
    # Passwords and secrets
    (r'(?i)(password|passwd|secret|private[_-]?key)["\s:=]+["\']?([^\s"\']{8,})["\']?', r'\1=[REDACTED]'),
    # Connection strings
    (r'(?i)(mongodb|postgres|mysql|redis|amqp)://[^\s]+@', r'\1://[REDACTED]@'),
    # JWT tokens
    (r'eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*', '[JWT_REDACTED]'),
]

def redact_summary(summary: str) -> str:
    """Redact potential secrets from summary."""
    redacted = summary
    for pattern, replacement in REDACTION_PATTERNS:
        redacted = re.sub(pattern, replacement, redacted)

    # Entropy-based detection for high-entropy strings (potential secrets)
    def has_high_entropy(s):
        if len(s) < 20:
            return False
        char_set = set(s)
        # High entropy = many unique chars relative to length
        return len(char_set) / len(s) > 0.6 and any(c.isdigit() for c in s) and any(c.isupper() for c in s)

    # Find and redact high-entropy strings
    words = redacted.split()
    for i, word in enumerate(words):
        if has_high_entropy(word):
            words[i] = '[REDACTED]'
    return ' '.join(words)
```

Apply `redact_summary()` before `truncate_summary()` in the processing pipeline.

**Zone-Specific Output:**

**Emergency Zone (95%+):**
```markdown
## Context for {agent_type}

🚨 **Token budget: Emergency ({usage_pct}%) - Checkpoint recommended**

Context assembly skipped due to token budget constraints.
Suggest: Complete current operation and start new session.
```

**Wrap-up Zone (85-95%):**
```markdown
## Context for {agent_type}

🔶 **Token budget: Wrap-up ({usage_pct}%) - Completing current operation**

### Essential Info Only

Minimal context mode active. Focus on completing current task.
```

**Conservative Zone (75-85%):**
```markdown
## Context for {agent_type}

🔶 **Token budget: Conservative ({usage_pct}%)**

### Priority Packages ({count}/{available}) - {priority_used} level

**[{PRIORITY}]** {file_path}
> {summary}
```

Note: `priority_used` comes from the fallback ladder response (critical/high/medium).

**Soft Warning Zone (60-75%):**
```markdown
## Context for {agent_type}

🔶 **Token budget: Soft Warning ({usage_pct}%) - Reduced summaries (200 char)**

### Relevant Packages ({count}/{available})

**[{PRIORITY}]** {file_path}
> {summary}  ← Truncated to 200 chars
```

**Normal Zone (0-60%):**
```markdown
## Context for {agent_type}

### Relevant Packages ({count}/{available})

**[{PRIORITY}]** {file_path}
> {summary}

**[{PRIORITY}]** {file_path}
> {summary}

### Prior Agent Reasoning ({reasoning_count} entries)
<!-- Only include if include_reasoning=true AND entries exist -->

**[developer] completion:** Successfully implemented the core logic with edge case handling...
**[qa_expert] decisions:** Focused test coverage on authentication flow boundaries...

### Error Patterns ({pattern_count} matches)

⚠️ **Known Issue**: "{error_signature}"
> **Solution**: {solution}
> **Confidence**: {confidence} (seen {occurrences} times)

📦 +{overflow_count} more packages available (re-invoke with higher limit to expand)
```

**Priority Indicators:**
- `[CRITICAL]` - Priority: critical
- `[HIGH]` - Priority: high
- `[MEDIUM]` - Priority: medium
- `[LOW]` - Priority: low

**Zone Indicators:**
- Normal zone: No indicator (full context)
- Soft Warning/Conservative/Wrap-up: `🔶` (orange diamond)
- Emergency: `🚨` (emergency symbol)

**Only show overflow indicator if overflow_count > 0 AND zone is Normal or Soft Warning.**

### Step 5b: Mark Packages as Consumed (consumption_scope table)

**IMPORTANT: Only run if zone is Normal or Soft_Warning (skip for Wrap-up/Emergency)**

After formatting output, mark delivered packages as consumed in the `consumption_scope` table to prevent repeated delivery and enable iteration-aware tracking:

```bash
# Only mark consumption if packages were actually delivered
if { [ "$ZONE" = "Normal" ] || [ "$ZONE" = "Soft_Warning" ]; } && [ ${#PACKAGE_IDS[@]} -gt 0 ]; then
    # Mark consumed packages using bazinga-db mark-context-consumed command
    marked=0
    for pkg_id in "${PACKAGE_IDS[@]}"; do
        if python3 .claude/skills/bazinga-db/scripts/bazinga_db.py --quiet \
            mark-context-consumed "$pkg_id" "$AGENT_TYPE" "$ITERATION" 2>/dev/null; then
            marked=$((marked + 1))
        fi
    done
    echo "Marked $marked/${#PACKAGE_IDS[@]} packages as consumed via bazinga-db"
else
    echo "Skipping consumption tracking (zone=$ZONE or no packages)"
fi
```

**Key features:**
- Uses **bazinga-db mark-context-consumed** command (proper skill invocation)
- Handles retry logic internally within bazinga-db skill
- Iteration-aware tracking per data-model.md
- **Skips** in Wrap-up/Emergency zones (nothing delivered)

### Step 6: Handle Edge Cases

**Empty Packages:**
If no context packages are found (count=0, available=0):
```markdown
## Context for {agent_type}

### Relevant Packages (0/0)

No context packages found for this session/group. The agent will proceed with task and specialization context only.
```

**Graceful Degradation:**
If ANY step fails (database unavailable, query error, etc.):
1. Log a warning (but do NOT block execution)
2. Return minimal context:
```markdown
## Context for {agent_type}

:warning: Context assembly encountered an error. Proceeding with minimal context.

**Fallback Mode**: Task and specialization context only. Context packages unavailable.
```

3. **CRITICAL**: The orchestrator should NEVER block on context-assembler failure

---

## Step 7: Strategy Extraction (Success Path)

**When:** Triggered after a task group completes successfully (Tech Lead APPROVED status).

**Purpose:** Extract and save successful approaches to the `strategies` table for future agent guidance.

### Trigger Conditions

Strategy extraction should run when:
- Tech Lead returns `APPROVED` status for a group
- Developer completes without needing escalation
- QA passes all tests on first attempt

### Strategy Extraction Process

**Note:** Strategy extraction is triggered by the orchestrator (phase_simple.md, phase_parallel.md) after Tech Lead approval using the `bazinga-db extract-strategies` command:

```
bazinga-db-context, please extract strategies:

Session ID: {session_id}
Group ID: {group_id}
Project ID: {project_id}
Lang: {detected_lang}
Framework: {detected_framework}
```
Then invoke: `Skill(command: "bazinga-db-context")`

**What the command does:**
1. Queries `agent_reasoning` table for completion/decisions/approach phases
2. Maps phases to topics: completion→implementation, decisions→architecture, approach→methodology
3. Generates deterministic `strategy_id` = `{project_id}_{topic}_{content_hash}`
4. Upserts to `strategies` table (increments helpfulness if exists)
5. Returns count of extracted strategies

### Strategy Schema Reference

| Field | Type | Description |
|-------|------|-------------|
| `strategy_id` | TEXT PK | Unique identifier (project_topic_hash) |
| `project_id` | TEXT | Project this strategy applies to |
| `topic` | TEXT | Category: implementation, architecture, methodology |
| `insight` | TEXT | The actual insight/approach (max 500 chars) |
| `helpfulness` | INT | Usage counter, incremented on reuse |
| `lang` | TEXT | Language context (python, typescript, etc.) |
| `framework` | TEXT | Framework context (react, fastapi, etc.) |
| `last_seen` | TEXT | Last time strategy was applied |
| `created_at` | TEXT | When strategy was first captured |

### Strategy Retrieval for Context

When assembling context, strategies can be queried for relevant hints:

```sql
SELECT topic, insight FROM strategies
WHERE project_id = ?
  AND (lang IS NULL OR lang = ?)
  AND (framework IS NULL OR framework = ?)
ORDER BY helpfulness DESC, last_seen DESC
LIMIT 3
```

---

## Configuration Reference

From `bazinga/skills_config.json`:

```json
{
  "context_engineering": {
    "enable_context_assembler": true,
    "enable_fts5": false,
    "retrieval_limits": {
      "developer": 3,
      "senior_software_engineer": 5,
      "qa_expert": 5,
      "tech_lead": 5,
      "investigator": 5
    },
    "redaction_mode": "pattern_only",
    "token_safety_margin": 0.15
  }
}
```

| Setting | Default | Description |
|---------|---------|-------------|
| `enable_context_assembler` | true | Enable/disable the skill |
| `enable_fts5` | false | Use FTS5 for relevance (requires SQLite FTS5) |
| `retrieval_limits.*` | 3 | Max packages per agent type |
| `redaction_mode` | pattern_only | Secret redaction mode |
| `token_safety_margin` | 0.15 | Safety margin for token budgets |

---

## Example Invocations

### Example 1: Developer Context Assembly

**Request:**
```
Assemble context for developer spawn:
- Session: bazinga_20250212_143530
- Group: group_a
- Agent: developer
```

**Output:**
```markdown
## Context for developer

### Relevant Packages (3/7)

**[HIGH]** research/auth-patterns.md
> JWT authentication patterns for React Native apps

**[MEDIUM]** research/api-design.md
> REST API design guidelines for mobile clients

**[MEDIUM]** findings/codebase-analysis.md
> Existing authentication code in src/auth/

### Error Patterns (1 match)

:warning: **Known Issue**: "Cannot find module '@/utils'"
> **Solution**: Check tsconfig.json paths configuration - ensure baseUrl is set correctly
> **Confidence**: 0.8 (seen 3 times)

:package: +4 more packages available (re-invoke with higher limit to expand)
```

### Example 2: Session-Wide Context (No Group)

**Request:**
```
Assemble context for tech_lead spawn:
- Session: bazinga_20250212_143530
- Group: (none - session-wide)
- Agent: tech_lead
```

**Commands used:**
```bash
python3 .claude/skills/bazinga-db/scripts/bazinga_db.py --quiet get-context-packages \
  "bazinga_20250212_143530" "" "tech_lead" 5
```

### Example 3: Empty Context

**Output:**
```markdown
## Context for qa_expert

### Relevant Packages (0/0)

No context packages found for this session/group. The agent will proceed with task and specialization context only.
```

### Example 4: Error/Fallback

**Output (if database unavailable):**
```markdown
## Context for tech_lead

:warning: Context assembly encountered an error. Proceeding with minimal context.

**Fallback Mode**: Task and specialization context only. Context packages unavailable.
```

---

## Security Notes

**Parameter Handling:**
- Always assign user-provided values to shell variables first
- Use quoted variable expansion (`"$VAR"`) in commands
- The bazinga-db CLI uses positional arguments (safer than string interpolation)
- Avoid constructing SQL strings with raw user input

**Example of safe vs unsafe:**
```bash
# SAFE: Use shell variables with quotes
SESSION_ID="user_provided_session"
python3 ... --quiet get-context-packages "$SESSION_ID" "$GROUP_ID" "$AGENT_TYPE" "$LIMIT"

# UNSAFE: Direct string interpolation (avoid this)
python3 ... --quiet query "SELECT * FROM t WHERE id = 'user_input'"
```

---

## Integration with Orchestrator

The orchestrator invokes this skill before spawning agents:

```python
# 1. Invoke context-assembler
Skill(command: "context-assembler")

# 2. Capture output and include in agent prompt
Task(
    prompt=f"""
    {context_assembler_output}

    ## Your Task
    {task_description}
    """,
    subagent_type="developer"
)
```

---

## Database Tables Used

| Table | Purpose |
|-------|---------|
| `context_packages` | Research files, findings, artifacts with priority/summary |
| `consumption_scope` | Iteration-aware package consumption tracking (per data-model.md) |
| `error_patterns` | Captured error signatures with solutions |
| `strategies` | Successful approaches extracted from completed tasks (Step 7) |
| `agent_reasoning` | Agent reasoning phases used for strategy extraction |
| `sessions` | Session metadata including project_id |

**Note:** The `consumption_scope` table has columns: `scope_id`, `session_id`, `group_id`, `agent_type`, `iteration`, `package_id`, `consumed_at`. Step 5b uses this for tracking delivery per session/group/agent/iteration to enable fresh context on retries.

**Note:** The `strategies` table is populated by Step 7 when tasks complete successfully. Strategies are queried during context assembly to provide insights from past successful implementations.

---

## Performance (SC-005)

**Target:** Context assembly must complete in <500ms.

**Estimated Performance:**

| Step | Operation | Time |
|------|-----------|------|
| Parse input | Step 1 | <5ms |
| Token zone detection | Step 2 | <5ms |
| Query packages | Step 3 (indexed) | 30-50ms |
| Token packing | Step 3c | 20-50ms |
| Query error patterns | Step 4 (indexed) | 30-50ms |
| Format output | Step 5 | <10ms |
| Mark consumption | Step 5b | 20-50ms |
| **Total** | | **~100-200ms** |

**Performance Prerequisites:**
- SQLite WAL mode enabled (concurrent reads)
- Indexes created on `context_packages`, `error_patterns`, `consumption_scope`
- Retry backoff (100ms, 200ms, 400ms) adds max 700ms only if database locked

**If performance degrades:**
1. Check `PRAGMA journal_mode` returns `wal`
2. Verify indexes exist: `SELECT name FROM sqlite_master WHERE type='index'`
3. Check for lock contention in parallel agent spawns

---

## References

See `references/usage.md` for detailed usage documentation and integration examples.


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/usage.md

```markdown
# Context-Assembler Usage Guide

**Version**: 1.5.2
**Status**: Production Ready (Phase 4.5 - Conservative Default Budget)

## Overview

The context-assembler skill provides intelligent context assembly for BAZINGA agents. It retrieves, ranks, and delivers relevant context packages while respecting token budgets and learning from error patterns.

## Dependencies

### tiktoken (Token Estimation)

The context-assembler uses `tiktoken` for accurate, model-aware token estimation with a 15% safety margin.

**Installation:**
```bash
pip install tiktoken
```

**Model-to-Encoding Mapping:**

| Model ID | Encoding | Chars/Token (approx) |
|----------|----------|----------------------|
| claude-opus-4-20250514 | cl100k_base | ~4 |
| claude-sonnet-4-20250514 | cl100k_base | ~4 |
| claude-3-5-sonnet | cl100k_base | ~4 |
| claude-3-5-haiku | cl100k_base | ~4 |
| haiku | cl100k_base | ~4 |
| sonnet | cl100k_base | ~4 |
| opus | cl100k_base | ~4 |

**Note:** Claude models use similar tokenization to GPT-4. The `cl100k_base` encoding provides approximate planning estimates; verify against actual usage where possible.

**Safety Margin:**
All token estimates include a 15% safety margin (configurable via `token_safety_margin` in skills_config.json):
```
effective_budget = model_limit * (1 - safety_margin)
```

**Fallback Behavior:**
If tiktoken is unavailable:
1. Uses character-based estimation (~4 chars per token)
2. Logs warning about reduced accuracy
3. Continues with heuristic token counting

## Invocation

From the orchestrator, before spawning an agent:

```python
# Invoke context-assembler to get relevant context
Skill(command: "context-assembler")
```

The skill reads the current session/group context from bazinga-db and outputs a structured markdown block.

## Output Format Examples

### Example 1: Developer Context with Error Pattern

```markdown
## Context for developer

### Relevant Packages (3/7)

**[HIGH]** research/auth-patterns.md
> JWT authentication patterns for React Native apps

**[MEDIUM]** research/api-design.md
> REST API design guidelines for mobile clients

**[MEDIUM]** findings/codebase-analysis.md
> Existing authentication code in src/auth/

### Error Patterns (1 match)

:warning: **Known Issue**: "Cannot find module '@/utils'"
> **Solution**: Check tsconfig.json paths configuration - ensure baseUrl is set correctly
> **Confidence**: 0.8 (seen 3 times)

:package: +4 more packages available (re-invoke with higher limit for details)
```

### Example 2: Empty Context (New Session)

```markdown
## Context for qa_expert

### Relevant Packages (0/0)

No context packages found for this session/group. The agent will proceed with task and specialization context only.
```

### Example 3: Fallback Mode (Database Error)

```markdown
## Context for tech_lead

:warning: Context assembly encountered an error. Proceeding with minimal context.

**Fallback Mode**: Task and specialization context only. Context packages unavailable.
```

## Heuristic Relevance Ranking

Packages are ranked using weighted scoring when FTS5 is unavailable:

| Factor | Weight | Description |
|--------|--------|-------------|
| Priority | 4 | critical=4, high=3, medium=2, low=1 |
| Same-group | 2 | Boost if package group_id matches request |
| Agent-type | 1.5 | Boost if package tagged for agent type |
| Recency | 1 | Prefer recently created packages |

**Scoring formula:**
```
score = (priority_weight * 4) + (same_group * 2) + (agent_match * 1.5) + (recency_factor)

Where:
- same_group = 1 if package.group_id == request.group_id, else 0
- agent_match = 1 if agent_type in package.consumers, else 0
- recency_factor = 1 / (days_since_created + 1)
```

## Token Budget Allocation

Different agent types receive different context budgets:

| Agent | Task | Specialization | Context Pkgs | Errors | Total |
|-------|------|----------------|--------------|--------|-------|
| Developer | 50% | 20% | 20% | 10% | 100% |
| Senior Software Engineer | 40% | 20% | 25% | 15% | 100% |
| QA Expert | 40% | 15% | 30% | 15% | 100% |
| Tech Lead | 30% | 15% | 40% | 15% | 100% |
| Investigator | 35% | 15% | 35% | 15% | 100% |

**Note:** SSE and Investigator handle escalations/complex debugging, so they receive more context and error budget than developers.

## Graduated Token Zones

| Zone | Usage | Behavior |
|------|-------|----------|
| Normal | 0-60% | Full context with all packages |
| Soft Warning | 60-75% | Prefer summarized content |
| Conservative | 75-85% | Minimal context only |
| Wrap-up | 85-95% | Complete current operation only |
| Emergency | 95%+ | Checkpoint and break |

**Zone indicator in output:**
```
Token budget: Soft Warning (70%) - using summaries
```

## Retrieval Limits

Configurable per agent type in `bazinga/skills_config.json`:

```json
{
  "context_engineering": {
    "retrieval_limits": {
      "developer": 3,
      "senior_software_engineer": 5,
      "qa_expert": 5,
      "tech_lead": 5,
      "investigator": 5
    }
  }
}
```

**Note:** SSE gets more packages (5) than developer (3) since it handles escalations requiring broader context.

## Error Pattern Learning

### Capture Flow
1. Agent fails with error X
2. Agent succeeds on retry
3. System extracts error signature
4. Redacts secrets (regex + entropy detection)
5. Stores pattern with confidence 0.5

### Injection Flow
1. Context-assembler queries error_patterns table
2. Matches current context against stored signatures
3. If confidence > 0.7, injects solution hint
4. Agent receives: ":warning: Known Issue: ... Solution: ..."

### Confidence Adjustment
- Each successful match: +0.1 (max 1.0)
- Each false positive: -0.2 (min 0.1)
- Below 0.3: Don't inject, observe only

## Configuration

Full configuration in `bazinga/skills_config.json`:

```json
{
  "context_engineering": {
    "enable_context_assembler": true,
    "enable_fts5": false,
    "retrieval_limits": {
      "developer": 3,
      "senior_software_engineer": 5,
      "qa_expert": 5,
      "tech_lead": 5,
      "investigator": 5
    },
    "redaction_mode": "pattern_only",
    "token_safety_margin": 0.15
  }
}
```

| Setting | Default | Description |
|---------|---------|-------------|
| `enable_context_assembler` | true | Enable/disable the skill |
| `enable_fts5` | false | Use FTS5 for relevance (requires SQLite FTS5) |
| `retrieval_limits.*` | 3 | Max packages per agent type |
| `redaction_mode` | pattern_only | `pattern_only`, `entropy`, or `both` |
| `token_safety_margin` | 0.15 | Safety margin for token budgets |

### Redaction Modes

| Mode | Description | Performance |
|------|-------------|-------------|
| `pattern_only` | Regex patterns for common secrets | Fast |
| `entropy` | High-entropy string detection | Slower |
| `both` | Regex + entropy (recommended for security) | Slowest |

## Error Handling

### Context-Assembler Fails
1. System injects minimal context (task + specialization only)
2. Logs warning to session
3. **Never blocks execution** - graceful degradation

### Database Locked
1. WAL mode allows concurrent reads
2. Writes retry with exponential backoff (100ms, 200ms, 400ms)
3. After 3 retries, proceed without write (log warning)

### FTS5 Unavailable
1. System uses heuristic fallback ranking:
   - Priority weight (critical > high > medium > low)
   - Same-group boost
   - Agent-type relevance
   - Recency
2. Logs info message about fallback mode

## Integration Example

```python
# In orchestrator, before spawning Developer agent:

# 1. Invoke context-assembler
Skill(command: "context-assembler")

# 2. Capture the output block
# 3. Include in Developer agent prompt
Task(
    prompt=f"""
    {context_assembler_output}

    ## Your Task
    Implement the authentication middleware...
    """,
    subagent_type="developer"
)
```

## Database Tables

The skill uses these tables (created in Phase 2):

| Table | Purpose |
|-------|---------|
| `context_packages` | Research files, findings, artifacts with priority/summary |
| `context_package_consumers` | Per-agent consumption tracking |
| `error_patterns` | Captured error signatures with solutions |
| `strategies` | Successful approaches from completions |
| `consumption_scope` | Per-iteration package consumption tracking |

## Metrics to Monitor

| Metric | Target | Measurement |
|--------|--------|-------------|
| Assembly latency | <500ms | Skill execution time |
| Context consumption | >50% | `consumption_scope` tracking |
| Error recurrence | <10% | `error_patterns.occurrences` |
| Prompt sizes | <80% | Token estimation logs |

## Priority Values

Packages can have one of four priority levels:

- **critical** - Must always be included (errors, blockers)
- **high** - Include if budget allows (research findings)
- **medium** - Default for most packages
- **low** - Include only if ample budget

## Related Documentation

- **Specification**: `specs/1-context-engineering/spec.md`
- **Data Model**: `specs/1-context-engineering/data-model.md`
- **Technical Plan**: `specs/1-context-engineering/plan.md`
- **Quickstart**: `specs/1-context-engineering/quickstart.md`

## Version History

- v1.3.0 (2025-12-12): Phase 4 - Graduated Token Management
  - Added tiktoken dependency for model-aware token estimation
  - Implemented 5 graduated token zones (Normal, Soft Warning, Conservative, Wrap-up, Emergency)
  - Added zone indicator to output (`🔶` for warnings, `🚨` for emergency)
  - Implemented summary-preference logic for Soft Warning zone
  - Implemented truncation behavior for Conservative/Wrap-up zones
  - Added token budget allocation per agent type (Developer: 50/20/20/10, QA: 40/15/30/15, Tech Lead: 30/15/40/15)
  - 15% safety margin applied to all token estimates
  - Fallback to character-based estimation when tiktoken unavailable
- v1.2.0 (2025-12-12): Bug fixes and improvements
  - Fixed agent_relevance calculation by adding LEFT JOIN to context_package_consumers
  - Fixed AGENT_TYPE variable passing to Python via sys.argv (not string interpolation)
  - Added per-agent default limits in Python fallback
  - Added deterministic tie-breaker (created_at DESC) for equal scores
  - Added note about system-generated session_ids in SQL queries
- v1.1.0 (2025-12-12): Critical fixes
  - Fixed query syntax for bazinga-db commands
  - Added Security Notes section
  - Clarified group_id handling (use empty string for session-wide)
- v1.0.0 (2025-12-12): Initial production release (Phase 3 complete)
  - Full heuristic relevance ranking
  - Package retrieval via bazinga-db
  - Output formatting with priority indicators
  - Empty packages handling
  - FTS5 availability check with fallback
  - Graceful degradation on failures
- v0.1.0 (2025-12-12): Phase 1 placeholder with directory structure

```