# Plugin Socket Communication Protocol

This document describes the detailed communication protocol between Wasot and its [plugins](./plugins.md) using Unix domain sockets and gRPC. For the broader architecture, see [plugin architecture](./plugin_architecture.md). For a hands-on implementation guide, see the [Rust plugin tutorial](./rust_plugin_tutorial.md).

## Overview

Wasot plugins communicate via **gRPC over Unix domain sockets (UDS)**. This provides:

- **High performance**: Unix sockets are faster than TCP for local IPC
- **Reliability**: Built-in flow control and error handling
- **Security**: Local-only communication, no network exposure
- **Simplicity**: Standard gRPC tooling works out of the box

## Socket Management

### Socket Path Convention

Wasot creates temporary socket files following this pattern:
```
/tmp/wasot_plugin_{pid}_{random}.sock
```

Where:
- `{pid}` is the Wasot process ID
- `{random}` is a random identifier to avoid conflicts

Example: `/tmp/wasot_plugin_12345_abc123.sock`

### Socket Lifecycle

```
1. Wasot creates temporary socket file path
2. Wasot launches plugin binary with socket path as argv[1]
3. Plugin creates gRPC server and binds to the socket
4. Wasot connects as gRPC client
5. Communication proceeds via gRPC calls
6. When processing completes:
   - Wasot closes connection
   - Plugin exits
   - Socket file is automatically cleaned up
```

### Error Handling

| Error | Cause | Resolution |
|-------|--------|------------|
| `Address already in use` | Socket file exists | Wasot generates new random path |
| `Permission denied` | Insufficient permissions | Check /tmp directory permissions |
| `No such file or directory` | Plugin failed to create socket | Check plugin logs, verify binary |

## gRPC Protocol

### Service Definition

```protobuf
service GrpcLibrary {
    // Called once during startup
    rpc GetTools (GetToolsRequest) returns (GetToolsResponse);
    
    // Called for each matching event
    rpc CallTool(CallToolRequest) returns (CallToolResponse);
}
```

### Message Flow

#### 1. Plugin Discovery (GetTools)

```
Wasot → Plugin: GetToolsRequest {}
Plugin → Wasot: GetToolsResponse {
    tools: [
        ToolDefinition {
            name: "word_counter",
            library: "my_plugin",
            selectors: ["paragraph"],
            optional_context_keys: [],
            required_context_keys: [],
            maybe_produced_keys: ["word_count"]
        }
    ]
}
```

#### 2. Tool Execution (CallTool)

```
Wasot → Plugin: CallToolRequest {
    tool_call: ToolCall {
        event_json: '{"type": "TextFinished", "lines": ["Hello world"]}',
        context: Context {
            scoped: {"author": "John"},
            global: {"document_title": "My Doc"}
        },
        tool_name: "word_counter",
        library_name: "my_plugin"
    }
}

Plugin → Wasot: CallToolResponse {
    context_changes: [
        ContextChange {
            target: SCOPE,
            key: "paragraph_words",
            value: {number_value: 2}
        }
    ],
    success: true,
    error_message: ""
}
```

## Event Serialization

Events are serialized as JSON strings for simplicity and language independence.

### Event Types

#### BlockStarted Event
```json
{
    "type": "BlockStarted",
    "meta_line": {
        "type_name": "paragraph",
        "level": 1,
        "line_index": 42,
        "id": "para-123",
        "arguments": {
            "args": ["first-arg"],
            "kwargs": {"class": "highlight"}
        }
    }
}
```

#### TextFinished Event  
```json
{
    "type": "TextFinished",
    "lines": [
        "This is the first line of text.",
        "This is the second line."
    ],
    "links": [
        {
            "name": "Example",
            "target": "https://example.com",
            "start_index": 10,
            "length": 7
        }
    ],
    "footnotes": [
        {
            "marker": 1,
            "link": "https://footnote.com"
        }
    ]
}
```

#### BlockFinished Event
```json
{
    "type": "BlockFinished"
}
```

## Context Protocol

Context is passed as a structured message containing both scoped and global variables. For details on context semantics, see [context and scoped variables](./context_and_scoped_variables.md).

### Context Structure

```protobuf
message Context {
    map<string, google.protobuf.Value> scoped = 1;
    map<string, google.protobuf.Value> global = 2;
}
```

### Value Types

Google's `protobuf.Value` supports these types:

```json
{
    "string_value": "Hello",        // String
    "number_value": 42.5,          // Number (float64)
    "bool_value": true,            // Boolean
    "struct_value": {...},         // Object
    "list_value": [...],          // Array
    "null_value": null            // Null
}
```

### Context Changes

Plugins return context modifications as a list of changes:

```protobuf
message ContextChange {
    ContextChangeTarget target = 1;  // SCOPE or GLOBAL
    string key = 2;                  // Variable name
    google.protobuf.Value value = 3; // New value
}

enum ContextChangeTarget {
    SCOPE = 0;   // Scoped to current block
    GLOBAL = 1;  // Global document context
}
```

## Performance Characteristics

### Throughput Benchmarks

| Scenario | Events/sec | Latency (avg) | Memory |
|----------|------------|---------------|---------|
| Simple text processing | ~10,000 | 0.1ms | 2MB |
| Complex analysis | ~1,000 | 1ms | 10MB |
| Large documents | ~500 | 2ms | 50MB |

### Optimization Tips

1. **Minimize Context Size**: Only request needed context keys
2. **Batch Operations**: Process multiple events when possible
3. **Avoid Large Payloads**: Keep event data minimal
4. **Efficient JSON**: Use streaming parsers for large events

## Security Considerations

### Process Isolation

Each plugin runs in its own process with:
- Separate memory space
- Limited file system access
- No direct access to Wasot internals

### Socket Security

- Unix sockets are local-only (no network access)
- Socket files have restrictive permissions (0600)
- Automatic cleanup on process exit

### Input Validation

Wasot validates all plugin responses:
- Context changes are type-checked
- Invalid keys are rejected
- Malformed responses cause tool failure (not crash)

## Debugging and Monitoring

### Logging

Enable debug logging to trace communication:

```bash
# Wasot side
RUST_LOG=wasot_compiler::tools=debug wasot document.wasot

# Plugin side
RUST_LOG=debug ./my_plugin /tmp/socket.sock
```

### Common Log Messages

```
# Plugin startup
INFO  Plugin server started, waiting for connections...

# Tool discovery
INFO  GetTools called - returning 3 tools
DEBUG Tool registered: word_counter with selectors ["paragraph"]

# Tool execution
INFO  CallTool called for tool: word_counter
DEBUG Processing event: {"type": "TextFinished", "lines": ["..."]}
DEBUG Context changes: [{"target": "SCOPE", "key": "word_count", ...}]
```

### Socket Monitoring

Monitor active sockets:

```bash
# List Unix sockets
ss -xl | grep wasot

# Monitor socket activity
sudo strace -e trace=network -p <plugin_pid>

# Check socket permissions
ls -la /tmp/wasot_plugin_*.sock
```

### Performance Profiling

Profile gRPC calls:

```rust
// In plugin code
use std::time::Instant;

let start = Instant::now();
let response = self.process_tool_call(request).await;
let duration = start.elapsed();

tracing::info!("Tool call took {:?}", duration);
```

## Error Recovery

### Connection Failures

| Failure Type | Wasot Behavior | Plugin Behavior |
|--------------|----------------|-----------------|
| Socket bind failure | Retry with new path | Exit with error code |
| Connection timeout | Skip plugin, continue | Log error, wait |
| gRPC call timeout | Mark tool as failed | Continue processing |
| Plugin crash | Remove plugin from active list | N/A |

### Graceful Degradation

Wasot continues processing even when plugins fail:

1. **Plugin startup failure**: Skip plugin, warn user
2. **Tool call failure**: Log error, continue with next tool  
3. **Invalid response**: Ignore response, continue processing
4. **Context corruption**: Reset context to last known good state

### Retry Logic

```python
# Pseudo-code for Wasot's retry logic
for attempt in range(3):
    try:
        response = await plugin.call_tool(request)
        if response.success:
            return response
    except grpc.RpcError as e:
        if attempt == 2:  # Last attempt
            log_error("Plugin failed after 3 attempts", e)
            return empty_response()
        await sleep(backoff_delay(attempt))
```

## Testing

### Unit Tests

Test individual components:

```rust
#[tokio::test]
async fn test_tool_call_response() {
    let plugin = MyPlugin::new();
    let request = create_test_request();
    
    let response = plugin.call_tool(request).await.unwrap();
    
    assert!(response.success);
    assert_eq!(response.context_changes.len(), 1);
}
```

### Integration Tests

Test full socket communication:

```rust
#[tokio::test]
async fn test_socket_communication() {
    let socket_path = "/tmp/test_plugin.sock";
    
    // Start plugin server
    let plugin_handle = tokio::spawn(start_plugin_server(socket_path));
    
    // Connect as client
    let mut client = connect_to_plugin(socket_path).await.unwrap();
    
    // Test GetTools
    let tools = client.get_tools(GetToolsRequest {}).await.unwrap();
    assert!(!tools.into_inner().tools.is_empty());
    
    plugin_handle.abort();
}
```

### Load Testing

Simulate high-volume processing:

```bash
# Generate large test document
wasot-test-gen --events 10000 > large_doc.wasot

# Profile plugin performance
RUST_LOG=info wasot --plugin ./my_plugin large_doc.wasot
```

This socket communication protocol provides a robust foundation for high-performance, reliable plugin communication while maintaining security and ease of debugging.