Skip to content

TriOnyx

track what agents see, not just what they do

A security-first agent runtime that tracks information flow between isolated LLM agents. Taint tracking, sensitivity labels, and bandwidth-constrained communication — built on Elixir/OTP for a single operator running their own agents.

The lethal trifecta

The term comes from Simon Willison's "The Lethal Trifecta" — the observation that AI agents become critically dangerous when they combine access to private data, exposure to untrusted content, and the ability to communicate externally. TriOnyx was built to address this directly.

Other agent runtimes sandbox capability — restrict filesystem, disable shell, limit network. This misses the point. The real danger isn't any single property of an agent. It's the combination of three.

Untrusted content
Web pages, emails, API responses — any of which can carry prompt injections.
×
🔑
Sensitive information
Credentials, private files, internal APIs — things an attacker wants to reach.
×
Capabilities
Shell access, file writes, inter-agent messaging — tools to act on a hijacked context.
=
High-risk agent
TriOnyx tracks information, not just capability — blocking tainted data from reaching sensitive resources.

Architecture

graph TB
    subgraph triggers["External Triggers"]
        WH[Webhooks]
        CH[Chat]
        CR[Cron]
        EM[Email]
    end

    subgraph gateway["Elixir/OTP Gateway"]
        direction TB
        LC[Agent Lifecycle]
        TT[Taint & Sensitivity Tracking]
        MV[Message Validation]
        RS[Risk Scoring]
        CM[Credential Management]
        BQ[BCP Approval Queue]
    end

    subgraph agents["Agent Containers"]
        direction LR
        subgraph agentA["Agent A"]
            PA[Python + Claude SDK]
            FA[FUSE Driver]
        end
        subgraph agentB["Agent B"]
            PB[Python + Claude SDK]
            FB[FUSE Driver]
        end
    end

    CON[Connector<br/>Matrix / Slack]

    triggers --> gateway
    gateway --> agents
    gateway <--> CON

    style triggers fill:#1c2536,stroke:#30363d,color:#8b949e
    style gateway fill:#0f2d1a,stroke:#238636,color:#3fb950
    style agents fill:#1a1e24,stroke:#30363d,color:#c9d1d9
    style agentA fill:#161b22,stroke:#21262d,color:#c9d1d9
    style agentB fill:#161b22,stroke:#21262d,color:#c9d1d9
    style CON fill:#1c2536,stroke:#1f6feb,color:#58a6ff
Gateway — Non-agentic control plane. No LLM. No autonomy. Deterministic security boundary.
Agents — Python + Claude SDK inside Docker. Communicate with the gateway over JSON Lines.
FUSE — Go driver enforcing per-file read/write policies. Logs all access.
Connector — Bridges the gateway to Matrix, Slack, or email.

Features

📦

Isolated containers

Each agent runs in its own Docker container with a per-agent FUSE filesystem, network rules, and no shared state.

🛡

Taint tracking

Biba integrity model tracks what each agent has been exposed to. Untrusted web data, user uploads, and API responses all carry taint.

🔒

Sensitivity labels

Bell-LaPadula confidentiality tracks access to secrets, credentials, and private data. The two axes are independent.

🔄

Information flow enforcement

The gateway intercepts all inter-agent messages and blocks flows that violate integrity or confidentiality constraints.

💬

Bandwidth-Constrained Protocol

Tainted agents communicate with clean agents through structured, bandwidth-limited, human-approvable message formats.

📂

FUSE filesystem

Custom Go driver enforces per-file read/write policies inside each container. O(1) path-trie lookups, structured access logging.

🌐

Browser sessions

Agents can get a headless Chromium browser with persistent login sessions from the host. No credential sharing.

🧩

Plugin system

Reusable agent extensions (news aggregation, bookmarks, diary) installable from git repos with FUSE-enforced access.

🔍

Auditable everything

Structured logs for file access, tool calls, message routing, and policy violations. Queryable audit API.

New here?

Start with the Getting Started guide for a complete walkthrough, or read the Comparison with OpenClaw to understand the security model.

Quick start

# Gateway image (Elixir/OTP)
docker build -f gateway.Dockerfile -t tri-onyx-gateway:latest .

# Agent runtime image (Python + FUSE sandbox)
docker build -f agent.Dockerfile -t tri-onyx-agent:latest .

# Connector image (Python, for Matrix chat bridge)
docker build -f connector.Dockerfile -t connector:latest .

The agent image requires a pre-built FUSE driver binary at fuse/tri-onyx-fs. See the FUSE Driver spec for build instructions.

docker compose up

Or run the gateway standalone:

docker run --rm -p 4000:4000 \
  -v $(pwd):/app -w /app \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e TRI_ONYX_HOST_ROOT=$(pwd) \
  --env-file .env \
  tri-onyx-gateway:latest mix run --no-halt
# Elixir gateway tests
docker run --rm -v $(pwd):/app -w /app \
  tri-onyx-gateway:latest mix test

# Go FUSE driver tests
docker run --rm --device /dev/fuse --cap-add SYS_ADMIN \
  --security-opt apparmor=unconfined \
  -v $(pwd)/fuse:/src -w /src golang:1.22 \
  bash -c "apt-get update -qq && \
    apt-get install -y -qq fuse3 2>/dev/null && \
    go test ./..."

# Python connector tests
docker run --rm -v $(pwd)/connector:/app -w /app \
  connector:latest uv run pytest