Skip to main content

Reading findings on the endpoint

An enrolled machine reports to the console, and the console is where its findings are read. This page is for the other case: operating an agent directly on the host — a self-managed or offline machine, a machine that was never enrolled, or a laptop running the agent locally to try it out.

Start here: lumen-agent status​

Before reading a single finding, ask the endpoint what it is doing:

lumen-agent status # exit 0 daemon answered, 3 daemon down, 1 no report
lumen-agent status --json | jq '.daemon.policy, .hooks[].policy_id, .warnings'

One screen answers the questions a security review asks: what policy is in force, is anything actually running, which supervisor would restart the daemon, where the proxy forwards to, which Claude Code guards are registered and what policy each of them enforces, and how deep the spool is.

These lines are worth knowing about before you need them:

  • control DISABLED is printed first, above the daemon block. A switched-off agent looks identical to a healthy one everywhere else.
  • policy … source embedded means the daemon is running the policy compiled into the binary, not your file, and a policy the console assigns is not applied either. That policy only logs, so the next line reads MONITOR (nothing is redacted or blocked) and nothing else looks wrong: the source is what gives it away. See Configuration.
  • MONITOR (nothing is redacted or blocked) is a policy whose every rule logs, such as Local default, the one every endpoint starts on. The endpoint's enforcement mode makes no difference to it; see From monitor to enforce.
  • MONITOR (console ceiling); the policy would: … means the endpoint holds a policy with promoted rules and the console holds it in Monitoring: the listed rules would redact or block, and all of them are logging until it is switched to Enforcing. MONITOR (agent default until the console's first heartbeat) is the same cap before the console has said anything since the agent started; it clears on the first heartbeat.
  • uplink is the findings upload, separate from the heartbeat. It always shows when an upload last succeeded, and reads FAILING (with a warning) when uploads are failing even though the heartbeat is fine, or PAUSED when the workspace has reached its monthly spend limit.
  • A warning that the hook guard and the daemon are running different policies is the one to act on.
  • spool … queued comes from inside the daemon, never from opening the file. A dead-letter N after it counts records the agent stopped trying to upload because the control plane could not store them, even alone, while it stored the records around them, and failed each of them once more right after storing another record. They stay in the spool on this machine for up to 30 days, the rest of the queue keeps draining, and the console will never show them. status lists a non-zero count as a warning. A control plane that is down, or that fails every upload, never moves a record there, whether it stays broken or recovers: the queue waits for it.
  • The routed tooling block lists what points at the capturing proxy. A target reading found, not routed is a tool whose traffic is not being captured, and that is the usual reason a healthy agent has recorded nothing. Run autoconfig apply to route it.
Both commands show the endpoint's mode

The console owns each endpoint's enforcement mode, and the running daemon knows it. status and policy effective both apply it, so an endpoint held at Monitoring whose policy has promoted rules prints MONITOR (console ceiling) followed by what the policy would do, instead of ENFORCING. A policy that only logs has nothing to hold back and prints the same at either mode. With the daemon down, policy effective says the ceiling is unknown. The hook rows in status still show the guard's own policy.

Findings: one JSON object per interaction​

Every inspection appends a line to /var/lib/lumen/findings.jsonl. No matched value is ever stored, whatever the action: a redacted value is stored as its placeholder ([REDACTED:credentials]), and a value that was only logged, including everything in monitor mode, as [MASKED:<label>]. The record keeps the current turn plus the earlier messages a rule matched, at most 64 KB; a cut record carries "text_truncated": true and the original size in text_bytes.

tail -f /var/lib/lumen/findings.jsonl | jq -c \
'{ts, stage, action: .action.effective, classes: [.detections[] | select(.matched) | .class], text}'
{"ts":"2026-07-27T16:41:02Z","stage":"input","action":"log",
"classes":["sensitive_data"],
"text":"deploy fails with key [MASKED:credentials.aws_access_key], what should I check?"}

That is the record the built-in policy writes, since it only logs. With pr_secrets promoted to redact, the same line reads "action":"redact" and the text carries [REDACTED:credentials], which is also what reached the provider.

Monitor mode forwards what it sees

In monitor mode nothing is redacted in flight, so a secret reaches the provider as typed. The record masks it ([MASKED:credentials.aws_access_key]), but the surrounding prompt text is still stored, and a file written by an agent release that predates masking can still hold raw values until it rotates out. The file is created 0600. Treat it accordingly, and promote pr_secrets to redact as soon as its false-positive rate looks acceptable.

Tuning a policy before you enforce​

The findings file is the tuning corpus. In monitor mode every detector your policy references runs and nothing acts, so these queries show what a stricter policy would have done. A detector no rule references does not run, so it leaves nothing here to query: add the rule at log first to size it.

# What is being detected, ranked
jq -r '.detections[] | select(.matched) | .class' findings.jsonl | sort | uniq -c | sort -rn

# Which rule would be doing the work
jq -r 'select(.action.effective!="log") | .action.by_rule' findings.jsonl | sort | uniq -c

# Everything that was blocked, and why
jq -r 'select(.action.effective=="block") | "\(.ts) \(.action.by_rule) \(.action.reason)"' findings.jsonl

Promoting a rule is a one-word change in policy.yaml (action: log → action: redact), the running agent hot-reloads it within about two seconds, and the same request changes answer with no restart. The full flow, and the second gate that applies to enrolled machines, is in From monitor to enforce.

Is it alive?​

lumen-agent status # the whole picture, exit 3 if not
curl -s 127.0.0.1:7645/v1/health | jq # the liveness probe installers use
curl -s 127.0.0.1:7645/v1/policy | jq # which policy version is loaded

Service logs​

PlatformCommand
Linuxjournalctl -u lumen-agent -f
macOStail -f /var/log/lumen/agent.err.log
WindowsGet-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='lumen-agent'} -MaxEvents 25. The service writes its log to the Application event log under the source lumen-agent, which the installer registers. An endpoint last installed by v2.10.0 or earlier needs the installer re-run first. If the service crashed, the reason is in C:\ProgramData\Lumen\state\crash.log (Get-Content C:\ProgramData\Lumen\state\crash.log). Windows is a preview platform

What a healthy first run looks like​

health: ok | version: v2.7.0 | policy: pol_packaged_monitor
verdict: log | the key is AKIAIOSFODNN7EXAMPLE

The credential coming back untouched is the expected first result, not a failure: the shipped policy is monitor mode. The agent saw it, classified it, and recorded it. Promote the rule and the same request changes answer within seconds, with no restart. See From monitor to enforce.