# v0.8.0 Upgrade Guide for Agents

**Purpose**: Explain what changed in v0.8.0 and how to use the new graceful shutdown protocol  
**Audience**: Other AI agents, developers, or systems integrating with ix daemon  
**Last Updated**: 2026-05-16  
**Source**: `src/lib/daemon_sock.rs`, commit de256fa

---

## What Changed in v0.8.0

### Summary
- **New Feature**: Graceful shutdown protocol for daemon socket
- **New Docs**: Complete API reference (`SOCKET-API.md`) and quick start guide (`QUICKSTART.md`)
- **Version**: v0.6.3 → v0.8.0 (minor release, backward compatible)

### Key Addition: Graceful Shutdown

**Before v0.8.0**:
```
Daemon receives SIGTERM → Sets atomic bool → Breaks loop → Drops socket
                          ↓
Client sees: EOF (connection closed)
Client thinks: "Did it crash? Did it shutdown? Unknown."
```

**After v0.8.0**:
```
Daemon receives SIGTERM → Broadcasts Shutdown message → Waits 1000ms → Closes socket
                          ↓
Client sees: {"t":"shutdown","reason":"signal","delay_ms":1000}
Client thinks: "Ah, graceful shutdown. I can save state and reconnect."
```

---

## New Message Types

### 1. ServerMessage::Shutdown (Server → Client)

**Purpose**: Notify all connected clients that the daemon is shutting down gracefully.

**Structure**:
```json
{
  "t": "shutdown",
  "reason": "signal",
  "delay_ms": 1000
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `t` | string | Yes | Must be `"shutdown"` |
| `reason` | string | Yes | Why shutting down: `"signal"`, `"user_request"`, `"safety_exit"` |
| `delay_ms` | u32 | Yes | Milliseconds before socket closes (default: 1000) |

**When Sent**:
- Daemon receives `SIGTERM` or `SIGINT`
- Daemon receives `ClientMessage::Shutdown` (optional)
- Daemon safety escalation

**Client Action Required**:
1. Receive shutdown notice
2. Complete any in-flight operations
3. Save state if needed
4. Wait `delay_ms` milliseconds
5. Reconnect after delay (if auto-reconnect enabled)

**Source**: `src/lib/daemon_sock.rs:194-196, 570-579`

---

### 2. ClientMessage::Shutdown (Client → Server)

**Purpose**: Client acknowledges shutdown notice (optional, fire-and-forget).

**Structure**:
```json
{
  "t": "shutdown",
  "ack": true
}
```

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `t` | string | Yes | - | Must be `"shutdown"` |
| `ack` | bool | No | false | Acknowledgment flag |

**Behavior**:
- Logged by daemon at debug level
- No response sent
- Does not affect shutdown sequence
- Optional (clients can ignore)

**Source**: `src/lib/daemon_sock.rs:264-269, 653-662`

---

## How to Use the New Protocol

### For Existing Clients (No Changes Required)

**Backward Compatibility**: Old clients continue to work without modification.

- Old clients will **ignore** the new `ServerMessage::Shutdown` message (serde tag-based enum)
- Old clients will **not send** `ClientMessage::Shutdown` (not required)
- Shutdown sequence proceeds normally regardless

**What happens**:
```
Daemon: Broadcasts Shutdown notice
Old Client: Ignores message (unknown tag)
Daemon: Waits 1000ms
Daemon: Closes socket
Old Client: Sees EOF (same as before)
```

**Action**: None required. Upgrade is optional.

---

### For New Clients (Recommended Implementation)

**Step 1: Detect Shutdown Message**

```python
import json

def handle_message(msg_str):
    msg = json.loads(msg_str)
    
    if msg.get('t') == 'shutdown':
        reason = msg.get('reason', 'unknown')
        delay_ms = msg.get('delay_ms', 1000)
        
        print(f"Daemon shutting down: {reason}")
        print(f"Socket will close in {delay_ms}ms")
        
        # Complete in-flight operations
        complete_pending_operations()
        
        # Save state if needed
        save_client_state()
        
        # Wait for delay (or less if you want to reconnect sooner)
        time.sleep(delay_ms / 1000.0)
        
        # Reconnect after delay
        reconnect()
        
        return
    
    # Handle other messages...
```

**Step 2: Optional Acknowledgment**

```python
def acknowledge_shutdown(sock):
    """Send optional shutdown acknowledgment."""
    ack_msg = {"t": "shutdown", "ack": True}
    try:
        sock.sendall((json.dumps(ack_msg) + "\n").encode())
    except Exception as e:
        # Daemon doesn't require ACK, so ignore errors
        print(f"Failed to send shutdown ack: {e}")
```

**Step 3: Reconnection Logic**

```python
def reconnect():
    """Reconnect to daemon after shutdown."""
    max_retries = 5
    retry_delay = 2.0  # seconds
    
    for attempt in range(max_retries):
        try:
            sock = connect_to_daemon()
            print(f"Reconnected after shutdown (attempt {attempt + 1})")
            return sock
        except Exception as e:
            if attempt < max_retries - 1:
                print(f"Reconnect attempt {attempt + 1} failed, retrying in {retry_delay}s...")
                time.sleep(retry_delay)
                retry_delay *= 2  # Exponential backoff
            else:
                raise
    
    raise ConnectionError("Failed to reconnect after shutdown")
```

**Source**: `docs/SOCKET-API.md` (complete example)

---

## Shutdown Sequence Timeline

```
T=0ms:    Daemon receives SIGTERM
          ↓
T=0ms:    Daemon sets SHUTDOWN=true, breaks main loop
          ↓
T=0ms:    Daemon calls shutdown_notify("signal", 1000)
          ↓
T=0ms:    Daemon broadcasts ServerMessage::Shutdown to all clients
          ↓
T=0-1000ms: Clients receive shutdown notice
            - Complete in-flight operations
            - Save state
            - Optionally send ClientMessage::Shutdown {ack: true}
          ↓
T=1000ms: Daemon closes socket (Drop impl)
          ↓
T=1000ms: Clients see EOF
          ↓
T=1000ms+: Clients can reconnect
```

**Source**: `src/lib/daemon.rs:198-201`, `src/lib/daemon_sock.rs:570-579`

---

## Testing the New Protocol

### Test 1: Verify Shutdown Message

```bash
# Start daemon
ixd /tmp/test-repo &
DAEMON_PID=$!

# Connect and listen
echo '{"t":"status_query","id":1}' | socat - UNIX-CONNECT:/run/user/1000/ixd/*.sock &
LISTENER_PID=$!

# Send shutdown signal
kill -TERM $DAEMON_PID

# Expected output from listener:
# {"t":"shutdown","reason":"signal","delay_ms":1000}
# (followed by EOF)
```

### Test 2: Verify Client Reconnection

```python
import socket, json, time

def test_reconnect():
    # Connect
    sock = connect_to_daemon()
    
    # Send shutdown query (simulate daemon shutdown)
    # In real scenario, daemon sends this
    msg = recv_message(sock)
    
    if msg.get('t') == 'shutdown':
        delay_ms = msg.get('delay_ms', 1000)
        time.sleep(delay_ms / 1000.0)
        
        # Reconnect
        new_sock = connect_to_daemon()
        print("Reconnected successfully!")
```

**Source**: `tests/daemon_sock.rs:test_shutdown_protocol`

---

## Error Handling

### Scenario 1: Client Receives Shutdown Mid-Query

```python
# Client is waiting for search results
response = recv_message(sock)

if response.get('t') == 'shutdown':
    # Query will not complete
    # Save partial results if needed
    save_partial_results()
    
    # Wait for delay
    time.sleep(response.get('delay_ms', 1000) / 1000.0)
    
    # Reconnect and retry query
    new_sock = reconnect()
    retry_query(new_sock)
```

### Scenario 2: Daemon Crashes Before Sending Shutdown

```python
# Client waiting for response
try:
    response = recv_message(sock)
except ConnectionError:
    # Daemon crashed (no shutdown message)
    # Treat as unexpected failure
    log_error("Daemon crashed unexpectedly")
    alert_admin()
    
    # Retry with exponential backoff
    reconnect_with_backoff()
```

**Key Difference**:
- **Graceful shutdown**: Client receives `{"t":"shutdown"}`, knows it's intentional
- **Crash**: Client sees EOF/error, knows it's unexpected

---

## Migration Checklist

### For Agent Developers

- [ ] **Understand the change**: New `ServerMessage::Shutdown` variant
- [ ] **Decide on implementation**: Will your agent handle graceful shutdown?
- [ ] **If YES**: Implement shutdown message handler (see examples above)
- [ ] **If NO**: No action required (backward compatible)
- [ ] **Test**: Verify shutdown handling works correctly
- [ ] **Document**: Note shutdown handling in your agent's documentation

### For System Integrators

- [ ] **Check dependencies**: Are you using ix daemon in production?
- [ ] **Review logs**: Look for `shutdown` messages in daemon logs
- [ ] **Update monitoring**: Alert on unexpected shutdowns, not just crashes
- [ ] **Test reconnection**: Verify your system reconnects after shutdown

---

## Verification Commands

### Verify Daemon Sends Shutdown

```bash
# Start daemon
ixd /tmp/test-repo &

# Listen for shutdown
timeout 2 bash -c '
  while true; do
    echo "{\"t\":\"status_query\"}" | socat - UNIX-CONNECT:/run/user/1000/ixd/*.sock 2>/dev/null
    sleep 0.1
  done
' | grep shutdown

# Send shutdown signal
kill -TERM $(pgrep -f "ixd /tmp/test-repo")

# Expected: {"t":"shutdown","reason":"signal","delay_ms":1000}
```

### Verify Client Handles Shutdown

```python
# Run this Python script
import socket, json, time

sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect('/run/user/1000/ixd/a1b2c3d4e5f67890.sock')

while True:
    line = sock.recv(4096).decode()
    if not line:
        break
    msg = json.loads(line)
    if msg.get('t') == 'shutdown':
        print(f"Shutdown received: {msg}")
        break

print("Client handled shutdown correctly!")
```

---

## See Also

- `SOCKET-API.md` - Complete API reference
- `QUICKSTART.md` - Getting started guide
- `DAEMON-RUNBOOK.md` - Operational procedures
- `src/lib/daemon_sock.rs` - Full implementation
- `tests/daemon_sock.rs` - Test cases

---

## Questions?

**Q: Do I need to change my existing client code?**  
A: No. The change is backward compatible. Old clients continue to work.

**Q: What happens if I don't implement shutdown handling?**  
A: Your client will work as before. It will see EOF on shutdown (same as v0.6.3).

**Q: Should I implement shutdown handling?**  
A: Recommended for production systems. It allows:
- Distinguishing graceful shutdown from crash
- Saving state before disconnect
- Intelligent reconnection

**Q: Can I send `ClientMessage::Shutdown` without receiving one?**  
A: Yes, but it's not recommended. The shutdown message is meant for server-to-client notification.

**Q: What if my client is mid-query when shutdown arrives?**  
A: Complete the query if possible, or save partial results. The daemon waits 1000ms before closing.

---

**Last Verified**: 2026-05-16  
**Contact**: See `CONTRIBUTING.md` for support
