Skip to main content

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 pathRuns 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 packagesNo. Run it by hand as each person
Any install, for a second person on the same machineNo. 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/messages and OpenAI-compatible at /v1/chat/completions, /v1/responses, … are recognized by path. --upstream name=url sends 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-beta or x-api-key header, or a Bearer token starting with sk-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_result is 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_skipped finding, 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
TargetWhat is changed
Shell profileOne 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 Codeenv.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
Zedlanguage_models.{anthropic,openai}.api_url. Opt-in with --only zed, same reason
Claude Code hooksThe 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 serversReported, not rewritten. The wrap command is printed instead

Because this edits other programs' configuration, four rules hold:

  1. 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.
  2. Every change is reversible with revert, which restores the file.
  3. A target already pointed somewhere else is never hijacked. A corporate gateway is reported as a conflict and skipped. --force is required to override.
  4. Redirection fails open, never closed. A stopped, crashed or uninstalled agent must never cut the machine off from its AI providers. apply refuses to route at a proxy that does not answer GET /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:

EventWhat it seesWhat it can do
UserPromptSubmitthe typed prompt, before the modelBlock. The contract offers no rewrite, so Redact blocks and says why
PreToolUsethe tool and its arguments, before executionDeny, or replace the arguments
PostToolUsethe tool's result, before the model reads itBlock, 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. PostToolUse changes what the model reads, not what the command did; stopping the action itself is PreToolUse.

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 DesktopCaptured?
Code tab: prompts, tool calls and tool resultsYes, through the hook guard
Code tab: the model's repliesNo. Hooks never see a reply, and the Code tab ignores ANTHROPIC_BASE_URL, so the proxy is not in its path
ChatNo
CoworkNo. It keeps its own configuration, so ~/.claude hooks do not apply to it

What is captured, and what is not​

SurfaceStatus
Claude Code, and any tool honouring ANTHROPIC_BASE_URLCaptured, prompt and completion, streaming included
What Claude Code does locally, such as commands, writes and fetched pagesCaptured through the hook collector
Claude Desktop's Code tabPrompts and tool calls captured through the hook collector; replies are not
Claude Desktop's chat, Cowork, the ChatGPT desktop appNot captured
Scripts, notebooks, CLIs using OpenAI or Anthropic SDKsCaptured through the shell profile export
MCP tool calls and resultsCaptured when wrapped with lumen-agent mcp
Browser AI, such as ChatGPT and Claude.aiNot yet. That is the browser extension
Closed desktop apps with no base-URL settingNot capturable this way

The honest summary: after autoconfig apply, developer AI traffic on the machine is monitored.