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 structuredtool_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
Truth stream (Endpoint Security + Network Extension)
When the agent executes the tool call, it makes system calls. ABash(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 aPROCESS_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 declaredEdit(/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 declaredRead(/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 reportedpython3 /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.
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.
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:- Frame extraction — turn narration into a structured frame (capabilities, resources, breadth, confidence).
- Envelope-entailment prediction — score which capability × resource cells the stated intent licenses.
- Alignment (NLI) scoring — score how well each level of the intent hierarchy entails the next.
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.
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 loop — models 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 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.