# LLM Agent Orchestration

Give the LLM the ability to spawn and manage parallel kollab sub-agents.

## Core Concept

LLM outputs XML → System spawns tmux agents → Agents complete → Output nudged back to LLM

## XML Syntax

### Spawn Agents

```xml
<agent>
  <lint-fixer>
    <task>
    objective: Fix all flake8 lint errors in core/io/ module

    context:
    - Project uses Black formatter (88 char line length)
    - Follow existing code patterns in the module
    - Do not change functionality, only fix lint issues

    errors to fix:
    - E501 line too long (3 occurrences in input_handler.py)
    - F401 unused import 'os' (buffer.py line 12)
    - E302 expected 2 blank lines (input_handler.py line 45)
    - W291 trailing whitespace (buffer.py lines 78, 92)

    todo:
    [ ] Run: flake8 core/io/ --max-line-length=88
    [ ] Fix each error maintaining code style
    [ ] Run flake8 again to verify zero errors
    [ ] Run: python -m py_compile core/io/*.py (syntax check)

    success criteria:
    - flake8 returns 0 errors
    - All files compile without syntax errors
    - No functional changes to the code
    </task>
    <files>
      <file>core/io/input_handler.py</file>
      <file>core/io/buffer.py</file>
    </files>
  </lint-fixer>

  <test-fixer>
    <task>
    objective: Fix failing test in test_conversation_logger.py

    context:
    - Test failure: test_save_conversation_creates_file
    - Error: AssertionError: File not found at expected path
    - Root cause: Recent refactor changed save path from ./conversations to ~/.kollabor-cli/projects/

    the fix:
    - Update test to use new centralized path structure
    - Mock get_project_data_dir() to return temp directory
    - Ensure cleanup removes temp files after test

    todo:
    [ ] Read the failing test and understand current assertions
    [ ] Update path expectations to match new structure
    [ ] Add proper mocking for get_project_data_dir()
    [ ] Run: python -m pytest tests/test_conversation_logger.py -v
    [ ] Verify test passes

    success criteria:
    - pytest returns 0 failures for this test file
    - No other tests broken by changes
    </task>
    <files>
      <file>tests/test_conversation_logger.py</file>
      <file>core/llm/conversation_logger.py</file>
      <file>core/utils/config_utils.py</file>
    </files>
  </test-fixer>
</agent>
```

The task includes everything the agent needs:
- Clear objective
- Relevant context and constraints
- Specific details about what to fix
- Step-by-step todo checklist
- Measurable success criteria

Files are auto-attached - agent has exact context, no searching needed.

Executes:
```bash
# Session naming: {project}-{agent-name}
# Example: kollabor-cli-lint-fixer

# 1. Create session and start kollab
tmux new-session -d -s kollabor-cli-lint-fixer
sleep 1
tmux send-keys -t kollabor-cli-lint-fixer "kollab" C-m
sleep 4  # wait for kollab init

# 2. Auto-attach files (read and inject as context)
# System reads each <file> and prepends to the task message:
#
#   --file_start core/io/input_handler.py--
#   <contents of input_handler.py>
#   --file_end--
#
#   --file_start core/io/buffer.py--
#   <contents of buffer.py>
#   --file_end--

# 3. Send the full task (files + task content)
tmux send-keys -t kollabor-cli-lint-fixer "${FILE_CONTENTS}

${TASK_CONTENT}" C-m
```

The agent receives a single message with:
1. All file contents (with clear delimiters)
2. The comprehensive task specification

No file searching needed - agent has everything to start working immediately.

All agents spawn immediately, non-blocking.

Response:
```
[spawned: lint-fixer, test-runner]
```

### Message Agent

```xml
<message to="lint-fixer">Also check for unused imports</message>
```

Executes:
```bash
tmux send-keys -t kollabor-cli-lint-fixer "Also check for unused imports" C-m
```

Response:
```
[message sent: lint-fixer]
```

### Stop Agent

```xml
<!-- single agent -->
<stop>lint-fixer</stop>

<!-- multiple agents -->
<stop>lint-fixer-0, lint-fixer-1</stop>
```

Executes:
```bash
# capture final output before killing
tmux capture-pane -t kollabor-cli-lint-fixer -p -S -100
# kill session
tmux kill-session -t kollabor-cli-lint-fixer
```

Response:
```
[stopped: lint-fixer @ 1m22s]
Partial: Fixed 8 of ~15 lint errors
```

### Check Status

```xml
<status />
```

Executes:
```bash
# list all project sessions with metadata
tmux list-sessions -F '#{session_name} #{session_activity}' | grep "^kollabor-cli-"
```

Response:
```
[agents]
lint-fixer   running  1m45s
test-runner  idle     2m10s
doc-writer   running  0m30s
```

### Capture Output

```xml
<!-- capture last 200 lines from agent -->
<capture>lint-fixer 200</capture>

<!-- capture last 50 lines (default) -->
<capture>test-runner</capture>

<!-- capture from multiple agents -->
<capture>lint-fixer, test-runner 100</capture>
```

Executes:
```bash
tmux capture-pane -t kollabor-cli-lint-fixer -p -S -200
```

Response:
```
[capture: lint-fixer @ 1m45s, 200 lines]
...
Running flake8 core/io/ --max-line-length=88
core/io/input_handler.py:45:1: E302 expected 2 blank lines, found 1
core/io/buffer.py:12:1: F401 'os' imported but unused

Fixing E302 in input_handler.py...
Added blank line at line 45.

Fixing F401 in buffer.py...
Removed unused import 'os' at line 12.

Running flake8 again...
All checks passed!
```

Use cases:
- Check progress without waiting for completion
- Debug stuck agents
- Get context before sending a message

## Completion Detection

Every 2 seconds:
1. Capture tmux pane content
2. MD5 hash it
3. If hash same for 3 checks (6 sec) → agent is done
4. Capture last 500 lines → send nudge to LLM

## Nudge Format

When agent completes, inject into conversation:

```
[done: lint-fixer @ 2m34s]
Fixed 15 lint errors:
- core/io/input_handler.py: 5 errors
- core/io/buffer.py: 3 errors
- core/llm/service.py: 7 errors

View full: /terminal view lint-fixer
```

LLM sees this and can respond.

## Example Flow

```
User: "Fix all lint errors"

LLM: I'll spawn two agents to fix lint errors in parallel.

  <agent>
    <lint-core>
      <task>
      objective: Fix all flake8 lint errors in core/ directory

      context:
      - Project uses Black formatter (88 char line length)
      - Do not change functionality, only fix lint issues

      todo:
      [ ] Run: flake8 core/ --max-line-length=88
      [ ] Fix each error maintaining code style
      [ ] Run flake8 again to verify zero errors

      success: flake8 returns 0 errors
      </task>
      <files>
        <file>core/io/input_handler.py</file>
        <file>core/io/buffer.py</file>
        <file>core/llm/service.py</file>
      </files>
    </lint-core>

    <lint-plugins>
      <task>
      objective: Fix all flake8 lint errors in plugins/ directory

      context:
      - Project uses Black formatter (88 char line length)
      - Do not change functionality, only fix lint issues

      todo:
      [ ] Run: flake8 plugins/ --max-line-length=88
      [ ] Fix each error maintaining code style
      [ ] Run flake8 again to verify zero errors

      success: flake8 returns 0 errors
      </task>
      <files>
        <file>plugins/save_conversation_plugin.py</file>
        <file>plugins/tmux_plugin.py</file>
      </files>
    </lint-plugins>
  </agent>

--sys_msg_start--
[spawned: lint-core, lint-plugins]
--sys_msg_end--

[1 min later, LLM checks progress]

LLM: Let me check on the agents.

  <capture>lint-core 100</capture>

--sys_msg_start--
[capture: lint-core @ 1m12s, 100 lines]
Running flake8 core/ --max-line-length=88
Found 15 errors. Fixing...
Fixed E501 in input_handler.py line 234
Fixed E501 in input_handler.py line 456
...
--sys_msg_end--

[2 min later, completions arrive]

--sys_msg_start--
[done: lint-core @ 2m12s]
Fixed 15 errors in 6 files:
- core/io/input_handler.py: 8 errors
- core/io/buffer.py: 4 errors
- core/llm/service.py: 3 errors
Verification: flake8 returns 0 errors
--sys_msg_end--

--sys_msg_start--
[done: lint-plugins @ 1m45s]
Fixed 8 errors in 2 files:
- plugins/save_conversation_plugin.py: 5 errors
- plugins/tmux_plugin.py: 3 errors
Verification: flake8 returns 0 errors
--sys_msg_end--

LLM: Both agents completed successfully. Fixed 23 total lint errors across 8 files.
All files now pass flake8 verification.
```

## Advanced

### Clone (with conversation context)

```xml
<clone>
  <security-audit>
    <task>
    objective: Security audit of authentication changes we discussed

    context:
    - Focus on the OAuth2 implementation changes
    - We discussed potential CSRF vulnerabilities earlier
    - Check against OWASP top 10

    todo:
    [ ] Review auth middleware changes
    [ ] Check token validation logic
    [ ] Verify CSRF protection
    [ ] Test for injection vulnerabilities
    [ ] Document findings

    success: Security report with no critical/high vulnerabilities
    </task>
    <files>
      <file>core/auth/oauth2.py</file>
      <file>core/auth/middleware.py</file>
    </files>
  </security-audit>
</clone>
```

Executes:
```bash
# 1. Export current conversation to temp file
kollab --export-conversation /tmp/conv-{session_id}.json

# 2. Create session with kollab in resume mode
tmux new-session -d -s kollabor-cli-security-audit
tmux send-keys -t kollabor-cli-security-audit "kollab --resume /tmp/conv-{session_id}.json" C-m
sleep 4

# 3. Send task with attached files
tmux send-keys -t kollabor-cli-security-audit "{files + task}" C-m
```

Clone agent gets full conversation history - knows all prior context.

### Team Lead

```xml
<team lead="refactor-mgr" workers="3">
  <task>
  objective: Refactor input_handler.py into modular components

  context:
  - File is 1500+ lines, needs splitting
  - Follow existing patterns in core/io/
  - Maintain backwards compatibility

  todo:
  [ ] Analyze current structure and dependencies
  [ ] Identify logical components to extract
  [ ] Spawn worker agents for each component
  [ ] Integrate and verify all tests pass

  success: Original file < 300 lines, all tests green
  </task>
  <files>
    <file>core/io/input_handler.py</file>
  </files>
</team>
```

Executes:
```bash
# 1. Create lead agent session
tmux new-session -d -s kollabor-cli-refactor-mgr
tmux send-keys -t kollabor-cli-refactor-mgr "kollab" C-m
sleep 4

# 2. Send task with special "team lead" system prompt
#    Lead agent can use <agent> tags to spawn workers
tmux send-keys -t kollabor-cli-refactor-mgr "You are a team lead agent.
You can spawn up to 3 worker agents using <agent> tags.
Coordinate their work and integrate results.

{files + task}" C-m
```

Lead agent can spawn its own worker sub-agents.

### Broadcast

```xml
<broadcast to="lint-*">Wrap up current work and report status.</broadcast>
```

Executes:
```bash
# find all matching sessions
sessions=$(tmux list-sessions -F '#{session_name}' | grep "^kollabor-cli-lint-")

# send message to each
for session in $sessions; do
  tmux send-keys -t "$session" "Wrap up current work and report status." C-m
done
```

Sends message to all agents matching pattern.

## Architecture

```
LLM Response
     ↓
XMLCommandParser → extract <agent>, <message>, <stop>, <capture>, <status>
     ↓
AgentOrchestrator → spawn/stop/message tmux sessions
     ↓
ActivityMonitor → poll panes, detect idle via MD5
     ↓
NudgeInjector → send completions back as user message
```

## Files

```
core/agents/
├── orchestrator.py     # spawn/stop/message agents
├── xml_parser.py       # parse XML from LLM response
├── activity_monitor.py # MD5 polling for completion
├── models.py           # AgentTask, AgentSession
└── nudge_system.py     # inject completions
```

## Config

```json
{
  "agents": {
    "enabled": true,
    "poll_interval": 2,
    "idle_threshold": 3,
    "capture_lines": 500,
    "max_concurrent": 10
  }
}
```

## System Prompt

```
You can spawn sub-agents for parallel work:

<agent>
  <name>
    <task>
    objective: What to accomplish
    context: Constraints and background
    todo:
    [ ] Step 1
    [ ] Step 2
    success: How to verify completion
    </task>
    <files>
      <file>path/to/file.py</file>
    </files>
  </name>
</agent>

<message to="name">Send instruction</message>
<capture>name 200</capture>
<stop>name</stop>
<status />

Responses:
  [spawned: name1, name2]
  [message sent: name]
  [capture: name @ duration, N lines] + output
  [stopped: name @ duration] + partial output
  [done: name @ duration] + output summary
  [agents] name status duration
```

## Implementation

1. **Parse** - Extract XML from LLM response
2. **Spawn** - Create tmux session (matches kollab-commands.sh):
   ```bash
   tmux new-session -d -s {project}-{name}
   sleep 1
   tmux send-keys -t {project}-{name} "kollab" C-m
   sleep 4  # init delay
   tmux send-keys -t {project}-{name} "{task + todo}" C-m
   ```
3. **Monitor** - Poll pane, hash content, detect idle
4. **Nudge** - On idle, capture output, inject completion to conversation
5. **Message** - `tmux send-keys -t {project}-{name} "message" C-m`
6. **Stop** - `tmux kill-session -t {project}-{name}`

That's it. Simple orchestration via XML.





  For kollab's architecture, option 4 fits best:

  ActivityMonitor (background task)
       │
       │ detects idle (3x same MD5 hash)
       │
       ▼
  EventBus.emit("agent_completed", {  // plugin-specific string literal
      name: "lint-fixer",
      duration: "2m34s",
      output: "last 500 lines..."
  })
       │
       ▼
  LLMService.on_agent_completed()
       │
       │ 1. format nudge message
       │ 2. append to conversation as "system" role
       │ 3. trigger LLM to continue
       │
       ▼
  LLM sees nudge, responds
  
  
  
  --sys_msg_start--
  terminal: bash_workspace running, to view: '<terminal>kollab --session bash_workspace --capture 200</terminal> '
  agent: explore_code working, to view agent will send <tcapture>explore_code</tcapture> '<terminal>kollab --session explore_code --capture 200</terminal>  For kollab's
  agent: explore_code working, to view '<terminal>kollab --session explore_code --msg "CRITICAL: FIX...."</terminal>  For kollab's
  
  --sys_msg_end--