Local proxy & autoconfig
A passive agent monitors nothing: something has to route AI traffic to it.
The agent solves this itself. It runs a local LLM API proxy, and
autoconfig points the machine's AI tooling at it, so no proxy address is
ever edited into a tool by hand.
The daemon and its proxy come up listening, and capture begins when
autoconfig apply runs. Most of what it edits lives in a user's home
directory, so it runs once per person whose tooling should be captured. Who
runs it depends on how the agent was installed:
| Install path | Runs autoconfig apply? |
|---|---|
install.sh (macOS and Linux one-liner) | Yes, once the service is running and, with a token, enrolled (not with --no-service), for the person who ran sudo (or, from a root shell, the person logged in to the session). If it can only find root, it routes root's own tooling and prints a warning with the fix |
install.ps1 (Windows one-liner) | Yes, once the service is running and, with a token, enrolled (not with -NoService), for the account that runs the installer. Windows has no --user, so that is the elevated account: when it is not the person logged in to the desktop, the installer says so and prints the command for that person to run from their own PowerShell |
.deb, .rpm and macOS .pkg packages | No. Run it by hand as each person |
| Any install, for a second person on the same machine | No. Run it as that person, or sudo lumen-agent autoconfig apply --user <login name> |
autoconfig status says which tools are routed, and so does the
routed tooling block of lumen-agent status.
The proxy
client ──► lumen-agent proxy (127.0.0.1:7646) ──► api.anthropic.com
│
├─ prompt inspected before it leaves the machine
├─ completion inspected before the client sees it
└─ finding recorded for both
- Providers. Anthropic at
/v1/messagesand OpenAI-compatible at/v1/chat/completions,/v1/responses, … are recognized by path.--upstream name=urlsends a provider somewhere else. - Other provider endpoints are relayed, not inspected. In a routed shell
every SDK call reaches the proxy, not only chat. A call outside the chat
paths (model lists, files, audio, images, moderations, batches, OpenAI's
Conversations API, Anthropic's Files API) goes unchanged to the vendor its
headers name. An
anthropic-version,anthropic-betaorx-api-keyheader, or a Bearer token starting withsk-ant-, is Anthropic. Any other Bearer token is OpenAI. These calls are not inspected and not recorded, and some of them carry prompt text or file contents, so this is a known capture gap. A request that names neither vendor, and a WebSocket upgrade such as OpenAI's Realtime API, get a 502 that says why. - Redact rewrites the request body, so the secret never reaches the provider. Block returns the vendor's own error shape, so the client reports a normal API failure, not a broken connection.
- Streaming relays SSE through the hold-and-release window. Only the
provably clean prefix is forwarded. Every text block of a message is
inspected, including the answer a model with thinking on writes after its
thinking block and the text before and after a tool call, and the message is
recorded as one reply. Thinking and tool-input deltas pass through
untouched. OpenAI Responses streams (
response.output_text.delta) are inspected too, and their closing events repeat the text as delivered, so a redacted value stays redacted there. A Block cuts the stream off: no tool call the model makes after the blocked text reaches the client. A model that thinks for a long time before it answers, even silently, is inspected all the same. - Provenance-aware. A chat request carries the whole transcript. The
newest user turn is enforced in full. A
tool_resultis judged by response rules, because third-party data is indirect injection. Earlier turns are logged but never blocked. Otherwise one paragraph about prompt injection in the history would wedge the session for good. - Never in the way. Unparseable bodies, other endpoints (above), provider
errors: forwarded unchanged. Oversized bodies over 8 MB are relayed uninspected
with a
capture_skippedfinding, never a 413. - Credentials pass through and are never logged, stored, or written to a finding.
Standalone use, without a service:
lumen-agent proxy # 127.0.0.1:7646
export ANTHROPIC_BASE_URL=http://127.0.0.1:7646
export OPENAI_BASE_URL=http://127.0.0.1:7646/v1
In an install, the daemon serves the proxy whenever the config carries a
proxy: section. One engine, one policy, one findings file.
Autoconfig
lumen-agent autoconfig status # what is here, and what is routed
lumen-agent autoconfig apply # point it at the proxy
lumen-agent autoconfig revert # put everything back
| Target | What is changed |
|---|---|
| Shell profile | One managed block exporting ANTHROPIC_BASE_URL and OPENAI_BASE_URL, guarded by a sub-second health probe. On Windows only where Git Bash has a profile (.bashrc or .bash_profile); PowerShell and cmd read no shell profile, so otherwise it reports that and writes nothing |
| Claude Code | env.ANTHROPIC_BASE_URL in its settings file. Opt-in with --only claude-code, because a JSON setting cannot fail open. The Claude Code CLI only: Claude Desktop's Code tab ignores it, so on a machine with only Claude Desktop it reports not found |
| Zed | language_models.{anthropic,openai}.api_url. Opt-in with --only zed, same reason |
| Claude Code hooks | The guard registered on UserPromptSubmit, PreToolUse, PostToolUse, for the Claude Code CLI and for Claude Desktop's Code tab. On Windows it needs Claude Code 2.1.139 or later |
| MCP servers | Reported, not rewritten. The wrap command is printed instead |
Because this edits other programs' configuration, four rules hold:
- Every change is backed up next to the original. A JSON settings file that does not parse (a trailing comma, comments) is not rewritten at all: that target fails naming the file, the others still run, and the command exits non-zero. A file saved with a UTF-8 byte-order mark, as Windows PowerShell 5.1 writes it, is read normally.
- Every change is reversible with
revert, which restores the file. - A target already pointed somewhere else is never hijacked. A
corporate gateway is reported as a conflict and skipped.
--forceis required to override. - Redirection fails open, never closed. A stopped, crashed or
uninstalled agent must never cut the machine off from its AI providers.
applyrefuses to route at a proxy that does not answerGET /lumen/health. The shell block probes before exporting. The targets that cannot be made conditional are opt-in.
Applying twice changes nothing the second time. autoconfig revert is
always the panic button.
Whose tooling it changes
Run with sudo, autoconfig acts for the person who ran it, not for root. When
sudo itself names only root (a shell opened with sudo -i or sudo su), it
asks the login session instead: the login uid on Linux, the person at the
screen on macOS. lumen-agent whoami prints who that is. If the only account it
can find is root, it warns before acting; name the person explicitly with
--user:
sudo lumen-agent autoconfig apply --user alice
Acting for someone else, the agent changes their files as them, not as root, so those files stay theirs and nothing outside their home can be written on their behalf.
How the console's coverage figure stays current
The agent's service cannot read anyone's home directory, so each person's view
is reported to it: by every autoconfig command, and every hour by the
per-user inventory job the installer schedules. That job runs in the person's
own session (a LaunchAgent on macOS, a systemd --user timer on Linux) and runs
while they are logged in. The installer starts it at once when the person is
logged in (at the screen on macOS, with a running user session on Linux);
otherwise it starts at their next login. It checks against the same agent
config and proxy address the installer routed with. The service keeps the
latest report per person across restarts, and forgets one after a week with no
report (the person has not logged in for that long). Running
lumen-agent autoconfig status refreshes it at once. On a machine guarded
through the enterprise policy file (--managed), the reports count that guard
instead of the person's own.
On Linux endpoints first installed by agent v2.1 to v2.5, the job was written
but never enabled, and upgrading does not fix that. Run
sudo lumen-agent inventory install --user <login name> once per person after
upgrading, or the coverage figure falls back to 0% a week after anyone last ran
an autoconfig command. Uninstalling forgets the stored reports, since they
describe routing the uninstall undoes.
The Claude Code hook collector
The proxy sees the conversation with the model. It cannot see what the agent
then does on the machine. lumen-agent hook covers those moments:
| Event | What it sees | What it can do |
|---|---|---|
UserPromptSubmit | the typed prompt, before the model | Block. The contract offers no rewrite, so Redact blocks and says why |
PreToolUse | the tool and its arguments, before execution | Deny, or replace the arguments |
PostToolUse | the tool's result, before the model reads it | Block, or rewrite. Indirect injection from a fetched page is neutralized here |
At PostToolUse the guard reads the parts of the result the model reads: a
command's output, a file's text, a fetched page, search matches, a subagent's
answer, and every text in an MCP tool's result. Images and PDFs are skipped.
Write and Edit results are not inspected again, because they echo what the
model wrote and PreToolUse already saw that.
- Redact replaces the matched text and hands the result back in the tool's own format, so Claude Code accepts it and the model reads the rest.
- Block withholds the result: the model reads
[Lumen withheld this tool output: <reason>]instead, plus the reason. - The tool has already run by then.
PostToolUsechanges what the model reads, not what the command did; stopping the action itself isPreToolUse.
Agent v2.7.0 and earlier read the result from a field Claude Code does not send, so this checkpoint inspected nothing and recorded nothing. Upgrade the agent to get it.
The guard is a fresh process per event and never talks to the daemon: it keeps enforcing after the daemon is stopped, crashed, or uninstalled. A guard that breaks the session is worse than no guard, so an unparseable payload or an internal error lets the action proceed and reports on stderr.
autoconfig apply --managed registers the guard in Claude Code's
enterprise policy file. It outranks user settings, applies to every user,
and needs administrator rights. That is the difference between a convenience
and a control.
Each record says which client the session ran in, under Entry point in
the console: the Claude Code CLI, claude -p, the Agent SDK, or Claude
Desktop's Code tab.
On Windows
The guard is written as a program with an argument list (Claude Code's exec
form) rather than as one command line. Without Git for Windows, Claude Code
runs a command-line hook through PowerShell, which does not run a line that
starts with a quoted path, so a guard written the old way was registered and
never started. The exec form needs Claude Code 2.1.139 or later. Running
autoconfig apply again rewrites an old guard, and lumen-agent status warns
about any guard still written the old way.
Claude Desktop
Claude Desktop's Code tab runs Claude Code and reads the same
~/.claude/settings.json hooks, so the guard inspects its prompts and tool
calls, with the same Block and Redact, on macOS and Windows. On a machine
with only Claude Desktop, autoconfig apply (which the installers run)
registers the guard even before the Code tab is first opened, and creates the
settings file when it is missing.
| Claude Desktop | Captured? |
|---|---|
| Code tab: prompts, tool calls and tool results | Yes, through the hook guard |
| Code tab: the model's replies | No. Hooks never see a reply, and the Code tab ignores ANTHROPIC_BASE_URL, so the proxy is not in its path |
| Chat | No |
| Cowork | No. It keeps its own configuration, so ~/.claude hooks do not apply to it |
What is captured, and what is not
| Surface | Status |
|---|---|
Claude Code, and any tool honouring ANTHROPIC_BASE_URL | Captured, prompt and completion, streaming included |
| What Claude Code does locally, such as commands, writes and fetched pages | Captured through the hook collector |
| Claude Desktop's Code tab | Prompts and tool calls captured through the hook collector; replies are not |
| Claude Desktop's chat, Cowork, the ChatGPT desktop app | Not captured |
| Scripts, notebooks, CLIs using OpenAI or Anthropic SDKs | Captured through the shell profile export |
| MCP tool calls and results | Captured when wrapped with lumen-agent mcp |
| Browser AI, such as ChatGPT and Claude.ai | Not yet. That is the browser extension |
| Closed desktop apps with no base-URL setting | Not capturable this way |
The honest summary: after autoconfig apply, developer AI traffic on the
machine is monitored.