Two questions get run together in every agent-governance conversation, and only one of them is usually being answered.
The first: did we govern what our agents were told? The team writes a CLAUDE.md, reviews it, commits it, points at it in the audit. That question feels answered.
The second: do we know what our agents were actually told? Different question. The file you reviewed is authored. The instruction set that reaches the model is assembled — at runtime, from a walk up the directory tree, from path-scoped rules that fire only when Claude touches a matching file, from @-imports that recurse as deep as four hops, from nested files in subdirectories that load on demand rather than at launch [2]. The reviewed artifact and the loaded set are not the same object, and nothing about writing the first tells you the second.
That gap is where InstructionsLoaded lives.
The narrow guarantee
Strip it to what it actually promises. InstructionsLoaded fires when a CLAUDE.md or a .claude/rules/*.md file is loaded into context — at session start for eagerly-loaded files, and again later when files load lazily. It hands the handler an absolute file_path, a memory_type naming the scope (User, Project, Local, or Managed), and a load_reason explaining why the file entered context at all [1].
That is the whole guarantee. It is small, and it is exact.
Two further fields make it more than a list. trigger_file_path names the file whose access pulled a lazy instruction file in. parent_file_path names the instruction file that @-imported this one [1]. Between them, the emission carries lineage — not just what loaded, but what caused it to load, and what included it. An import chain expanded four hops deep can be walked backward from the leaf.
And the event has no decision control. Anthropic states it plainly: the hook cannot block or modify instruction loading; the exit code is ignored; it runs asynchronously. Their stated purpose for the event is audit logging, compliance tracking, and observability [1].
The property is not the primitive
The property an enterprise actually wants is not observability of instruction loads. It is this: proof that the instructions governing our agents were the instructions we authorized — a claim that survives a risk committee.
That property is not in the hook. The hook is an emitter. The property lives in what you build around the emission, and there are three pieces of it, none of which Anthropic ships for you.
An emission is only evidence against something. Without a reviewed, signed manifest of the instruction set an agent is permitted to carry, file_path is just a string. The comparison is the control; the hook only supplies the left-hand side.
The event fires and is gone. It runs asynchronously, and where the record goes is your problem. Hooks accept type: "http", so the emission can POST to a service rather than a log file nobody reads [1]. The common input fields carry session_id and prompt_id, and the docs note that prompt_id matches the prompt.id attribute on the OpenTelemetry events [1] — so the instruction-load record can be correlated to the turn it governed. That correlation is the difference between a log and a trace.
The hook cannot stop a load. Enforcement lives elsewhere in the same product and always did: PreToolUse blocks a tool call, permission rules deny by path, managed settings enforce regardless of what the model decides. InstructionsLoaded is not the gate and does not pretend to be. It is the witness.
load_reason is a trust taxonomy
The five values are not five ways of saying “loaded.” They are five distinct provenance classes, and an architect should treat them differently [1].
session_start — eager load, from the directory hierarchy above the working directory. The set you most likely reviewed.
include — pulled in by @-import from a parent file. parent_file_path gives you the edge. Transitive, and reviewable only if you walked the graph.
path_glob_match — a conditional rule fired because Claude touched a matching file. globs tells you the pattern. The instruction set is now a function of which files got read.
nested_traversal — a file deeper in the tree loaded because Claude went there. This is the one nobody reviewed, and it is the class an enterprise should alarm on by default.
compact — instruction files re-injected after a compaction event. The set is reconstituted mid-session, and a naive audit that samples only at session start misses it entirely.
A monorepo with per-team rules, imports, and path-scoped frontmatter does not have an instruction set. It has a different one per session, assembled from a graph. load_reason is the only thing that tells you which assembly happened.
Assurance is evidentiary, not correct
This is the whole of it, and it is worth being blunt.
Correctness was never available. A model cannot reliably separate instruction from data — OWASP has held that position since 2025, and it is why the text that reaches the context window is a security object rather than a configuration one [3]. Luminity’s own position has been on the record since March: an instruction file cannot govern behavior, because governance is what the harness enforces, not what the file requests [5]. Neither of those changes because a hook now fires.
And the standards bodies an enterprise actually answers to have already chosen this ground. NIST’s Generative AI Profile is a profile of the AI Risk Management Framework, and its suggested actions are organized around four primary considerations: Governance, Content Provenance, Pre-deployment Testing, and Incident Disclosure [4]. Provenance is one of the four. Not accuracy. Not correctness. The framework asks an organization to show where content and instruction came from and to govern that origin — which is precisely the class of claim an emission can support and a correctness claim never could.
What changes is what can be proved. No enterprise will ever stand in front of an auditor and assert that its agent’s instructions were right. That claim is unfalsifiable and nobody credible is asking for it. The defensible claim is narrower and entirely different in kind: the system recorded every instruction file that entered context, with its scope, its lineage, and the reason it loaded — and we can show that the set which loaded is the set that was authorized.
That is an evidentiary claim. It is provable, reproducible, and it survives cross-examination. InstructionsLoaded is the primitive that makes it constructible, and it is the only event in the lifecycle that does.
What the hook does not do
Naming this out loud is the point of the reading.
It does not know who wrote the file. file_path is a path, not an author. The hook will faithfully report an instruction file that a compromised dependency wrote into the working directory thirty seconds earlier, and it will report it with the same confidence as the one your staff engineer reviewed. Authorship and authorization are outside the event entirely; they have to come from the baseline you compare against.
It cannot block. No decision control, exit code ignored [1]. A file that should never have loaded still loads. If the requirement is prevention rather than evidence, the answer is PreToolUse, permission rules, or managed settings — not this.
It does not read the content. The hook fires on the load, not on what the file says. Semantic review is yours.
Its documented triggers are CLAUDE.md and .claude/rules/*.md [1]. An instruction file belonging to another format is not named among the triggers; where such a file is brought in by import, it is the importing file that is the documented object. Confirm the coverage you actually need against the docs before you build the control on top of it.
The emission is not the event, and a ledger must be built as though it isn’t. An open issue against Claude Code, filed with a reproducer and several thousand captured events, reports that a single compaction can produce three InstructionsLoaded emissions per file, each carrying an identical memory_type and load_reason [6]. The report is a community observation, not documented behavior, and Luminity has not verified it against a running system — but the architectural consequence does not depend on whether that specific count is right. An emitter running asynchronously, outside any transaction, gives you no delivery semantics at all. Build the ledger idempotent on the tuple that identifies a load — session_id, file_path, load_reason, parent_file_path — and derive counts from distinct loads rather than from emission volume. A ledger that trusts arrival-once will over-report the very thing an auditor asked it to count.
The emitter must be made unforgeable, and that is a settings problem, not a hook problem. A hook defined in a user or project settings file can be removed by whoever owns that file. The controls that fix this are adjacent: hooks can be defined in managed policy settings, allowManagedHooksOnly blocks user, project, and plugin hooks, and disableAllHooks set below the managed layer cannot disable managed hooks [1]. An instruction-provenance hook that the local user can switch off is not an assurance control. It is a preference.
And the sink is entirely yours. Anthropic emits. Retention, integrity, and tamper-evidence of the resulting record are the enterprise’s to build, and an auditor will ask about all three.
The hard claim
Enforcement and attestation are different properties, they live at different events, and Claude Code ships both — but only enforcement arrives as a control.
Attestation arrives as an emission, and an emission is not an audit trail until someone builds the ledger, sets the baseline, makes the emitter unforgeable, and treats the stream as at-least-once rather than exactly-once.
The enterprises that will be able to answer what were our agents told are the ones that treated InstructionsLoaded as the first line of a control they still had to finish.
Every serious governance regime eventually demands the same artifact: a record of the instructions in force at the moment a decision was made. The instruction layer has now emitted one. Whether it becomes evidence depends entirely on what is standing underneath it when it fires.
