Skip to content

Architecture

Marty McEnroe edited this page Feb 15, 2026 · 2 revisions

Architecture

Unleashed wraps Claude Code in a pseudo-terminal (PTY), intercepts its output stream, and auto-approves permission prompts. The architecture is driven by one constraint: the PTY reader thread must never block.

Three-Thread Design

flowchart TD
    subgraph THREADS ["Unleashed Process"]
        direction TB
        T1["Thread 1: Stdin Reader<br/>(keyboard → PTY)"]
        T2["Thread 2: PTY Reader<br/>(PTY → terminal + detection)"]
        T3["Thread 3: Resize Monitor<br/>(terminal size → PTY)"]
        T4["Worker Thread<br/>(sentinel API call)"]
    end

    KB["Keyboard"] -->|"msvcrt / Win32 API"| T1
    T1 -->|"pty.write()"| PTY["pywinpty PTY"]
    PTY -->|"pty.read(8192)"| T2
    T2 -->|"sys.stdout"| TERM["Terminal"]
    T2 -->|"spawn on permission"| T4
    T4 -->|"pty.write(CR)"| PTY
    T3 -->|"pty.setwinsize()"| PTY

    style T2 fill:#f9d71c,stroke:#333,color:#000
    style T4 fill:#4ade80,stroke:#333,color:#000
Loading
Thread Role Lifetime Failure Mode
t1: Stdin Reader Reads keyboard input, maps Windows key codes to ANSI sequences, writes to PTY Entire session Bare except: break — silent death (#42)
t2: PTY Reader Reads PTY output, passes through to terminal, detects permissions, manages mirror Entire session Logged exceptions; session ends
t3: Resize Monitor Polls terminal size every 300ms, forwards changes to PTY Entire session Logged; session continues with wrong size
Worker Sentinel API call for permission evaluation Per-permission Fail-open; clears in_approval in finally

Data Flow

flowchart TD
    USER["User Types"] --> STDIN["Stdin Reader (t1)"]
    STDIN -->|"key mapping"| PTY["pywinpty PTY<br/>(Claude Code runs here)"]
    PTY -->|"raw bytes"| READER["PTY Reader (t2)"]

    READER --> PASS["Pass to Terminal<br/>(sys.stdout.buffer)"]
    READER --> ANSI["ANSI Strip<br/>(context buffer)"]
    READER --> OVERLAP["Overlap Buffer<br/>(256-byte sliding window)"]

    ANSI --> PATTERN{"Permission<br/>Pattern Match?"}
    PATTERN -->|"No"| MIRROR["Mirror Buffer<br/>(200ms rate limit)"]
    PATTERN -->|"Yes"| EXTRACT["Extract Tool Type<br/>(TOOL_CALL_RE)"]

    EXTRACT --> SHADOW["Shadow Log<br/>(if --sentinel-shadow)"]
    EXTRACT --> APPROVAL{"Sentinel<br/>Enabled?"}

    APPROVAL -->|"No"| CR["Send CR<br/>(instant approve)"]
    APPROVAL -->|"Yes, tool in scope"| WORKER["Worker Thread<br/>(sentinel check)"]
    APPROVAL -->|"Yes, tool not in scope"| CR

    WORKER -->|"ALLOW"| CR
    WORKER -->|"BLOCK"| BLOCK["Withhold CR<br/>(user sees prompt)"]

    MIRROR --> FILTER["Garbage Filter<br/>(95 patterns)"]
    FILTER --> DEDUP["Dedup"]
    DEDUP --> FILE["Mirror File<br/>(logs/)"]

    style WORKER fill:#f9d71c,stroke:#333,color:#000
    style BLOCK fill:#ef4444,stroke:#333,color:#fff
    style CR fill:#4ade80,stroke:#333,color:#000
Loading

Component Map

Module Lines Role
unleashed-c-21.py ~1050 PTY wrapper, permission detection, approval, mirror, sentinel integration
unleashed-c-18.py ~450 Stable production version (no sentinel, no mirror)
sentinel_gate.py ~105 Haiku API gate class with lazy client init and stats tracking
sentinel_rules.py ~145 Local regex rules: 12 safe patterns, loads from ~/.agentos/
transcript_filters.py ~300 95 compiled regex patterns for garbage detection
clean_transcript.py ~200 Post-session transcript cleaner with block dedup
sentinel.py ~105 Standalone CLI sentinel (separate from the integrated gate)

Design Principles

  1. Non-blocking PTY reader. The reader thread processes bytes as fast as they arrive. All slow operations (sentinel API, mirror file I/O) happen in worker threads or buffered queues. The failed unleashed-guarded.py (in archive/) proved that blocking the reader kills the session. See ADR-002.

  2. Fail-open safety. Sentinel never freezes the session. API errors result in auto-approval with a warning. The safety system must not be scarier than the thing it's protecting. See ADR-003.

  3. Screen scraping by necessity. Claude Code has no machine-readable permission API. The PTY byte stream is the only observable output. See ADR-005.

  4. Layered defense. Local rules handle 80%+ of commands instantly. The API handles ambiguous cases. Errors fail open. Each tier is independently valuable. See ADR-004.

  5. Invisible when working. The user should not notice unleashed is running. No stdout during sessions, no visible artifacts, instant approval for safe commands. The session mirror and friction logs are the diagnostic tools, not the primary interface.

Clone this wiki locally