AGENTS / GITHUB / creel
githubinferredactive

creel

provenance:github:Creel-ai/creel

Secure, self-hosted personal AI agent with per-tool container isolation

View Source ↗First seen 2mo agoNot yet hireable
README
# Creel

<p align="center">
  <img src="assets/creel-logo.jpg" alt="Creel" width="400">
</p>

A secure LLM task runner and personal AI assistant that separates credential-bearing data fetching from LLM processing. Supports both scheduled tasks (morning briefings, weather summaries) and interactive agent mode (chat via CLI or iMessage with tool calling).

A creel is a wicker basket usually used for carrying fish or blocks of peat. It is also the fish trap used to catch lobsters and other crustaceans.

## Why

Agentic LLM systems give the model access to tools, credentials, and untrusted input all at once. Creel enforces **per-tool isolation** — each executor runs in its own container with only the credentials it needs:

| Component | Has access to | Does NOT have |
|-----------|--------------|---------------|
| Each executor | Only its own credential (one OAuth scope, one API key) | LLM, other tools' credentials |
| Bridge executors | Scoped HTTP token for one macOS app | LLM, other bridge endpoints |
| LLM Runner | Anthropic API key only | Any tool credentials |
| Orchestrator | All secrets, LLM output | Untrusted external input |

Even if prompt injection occurs (e.g., via a calendar event title), the LLM container has nothing to exfiltrate except its own API key. A compromised executor can only access its one scoped credential — not your email, not your files, not your messages.

## Architecture

```mermaid
flowchart TD
    subgraph orch["Orchestrator"]
        direction TB
        schedule["Scheduler / Agent Loop"]
        guardian["Guardian Pipeline"]
        template["Prompt Builder"]
        output["Output Router"]
    end

    subgraph containers["Docker Executors (isolated)"]
        direction TB
        google["Google Suite\n📅 Calendar · 📧 Gmail · 📁 Drive"]
        web["Web Tools\n🔍 Search · 🌐 Fetch · 🌤 Weather"]
        exec["Shell / Exec\n⚙️ Mounted paths only"]
    end

    subgraph bridge["Host Bridge (macOS native)"]
        direction TB
        bridge_api["FastAPI Server"]
        native["📝 Notes · ✅ Reminders\n📋 Things 3 · 💬 iMessage"]
    end

    subgraph llm_container["LLM Container"]
        llm["Claude\n🔑 Anthropic API key only"]
    end

    subgraph channels["Channels"]
        cli["TUI / CLI"]
        imsg["iMessage"]
    end

    channels -- "message" --> orch
    schedule -- "tool call" --> guardian
    guardian -- "approved" --> containers
    guardian -- "approved" --> bridge_api
    bridge_api --> native
    containers -- "JSON result" --> template
    bridge_api -- "JSON result" --> template
    template -- "prompt\n(no secrets)" --> llm
    llm -- "response" --> output
    output --> channels

    style containers fill:#2d333b,stroke:#f47067,stroke-width:2px,color:#f0f0f0
    style bridge fill:#2d333b,stroke:#fd7e14,stroke-width:2px,color:#f0f0f0
    style llm_container fill:#2d333b,stroke:#f47067,stroke-width:2px,color:#f0f0f0
    style orch fill:#2d333b,stroke:#58a6ff,stroke-width:2px,color:#f0f0f0
    style channels fill:#2d333b,stroke:#3fb950,stroke-width:2px,color:#f0f0f0
```

> **Key insight:** Each red box is a separate security boundary. The LLM never sees credentials. Executors only get their own scoped secret. Even a compromised tool can't reach other tools' data.

### Agent Mode

In agent mode, the same security boundary applies — the LLM requests tool calls, but the orchestrator handles secrets injection and executor execution:

```mermaid
flowchart TD
    CH["Channels\nstdin | iMsg | BB"] -- "incoming message" --> SM["Session Manager\n(JSON files)"]
    SM -- "message + history" --> AL["Agent Loop"]
    AL --> LLM["LLM call"]
    LLM --> TU{"tool_use?"}
    TU -- no --> resp["Response"]
    TU -- yes --> EX["Execute via executor\n(secrets injected)"]
    EX --> TR["tool_result"] --> AL
```

Scheduled tasks can also use agent mode by setting `mode: agent` in the task YAML.

### Guardian

The guardian layer screens inputs and validates tool calls before they execute. All stages are optional and independently configurable in `agent.yaml`:

```mermaid
flowchart TD
    A["Incoming message"] --> B["screen_input(text)\n← before session history"]
    B --> FC["FastClassifier\nDeBERTa/ONNX, ~10ms"]
    B --> LJ["LLMJudge\nHaiku, ~300ms, off by default"]
    FC --> blocked{"blocked?"}
    LJ --> blocked
    blocked -- yes --> reject["Return rejection,\nskip agent loop"]
    blocked -- no --> agent["Agent loop → LLM returns tool_use"]
    agent --> VA["validate_action(tool, args)\n← before execute_tool_call"]
    VA --> PE["PolicyEngine\nYAML rules, <1ms"]
    VA --> CC["CoherenceCheck\nHaiku, ~300ms"]
    PE -- allow --> execute["Execute"]
    PE -- review --> approval["Human approval\nor auto_approve"]
    PE -- deny --> err1["Return error to LLM"]
    CC -- coherent --> execute
    CC -- incoherent --> err2["Return error to LLM"]
```

**Stages:**

| Stage | Component | What it does |
|-------|-----------|--------------|
| 1 | Fast classifier | Local DeBERTa model scores prompt-injection likelihood against a confidence threshold |
| 2 | LLM judge | Secondary Haiku-based check (disabled by default) |
| 3 | Policy engine | `fnmatch` rules in `policies/default.yaml` map tool names to allow/review/deny |
| 4 | Coherence check | LLM-based check that tool calls match the user's original intent (catches prompt injection causing unrelated actions) |

**Policy rules** (`policies/default.yaml`):

```yaml
allow:  [check_weather, check_calendar, check_email, read_email, check_drive, check_messages, get_chats, react_imessage]
review: [send_*, upload_*, create_*, mark_*, trash_*]
deny:   [delete_*]

# Tools that match 'review' rules but can skip human approval
auto_approve:
  - mark_read
  - react_imessage
```

Deny wins over review, review wins over allow. Unknown tools default to review. Tools listed in `auto_approve` skip the human confirmation prompt even when matched by a review rule.

**Human-in-the-loop review:** Tools matching `review` rules prompt the user for approval before executing. In the TUI, this appears as an inline confirmation dialog. A configurable timeout (default 60s) denies the action if no response is received.

**Audit logging** writes to `guardian_audit.jsonl` with hashed inputs (never raw text), tool names, arg keys (not values), verdicts, and confidence scores.

**Configuration** in `agent.yaml`:

```yaml
guardian:
  enabled: true
  review:
    timeout_seconds: 60
    default_on_timeout: deny
  fast_classifier:
    enabled: true
    threshold: 0.95
    model_name: protectai/deberta-v3-base-prompt-injection-v2
  llm_judge:
    enabled: false
  coherence:
    enabled: true
    model: claude-haiku-4-5-20251001
    max_tokens: 256
  policy:
    enabled: true
    policy_file: policies/default.yaml
  audit:
    enabled: true
    log_file: guardian_audit.jsonl
```

## Host Bridge

For macOS-specific tools (Apple Notes, Apple Reminders, Things 3, iMessage), Docker containers can't directly execute AppleScript or access macOS applications. The host bridge solves this by running a FastAPI server on the host system that provides authenticated HTTP endpoints for containerized executors.

**Why**: Docker containers are sandboxed and can't access the macOS scripting bridge or application APIs that tools like Notes, Reminders, and Things 3 require.

**How**: A FastAPI server (`python -m bridge.server`) runs as a host process and exposes REST endpoints at `/notes/*`, `/reminders/*`, `/things/*`, and `/imessage/*`. Containerized executors make HTTP requests to these endpoints with scoped authentication tokens.

**Security**: Each executor receives a scoped token that only grants access to its specific tool endpoints. For example, the `apple_notes` executor can only call `/notes/*` endpoints, not `/reminders/*` or `/things/*`.

**CLI Integration**: The bridge server delegates to command-line tools:
- **Apple Notes**: `memo` CLI for reading/writing notes
- **Apple Reminders**: `remindctl` CLI for managing rem

[truncated…]

PUBLIC HISTORY

First discoveredMar 23, 2026

IDENTITY

inferred

Identity inferred from code signals. No PROVENANCE.yml found.

Is this yours? Claim it →

METADATA

platformgithub
first seenFeb 5, 2026
last updatedMar 22, 2026
last crawled17 days ago
version

README BADGE

Add to your README:

![Provenance](https://getprovenance.dev/api/badge?id=provenance:github:Creel-ai/creel)