Skip to main content

Intent vs Truth

Quint sits in two data paths at once. The forward proxy sees intent — every tool call the language model emits before the agent executes it. The macOS Endpoint Security framework sees truth — every process, file, and network action the OS actually performs. Correlating these two streams per session produces a detection signal that no single-stream product can replicate.

The two streams

Intent stream (proxy)

When an AI agent like Claude Code receives a response from its language model provider, the response contains structured tool_use blocks. Each block is a declaration: “I will now run this Bash command,” “I will now read this file,” “I will now make this HTTP request.” Quint’s forward proxy intercepts every LLM API response, parses the tool calls out of the body, and emits a structured intent event with the declared action, the target resource, the session identifier, and the timestamp. Supported LLM API protocols today:
  • Anthropic Messages API
  • OpenAI Chat Completions
  • OpenAI Responses API
  • Google Gemini
  • AWS Bedrock Converse
  • Azure OpenAI
  • Generic fallback for novel formats
Every tool call from every supported agent becomes an intent event.

Truth stream (Endpoint Security + Network Extension)

When the agent executes the tool call, it makes system calls. A Bash(curl ...) triggers a process spawn, a network connection, and potentially writes to the filesystem. The macOS Endpoint Security framework reports these events with kernel-backed reliability. The Network Extension reports outbound network flows — which IPs, which ports, how much data. Both streams are filtered to the agent’s process tree and emitted as truth events with the observed action, the observed target, the process identifier chain, and the timestamp. Every process spawn, every file access, every network connection under the monitored agent’s process tree becomes a truth event.

The join

Intent and truth events share a session identifier derived from the agent’s root process. The unified session tracker in the daemon attributes both streams to the same session before they leave the machine. The divergence detector then correlates them primarily by the process (PID) tree — each truth event carries the PID chain of the process that produced it, which links back to the tool call that spawned it. A time window (±5s) and per-tool causality (toolCausalByPID, 60s TTL) are only secondary and tertiary fallbacks. A pure time-window join can’t be primary: Claude Code fires ~12K exec events/sec during a build, so windowed joins produce cross-talk between unrelated actions.
Session IDs are deterministic UUIDs computed from uuid.NewSHA1(NameSpaceOID, pid_timestamp). The same PID+timestamp on the edge maps to the same UUID in the cloud without coordination.

The six divergence types

Correlation produces one of seven outcomes: normal (streams agree) or one of six divergence types. Each has a distinct security meaning.

1. Intent without truth

The model declared an action. The OS never observed it. Example: The agent said it would read /app/config.yml, but the ES extension reported no file read. Typical cause: Telemetry gap. The ES extension was briefly down, or the file was served from the OS page cache without emitting an open event. Almost always benign. Product behavior: Log as coverage gap. If unmatched intent rate exceeds 20% per session, flag telemetry health to operations.

2. Truth without intent

The OS observed an action. The model never declared it. Example: ES reported a PROCESS_EXEC of /usr/bin/curl connecting to an external IP under the agent’s process tree, but the proxy captured no corresponding tool_use. Typical cause: Prompt injection. The agent’s input was poisoned and executed a tool call the developer never authorized, or a compromised process is operating under the agent’s identity. Product behavior: High-severity alert. Divergence runs in shadow mode (alert-only) — an OS-observed divergence has no blocking surface of its own, since the Endpoint Security framework and Network Extension are notify-only. (Enforcement, when it happens, is upstream: local tool calls via the quint-hook PreToolUse deny, network egress via the proxy.) This is the canonical prompt-injection signal.

3. Intent mismatches truth target

Both streams fired within the window, same action class, different targets. Example: The agent declared Edit(/app/config.yml) but the OS observed a write to /etc/passwd. Typical cause: Path traversal, symbolic link exploitation, or prompt injection where the agent’s declared target does not match the actual OS operation. Product behavior: Critical alert with claimed-vs-observed diff surfaced in the dashboard. Alert-only in shadow mode (see note under Truth without intent — divergence itself does not block).

4. Intent mismatches truth scale

The model declared one action, the OS observed many. Example: The agent declared Read(/app/src/main.go) but the ES extension reported 847 FILE_READ events under /app/src/** within the 5-second window. Typical cause: Data exfiltration via recursive file access. A tool call that should have read one file instead enumerated a directory tree. Product behavior: High alert for network scale mismatches (potential exfiltration), medium for file read mismatches (potential reconnaissance).

5. Intent mismatches truth timing

Matched pair with an abnormally large timestamp gap. Example: Intent recorded at T=0, matching truth event at T=45s. Typical cause: Stored or replayed execution. Normal execution latency is under 5 seconds; gaps beyond 30s suggest scheduled or queued payloads. Product behavior: Low severity on first occurrence, escalating to medium on 3+ within the same session.

6. Truth precedes intent

The OS observed an action before the model declared anything matching it. Example: ES reported python3 /tmp/payload.py at T=0, then the proxy captured tool_use(Bash, "python3 /tmp/payload.py") at T=5s. Typical cause: Credential harvesting or process hijacking. Something is acting under the agent’s identity before the agent asks for it. Product behavior: Critical alert when the truth event is a network connection or process exec with a gap exceeding 10 seconds.

Why the two-stream architecture is defensible

The architectural property that makes Quint’s detection possible is that both streams are observed on the same machine, by the same daemon, within the same process namespace. This enables:
  • Shared session attribution. The unified session tracker sees both streams and assigns them to the same session identifier without any cross-process coordination.
  • Monotonic timestamps. Both streams use the same kernel monotonic clock. Clock skew between streams is measured in microseconds.
  • Reliable PID-tree correlation. Because both streams pass through the same daemon in the same process namespace, each truth event’s PID chain can be joined to the intent that spawned it — a structural link, not a guess. This is why correlation is primarily PID-tree based rather than a time-windowed join, which would cross-talk under high exec volume.
Products that observe only intent (browser extensions, LLM gateways) cannot detect truth-without-intent attacks. Products that observe only truth (traditional endpoint detection, runtime agents) cannot detect intent-target mismatches. The combination of both streams, correlated per session, produces detection capabilities that neither stream alone can replicate.

From divergence to intent scope

Divergence is the truth-side signal: it catches actions the OS performed that the conversation never licensed. The intent side is what makes “licensed” a precise, enforceable notion. Rather than score each action against the conversation (slow, and it puts a model on the blocking path), Quint compiles the conversation into a scope once per turn, then checks each action against that scope deterministically.
  • Fuzzy compile, off the hot path. Per turn, asynchronously, QIM extracts the agent’s stated intent and compiles it into a scope — a set of allowed capability classes and resource globs. ML and heuristics decide what goes into a scope.
  • Exact check, on the hot path. Each action is matched against the compiled scope with a bitmask + glob test in ~1µs (Gate 0.5). This is the only thing in the blocking path. The model never makes an enforcement decision — the model proposes, the scope disposes.
Key invariants: intent only narrows (a compiled scope is intersected with the org policy ceiling and static sensitivity floors — it can never grant more than policy already allows); vagueness earns nothing (unbounded breadth compiles to the minimum scope, not a blank cheque); and ground truth is agent-unauthorable (the ES/NE truth stream checks behavior regardless of any conversation-layer compromise). Narration is treated as commitment, never belief: a benign narration compiles a benign scope, so malicious actions land outside it — the more convincingly innocent the story, the tighter the cage.
Maturity note: the deterministic compile-and-check spine (M0 lexicon + Gate 0.5) ships today. QIM v1, the re-aimed GNN, and the enterprise/global training tiers below are phased (the rollout gates are tracked internally). Phase markers are called out inline.

QIM — the Quint Intent Model

Extraction and alignment are done by QIM, a compact local model (a bi-encoder, exported to an optimized ONNX sidecar). Because the daemon is pure Go (CGO-free), QIM ships as a separate signed sidecar, not in-process — and it never sits on the per-action blocking path. QIM has exactly three pre-enforcement jobs:
  1. Frame extraction — turn narration into a structured frame (capabilities, resources, breadth, confidence).
  2. Envelope-entailment prediction — score which capability × resource cells the stated intent licenses.
  3. Alignment (NLI) scoring — score how well each level of the intent hierarchy entails the next.
Architecturally, QIM is a bi-encoder with two projection heads, f_intent and g_action, and an asymmetric entailment score E = f_intent(I) · g_action(a). The asymmetry is deliberate: licensing is directional (a “fix the build” intent entails npm install, but npm install does not entail arbitrary intent), while plain cosine similarity is symmetric. That asymmetry is also why embeddings alone cannot be the judge — read ~/.ssh/id_rsa and read ~/.ssh/known_hosts are near-identical in embedding space but worlds apart in sensitivity. QIM matures along a ladder: M0 deterministic verb-lexicon (ships today, ~10µs) → M1 compact bi-encoder + retrieval index → M2 fleet-trained joint bi-encoder (QIM v1, the marketable step) → M3 NLI head + distilled judge → M4 on-device SLM judge. A larger LLM judge is used only as a cold-start teacher, only on the uncertain band — never as the deployed edge artifact, and never per action on the hot path.

QAR — the canonical action record

QIM never embeds raw text on the action side. Every action is first canonicalized into a QAR (Quint Action Record): a compact serialized descriptor with a capability class, a sensitivity class, a resource-shape descriptor, a tool family, and flags. Two representations are derived per action:
  • a discrete policy key (capability × sensitivity, human-named, never learned) — used for policy, audit, and floors; and
  • a semantic point — the QAR embedded into the joint intent–action space — used for detection, retrieval, and novelty.
Crucially, sensitivity is an input from ground truth (path and OAuth-scope floors, org policy, confirmed catalogs), never a model output — that is what structurally defeats the id_rsa-vs-known_hosts collapse. Canonicalization is a compiler, not a fixed catalog: an unseen tool is canonicalized into transparent keys before it ever reaches the model, and novel tools inherit envelopes by geometry. QAR carries one hard engineering constraint: its serialization must be byte-identical between the Go edge and the Python trainer, pinned by shared golden vectors run in both CIs. Train–serve skew here would silently destroy the model, so the QAR contract is versioned and golden-tested on both sides.

Detecting enterprise policy violations

An action violates enterprise policy when it falls outside the compiled scope and the resource carries real sensitivity. Risk is modeled as a relation, not a property — five evidence channels with fixed authority: Composition is monotone: learned signals can raise attention but can never, on their own, lift an action across the block threshold. Only floors, confirmed org catalogs (e.g. a Unity Catalog data classification), and confirmed classifications are block-capable; the sensitivity multiplier is never a learned weight. In-scope actions emit nothing (a 95%-silent noise discipline); the system fails open on scope and fails closed only on a scope violation against a dangerous capability. This is also where salami-slicing is caught. A single “read AWS credentials” call passes the narrow, per-action alignment check (alignment1) but fails against the turn’s step and the session goal (alignment2/alignment3): nothing about “fix the failing build” entails auditing cloud credentials. Alignment2 drift, decayed by EWMA, is the boiling-frog detector. Provenance is enforced by type, not convention: SourceUserText (authoritative) > SourceAgentNarration (commitment) > SourceHookDeclaration (per-action ground truth), and SourceToolResult never compiles scope — indirect prompt injection through tool output is excluded structurally.

Threat geometry: training enterprise and global models

Local determinations flow back up a federated distillation loopmodels ship to the data, only geometry ships back. What leaves the endpoint is never text: intent frames (hash + embedding), action key + embedding, scope decisions, analyst outcome labels (TP/FP — the scarcest, highest-value signal), and a provenance skeleton (goal → step → action edges over opaque IDs). The three tiers are composed, never merged:
  • Org — the local corpus, workflow specialists, and exemplar index.
  • Enterprise — org geometry pooled with per-org contribution caps.
  • Global — cross-org priors and campaign-shape learning.
The down-path pushes back signed, versioned, rollback-able artifacts: QIM weight versions (versioned, weekly-class, behind a pre-registered eval gate), exemplar embeddings (KBs — herd immunity in hours), workflow priors, and scope templates. A cloud outage degrades learning, never detection: every enforcement decision is local.

The re-aimed GNN (global tier, post-Phase-2)

The GNN belongs to this global tier — and it is not the per-action VGAE scorer the earlier docs described (that design is retired). Re-aimed, the GNN runs offline at the global tier over intent-annotated provenance graphs built from the up-path skeletons, trained on real, analyst-confirmed divergence labels rather than hand-authored anomalies. The geometry is the whole point: unjustified activity is a subgraph with no path to a goal node. On that substrate it does three things a per-action score cannot:
  • Workflow mining — discovering the recurring task shapes that become specialists and workflow priors.
  • Divergence-shape learning — generalizing attack topologies from confirmed labels instead of enumerating signatures.
  • Cross-org campaign detection — spotting a shape that recurs across tenants.
It never touches an edge enforcement decision; it emits priors and templates that push down through the same signed, gated channel as everything else. It is sequenced after Phase 2, once there is a corpus of real, analyst-confirmed divergence to learn from.