# Background Tasks Plugin Specification

## Overview

The Background Tasks Plugin enables the LLM to run terminal commands asynchronously without blocking the main conversation loop. Uses asyncio subprocess with output buffering - no tmux dependency required.

**Location:** `plugins/background_tasks/`

**Version:** 1.0.0

## Features

### Async Command Execution

- **Non-blocking**: Commands run in separate processes, conversation continues immediately
- **Output buffering**: Stdout/stderr captured to circular buffer (default 1000 lines)
- **Process groups**: Child processes properly managed and killed together
- **Auto-cleanup**: All tasks terminated on plugin shutdown

### Task Lifecycle Management

- **Start tasks**: Launch commands with optional timeout and working directory
- **Check status**: View all running/completed tasks with duration
- **Get output**: Retrieve buffered stdout/stderr on demand
- **Kill tasks**: Graceful (SIGTERM) or forced (SIGKILL) termination
- **Wait for completion**: Block until task finishes with optional timeout

### LLM Integration

- **XML tool calls**: LLM uses `<bg>` tags in responses to start tasks
- **Keyword injection**: Instructions automatically injected when user mentions "background"
- **Result injection**: Task results injected back to conversation for LLM context
- **Force continuation**: LLM automatically responds to task results

## XML Syntax

### Start Background Task

```xml
<bg id="server">npm run dev</bg>
<bg id="build" timeout="5m">npm run build</bg>
<bg id="test" cwd="/app/tests">pytest</bg>
```

| Attribute | Required | Description |
|-----------|----------|-------------|
| `id` | No | Task identifier (auto-generated if omitted) |
| `timeout` | No | Auto-kill after duration (30s, 5m, 1h) |
| `cwd` | No | Working directory (defaults to current) |

### Check Status

```xml
<bg-status />                    <!-- All tasks -->
<bg-status>server</bg-status>    <!-- Specific task -->
```

### Get Output

```xml
<bg-output>server</bg-output>                    <!-- Last 50 lines -->
<bg-output lines="200">server</bg-output>        <!-- Last 200 lines -->
<bg-output stream="stderr">build</bg-output>     <!-- Stderr only -->
```

| Attribute | Default | Description |
|-----------|---------|-------------|
| `lines` | 50 | Number of lines to retrieve |
| `stream` | "both" | Output stream: "stdout", "stderr", or "both" |

### Kill Task

```xml
<bg-kill>server</bg-kill>                       <!-- Graceful SIGTERM -->
<bg-kill signal="SIGKILL">server</bg-kill>      <!-- Force kill -->
<bg-kill>*</bg-kill>                            <!-- Kill all tasks -->
```

### Wait for Completion

```xml
<bg-wait>build</bg-wait>                        <!-- Wait indefinitely -->
<bg-wait timeout="60s">build</bg-wait>          <!-- Wait with timeout -->
```

## Slash Commands

### `/bg` (aliases: `/background`, `/tasks`)

User command for managing background tasks.

#### Subcommands

| Subcommand | Arguments | Description |
|------------|-----------|-------------|
| `list` | - | List all background tasks (default) |
| `kill` | `<id>` | Kill a specific task |
| `output` | `<id>` | Show last 50 lines of task output |
| `clear` | - | Remove completed tasks from registry |

#### Examples

```bash
# List all tasks
/bg
/bg list

# Kill a task
/bg kill server

# View output
/bg output build

# Clear completed
/bg clear
```

## Configuration

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | boolean | `true` | Enable the plugin |
| `max_tasks` | int | `10` | Maximum concurrent background tasks |
| `default_buffer_lines` | int | `1000` | Lines to buffer per task |
| `default_timeout` | string | `null` | Default timeout for all tasks |
| `enable_mode` | string | `"keyword"` | How to inject LLM instructions |

### Enable Modes

| Mode | Description |
|------|-------------|
| `"keyword"` | Inject instructions when user mentions trigger keywords (default) |
| `"startup"` | Add instructions to system prompt at initialization |
| `"disabled"` | Don't inject instructions automatically |

### Configuration File

Edit `~/.kollabor-cli/config.json`:

```json
{
  "plugins": {
    "background_tasks": {
      "enabled": true,
      "max_tasks": 10,
      "default_buffer_lines": 1000,
      "default_timeout": null,
      "enable_mode": "keyword"
    }
  }
}
```

## Architecture

### Plugin Structure

```
plugins/background_tasks/
  __init__.py           # Package exports, Plugin alias
  plugin.py             # Main plugin class, hooks, XML parsing
  models.py             # BackgroundTask, TaskStatus, ParsedBgCommand
  task_registry.py      # Thread-safe task tracking
  process_executor.py   # Asyncio subprocess management
```

### Data Models

```python
class TaskStatus(Enum):
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    KILLED = "killed"

@dataclass
class BackgroundTask:
    id: str                    # Task identifier
    command: str               # Shell command
    status: TaskStatus         # Current status
    exit_code: Optional[int]   # Exit code when completed
    start_time: float          # Start timestamp
    end_time: Optional[float]  # End timestamp
    process: Process           # Asyncio subprocess
    stdout_buffer: List[str]   # Buffered stdout lines
    stderr_buffer: List[str]   # Buffered stderr lines
    max_buffer_lines: int      # Buffer limit (circular)
```

### Event Integration

The plugin integrates with the event system:

- **LLM_RESPONSE_POST**: Parses XML commands from LLM responses
- **USER_INPUT_PRE**: Injects instructions on keyword detection
- **Command Registration**: Registers `/bg` command via `SlashCommandRegistry`
- **Display Filter**: Strips `<bg>` XML from displayed messages

### Custom Events Emitted

| Event | Data | Description |
|-------|------|-------------|
| `bg_task_started` | `{id, command}` | Task successfully started |
| `bg_task_killed` | `{id}` | Task was killed |

## Process Management

### Subprocess Execution

Tasks are started using `asyncio.create_subprocess_shell()`:

```python
process = await asyncio.create_subprocess_shell(
    command,
    stdout=asyncio.subprocess.PIPE,
    stderr=asyncio.subprocess.PIPE,
    cwd=work_dir,
    preexec_fn=os.setsid,  # New process group
)
```

### Process Group Killing

When killing tasks, the entire process group is terminated:

```python
pgid = os.getpgid(process.pid)
os.killpg(pgid, signal.SIGTERM)
```

This ensures child processes spawned by the command are also killed.

### Output Buffering

Output is read asynchronously and stored in circular buffers:

```python
async def read_stream(stream, append_fn):
    while True:
        line = await stream.readline()
        if not line:
            break
        append_fn(line.decode("utf-8").rstrip())
```

## Error Handling

| Error | Cause | Resolution |
|-------|-------|------------|
| `task 'x' already exists` | Duplicate task ID | Use different ID or kill existing |
| `task 'x' not found` | Invalid task ID | Check `/bg list` for valid IDs |
| `max tasks (10) reached` | Too many tasks | Kill some tasks first |
| `task 'x' not running` | Task already finished | Task completed or was killed |
| `no command specified` | Empty `<bg>` tag | Provide command in tag content |

## Usage Examples

### Starting a Dev Server

```xml
<!-- LLM response -->
<bg id="dev-server">npm run dev</bg>
```

Result injected:
```
[started: dev-server] npm run dev...
```

### Monitoring Long-Running Build

```xml
<bg id="build" timeout="15m">npm run build:prod</bg>

<!-- Later... -->
<bg-status>build</bg-status>

<!-- Result: [build] running (2m34s) -->

<bg-wait timeout="5m">build</bg-wait>

<!-- Result: [build] completed (exit=0, 4m12s) -->

<bg-output stream="stderr">build</bg-output>
```

### Running Tests with Server

```xml
<bg id="server">npm run dev</bg>
<bg id="tests" timeout="10m">npm test</bg>

<bg-status />
```

Result:
```
[bg-tasks]
  [*] server    running    0m05s
  [*] tests     running    0m02s
```

## Dependencies

- **asyncio**: Subprocess management and async I/O
- **threading**: Thread-safe task registry
- **signal/os**: Process group management
- **core.plugins.base**: BasePlugin inheritance
- **core.events**: Hook registration and event emission
- **core.io.visual_effects**: ColorPalette for tool indicators
- **core.io.message_renderer**: DisplayFilterRegistry

## Testing

### Manual Testing

```python
import asyncio
from plugins.background_tasks import TaskRegistry, ProcessExecutor

async def test():
    registry = TaskRegistry()
    executor = ProcessExecutor(registry)

    # Start a task
    task = await executor.start('test', 'echo hello && sleep 1 && echo done')

    # Wait for completion
    await executor.wait('test')

    # Check output
    print(task.get_output(10))

    # Cleanup
    await executor.cleanup()

asyncio.run(test())
```

### Unit Tests

```bash
# Run plugin tests
python -m pytest tests/unit/test_background_tasks.py -v
```

## Implementation Checklist

- [x] Asyncio subprocess execution
- [x] Output buffering with circular buffer
- [x] Process group management
- [x] Timeout auto-kill
- [x] XML command parsing
- [x] Keyword-triggered instruction injection
- [x] Result injection to conversation
- [x] `/bg` slash command
- [x] Display filter for XML stripping
- [x] Event emission for task lifecycle
- [x] Thread-safe task registry
- [x] Graceful shutdown cleanup

## Related Documentation

- **Slash Commands Guide**: `/docs/reference/slash-commands-guide.md`
- **Plugin Development**: `/CLAUDE.md` (Plugin System section)
- **Agent Orchestrator**: `/docs/plugins/core/agent_orchestrator_spec.md` (similar pattern)
- **Background Tasks Spec**: `/docs/specs/background-tasks-plugin-spec.md`
