Last updated: Aug 12, 2025, 08:12 AM UTC

Sasha Tools Development Guide

Generated: 2025-08-06 UTC
Purpose: Comprehensive guide for developing tools in Sasha's LLxprt-powered system
Audience: Developers creating new tools for Sasha


Overview

This guide explains how to develop tools for Sasha using the hybrid approach of JSON schemas for execution and markdown for documentation, powered by LLxprt's orchestration capabilities.

Tool Architecture

Tool Components

Each tool consists of three essential parts:

/tools/
β”œβ”€β”€ schemas/              # JSON definitions
β”‚   └── my-tool.json     # Tool schema with parameters
β”œβ”€β”€ handlers/            # Executable implementations  
β”‚   └── my-tool.sh      # Handler script (shell/Python/JS)
└── docs/               # Human documentation
    └── my-tool.md      # User-facing documentation

Step-by-Step Tool Creation

Step 1: Define the Tool Schema

Create a JSON schema file in /tools/schemas/:

{
  "name": "create_report",
  "version": "1.0.0",
  "description": "Creates a formatted report from data",
  "author": "developer@example.com",
  "created": "2025-08-06",
  "parameters": {
    "title": {
      "type": "string",
      "description": "Report title",
      "required": true,
      "maxLength": 200
    },
    "data_source": {
      "type": "string",
      "description": "Path to data file",
      "required": true,
      "pattern": "^/workspace/.*"
    },
    "format": {
      "type": "string",
      "description": "Output format",
      "required": false,
      "default": "pdf",
      "enum": ["pdf", "html", "markdown"]
    },
    "include_charts": {
      "type": "boolean",
      "description": "Include data visualizations",
      "required": false,
      "default": true
    }
  },
  "handler": {
    "type": "python",
    "path": "tools/handlers/create-report.py",
    "timeout": 30000,
    "memoryLimit": "512MB"
  },
  "permissions": {
    "riskLevel": "low",
    "requiresConfirmation": true,
    "category": "creation",
    "allowedPaths": ["/workspace/reports", "/workspace/data"],
    "restrictedPaths": ["/workspace/config", "/workspace/secrets"],
    "maxFileSizeMB": 50
  },
  "triggers": [
    "create a report",
    "generate report from",
    "make a report about",
    "build report for"
  ],
  "examples": [
    {
      "input": "Create a report from the Q4 sales data",
      "parameters": {
        "title": "Q4 Sales Report",
        "data_source": "/workspace/data/q4-sales.csv"
      }
    }
  ]
}

Step 2: Implement the Handler

Choose your handler type based on the task complexity:

Shell Script Handler (tools/handlers/create-report.sh)

#!/bin/bash
set -e

# Parse arguments
while [[ $# -gt 0 ]]; do
  case $1 in
    --title) TITLE="$2"; shift 2 ;;
    --data_source) DATA_SOURCE="$2"; shift 2 ;;
    --format) FORMAT="$2"; shift 2 ;;
    --include_charts) INCLUDE_CHARTS="$2"; shift 2 ;;
    *) shift ;;
  esac
done

# Validate inputs
if [ -z "$TITLE" ] || [ -z "$DATA_SOURCE" ]; then
  echo '{"success": false, "error": "Missing required parameters"}' >&2
  exit 1
fi

# Process data and create report
OUTPUT_FILE="/workspace/reports/${TITLE// /_}.${FORMAT:-pdf}"

# Your report generation logic here
echo "Generating report: $TITLE"
echo "Data source: $DATA_SOURCE"
echo "Format: ${FORMAT:-pdf}"

# Return success response
cat <<EOF
{
  "success": true,
  "file": "$OUTPUT_FILE",
  "size": $(stat -f%z "$OUTPUT_FILE" 2>/dev/null || echo 0),
  "message": "Report generated successfully"
}
EOF

Python Handler (tools/handlers/create-report.py)

#!/usr/bin/env python3
import sys
import json
import os
from datetime import datetime

def create_report(params):
    """
    Creates a formatted report from data
    """
    title = params['title']
    data_source = params['data_source']
    format_type = params.get('format', 'pdf')
    include_charts = params.get('include_charts', True)
    
    try:
        # Validate data source exists
        if not os.path.exists(data_source):
            return {
                'success': False,
                'error': f'Data source not found: {data_source}'
            }
        
        # Generate report (implement your logic here)
        output_path = f"/workspace/reports/{title.replace(' ', '_')}.{format_type}"
        
        # Mock report generation
        with open(output_path, 'w') as f:
            f.write(f"# {title}\n\n")
            f.write(f"Generated: {datetime.now().isoformat()}\n")
            f.write(f"Data from: {data_source}\n")
            
            if include_charts:
                f.write("\n## Charts\n[Chart placeholders]\n")
        
        return {
            'success': True,
            'file': output_path,
            'size': os.path.getsize(output_path),
            'format': format_type,
            'message': f'Report "{title}" generated successfully'
        }
        
    except Exception as e:
        return {
            'success': False,
            'error': str(e)
        }

if __name__ == '__main__':
    # Parse JSON parameters from command line
    if len(sys.argv) > 1:
        params = json.loads(sys.argv[1])
    else:
        params = json.loads(sys.stdin.read())
    
    # Execute and return result
    result = create_report(params)
    print(json.dumps(result))

JavaScript Handler (tools/handlers/create-report.js)

#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

function createReport(params) {
    const { title, data_source, format = 'pdf', include_charts = true } = params;
    
    try {
        // Validate data source
        if (!fs.existsSync(data_source)) {
            return {
                success: false,
                error: `Data source not found: ${data_source}`
            };
        }
        
        // Generate report
        const outputPath = `/workspace/reports/${title.replace(/ /g, '_')}.${format}`;
        
        // Mock report content
        const content = `
# ${title}

Generated: ${new Date().toISOString()}
Data from: ${data_source}

${include_charts ? '## Charts\n[Chart placeholders]' : ''}
        `.trim();
        
        fs.writeFileSync(outputPath, content);
        
        return {
            success: true,
            file: outputPath,
            size: fs.statSync(outputPath).size,
            format: format,
            message: `Report "${title}" generated successfully`
        };
        
    } catch (error) {
        return {
            success: false,
            error: error.message
        };
    }
}

// Parse parameters from command line
const params = JSON.parse(process.argv[2] || '{}');

// Execute and output result
const result = createReport(params);
console.log(JSON.stringify(result));

Step 3: Create Documentation

Write user-friendly documentation in /tools/docs/create-report.md:

# πŸ“Š Create Report Tool

## Purpose
Generates professional reports from your data with optional charts and multiple format options.

## What It Does
- Analyzes data from CSV, Excel, or JSON files
- Creates formatted reports with sections and visualizations
- Exports to PDF, HTML, or Markdown formats
- Includes optional charts and graphs

## How to Use It

### Natural Language Examples
- "Create a report from the Q4 sales data"
- "Generate a PDF report about customer analytics"
- "Make an HTML report from the survey results without charts"

### Parameters You Can Specify
- **Title**: What to call your report
- **Data Source**: Which file contains your data
- **Format**: PDF (default), HTML, or Markdown
- **Charts**: Whether to include visualizations (default: yes)

## Examples

### Basic Report
"Create a sales report from this month's data"
β†’ Generates a PDF with charts from the most recent sales file

### Custom Format
"Generate an HTML report called 'User Analytics' from /workspace/data/users.csv"
β†’ Creates an HTML report with specified title and data

### Without Charts
"Make a markdown report from the inventory data without charts"
β†’ Creates a text-only Markdown report

## Output
Reports are saved to `/workspace/reports/` with your specified format.

## Tips
- Use descriptive titles for easy identification
- PDF format is best for sharing
- HTML format is best for web viewing
- Markdown format is best for further editing

Testing Your Tool

Unit Test Template

// test/tools/create-report.test.js
const { executeToolHandler } = require('../../services/llxprt-bridge');

describe('Create Report Tool', () => {
  test('creates PDF report with valid data', async () => {
    const result = await executeToolHandler('create_report', {
      title: 'Test Report',
      data_source: '/workspace/test-data.csv',
      format: 'pdf'
    });
    
    expect(result.success).toBe(true);
    expect(result.file).toContain('.pdf');
  });
  
  test('fails with missing data source', async () => {
    const result = await executeToolHandler('create_report', {
      title: 'Test Report',
      data_source: '/nonexistent/file.csv'
    });
    
    expect(result.success).toBe(false);
    expect(result.error).toContain('not found');
  });
  
  test('respects format parameter', async () => {
    const result = await executeToolHandler('create_report', {
      title: 'Test Report',
      data_source: '/workspace/test-data.csv',
      format: 'html'
    });
    
    expect(result.success).toBe(true);
    expect(result.file).toContain('.html');
  });
});

Integration Test Script

#!/bin/bash
# test/integration/test-create-report.sh

echo "Testing Create Report Tool"

# Test 1: Basic execution
echo "Test 1: Basic PDF generation"
RESULT=$(node tools/handlers/create-report.js '{
  "title": "Integration Test Report",
  "data_source": "/workspace/test-data.csv"
}')

if echo "$RESULT" | grep -q '"success": true'; then
  echo "βœ… Test 1 passed"
else
  echo "❌ Test 1 failed: $RESULT"
fi

# Test 2: Different format
echo "Test 2: HTML format"
RESULT=$(node tools/handlers/create-report.js '{
  "title": "HTML Test Report",
  "data_source": "/workspace/test-data.csv",
  "format": "html"
}')

if echo "$RESULT" | grep -q '.html'; then
  echo "βœ… Test 2 passed"
else
  echo "❌ Test 2 failed: $RESULT"
fi

# Test 3: Error handling
echo "Test 3: Missing file error"
RESULT=$(node tools/handlers/create-report.js '{
  "title": "Error Test",
  "data_source": "/nonexistent.csv"
}')

if echo "$RESULT" | grep -q '"success": false'; then
  echo "βœ… Test 3 passed"
else
  echo "❌ Test 3 failed: $RESULT"
fi

Security Best Practices

Input Validation

function validateInput(params, schema) {
  const errors = [];
  
  // Check required parameters
  for (const [key, config] of Object.entries(schema.parameters)) {
    if (config.required && !params[key]) {
      errors.push(`Missing required parameter: ${key}`);
    }
  }
  
  // Validate types
  for (const [key, value] of Object.entries(params)) {
    const config = schema.parameters[key];
    if (!config) continue;
    
    if (config.type === 'string' && typeof value !== 'string') {
      errors.push(`${key} must be a string`);
    }
    if (config.type === 'integer' && !Number.isInteger(value)) {
      errors.push(`${key} must be an integer`);
    }
    if (config.type === 'boolean' && typeof value !== 'boolean') {
      errors.push(`${key} must be a boolean`);
    }
  }
  
  // Validate enums
  for (const [key, value] of Object.entries(params)) {
    const config = schema.parameters[key];
    if (config?.enum && !config.enum.includes(value)) {
      errors.push(`${key} must be one of: ${config.enum.join(', ')}`);
    }
  }
  
  return errors;
}

Path Security

function validatePath(filePath, allowedPaths, restrictedPaths) {
  const resolved = path.resolve(filePath);
  
  // Check restricted paths first
  for (const restricted of restrictedPaths) {
    if (resolved.startsWith(path.resolve(restricted))) {
      throw new Error(`Access denied: ${filePath} is in restricted area`);
    }
  }
  
  // Check allowed paths
  const isAllowed = allowedPaths.some(allowed => 
    resolved.startsWith(path.resolve(allowed))
  );
  
  if (!isAllowed) {
    throw new Error(`Access denied: ${filePath} is outside allowed paths`);
  }
  
  return resolved;
}

Command Injection Prevention

function escapeShellArg(arg) {
  return `'${arg.replace(/'/g, "'\\''")}'`;
}

// Never use string concatenation for shell commands
// Bad: exec(`ls ${userInput}`)
// Good: spawn('ls', [userInput])

Performance Optimization

Handler Optimization Tips

  1. Stream Large Files
const stream = fs.createReadStream(largefile);
stream.on('data', chunk => processChunk(chunk));
  1. Use Worker Threads for CPU-Intensive Tasks
const { Worker } = require('worker_threads');
const worker = new Worker('./heavy-computation.js');
  1. Implement Caching
const cache = new Map();
function getCachedOrCompute(key, computeFn) {
  if (cache.has(key)) return cache.get(key);
  const result = computeFn();
  cache.set(key, result);
  return result;
}

Deployment Checklist

Before deploying a new tool:

  • Schema validates against JSON schema spec
  • Handler executes without errors
  • All required parameters are documented
  • Error cases return proper JSON responses
  • Security paths are validated
  • Performance meets requirements (<5s for simple operations)
  • Documentation includes clear examples
  • Tests pass (unit and integration)
  • Tool is registered in tool-permissions.json
  • Activity logging captures tool usage

Advanced Topics

Multi-Step Tools

For complex operations requiring multiple steps:

async function multiStepTool(params) {
  const steps = [
    { name: 'validate', fn: validateInput },
    { name: 'prepare', fn: prepareData },
    { name: 'process', fn: processData },
    { name: 'generate', fn: generateOutput },
    { name: 'cleanup', fn: cleanupTemp }
  ];
  
  const results = {};
  
  for (const step of steps) {
    try {
      results[step.name] = await step.fn(params, results);
      
      // Report progress
      console.log(JSON.stringify({
        type: 'progress',
        step: step.name,
        completed: steps.indexOf(step) + 1,
        total: steps.length
      }));
      
    } catch (error) {
      // Rollback on failure
      await rollback(results);
      throw new Error(`Failed at ${step.name}: ${error.message}`);
    }
  }
  
  return results.generate;
}

Tool Composition

Combining multiple tools:

async function composedTool(params) {
  // Use output of one tool as input to another
  const data = await executeToolHandler('extract_data', {
    source: params.source
  });
  
  const analysis = await executeToolHandler('analyze_data', {
    data: data.output
  });
  
  const report = await executeToolHandler('create_report', {
    title: params.title,
    analysis: analysis.output
  });
  
  return report;
}

Dynamic Tool Loading

Hot-reload tools without restart:

class DynamicToolLoader {
  constructor() {
    this.watchers = new Map();
  }
  
  watchTool(toolPath) {
    const watcher = fs.watch(toolPath, (event) => {
      if (event === 'change') {
        this.reloadTool(toolPath);
      }
    });
    
    this.watchers.set(toolPath, watcher);
  }
  
  async reloadTool(toolPath) {
    // Clear require cache
    delete require.cache[require.resolve(toolPath)];
    
    // Reload tool
    const tool = require(toolPath);
    
    // Re-register
    await toolRegistry.registerTool(tool);
    
    console.log(`♻️ Reloaded tool: ${toolPath}`);
  }
}

Best Practices Summary

  1. Keep handlers focused - One tool, one purpose
  2. Return structured JSON - Consistent success/error format
  3. Validate all inputs - Never trust user data
  4. Handle errors gracefully - Always return meaningful errors
  5. Document thoroughly - Users need clear examples
  6. Test comprehensively - Cover success and failure cases
  7. Log appropriately - Track usage without exposing sensitive data
  8. Optimize for common cases - Make frequent operations fast
  9. Version your tools - Track changes over time
  10. Monitor performance - Know how your tools perform in production

This guide provides everything needed to develop robust, secure, and user-friendly tools for the Sasha system.