Why this piece exists
Good systems separate the tool from the policy around the tool.
If every tool has to enforce its own approval logic, content rules, and
logging, those concerns get copied everywhere. Hooks give you stable
moments around execution where the host can say yes, say no, or record
what happened without changing the tool's actual job.
A familiar analog is Git hooks. pre-commit can block a bad
commit before it lands, and post-commit can trigger follow-up
automation after the fact. This lab uses the same pattern around a fake
search tool: gate first, run if allowed, then log what happened.
Expected output
What allowed and blocked calls look like in the audit log.
Your file paths may appear as absolute paths depending on where you run the script — that's expected.
An allowed call writes a JSONL line like this:
{"time": "2026-05-06T00:19:35Z", "tool": "term_count", "arguments": {"term": "agent", "files": ["labs/sample_docs/agents.txt"]}, "decision": {"allow": true, "reason": "read-only search"}, "result": {"ok": true, "summary": {"total": 2}}}
A blocked call still gets logged, but the decision changes:
{"time": "2026-05-06T00:19:35Z", "tool": "term_count", "arguments": {"term": "password", "files": ["labs/sample_docs/agents.txt"]}, "decision": {"allow": false, "reason": "blocked sensitive term: password"}, "result": {"ok": false, "error": "blocked_by_hook"}}
That is the point of the pattern. Even rejected calls leave a trace. You
can see what was attempted, why it was blocked, and what the system did
instead of running the tool.