> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asymptotelabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Lenses

> Purpose-built views of one agent trace, as tabs in the local dashboard, and how to make your own

## Overview

A trace holds everything an agent did in a session: what it was asked, the commands it
ran, the files it changed, what it spent, and what the threat rules flagged. The full event
list shows all of it at once. A **lens** shows one trace from one angle: every file the
agent changed with its diffs, or the run as an itemized bill.

Lenses are tabs on a session's page in the local dashboard
([`beacon endpoint dashboard`](https://docs.asymptotelabs.ai/cli/dashboard)), next to **Full session**. Click **+** to
add one; the dashboard remembers which lenses you opened and keeps the selected one in the
page URL, so a link opens straight to it.

Beacon ships seven lenses, and you can add your own. A lens is one HTML file, so any coding
agent can write one.

## Built-in lenses

| Lens | What it shows |
| - | - |
| **Activity** | What the agent did, counted by kind, the commands it ran with their exit codes, and each rule finding linked to its evidence. It is also the spec's example lens. |
| **Files Changed** | Every file the agent created, edited or deleted, as a tree with line counts. Selecting a file shows each of its edits in order, with the diff, or the diff's hash and size when it was not retained. Files only read are counted, not listed. |
| **Token Usage** | An itemized bill: counted usage and runtime-reported cost, split by model and by turn, then every event that reported usage. When the events' own numbers add up to more than Beacon counts (usage reported on two channels, or as a cumulative counter), the lens says so. It also says when a runtime that should report usage did not, and when a runtime never exposes usage at all. |
| **Security Review** | [Threat-rule](https://docs.asymptotelabs.ai/detections) findings grouped as **untrusted input** (prompt injection), **sensitive access** (credentials, metadata endpoints) and **consequential action** (exfiltration, destructive commands, sensitive edits). It calls out a trace that has all three, which is how an injected instruction turns into stolen data, and shows each finding's evidence events. |
| **Approvals & Policy** | Every approval decision in order: allowed or denied, whether the operator or a policy provider (the optional `BEACON_POLICY_PROVIDER` seam) decided, what it was about, and whether the runtime reported it or Beacon inferred it. |
| **Skills** | Every skill the agent loaded, where each entered the run (the event, and the prompt it came under) and how (a skill tool call, a read of a `SKILL.md`, or a shell command that printed one), and the exact instructions the agent received, when the runtime reported them and the log retained them. A load whose text was not retained shows its hash and size instead. |
| **MCP Calls** | Every MCP tool call, grouped by server and then by tool, with how many calls each tool took and how many failed. Each call shows when it ran, its method and resource URI where the runtime reported them, and the arguments the agent sent and the result that came back. A call whose result reports `isError` or an error is marked failed. |

## How a lens runs

A lens is code looking at sensitive data, so the dashboard runs it on a short leash:

* **No ordinary network.** The lens runs in a sandboxed frame under a Content Security
  Policy with `connect-src 'none'`. It cannot fetch, open a socket, load a remote image or
  font, or navigate its frame anywhere else. A lens that tries to navigate is closed.
* **No dashboard access.** The frame has an opaque origin. It cannot read the dashboard's
  pages, storage or API.
* **One delivery of data.** The trace arrives once, through `window.beacon.getTrace()`,
  over a private message channel. It is computed from the local log when the page loads and
  is never refreshed.
* **Failure is contained.** If a lens throws before it renders, never asks for its trace,
  or has not rendered after ten seconds, the dashboard closes it and shows the full session
  with a notice.

The data is the trace bundle every Beacon trace consumer reads, plus the trace's rule
findings and its token usage as [`beacon token-usage`](https://docs.asymptotelabs.ai/cli/token-usage) counts it. A lens
sees whatever content the local log retained, after Beacon's redaction: prompts, command
output and diffs when retention is `full`.
[Forwarding privacy modes](https://docs.asymptotelabs.ai/concepts/vector-forwarding.md) control what Beacon sends off the
machine; they do not reduce what a local lens receives.

### What the sandbox does not guarantee

The sandbox keeps a lens away from Beacon and the dashboard and blocks the ordinary network
APIs, but it cannot fully prevent a lens from sending the trace data it is shown out of the
machine. Some browser features are not governed by a Content Security Policy or any other
control a page can set: WebRTC, and resource hints and speculative loading such as
`preconnect` and speculation rules. The dashboard removes WebRTC from the lens and refuses
nested frames, `<link>` elements and speculation rules before any lens code runs. That is
best-effort hardening that closes the common paths, not a boundary: a determined lens may
find another.

So an installed lens is code you trust with the trace data it is shown. The built-in lenses
ship with Beacon. Read any other lens before you install it, and install only lenses you
trust; [`beacon lenses add`](https://docs.asymptotelabs.ai/cli/lenses#beacon-lenses-add) asks you to confirm that.

## Make your own lens

The fastest way is to ask your coding agent. The `beacon-lens-create` skill in Beacon's
[Agent Skills plugin](https://docs.asymptotelabs.ai/concepts/beacon-skills.md) walks it through the whole loop. Without the
skill, give it this:

```text theme={null}
Build a Beacon lens that <describe the view>. Read `beacon lenses spec` first, look at
real data with `beacon lenses data`, lint with `beacon lenses lint`, and preview it with
`beacon lenses preview` before installing it with `beacon lenses add`.
```

By hand, the loop is the same:

```bash theme={null}
beacon lenses spec                       # the format, the data, the sandbox, the style tokens
beacon lenses spec --example > my.lens.html
beacon lenses data --session <id>        # exactly what getTrace() returns for a real session
beacon lenses lint my.lens.html          # spec errors, and code the sandbox would block
beacon lenses preview my.lens.html       # the dashboard with your lens added; edit and reload
beacon lenses add my.lens.html           # install it into the lens store
```

1. **Read the spec.** It defines the manifest every lens carries, the data, the sandbox
   and the style tokens that match the dashboard.
2. **Look at real data.** Design for the fields your sessions actually carry, and for
   sessions that lack them.
3. **Lint.** Errors stop a lens from running. Warnings name the line where the lens does
   something the sandbox will block, such as `fetch`, `innerHTML` or a script loaded from a
   CDN.
4. **Preview.** `beacon lenses preview` serves the dashboard with the file added and prints
   the session page to open. The file is read again on every reload.
5. **Install.** `beacon lenses add` asks you to confirm that you trust the lens, then copies
   the file into the lens store (`~/.beacon/endpoint/lenses`), where every session page
   offers it. To update a lens, bump the `version` in its manifest and add it again.

See [`beacon lenses`](https://docs.asymptotelabs.ai/cli/lenses) for every command and flag, and the
[lens specification](https://github.com/asymptote-labs/agent-beacon/blob/main/spec/lenses/SPEC.md)
for the format.

## What a lens file looks like

```html theme={null}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="application/beacon-lens+json">
{ "id": "command-log", "title": "Command Log", "version": 1, "api": "beacon.lens.v1" }
</script>
</head>
<body>
<p id="status">Loading trace…</p>
<ol id="commands"></ol>
<script>
window.beacon.getTrace().then(function (data) {
  var list = document.getElementById("commands");
  data.trace.events.forEach(function (event) {
    if (!event.command) return;
    var item = document.createElement("li");
    item.textContent = event.command.command;   // never innerHTML: trace content is untrusted
    list.appendChild(item);
  });
  document.getElementById("status").hidden = true;
});
</script>
</body>
</html>
```

## Limits

* A lens is one file of at most 16 MiB, with everything inline.
* A lens sees one trace. A view across several sessions is not supported yet.
* Lenses in the local dashboard are local. The store is per user on this machine; to share a
  local lens, share the file.
* Very large traces are cut to fit: the lens receives the longest prefix of events that
  fits, and the data says it was truncated.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.