# Sister Session — Detailed Design

Proactive autonomous work layer for Captain Claw. Each user session gets a paired "sister session" that independently investigates insights and intuitions, then reports findings back to the user.

---

## Table of Contents

- [Concept](#concept)
- [Architecture](#architecture)
- [Data Model](#data-model)
  - [ProactiveTask](#proactivetask)
  - [Briefing](#briefing)
  - [Session Linking](#session-linking)
  - [SQLite Schema](#sqlite-schema)
- [Task Sourcing](#task-sourcing)
  - [Automatic Triggers](#automatic-triggers)
  - [Manual Triggers](#manual-triggers)
  - [Trigger Evaluation](#trigger-evaluation)
- [Priority and Scoring](#priority-and-scoring)
- [Execution Engine](#execution-engine)
  - [Sister Agent Lifecycle](#sister-agent-lifecycle)
  - [Tool Restrictions](#tool-restrictions)
  - [Prompt Construction](#prompt-construction)
  - [Budget and Limits](#budget-and-limits)
  - [Execution Flow](#execution-flow)
- [Briefing Delivery](#briefing-delivery)
  - [Delivery Channels](#delivery-channels)
  - [Context Injection](#context-injection)
  - [WebSocket Notifications](#websocket-notifications)
  - [Briefing Lifecycle](#briefing-lifecycle)
- [Integration Points](#integration-points)
  - [Nervous System](#nervous-system-integration)
  - [Insights](#insights-integration)
  - [Cron System](#cron-system-integration)
  - [Execution Queue](#execution-queue-integration)
  - [Session Management](#session-management-integration)
- [User Interface](#user-interface)
  - [Briefing Page](#briefing-page)
  - [Slash Commands](#slash-commands)
  - [Home Page Card](#home-page-card)
  - [Computer / Chat Indicators](#computer--chat-indicators)
  - [Monitor Pane Integration](#monitor-pane-integration)
- [REST API](#rest-api)
- [Settings and Configuration](#settings-and-configuration)
- [Public Mode](#public-mode)
- [Implementation Plan](#implementation-plan)
  - [Phase 1 — Briefing Queue](#phase-1--briefing-queue)
  - [Phase 2 — Autonomous Execution](#phase-2--autonomous-execution)
  - [Phase 3 — Full UI and Delivery](#phase-3--full-ui-and-delivery)
- [File Inventory](#file-inventory)

---

## Concept

The sister session is not a chat session. It is a **work session** — it has no human interlocutor. It receives tasks generated by the system (from insights, intuitions, and user-pinned watches), executes them autonomously with a restricted toolset, and produces **briefings** that are delivered to the parent session.

The relationship is 1:1 — each user session has at most one sister session. The sister session is created lazily on the first proactive trigger and persists across server restarts (it's a real session in the database).

```
User Session "research-project"
    │
    ├── conversation messages (user ↔ agent)
    ├── insights extracted
    ├── intuitions dreamed
    │
    └── Sister Session "sister::research-project"
            │
            ├── proactive task queue
            ├── autonomous execution (read-only tools)
            └── briefing queue → delivered to parent
```

The metaphor: the user session is the conscious mind. The sister session is the assistant who quietly does homework in the background and leaves a note on your desk.

---

## Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                         Trigger Layer                               │
│                                                                     │
│  ┌────────────┐  ┌─────────────┐  ┌──────────┐  ┌──────────────┐  │
│  │ Intuitions │  │  Insights   │  │  Watches  │  │  Scheduler   │  │
│  │ (≥0.7 conf)│  │ (deadlines) │  │ (/watch)  │  │  (periodic)  │  │
│  └─────┬──────┘  └──────┬──────┘  └─────┬────┘  └──────┬───────┘  │
│        └────────────┬───┴──────────────┬─┘              │          │
│                     ▼                  ▼                 │          │
│              ┌──────────────┐   ┌───────────┐           │          │
│              │ Task Sourcer │   │ Watch Poll │◄──────────┘          │
│              └──────┬───────┘   └─────┬─────┘                      │
│                     └────────┬────────┘                             │
│                              ▼                                      │
└──────────────────────────────┼──────────────────────────────────────┘
                               │
┌──────────────────────────────┼──────────────────────────────────────┐
│                         Task Queue                                  │
│                              ▼                                      │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │  Priority Queue (SQLite)                                      │  │
│  │                                                               │  │
│  │  ┌──────┬──────────┬──────────┬────────┬──────────┬────────┐ │  │
│  │  │ pri  │ source   │ reason   │ status │ budget   │ parent │ │  │
│  │  ├──────┼──────────┼──────────┼────────┼──────────┼────────┤ │  │
│  │  │  9   │ insight  │ deadline │ queued │ 15000    │ ses_01 │ │  │
│  │  │  7   │ intuit.  │ hypothes │ queued │ 10000    │ ses_01 │ │  │
│  │  │  5   │ watch    │ periodic │ queued │ 8000     │ ses_02 │ │  │
│  │  └──────┴──────────┴──────────┴────────┴──────────┴────────┘ │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                              │                                      │
│                  Rate limit: 3/hour/session                         │
│                  Budget cap: 100k tokens/day                        │
└──────────────────────────────┼──────────────────────────────────────┘
                               │
┌──────────────────────────────┼──────────────────────────────────────┐
│                      Execution Engine                               │
│                              ▼                                      │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │  Sister Agent                                                 │  │
│  │  ├── Session: sister::research-project                       │  │
│  │  ├── Tools: read-only subset (search, fetch, read, memory)   │  │
│  │  ├── Max iterations: 3                                       │  │
│  │  ├── Token budget: per-task cap                              │  │
│  │  ├── Auto-approve all tools                                  │  │
│  │  └── No planning mode, no scale loop, no sub-orchestration   │  │
│  └───────────────────────────┬───────────────────────────────────┘  │
│                              │                                      │
│                              ▼                                      │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │  Task Result → Briefing                                       │  │
│  │  ├── summary (≤200 chars, for context note)                  │  │
│  │  ├── body (full markdown, for briefing page)                 │  │
│  │  ├── actionable (bool — does it suggest the user do something)│  │
│  │  ├── confidence (how certain is the finding)                 │  │
│  │  └── source_task_id                                          │  │
│  └───────────────────────────┬───────────────────────────────────┘  │
└──────────────────────────────┼──────────────────────────────────────┘
                               │
┌──────────────────────────────┼──────────────────────────────────────┐
│                      Delivery Layer                                 │
│                              ▼                                      │
│  ┌─────────────────┐  ┌─────────────────┐  ┌────────────────────┐  │
│  │ Context Note    │  │ WebSocket       │  │ Monitor Pane       │  │
│  │ (next user turn)│  │ Notification    │  │ (thinking line)    │  │
│  │                 │  │ (badge count)   │  │                    │  │
│  │ "Your sister    │  │ {type:          │  │ 📋 Briefing ready: │  │
│  │  session found  │  │  "briefing_     │  │ "Vendor deadline   │  │
│  │  X. /briefing"  │  │  ready",        │  │  passed — draft    │  │
│  │                 │  │  count: 3}      │  │  follow-up ready"  │  │
│  └─────────────────┘  └─────────────────┘  └────────────────────┘  │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │ /briefing command — review and act on briefings             │    │
│  │ /briefings page — full web dashboard                        │    │
│  └─────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘
```

---

## Data Model

### ProactiveTask

A task that the sister session should investigate.

```python
@dataclass
class ProactiveTask:
    id: str                         # UUID
    parent_session_id: str          # The user session that owns this task
    sister_session_id: str          # The sister session that will execute it
    source_type: str                # "intuition" | "insight" | "watch" | "deadline"
    source_id: str                  # ID of the triggering record
    trigger_reason: str             # Human-readable: "High-confidence hypothesis about X"
    prompt: str                     # The actual prompt sent to the sister agent
    status: str                     # "queued" | "running" | "done" | "failed" | "dismissed"
    priority: int                   # 1-10 (10 = most urgent)
    token_budget: int               # Max tokens for this task
    tokens_used: int                # Actual tokens consumed
    result_summary: str             # ≤200 chars for context note
    result_body: str                # Full markdown briefing
    actionable: bool                # Does the result suggest user action?
    confidence: float               # 0.0-1.0 — how confident is the finding
    created_at: str                 # ISO timestamp
    started_at: str | None          # When execution began
    completed_at: str | None        # When execution finished
    error: str | None               # Error message if failed
```

### Briefing

The user-facing output of a completed task.

```python
@dataclass
class Briefing:
    id: str                         # UUID (same as task ID for 1:1 mapping)
    parent_session_id: str          # Which user session to deliver to
    source_type: str                # From task
    trigger_reason: str             # From task
    summary: str                    # ≤200 chars
    body: str                       # Full markdown
    actionable: bool                # Suggests user action
    confidence: float               # Finding confidence
    status: str                     # "unread" | "read" | "dismissed" | "acted"
    created_at: str                 # When the briefing was produced
    read_at: str | None             # When user viewed it
    dismissed_at: str | None        # When user dismissed it
```

### Session Linking

Sister sessions are linked via the `metadata` field on both sessions:

```python
# On the user session:
session.metadata["sister_session_id"] = "uuid-of-sister"

# On the sister session:
session.metadata["is_sister"] = True
session.metadata["parent_session_id"] = "uuid-of-parent"
```

The sister session naming convention: `sister::{parent_session_name}`

This uses the existing `Session.metadata` dict — no schema changes needed to the sessions table.

### SQLite Schema

The task queue and briefings are stored in a dedicated SQLite database, separate from sessions. This follows the same pattern as `insights.db` and `intuitions.db`.

```sql
-- ~/.captain-claw/sister_sessions.db

CREATE TABLE IF NOT EXISTS proactive_tasks (
    id              TEXT PRIMARY KEY,
    parent_session_id TEXT NOT NULL,
    sister_session_id TEXT NOT NULL,
    source_type     TEXT NOT NULL,        -- intuition | insight | watch | deadline
    source_id       TEXT NOT NULL,
    trigger_reason  TEXT NOT NULL,
    prompt          TEXT NOT NULL,
    status          TEXT NOT NULL DEFAULT 'queued',
    priority        INTEGER NOT NULL DEFAULT 5,
    token_budget    INTEGER NOT NULL DEFAULT 10000,
    tokens_used     INTEGER NOT NULL DEFAULT 0,
    result_summary  TEXT NOT NULL DEFAULT '',
    result_body     TEXT NOT NULL DEFAULT '',
    actionable      INTEGER NOT NULL DEFAULT 0,
    confidence      REAL NOT NULL DEFAULT 0.0,
    created_at      TEXT NOT NULL,
    started_at      TEXT,
    completed_at    TEXT,
    error           TEXT
);

CREATE INDEX idx_tasks_parent_status ON proactive_tasks(parent_session_id, status);
CREATE INDEX idx_tasks_priority ON proactive_tasks(status, priority DESC, created_at ASC);
CREATE INDEX idx_tasks_source ON proactive_tasks(source_type, source_id);

CREATE TABLE IF NOT EXISTS briefings (
    id              TEXT PRIMARY KEY,      -- Same as task ID
    parent_session_id TEXT NOT NULL,
    source_type     TEXT NOT NULL,
    trigger_reason  TEXT NOT NULL,
    summary         TEXT NOT NULL,
    body            TEXT NOT NULL,
    actionable      INTEGER NOT NULL DEFAULT 0,
    confidence      REAL NOT NULL DEFAULT 0.0,
    status          TEXT NOT NULL DEFAULT 'unread',
    created_at      TEXT NOT NULL,
    read_at         TEXT,
    dismissed_at    TEXT
);

CREATE INDEX idx_briefings_parent_status ON briefings(parent_session_id, status);
CREATE INDEX idx_briefings_unread ON briefings(parent_session_id, status) WHERE status = 'unread';

CREATE TABLE IF NOT EXISTS watches (
    id              TEXT PRIMARY KEY,
    parent_session_id TEXT NOT NULL,
    query           TEXT NOT NULL,          -- What to watch for
    check_type      TEXT NOT NULL DEFAULT 'search',  -- search | fetch | memory
    check_target    TEXT NOT NULL DEFAULT '',         -- URL, search query, etc.
    interval_seconds INTEGER NOT NULL DEFAULT 3600,   -- Check every N seconds
    last_checked_at TEXT,
    next_check_at   TEXT NOT NULL,
    enabled         INTEGER NOT NULL DEFAULT 1,
    created_at      TEXT NOT NULL,
    metadata        TEXT NOT NULL DEFAULT '{}'        -- JSON for extra params
);

CREATE INDEX idx_watches_next ON watches(enabled, next_check_at);

-- Daily budget tracking
CREATE TABLE IF NOT EXISTS daily_budget (
    date            TEXT NOT NULL,          -- YYYY-MM-DD
    session_id      TEXT NOT NULL,          -- Parent session ID
    tokens_used     INTEGER NOT NULL DEFAULT 0,
    tasks_run       INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (date, session_id)
);
```

---

## Task Sourcing

### Automatic Triggers

Tasks are sourced from four channels. Each trigger runs as part of existing post-turn hooks, never as a separate polling loop.

#### 1. High-Confidence Intuitions

When the nervous system produces an intuition during a dream cycle:

```python
# In nervous_system.py, after storing an intuition:
if intuition["confidence"] >= 0.7 and intuition["thread_type"] == "hypothesis":
    await maybe_create_proactive_task(
        parent_session_id=agent.session.id,
        source_type="intuition",
        source_id=intuition["id"],
        trigger_reason=f"High-confidence hypothesis: {intuition['content'][:80]}",
        priority=_score_intuition_priority(intuition),
    )
```

**Which intuitions trigger tasks:**
- `thread_type == "hypothesis"` with `confidence >= 0.7` — always (hypotheses are investigatable)
- `thread_type == "connection"` with `confidence >= 0.8` — only if tagged `actionable`
- `thread_type == "pattern"` with `importance >= 8` — patterns worth validating
- `thread_type == "association"` — never (associations are contextual, not investigatable)

#### 2. Deadline-Approaching Insights

When insight extraction runs, check for time-sensitive insights:

```python
# In insights.py, after storing insights:
for insight in stored:
    if insight.get("category") == "deadline":
        deadline_date = _parse_deadline(insight["content"])
        if deadline_date and (deadline_date - now).days <= 2:
            await maybe_create_proactive_task(
                parent_session_id=agent.session.id,
                source_type="deadline",
                source_id=insight["id"],
                trigger_reason=f"Deadline approaching: {insight['content'][:80]}",
                priority=9,  # Deadlines are always high priority
            )
```

#### 3. Watch-Triggered Tasks

The `/watch` command lets users explicitly request periodic checks:

```
/watch "check if PR #123 is merged" every 30m
/watch "monitor HN front page for our launch" every 1h
/watch "check if vendor replied to email" every 2h
```

Watches are polled by the sister session scheduler (piggybacks on the cron scheduler loop — see [Cron System Integration](#cron-system-integration)).

#### 4. Proactive Follow-ups (Phase 3)

In later phases, the system can generate tasks from conversation analysis:

- User discussed a topic but didn't reach a conclusion → "Research X further"
- User asked a question the agent couldn't fully answer → "Find more about Y"
- Multiple sessions reference the same problem → "Synthesize approaches to Z"

This requires an LLM call to evaluate conversation endings, so it's deferred to Phase 3.

### Manual Triggers

Users can also manually create tasks:

```
/sister investigate "what are the alternatives to Typesense for hybrid search?"
/sister research "compare pricing of AWS vs GCP for our workload"
/sister check "is the staging deployment healthy?"
```

Manual tasks bypass all confidence/priority filters and execute immediately.

### Trigger Evaluation

Not every potential trigger becomes a task. The `maybe_create_proactive_task` function applies filters:

```python
async def maybe_create_proactive_task(
    parent_session_id: str,
    source_type: str,
    source_id: str,
    trigger_reason: str,
    priority: int = 5,
) -> str | None:
    """Create a proactive task if budget/dedup/limits allow. Returns task ID or None."""
    mgr = get_sister_session_manager()

    # 1. Dedup — don't create duplicate tasks for the same source
    existing = await mgr.find_task_by_source(source_type, source_id)
    if existing and existing.status in ("queued", "running", "done"):
        return None

    # 2. Rate limit — max tasks per hour per session
    cfg = get_config().sister_session
    recent_count = await mgr.count_recent_tasks(
        parent_session_id,
        since_seconds=3600,
    )
    if recent_count >= cfg.max_tasks_per_hour:
        log.info("Sister session rate limit hit", session=parent_session_id)
        return None

    # 3. Daily budget — check token budget
    today = date.today().isoformat()
    budget = await mgr.get_daily_budget(today, parent_session_id)
    if budget.tokens_used >= cfg.max_tokens_per_day:
        log.info("Sister session daily budget exhausted", session=parent_session_id)
        return None

    # 4. Queue depth — don't pile up too many tasks
    queued_count = await mgr.count_tasks(parent_session_id, status="queued")
    if queued_count >= cfg.max_queued_tasks:
        return None

    # 5. Build the investigation prompt
    prompt = await _build_investigation_prompt(
        source_type=source_type,
        source_id=source_id,
        trigger_reason=trigger_reason,
        parent_session_id=parent_session_id,
    )

    # 6. Create the task
    task_id = await mgr.create_task(
        parent_session_id=parent_session_id,
        source_type=source_type,
        source_id=source_id,
        trigger_reason=trigger_reason,
        prompt=prompt,
        priority=priority,
        token_budget=cfg.default_token_budget,
    )

    # 7. Schedule execution
    asyncio.create_task(_execute_next_task(parent_session_id))

    return task_id
```

---

## Priority and Scoring

Tasks are executed in priority order. Priority is a 1-10 integer, calculated from the source:

| Source | Base Priority | Modifiers |
|---|---|---|
| Deadline (≤24h) | 10 | — |
| Deadline (≤48h) | 9 | — |
| Manual (`/sister`) | 8 | — |
| Watch (overdue) | 7 | +1 if previously found changes |
| Intuition (hypothesis, conf ≥ 0.9) | 7 | +1 if validated intuition |
| Intuition (hypothesis, conf ≥ 0.7) | 6 | — |
| Intuition (pattern, imp ≥ 8) | 5 | — |
| Intuition (connection, conf ≥ 0.8) | 4 | — |
| Watch (routine check) | 3 | — |

Within the same priority, tasks are ordered by creation time (FIFO).

---

## Execution Engine

### Sister Agent Lifecycle

```python
async def _get_or_create_sister_agent(
    parent_session_id: str,
    provider: LLMProvider,
) -> Agent:
    """Get or create the sister agent for a user session."""
    mgr = get_sister_session_manager()

    # 1. Find or create the sister session
    sister_session_id = await mgr.get_sister_session_id(parent_session_id)

    if not sister_session_id:
        parent_session = await session_manager.load_session(parent_session_id)
        parent_name = parent_session.name if parent_session else "unnamed"

        sister_session = await session_manager.create_session(
            name=f"sister::{parent_name}",
            metadata={
                "is_sister": True,
                "parent_session_id": parent_session_id,
            },
        )

        # Link parent → sister
        if parent_session:
            parent_session.metadata["sister_session_id"] = sister_session.id
            await session_manager.save_session(parent_session)

        sister_session_id = sister_session.id
        await mgr.set_sister_session_id(parent_session_id, sister_session_id)

    # 2. Create agent (not pooled — one per execution, lightweight)
    agent = Agent(
        provider=provider,
        approval_callback=lambda msg: True,  # Auto-approve everything
    )

    session = await session_manager.load_session(sister_session_id)
    agent.session = session
    agent.session_manager = session_manager

    # 3. Restrict capabilities
    agent._is_sister = True
    agent.max_iterations = 3
    agent.planning_enabled = False
    agent._skip_deferred_scale = True

    # 4. Register ONLY read-only tools
    _register_sister_tools(agent)

    # 5. Load instructions (shared from main agent)
    agent.instructions = _load_sister_instructions()

    agent._initialized = True
    return agent
```

### Tool Restrictions

The sister agent gets a curated subset of read-only tools:

```python
def _register_sister_tools(agent: Agent) -> None:
    """Register only safe, read-only tools for the sister agent."""
    # Information gathering
    agent.tools.register(WebSearchTool())       # Search the web
    agent.tools.register(WebFetchTool())        # Fetch a URL
    agent.tools.register(WebGetTool())          # GET request
    agent.tools.register(ReadTool())            # Read local files
    agent.tools.register(GlobTool())            # Find files

    # Memory access (read-only)
    agent.tools.register(InsightsTool(read_only=True))    # Search insights
    agent.tools.register(TypesenseTool(search_only=True)) # Search deep memory

    # EXCLUDED (destructive or expensive):
    # ShellTool, WriteTool, EditTool, ImageGenTool, SendMailTool,
    # DatastoreTool (write), TodoTool (write), BotportTool,
    # DesktopActionTool, ScreenCaptureTool, PersonalityTool
```

**Why these tools:**
- `web_search` + `web_fetch` — the primary investigation tools. Most proactive tasks require web research.
- `read` + `glob` — access local files for context (project files, saved outputs).
- `insights` (read-only) — search the knowledge base without modifying it.
- `typesense` (search-only) — access deep memory archive.

**Why not shell?** Too dangerous for unsupervised execution. Even `curl` or `ls` could be fine, but the risk of `rm`, `git push`, or network-modifying commands is too high for autonomous work.

### Prompt Construction

Each task gets a carefully structured prompt that explains the task, provides context, and constrains the output:

```python
async def _build_investigation_prompt(
    source_type: str,
    source_id: str,
    trigger_reason: str,
    parent_session_id: str,
) -> str:
    """Build the prompt that the sister agent will execute."""
    # Load the source record
    source_content = await _load_source_content(source_type, source_id)

    # Load recent parent session context (last 5 messages for relevance)
    parent_session = await session_manager.load_session(parent_session_id)
    recent_context = _extract_recent_context(parent_session, max_messages=5)

    # Load relevant insights and intuitions for additional context
    insights_text = await _load_relevant_insights(parent_session_id, limit=5)
    intuitions_text = await _load_relevant_intuitions(parent_session_id, limit=3)

    # Render the investigation prompt template
    return instructions.render(
        "sister_investigation_prompt.md",
        source_type=source_type,
        trigger_reason=trigger_reason,
        source_content=source_content,
        recent_context=recent_context,
        insights_text=insights_text,
        intuitions_text=intuitions_text,
    )
```

**Investigation prompt template** (`instructions/sister_investigation_prompt.md`):

```markdown
You are investigating a lead on behalf of the user. You are running autonomously
in a sister session — the user is not present and will read your findings later.

## Your Task

**Source:** {source_type}
**Reason:** {trigger_reason}

{source_content}

## Context from the User's Session

{recent_context}

## Related Knowledge

{insights_text}

{intuitions_text}

## Instructions

1. Investigate the lead using your available tools (web search, file reading,
   memory search).
2. Be thorough but concise — you have a limited token budget.
3. Focus on **actionable findings** — what should the user know or do?
4. If the lead turns out to be a dead end, say so clearly.
5. Do NOT perform any actions (sending emails, modifying files, etc.) — only
   research and report.

## Output Format

End your response with a structured summary block:

```json
{
  "summary": "One-sentence finding (≤200 chars)",
  "actionable": true/false,
  "confidence": 0.0-1.0,
  "tags": ["tag1", "tag2"]
}
```
```

### Budget and Limits

Token budget is enforced at the agent level:

```python
class SisterSessionConfig(BaseModel):
    """Sister session — proactive autonomous work."""

    enabled: bool = False
    max_tasks_per_hour: int = 3           # Per parent session
    max_tokens_per_day: int = 100000      # Per parent session
    default_token_budget: int = 15000     # Per task
    max_queued_tasks: int = 10            # Per parent session
    max_iterations: int = 3               # Agent loop iterations per task
    auto_trigger_intuitions: bool = True  # Auto-create tasks from intuitions
    auto_trigger_deadlines: bool = True   # Auto-create tasks from deadlines
    auto_trigger_watches: bool = True     # Auto-poll watches
    watch_poll_seconds: int = 60          # How often to check for due watches
    briefing_inject_in_context: bool = True  # Inject unread briefing notes
    max_briefings_in_context: int = 2     # Max briefing notes per turn
    allow_public: bool = False            # Disabled in public mode
    db_path: str = "~/.captain-claw/sister_sessions.db"
```

The agent's `complete()` call is wrapped with token tracking:

```python
# In the execution function:
response = await agent.complete(task.prompt)

# After completion, record token usage
tokens_used = _count_response_tokens(response)
await mgr.update_task(task.id, tokens_used=tokens_used)
await mgr.increment_daily_budget(today, parent_session_id, tokens_used)
```

If the daily budget is exhausted mid-task, the task is marked `failed` with `error="daily_budget_exhausted"` and remaining queued tasks are skipped until the next day.

### Execution Flow

```python
async def _execute_next_task(parent_session_id: str) -> None:
    """Execute the next queued task for a parent session."""
    mgr = get_sister_session_manager()
    cfg = get_config().sister_session

    if not cfg.enabled:
        return

    # 1. Check rate limit
    recent = await mgr.count_recent_tasks(parent_session_id, since_seconds=3600)
    if recent >= cfg.max_tasks_per_hour:
        return

    # 2. Check daily budget
    today = date.today().isoformat()
    budget = await mgr.get_daily_budget(today, parent_session_id)
    if budget.tokens_used >= cfg.max_tokens_per_day:
        return

    # 3. Get next task by priority
    task = await mgr.get_next_task(parent_session_id)
    if not task:
        return

    # 4. Mark as running
    await mgr.update_task(task.id, status="running", started_at=_utcnow_iso())

    try:
        # 5. Create the sister agent
        provider = _get_current_provider()  # Share the main agent's provider
        agent = await _get_or_create_sister_agent(parent_session_id, provider)

        # 6. Execute
        response = await asyncio.wait_for(
            agent.complete(task.prompt),
            timeout=120.0,  # 2-minute hard timeout
        )

        # 7. Parse the result
        result_text = str(response)
        summary, actionable, confidence, tags = _parse_investigation_result(result_text)

        # 8. Store briefing
        await mgr.create_briefing(
            id=task.id,
            parent_session_id=parent_session_id,
            source_type=task.source_type,
            trigger_reason=task.trigger_reason,
            summary=summary,
            body=result_text,
            actionable=actionable,
            confidence=confidence,
        )

        # 9. Update task
        tokens_used = _count_response_tokens(response)
        await mgr.update_task(
            task.id,
            status="done",
            completed_at=_utcnow_iso(),
            result_summary=summary,
            result_body=result_text,
            actionable=actionable,
            confidence=confidence,
            tokens_used=tokens_used,
        )
        await mgr.increment_daily_budget(today, parent_session_id, tokens_used)

        # 10. Notify via WebSocket
        await _notify_briefing_ready(parent_session_id, summary)

        # 11. Log to sister session history
        agent.session.add_message(
            role="tool",
            content=f"[SISTER] Task completed: {summary}",
            tool_name="sister_session",
        )
        await agent.session_manager.save_session(agent.session)

    except asyncio.TimeoutError:
        await mgr.update_task(task.id, status="failed", error="timeout")
    except Exception as exc:
        await mgr.update_task(task.id, status="failed", error=str(exc)[:500])

    # 12. Check if there are more tasks to run
    remaining = await mgr.count_tasks(parent_session_id, status="queued")
    if remaining > 0:
        # Schedule next task after a brief pause (don't hog the event loop)
        await asyncio.sleep(5)
        asyncio.create_task(_execute_next_task(parent_session_id))
```

---

## Briefing Delivery

### Delivery Channels

Briefings are delivered through multiple channels, from passive to active:

| Channel | When | Intrusiveness | User Action |
|---|---|---|---|
| Context note | Next user turn | Passive | None — appears in agent's response naturally |
| Monitor pane | Immediately | Low | Visible in background pane |
| WebSocket notification | Immediately | Medium | Badge count updates |
| `/briefing` command | On demand | None | User pulls when ready |
| Briefings page | On demand | None | Full dashboard view |

### Context Injection

When the user sends their next message, unread briefings are injected as context notes — the same pattern used by insights and the nervous system:

```python
# In agent_context_mixin.py:

def _build_briefing_context_note(self) -> str:
    """Build context note from unread briefings."""
    cfg = get_config()
    if not cfg.sister_session.enabled or not cfg.sister_session.briefing_inject_in_context:
        return ""

    items = getattr(self, "_briefing_context_cache", None)
    if not items:
        return ""

    lines = ["Your sister session has findings ready:"]
    for b in items[:cfg.sister_session.max_briefings_in_context]:
        icon = "⚡" if b["actionable"] else "📋"
        lines.append(f"- {icon} [{b['source_type']}] {b['summary']}")
    lines.append('The user can review details with /briefing.')
    return "\n".join(lines)
```

This means the agent will **naturally reference** the findings in its response: "By the way, my sister session looked into X and found Y. You can run `/briefing` for the full details."

### WebSocket Notifications

When a briefing is created, a WebSocket message is sent to the parent session:

```python
async def _notify_briefing_ready(parent_session_id: str, summary: str) -> None:
    """Send WebSocket notification to the parent session."""
    # Count unread briefings
    mgr = get_sister_session_manager()
    unread = await mgr.count_briefings(parent_session_id, status="unread")

    # Broadcast to all clients connected to this session
    msg = {
        "type": "briefing_ready",
        "session_id": parent_session_id,
        "summary": summary,
        "unread_count": unread,
    }
    _broadcast_to_session(parent_session_id, msg)
```

**Client-side handling** (in `computer.js` / `chat.js`):

```javascript
case "briefing_ready":
    // Update badge count
    updateBriefingBadge(data.unread_count);
    // Optional toast notification
    showToast(`📋 ${data.summary}`, { duration: 5000, action: '/briefing' });
    break;
```

### Briefing Lifecycle

```
                      ┌──────────┐
                      │  unread  │◄── Created after task completion
                      └────┬─────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌───────────┐
        │   read   │ │ dismissed│ │   acted   │
        │          │ │          │ │           │
        │ User     │ │ User     │ │ User ran  │
        │ viewed   │ │ swiped   │ │ the       │
        │ /briefing│ │ away     │ │ suggested │
        │ detail   │ │          │ │ action    │
        └──────────┘ └──────────┘ └───────────┘
```

- **unread** → delivered, not yet seen by user
- **read** → user viewed the briefing (via `/briefing` or the web page)
- **dismissed** → user explicitly dismissed it (won't appear in context notes again)
- **acted** → user followed through on the suggestion (tracked for relevance scoring)

---

## Integration Points

### Nervous System Integration

The nervous system's `dream()` function is the primary automatic trigger:

```python
# In nervous_system.py, at the end of dream():

# After storing intuitions, check if any should trigger proactive work
if cfg.sister_session.enabled and cfg.sister_session.auto_trigger_intuitions:
    for intuition in stored:
        if _should_trigger_task(intuition):
            asyncio.create_task(
                maybe_create_proactive_task(
                    parent_session_id=str(agent.session.id),
                    source_type="intuition",
                    source_id=intuition["id"],
                    trigger_reason=_format_intuition_reason(intuition),
                    priority=_score_intuition_priority(intuition),
                )
            )
```

### Insights Integration

Insight extraction checks for deadline-approaching entries:

```python
# In insights.py, at the end of extract_insights():

if cfg.sister_session.enabled and cfg.sister_session.auto_trigger_deadlines:
    for insight in stored:
        if _is_approaching_deadline(insight):
            asyncio.create_task(
                maybe_create_proactive_task(
                    parent_session_id=str(agent.session.id),
                    source_type="deadline",
                    source_id=str(insight["id"]),
                    trigger_reason=f"Deadline approaching: {insight['content'][:80]}",
                    priority=9,
                )
            )
```

### Cron System Integration

Watch polling piggybacks on the existing cron scheduler loop rather than creating a new one:

```python
# In cron_dispatch.py, inside cron_scheduler_loop():

# After polling cron jobs, also poll watches
if cfg.sister_session.enabled and cfg.sister_session.auto_trigger_watches:
    try:
        from captain_claw.sister_session import poll_due_watches
        await poll_due_watches(ctx)
    except Exception:
        pass
```

This ensures watches use the same scheduling infrastructure as cron jobs — same poll interval, same error handling, same lifecycle.

### Execution Queue Integration

Sister session tasks run on a new dedicated lane:

```python
# In execution_queue.py:
class CommandLane:
    MAIN = "main"
    CRON = "cron"
    SUBAGENT = "subagent"
    NESTED = "nested"
    AGENT_RUNTIME = "agent_runtime"
    SISTER = "sister"              # ← New lane
```

The `SISTER` lane is independent from `MAIN` and `CRON`, meaning sister session work never blocks user interactions or cron jobs.

### Session Management Integration

Sister sessions appear in the session list with a special marker:

```python
# When listing sessions (in rest_sessions.py):
for s in sessions:
    s_dict = s.to_dict()
    if s.metadata.get("is_sister"):
        s_dict["is_sister"] = True
        s_dict["parent_session_id"] = s.metadata.get("parent_session_id")
    result.append(s_dict)
```

The sessions page shows sister sessions with a distinct icon/tag so they're visible but clearly not user-created.

---

## User Interface

### Briefing Page

New web page at `/briefings` following the existing dashboard page pattern:

**Layout:**
```
┌─────────────────────────────────────────────────────────────┐
│  📋 Briefings                        [← Home] [Chat] [Cmp] │
├─────────────────────────────────────────────────────────────┤
│  Stats: 3 unread │ 12 total │ 8 actionable │ 45k tokens    │
├─────────────────────────────────────────────────────────────┤
│  Filter: [All ▾] [Unread ▾] [Source ▾]        [🔍 Search ] │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ ⚡ Deadline approaching                    ● unread │    │
│  │ "Vendor proposal due Friday — no activity detected" │    │
│  │ Source: insight #42 │ Confidence: 0.85 │ 2h ago     │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 📋 Hypothesis investigated                 ● unread │    │
│  │ "Typesense supports hybrid search natively since…"  │    │
│  │ Source: intuition #7 │ Confidence: 0.72 │ 5h ago    │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 📋 Watch result                              read  │    │
│  │ "PR #123 still open — CI passing, 1 review pending" │    │
│  │ Source: watch │ Confidence: 0.95 │ 1d ago           │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

**Detail Modal:**
- Full markdown body rendered
- Dismiss / Mark as Acted buttons
- Link to source intuition/insight
- Token usage for the investigation
- Sister session conversation log link

### Slash Commands

```
/briefing                    — List unread briefings (summary view)
/briefing all                — List all briefings including read/dismissed
/briefing <id>               — Show full briefing detail
/briefing dismiss <id>       — Dismiss a briefing
/briefing dismiss all        — Dismiss all unread briefings

/sister investigate <query>  — Manually trigger an investigation
/sister status               — Show sister session status and stats
/sister pause                — Pause proactive work for this session
/sister resume               — Resume proactive work

/watch <query> [every <interval>]  — Add a watch
/watch list                        — List active watches
/watch delete <id>                 — Remove a watch
```

### Home Page Card

```html
<a href="/briefings" class="home-card">
    <div class="home-card-icon">📋</div>
    <div class="home-card-title">Briefings</div>
    <div class="home-card-desc">Review findings from proactive investigations.
    Your sister sessions autonomously research leads from insights and dreams.</div>
</a>
```

### Computer / Chat Indicators

**Briefing badge** — a small counter badge near the chat input or in the toolbar:

```html
<span class="briefing-badge" id="briefingBadge" style="display:none">0</span>
```

Updated via WebSocket `briefing_ready` messages. Clicking opens `/briefing` inline or navigates to `/briefings`.

**Sister session activity indicator** — a subtle animated dot in the status bar when the sister session is actively working:

```html
<span class="sister-indicator" title="Sister session working…">
    <span class="sister-dot"></span>
</span>
```

### Monitor Pane Integration

Sister session activity appears in the monitor pane with a distinct prefix:

```
[sister] 📋 Investigating: "Typesense hybrid search capabilities"
[sister] 🔍 web_search("typesense hybrid search BM25 vector")
[sister] 📄 web_fetch("https://typesense.org/docs/...")
[sister] ✅ Briefing ready: "Typesense supports hybrid search natively…"
```

This uses the existing thinking callback pattern but with the `[sister]` prefix so users can distinguish sister session activity from their own session's work.

---

## REST API

```
GET    /api/briefings                      — List briefings (params: status, source_type, q, limit)
GET    /api/briefings/stats                — Unread count, total, tokens used today
GET    /api/briefings/{id}                 — Get full briefing
PATCH  /api/briefings/{id}                 — Update status (read, dismissed, acted)
DELETE /api/briefings/{id}                 — Delete a briefing

GET    /api/sister/tasks                   — List proactive tasks (params: status, limit)
GET    /api/sister/tasks/{id}              — Get task detail
POST   /api/sister/tasks                   — Manually create a task
DELETE /api/sister/tasks/{id}              — Cancel/delete a task

GET    /api/sister/watches                 — List watches
POST   /api/sister/watches                 — Create a watch
PATCH  /api/sister/watches/{id}            — Update a watch
DELETE /api/sister/watches/{id}            — Delete a watch

GET    /api/sister/stats                   — Overall stats (tasks/day, tokens/day, budget remaining)
POST   /api/sister/pause                   — Pause proactive work
POST   /api/sister/resume                  — Resume proactive work
```

---

## Settings and Configuration

Added to the Settings UI under the "Features" group, after "Nervous System":

```python
# In rest_settings.py, _build_schema():

{
    "key": "sister_session",
    "title": "Sister Session (Proactive Work)",
    "description": "Autonomous background investigations triggered by insights and dreams",
    "fields": [
        {"key": "enabled", "type": "toggle", "label": "Enable sister sessions",
         "description": "Allow proactive autonomous work in background sessions"},
        {"key": "auto_trigger_intuitions", "type": "toggle",
         "label": "Auto-trigger from intuitions",
         "description": "Automatically investigate high-confidence hypotheses"},
        {"key": "auto_trigger_deadlines", "type": "toggle",
         "label": "Auto-trigger from deadlines",
         "description": "Automatically check on approaching deadlines"},
        {"key": "auto_trigger_watches", "type": "toggle",
         "label": "Auto-poll watches",
         "description": "Periodically check user-defined watch queries"},
        {"key": "max_tasks_per_hour", "type": "number", "label": "Max tasks/hour",
         "min": 1, "max": 10},
        {"key": "max_tokens_per_day", "type": "number", "label": "Daily token budget",
         "min": 10000, "max": 500000, "step": 10000},
        {"key": "default_token_budget", "type": "number", "label": "Per-task token budget",
         "min": 5000, "max": 50000, "step": 1000},
        {"key": "max_queued_tasks", "type": "number", "label": "Max queued tasks",
         "min": 1, "max": 50},
        {"key": "max_iterations", "type": "number", "label": "Max agent iterations",
         "min": 1, "max": 10},
        {"key": "briefing_inject_in_context", "type": "toggle",
         "label": "Inject briefings in context",
         "description": "Show unread briefing summaries in the agent's context"},
        {"key": "max_briefings_in_context", "type": "number",
         "label": "Max briefings in context", "min": 1, "max": 5},
    ],
}
```

---

## Public Mode

Sister sessions are **disabled in public mode by default** (`allow_public: false`). The reasons:

1. **Cost** — public users would consume server tokens for autonomous work
2. **Abuse** — a public user could trigger expensive research loops
3. **Session isolation** — sister sessions bleeding across public users would be a privacy issue

If `allow_public` is enabled, sister sessions are fully session-scoped (same pattern as insights/nervous system in public mode):

```python
if cfg.web.public_run and not cfg.sister_session.allow_public:
    return  # Silently skip

if cfg.web.public_run:
    mgr = get_session_sister_manager(session_id)  # Per-session DB
else:
    mgr = get_sister_session_manager()  # Global DB
```

---

## Implementation Plan

### Phase 1 — Briefing Queue

**Goal:** Validate that the system can identify useful proactive tasks before investing in autonomous execution.

**What gets built:**
1. `SisterSessionConfig` in `config.py`
2. `sister_session.py` — `SisterSessionManager` with SQLite schema (tasks, briefings, watches, budget)
3. Integration hooks in `nervous_system.py` and `insights.py` that **create tasks but don't execute them**
4. Tasks are shown as "suggested investigations" via `/briefing` command
5. User can manually trigger execution: `/briefing run <id>` → runs in the current session (not a sister)
6. Settings UI section
7. REST API for tasks and briefings (CRUD)

**What's NOT built yet:**
- No automatic execution
- No sister agent creation
- No WebSocket notifications
- No web page
- No watches

**Files:**
- `captain_claw/config.py` — Add `SisterSessionConfig`
- `captain_claw/sister_session.py` — Manager class (~300 lines)
- `captain_claw/nervous_system.py` — Add task creation hook
- `captain_claw/insights.py` — Add deadline detection hook
- `captain_claw/web/rest_sister_session.py` — REST handlers
- `captain_claw/web/slash_commands.py` — `/briefing`, `/sister` commands
- `captain_claw/web_server.py` — Route registration, COMMANDS
- `captain_claw/web/rest_settings.py` — Settings schema
- `captain_claw/instructions/sister_investigation_prompt.md` — Investigation prompt

### Phase 2 — Autonomous Execution

**Goal:** Add the actual sister agent that executes tasks autonomously.

**What gets built:**
1. Sister agent creation with tool restrictions
2. Automatic task execution via `asyncio.create_task`
3. Execution queue `SISTER` lane
4. Token budget enforcement
5. Result parsing and briefing creation
6. WebSocket `briefing_ready` notification
7. Context injection (`_build_briefing_context_note`)
8. Monitor pane integration (`[sister]` prefix)
9. `/watch` command with cron integration

**Files:**
- `captain_claw/sister_session.py` — Add execution engine (~200 more lines)
- `captain_claw/agent_context_mixin.py` — Add briefing cache and context note
- `captain_claw/instructions/system_prompt.md` — Add `{briefing_block}` placeholder
- `captain_claw/instructions/micro_system_prompt.md` — Same
- `captain_claw/execution_queue.py` — Add `SISTER` lane
- `captain_claw/cron_dispatch.py` — Add watch polling hook
- `captain_claw/web/ws_handler.py` — Handle `briefing_ready` messages

### Phase 3 — Full UI and Delivery

**Goal:** Complete web experience and advanced triggers.

**What gets built:**
1. Briefings web page (`/briefings`)
2. Home page card
3. Computer/Chat briefing badge
4. Sister session activity indicator
5. Briefing detail modal with markdown rendering
6. Sister session conversation log viewing
7. Proactive follow-up triggers (conversation analysis)
8. Session list sister markers
9. Public mode support (if enabled)

**Files:**
- `captain_claw/web/static/briefings.html` — Dashboard page
- `captain_claw/web/static/briefings.js` — JavaScript
- `captain_claw/web/static/briefings.css` — Styles
- `captain_claw/web/static/home.html` — Add card
- `captain_claw/web/static/computer.html` — Badge element
- `captain_claw/web/static/computer.js` — Badge handling
- `captain_claw/web/static/computer.css` — Badge/indicator styles
- `captain_claw/web/static_pages.py` — `serve_briefings`
- `captain_claw/web/public_auth.py` — Add routes if `allow_public`

---

## File Inventory

Complete list of files that will be created or modified:

### New Files

| File | Phase | Description |
|---|---|---|
| `captain_claw/sister_session.py` | 1+2 | Core module: manager, task sourcing, execution engine |
| `captain_claw/web/rest_sister_session.py` | 1 | REST API handlers |
| `captain_claw/instructions/sister_investigation_prompt.md` | 1 | Investigation prompt template |
| `captain_claw/instructions/sister_system_prompt.md` | 2 | Sister agent system prompt |
| `captain_claw/web/static/briefings.html` | 3 | Briefings dashboard page |
| `captain_claw/web/static/briefings.js` | 3 | Briefings dashboard JavaScript |
| `captain_claw/web/static/briefings.css` | 3 | Briefings dashboard styles |

### Modified Files

| File | Phase | Changes |
|---|---|---|
| `captain_claw/config.py` | 1 | Add `SisterSessionConfig`, field on `Config` |
| `captain_claw/nervous_system.py` | 1 | Add task creation hook after dream |
| `captain_claw/insights.py` | 1 | Add deadline detection hook after extraction |
| `captain_claw/web/slash_commands.py` | 1 | Add `/briefing`, `/sister`, `/watch` routing |
| `captain_claw/web_server.py` | 1 | Add REST wrappers, routes, COMMANDS |
| `captain_claw/web/rest_settings.py` | 1 | Add sister_session settings section |
| `captain_claw/execution_queue.py` | 2 | Add `SISTER` lane |
| `captain_claw/agent_context_mixin.py` | 2 | Add briefing cache, context note, system prompt block |
| `captain_claw/instructions/system_prompt.md` | 2 | Add `{briefing_block}` placeholder |
| `captain_claw/instructions/micro_system_prompt.md` | 2 | Add `{briefing_block}` placeholder |
| `captain_claw/cron_dispatch.py` | 2 | Add watch polling in scheduler loop |
| `captain_claw/web/ws_handler.py` | 2 | Handle sister session WebSocket events |
| `captain_claw/web/static/home.html` | 3 | Add Briefings card |
| `captain_claw/web/static/computer.html` | 3 | Add badge element |
| `captain_claw/web/static/computer.js` | 3 | Add badge handler, briefing_ready case |
| `captain_claw/web/static/computer.css` | 3 | Badge and indicator styles |
| `captain_claw/web/static_pages.py` | 3 | Add `serve_briefings` |
| `captain_claw/web/public_auth.py` | 3 | Add routes to `_SECTION_ROUTES` |
