AI Agent Allowlist
Home Page-Types Database Agent Guardrails 2026 Incidents API Docs Pricing
Resources
Use Cases Industries & Buyers Learn: Core Concepts Implementation Guides Comparisons Schema & Data Reference FAQ Glossary
Why It Matters
2026 Agent Incidents Category Targeting Database Refreshes Contact Customer Login
Download Free Sample
audit-log design for agent browsing

What to Log Every Time an Agent Decides to Navigate

A policy engine that allows or denies a URL is only half the system. The other half is a record good enough that, six months later, someone can reconstruct exactly why an agent was let onto a page, or stopped in front of one — without re-running the agent. This guide sets out the fields to capture per navigation decision, how long to keep them, and how to structure the log so an incident review is a query, not an archaeology project.

6Core fields per decision, minimum
28Page types a verdict can cite
~40Egress rules that can fire per URL
1Query, not an archaeology project
Why this is a separate design problem

A verdict without a reason is not an audit log

It is tempting to treat logging as a side effect of the policy check — write a line, move on. That produces a log that answers "did we deny it" but not "why," and the second question is the one an incident review, a compliance audit, or a confused engineer actually asks. The difference between the two is a handful of fields captured at the moment of decision, not reconstructed afterward from partial context.

Reconstruction after the fact is unreliable for a structural reason: the page-type database, the egress rules, and the host list are all versioned and can change between the moment of the decision and the moment someone investigates it. A URL that resolves to login today might have resolved to an unclassified page type when the agent actually requested it, if that domain was re-verified in between. The only trustworthy record of what the agent was told at decision time is the one written at decision time.

There is also an organizational reason this needs to be designed deliberately rather than left to whatever a logging framework does by default. The person who reviews a navigation log is rarely the person who wrote the policy check — often it is a security analyst, a compliance reviewer, or an engineer from a different team entirely, pulled in after something has already gone wrong. That person has no context on which egress rule numbers correspond to which risk categories, no memory of which page types your organization treats as sensitive, and no time to go spelunking through source code while an incident is active. The log is the interface between the policy engine and that reviewer, and like any interface, it fails silently when it assumes context the reader does not have.

The fields

Six fields every navigation decision should record

These are the minimum fields for a log entry that can stand alone in an incident review, without needing to be joined against a live system that may have moved on. Add fields your organization needs (a request ID, a session ID, a cost or token count) on top of these, not instead of them.

url

The exact URL requested

Full URL including query string, before any redirect. If the agent's own instruction differed from the literal URL it attempted (see the injection-defense guide below), capture both.

page_type

The resolved page type, or "unclassified"

The value returned by the lookup — one of the 28 types, or an explicit unclassified marker. Never leave this blank; a missing value is indistinguishable from a logging bug during review.

rule_fired

Which layer produced the verdict

Host list, page-type database, a specific egress rule ID, or default-deny. This is what turns "denied" into "denied, and here is exactly why," which is the sentence a reviewer needs.

verdict

Allow, deny, or flag

Record the verdict actually enforced, not just the database's raw classification — if your policy overrides a raw "allow" to a local "deny" for a specific agent role, log the enforced result.

timestamp

When the decision was made, not when it was logged

Use the decision-time clock, in a fixed time zone (UTC), with sub-second precision if your agents can make multiple requests per second — ordering matters more than most teams expect during a chain reconstruction.

agent_id

Which agent, run, and task

The agent instance or fleet identifier, the run or session ID, and ideally a task description or task ID. A verdict without an agent identity cannot be attributed to a deployment during a multi-agent incident.

A seventh field worth adding once the first six are solid: policy_version — the version or refresh date of the page-type database and egress rules in effect at decision time. Without it, a reviewer six months later cannot tell whether a "deny" reflects the rules as they existed then or as they exist now.

Illustrative code

Emitting a structured log entry alongside the check

This is a conceptual sketch, not tied to any specific logging library. The structural point is that the log write happens in the same code path as the policy check, using the same lookup result — not reconstructed from a separate trace afterward.

# Illustrative sketch — adapt field names and the logging call to your stack.
import time, uuid

def check_and_log(url, agent_id, run_id):
  result = check_url(url)  # the lookup from our implementation guide
  entry = {
    "decision_id": str(uuid.uuid4()),
    "timestamp": time.time_ns(),  # UTC, nanosecond precision
    "agent_id": agent_id,
    "run_id": run_id,
    "url": url,
    "page_type": result.get("page_type", "unclassified"),
    "rule_fired": result.get("rule_id", "default_deny"),
    "verdict": result["decision"],
    "policy_version": result.get("policy_version"),
  }
  write_audit_log(entry)  # append-only sink — see retention section below
  return result

Two structural details matter more than the exact schema: the log write happens before the caller acts on the result (so a crash immediately after cannot lose the record of what was decided), and a lookup failure or timeout is logged with its own explicit verdict — typically deny, matching default-deny — rather than silently skipped.

Retention

How long to keep navigation logs, and in what shape

Retention is a balance between incident-review usefulness and storage cost, and the right answer differs by how the logs will actually get used. A reasonable default, adjustable to your own compliance obligations:

Log tierSuggested retentionTypical use
Full-fidelity per-decision log (all six-plus fields)30–90 days, hot storageActive incident review, recent-behavior debugging
Aggregated daily summary (counts by verdict, page type, agent)12–24 monthsTrend review, policy tuning, audit reporting
Denied and flagged decisions only, full fidelity12+ months, cold storageLong-horizon incident review; the highest-value subset to keep longest
Allowed, routine reads (docs, blog, status)Shortest retention of the fourLowest incident value; safe to age out first under storage pressure

The asymmetry in that table is deliberate: a denied or flagged decision is far more likely to matter in six months than an allowed read of a blog post, so if storage pressure forces a shorter retention window somewhere, shorten it on the routine-allow tier first, never on the denies.

Structuring for review

Making an incident review a query, not a re-run

1

Index by agent_id and time range first

The first question in almost every review is "what did this agent, or this fleet, request between time X and time Y." If that is not an indexed, fast query, everything downstream is slower than it needs to be.

2

Index by rule_fired second

The second most common question after an incident is "how many other agents hit this exact rule," which is how a reviewer scopes whether a single agent misbehaved or a whole fleet is walking into the same trap.

3

Keep url and page_type as plain, searchable text

Do not hash or truncate the URL field for storage efficiency. A reviewer needs to search for a specific domain or path pattern across the full log, and a hashed field makes that impossible without a lookup table nobody remembers to maintain.

4

Reconstruct a chain by run_id, ordered by timestamp

Given a run_id, the full sequence of navigation decisions for that run — in order — should be a single query. That sequence is what shows a reviewer whether an agent was steered off-task gradually or jumped straight to a denied surface.

A worked example: an agent's run_id shows twelve allowed reads of documentation and blog pages, then a denied attempt at a signup URL on an unrelated domain, then the run ends. That sequence — visible in one ordered query — tells a reviewer the agent's task drifted before the policy caught the drift, which is a very different finding from a run that goes straight from task start to a denied checkout attempt with nothing in between.

One more indexing choice pays for itself repeatedly: a secondary index on page_type alongside rule_fired. A question that comes up constantly in practice is not "what did this one agent do" but "across every agent we run, how many attempts landed on a checkout or password_reset page type last month, and were any of them allowed." Answering that from an unindexed or loosely structured log means scanning the entire table; answering it from a log designed around these fields is a filter and a count.

A worked review

Walking a real incident review through the log, start to finish

Abstract field lists are easier to evaluate against a concrete scenario. Here is how a well-structured log carries a security team from "something looks wrong" to a written finding, without a single re-run of the agent involved.

Say a weekly summary report — the aggregated tier from the retention table above — shows an unusual spike in denied requests from one agent fleet over a 48-hour window, most of them hitting signup and password_reset page types on domains the fleet has no history of visiting. That single aggregate number is the trigger, not the finding; the actual investigation starts by dropping into the full-fidelity log for that fleet's agent_id across the same window. Because the log is indexed by agent_id and time range first, that query returns in seconds rather than requiring a data engineer to write a one-off extraction job.

The ordered, per-run view (indexed by run_id, sorted by timestamp) shows the actual sequence: each affected run starts with several allowed reads of documentation and about pages that match the fleet's normal research task, then pivots to a burst of identity-page attempts on domains unrelated to the stated task, all denied by the same egress rule. Because rule_fired was captured at decision time rather than inferred afterward, the reviewer can see immediately that every denial in the cluster traces to one rule — the identity-surface group in the egress rules library — rather than needing to manually classify two hundred URLs by hand to notice the pattern.

The policy_version field answers the next question a reviewer always asks: was this new behavior from the agent, or a change in what the database considers a match? In this scenario the field shows no database refresh happened in the affected window, which rules out a false-positive spike from a reclassification and points the investigation back toward the fleet's own prompt or task configuration — likely a shared tool or sub-task that started generating URLs it should not have generated. None of that reasoning chain is available from a verdict-only log; it depends on having rule_fired, policy_version, and an ordered per-run view as first-class, queryable fields rather than something a reviewer would need to reconstruct from raw request traces after the fact.

The review closes with two outputs that only exist because the log made them cheap to produce: a written timeline citing exact decision_id values for the audit trail, and a count of exactly how many requests the same rule denied fleet-wide in the same window, which is what tells the team whether to treat this as an isolated task-configuration bug or a fleet-wide pattern worth a broader prompt review.

Common mistakes

Logging gaps that only surface during a real review

Most logging gaps are invisible until the day someone actually needs the log for an investigation, at which point they are expensive to discover. These are the ones we would flag first in a design review.

gap

Logging the domain, not the full URL

A log keyed only to the domain cannot distinguish a denied login attempt from an allowed documentation read on the same site. The page type, not the domain, is what a review needs to see.

gap

Sampling the log to save storage

Sampled logs work for trend dashboards and fail completely for incident review, where the one request that matters is exactly the kind a random sample is likely to drop.

gap

Storing verdicts without the rule that produced them

"Denied" with no rule_fired value forces a reviewer to re-run the URL through the current policy to guess why — which, per the versioning point above, may give a different answer than the one the agent actually received.

gap

No agent-to-human mapping

When an agent acts on behalf of a specific employee or customer, that mapping needs to be queryable from the log, or every review starts with a separate request to a different team just to find out who was responsible for launching the run.

The 2026 incidents that a good log would have caught earlier

Several 2026 agent incidents ran for a long time before anyone noticed — the DseWiki hijack produced roughly 15,000 edits over about seven weeks before detection. Our analysis shows a per-decision log with the fields above would have surfaced the pattern in the first cluster of denied or flagged write attempts, not weeks later.

See the incident-by-incident prevention analysis Read the DseWiki hijack case

The honest fine print — the same two assumptions we publish, plus two operational ones

  1. The policy engine must see every request — an agent with raw socket access or a second network path bypasses everything; enforcement belongs at the egress proxy/network layer, not only in an SDK hook.
  2. Default-deny must be on. In flag-only mode these become alerts within minutes rather than prevention — still transformative versus the real timeline (DseWiki ran ~7 weeks undetected), but not a block.
  3. For full URL+method matching on HTTPS you need to be the proxy or in-process hook — SNI alone shows only the host, which still catches the entire host-list layer.
  4. Policy can’t read intent inside a legitimately allowed action: an agent whose job is publishing packages keeps registry access. The 2026 agents had no such jobs — every crossing was outside any plausible allowlist.
Related reading

Where this connects

FAQ

Audit-logging questions, answered

Do I need to log allowed requests, or just denies?
Log both, but consider different retention windows (see the table above). Allowed requests matter for reconstructing an agent's full task path during a review, even when no single request was itself a problem — the pattern across many allowed requests is sometimes the finding.
What if the lookup times out — do I still log something?
Yes. Log the attempt with an explicit verdict of deny (matching default-deny behavior) and a rule_fired value like "lookup_timeout." A missing log entry is worse than a slightly unusual one, because it looks like the request never happened.
Should the log include the content of the page the agent fetched?
That is a separate decision from navigation logging and carries its own storage and privacy tradeoffs. At minimum, log the six-plus navigation fields on this page for every decision; treat full page-content capture as an optional, higher-cost addition for specific high-risk page types.
How long should navigation logs be kept?
A reasonable default is 30–90 days of full-fidelity logs, 12–24 months of aggregated summaries, and longer retention for denied or flagged decisions specifically, since they carry the highest incident-review value. See the retention table above for a fuller breakdown.
Does this logging pattern work with an on-premise database license instead of the API?
Yes. The logging pattern is independent of whether the page-type lookup comes from the hosted API or a licensed on-premise database (see pricing) — log the resolved page type and rule either way, and add the policy_version field so a reviewer can tell which database snapshot produced the verdict.

Wire the six fields into your policy check today

Download the sample, adapt the sketch above, and confirm every denied request is landing in an append-only sink.

Download the Sample