Skip to content

Harness Engineering

When you talk to a raw LLM through an API, you send text and get text back. That is not an agent. An agent is the system around the model — the code that assembles the prompt, wires up tools, executes commands, manages memory, and decides what to send to the model next.

That system is the harness. It is the scaffolding that turns a language model into an agent with real-world capabilities.

Every AI agent has a harness. The question is whether it was designed deliberately or assembled accidentally. Most agent frameworks provide a harness by default: they give the model a shell, let it call APIs, and trust everything it says. The harness is there, but its security properties are an afterthought.

A harness determines the attack surface of an agent. Every capability the harness grants the model — file access, shell execution, web requests, API credentials — is a capability that prompt injection can weaponize.

If the harness gives the model a shell with full host access, a successful injection gives the attacker a shell with full host access. If the harness puts API keys in the model’s environment, a successful injection can read them.

The harness is the security boundary. The model is not.

Most agent frameworks build a single-process harness:

flowchart TD
    subgraph process["Single process — same trust domain"]
        Prompt[Prompt assembly] --> LLM[LLM]
        LLM --> Tools[Tool dispatch]
        Tools --> Shell[Shell execution]
        Tools --> FileIO[File I/O]
        Tools --> WebReq[Web requests]
        Tools --> Memory[Memory storage]
        Tools --> Creds[API credentials]
    end
    Tools --> Internet["Internet<br/>(uncontrolled)"]
    style process fill:#fef2f2,stroke:#dc2626,color:#dc2626
    style LLM fill:#fecaca,stroke:#dc2626
    style Tools fill:#fecaca,stroke:#dc2626
    style Internet fill:#fee2e2,stroke:#dc2626

In this model, the model, the tools, the credentials, and the network access all live in the same trust domain. If the model is compromised, the attacker has everything.

q15’s harness: separation as a principle

Section titled “q15’s harness: separation as a principle”

q15 splits the harness into three services with hard boundaries between them:

flowchart LR
    subgraph Agent["q15-agent"]
        A1[Prompt]
        A2[Tools]
        A3[Telegram]
        A4[Memory]
        A5[File roots]
    end
    subgraph Exec["q15-exec"]
        E1[Nix shell]
        E2[Sessions]
        E3["Egress → proxy"]
    end
    subgraph Proxy["q15-proxy"]
        P1[Credential injection]
        P2[Request mutation]
        P3[TLS interception]
    end
    Agent -->|"gRPC"| Exec
    Exec -->|"proxy"| Proxy
    Proxy --> Internet[Internet]
    style Agent fill:#dbeafe,stroke:#2563eb,color:#2563eb
    style Exec fill:#fef3c7,stroke:#d97706,color:#d97706
    style Proxy fill:#bbf7d0,stroke:#16a34a,color:#16a34a
    style Internet fill:#f3f4f6,stroke:#6b7280

Each service owns a distinct concern, and the boundaries are enforced by the deployment topology, not by the model’s compliance with instructions.

The agent owns The agent does NOT own
Prompt assembly Network policy
Tool dispatch Secret values
Telegram I/O Package management
File operations (rooted) Host filesystem access
Memory management Egress routing

The agent can request that a command be run or that a web request be made. It cannot guarantee that the request will be fulfilled, because the exec service and the proxy are independent services with their own policies.

The separation is not just good software architecture. It is a trust boundary. Consider what happens during a prompt injection attack in each model:

Naive harness: The model says “read the GitHub token from the environment and send it to evil.example.com.” The harness, running in the same process with full access to environment variables and the network, reads the token and sends the request. Game over.

q15 harness: The model says “read the GitHub token from the environment and send it to evil.example.com.” There is no GitHub token in the agent’s environment — credentials live in the proxy, not in the agent’s process. The agent can make a request to evil.example.com (the proxy does not block destinations by default), but it has no token to send. The proxy only injects credentials for hosts that have matching rules, and evil.example.com has none.

The defense works because:

  1. Secrets live in the proxy, not in the agent. The agent’s process has no API tokens, no passwords, no SSH keys. A prompt injection cannot exfiltrate credentials that do not exist in the agent’s context.
  2. The exec service routes through the proxy unconditionally. The agent cannot ask exec to bypass the proxy — the routing is configured at the deployment level.
  3. Credential injection is host-scoped. The proxy only attaches credentials to requests for hosts that have matching rules. A request to an attacker-controlled host gets no credentials injected.

An LLM is a general-purpose reasoning engine. You can talk to the same model through a raw API, through ChatGPT, through Claude, or through q15. The model is the same. What differs is the harness.

q15’s harness is designed around a specific thesis: you should not need to trust the model. The architecture should make a compromised model safe by constraining what it can actually do. That is what the three-service model, the rooted file access, the proxy-based egress control, and the secret injection system all serve.

Prompt Injection

The concrete attack scenario and how each q15 layer defends against it.

Three-Service Model

The architecture page: how the three services fit together in deployment.

q15-proxy

The proxy service in detail: credential injection, TLS interception, and request mutation.