agent-lookout 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  Changes that a user of Agent Lookout would notice, newest first.
4
4
 
5
+ ## 0.2.5 - 2026-10-07
6
+
7
+ Answer a Claude Code permission prompt from the dashboard, watch the sessions on another machine over SSH, copy the command that resumes a finished session, and set reminders, quiet hours and how long a session is idle before it counts as stale.
8
+
9
+ ### Added
10
+
11
+ - Answer a Claude Code permission prompt from the dashboard. Install the Agent Lookout plugin in Claude Code with `/plugin marketplace add Olanetsoft/agent-lookout` and `/plugin install agent-lookout@agent-lookout`, and a session waiting for permission shows Allow and Deny in Needs you and in its details. For a shell command it shows the whole command, every line, before Allow, and for any other tool its name and every input. Deny comes first and is always offered. Allow is not offered for an edit or a new file, a plan or a question, or a request too long to show whole, with more than two blank lines in a row, with characters that cannot be shown as they are, or, for a command, with right-to-left letters. Neither button takes a press in the first second a request is shown. Agent Lookout allows or denies that one request: it rewrites no command and saves no permission rule. The prompt in the session still works, and whichever is answered first wins. Without Agent Lookout running, the plugin does nothing and Claude Code asks as usual. The Events log records each answer. [Answer a permission prompt](docs/GUIDE.md#answer-a-permission-prompt) has the details.
12
+ - `AGENT_LOOKOUT_ANSWER=off` takes Allow and Deny away, and `AGENT_LOOKOUT_ANSWER_WAIT` sets how long a request waits for an answer from the dashboard, 300 seconds unless you set it.
13
+ - Watch the sessions on another machine you reach with ssh, such as a dev box or a cloud VM, beside this computer's. Start Agent Lookout there as usual, then start it here with `AGENT_LOOKOUT_REMOTES=devbox=dev@devbox.local`, naming more machines with commas, up to eight, and a port after a colon when it is not 4777. Agent Lookout runs your own `ssh`, which signs in with your config and agent and is never asked for a password, in a connection of its own that never joins one you have open and ends when Agent Lookout stops, forwards a port on each side's loopback, and every 2 seconds asks Agent Lookout there for `/api/health` and `/api/sessions`, and nothing else. Its sessions appear in the same lists with the machine's name beside each, and in the Timeline, the Events log, the waits, `agent-lookout status` and `agent-lookout mcp`, waiting ones in Needs you and its notifications, with no Jump, no Stop and no Allow or Deny. Sources has a card for each machine that says Connected, Connecting or Not connected, why and what to do, and What each agent can report a row for each agent there. While a machine is not connected, the header and the hero say so, so the Overview never looks all clear for sessions it cannot see. A dropped connection is started again on its own. A setting that cannot be read is shown in Sources as well as on the console. `AGENT_LOOKOUT_SSH_BIN` names another `ssh`. [Another machine over SSH](docs/GUIDE.md#another-machine-over-ssh) has the details.
14
+ - [Your own agents](docs/GUIDE.md#your-own-agents) has a short example in Python and one in Node.js, with nothing to install, for an agent of your own to copy. Each writes its status file as the work starts, when it waits for you and when it works again, deletes it when the program ends, also on Ctrl-C, `kill` or a closed terminal, and writes its `pid`, so its session goes when it is killed outright too.
15
+ - Resume a Claude Code session that has ended. A background job that finished, failed or was stopped has Resume where a Jump would be, in the Sessions list, on the board and in its details, and a session you stop with Stop has it in its details. Resume copies `cd '<folder>' && claude --resume <session ID>`, which goes to the session's folder and continues its conversation from any folder, in sh, bash or zsh. Agent Lookout runs nothing: it puts the command on the clipboard, shows it on hover and in full in the details, and leaves it on the page to select by hand if the clipboard is refused. A session whose process still runs, a Codex session, a session from a status file and one whose folder holds a backslash or a control character have no Resume. The Mac app's window may now write to the clipboard, and still never reads it. [Resume a session](docs/GUIDE.md#resume-a-session) has the details.
16
+ - Time rules, a card in Settings with three rules, each with its own Off and On, and all off until you turn them on. Agent Lookout keeps them in `~/.agent-lookout/settings.json`, or the file `AGENT_LOOKOUT_SETTINGS_FILE` names, so they hold with no dashboard open and after a restart. They cover the sessions of another machine read over SSH too, by the rules set on this computer. [Time rules](docs/GUIDE.md#time-rules) has the details.
17
+ - Remind me of a long wait tells you again once a session has waited the minutes you set, 10 unless you change them: once for each wait, by each of notifications, email and the webhook that is on for a wait, in words such as "checkout-flow has waited 10 minutes for permission".
18
+ - Mark idle sessions stale sets how long a session is idle before it counts as stale, from an hour to 30 days, 2 days unless you change it. The Stale count, the Timeline and Left running go by it, and so does ending the sessions left running. With it off, a session is stale after a day, as before.
19
+ - Quiet hours hold back every notification, email and webhook post between two times, such as 22:00 to 08:00, past midnight if the end comes first, on the days you tick. When they end, a session still waiting is notified as usual, and the rest comes as one summary on each channel, such as "While quiet: checkout-flow waited 25 minutes and billing-webhooks finished". The summary can leave out the waits that were answered. While they hold, the Time rules card says so, and in the Mac app the notice of a new version waits for them to end.
20
+ - `GET /api/settings` gives the time rules, and `POST /api/settings/time-rules`, which only the dashboard's own page can send, changes them. Each snapshot from `GET /api/sessions` carries the rules it was made by, as `timeRules`, and whether it was made in quiet hours, as `quiet`.
21
+
22
+ ### Changed
23
+
24
+ - Agent Lookout now answers a permission prompt, with the plugin installed, only when you press Allow or Deny, and never on its own. What each agent can report, in Sources, has an Answer column, and Settings has a Permission prompts card that says whether the plugin's requests arrive.
25
+
26
+ ### Fixed
27
+
28
+ - Waits counted a wait that ended with Stop as two waits, one on each side of the stop. It is one wait again, and so is one answered with Allow or Deny.
29
+ - On a phone, Sources split a command in the middle of a flag, `claude agents --json -` on one line and `-all` on the next. A command or a path on any view now breaks only between its words, and a path after a slash, and one too long to sit beside its label takes a line of its own under it.
30
+
5
31
  ## 0.2.4 - 2026-10-06
6
32
 
7
33
  Stop a Claude Code session from its details and end the ones left running, see the pull request and checks for a session's branch, and install a much smaller package with npx.
package/PRIVACY.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # Privacy
2
2
 
3
- Agent Lookout runs on your machine and reads a small amount of metadata about your Claude Code and Codex sessions, and about the sessions of any agent that writes a status file for Agent Lookout to read, and which git branch each session's folder has checked out. By default Agent Lookout itself sends nothing anywhere. Email notifications are off unless you set them up. Once you do, it sends a short email when a session has waited, and, if you choose, when one finishes, fails or ends, through the mail server you name, to the address you name, and nothing else. [Email](#email) says what one holds. Webhook posts are off unless you set an address for them. Once you do, it sends the same notices as short JSON posts to that one address, such as a Slack channel's incoming webhook, and nowhere else. [Webhook](#webhook) says what one holds. The Mac app, and only the Mac app, asks GitHub about once a day whether a newer version of it has been released, unless you turn that off in Settings. It sends nothing about your sessions: [Updates (Mac app only)](#updates-mac-app-only) says what it sends. The npm package, `npx agent-lookout` and the repository never check for anything. Pull requests are off unless you start Agent Lookout with `AGENT_LOOKOUT_PULL_REQUESTS=on`. Then it runs your own GitHub CLI, `gh`, which sends GitHub the owner and name of a session's repository and the name of its branch, with `gh`'s own login, which Agent Lookout never reads: [`gh`](#gh) says what it sends and when. It does run Claude Code's own listing command, which may contact Anthropic the way Claude Code normally does. Its MCP server, `agent-lookout mcp`, runs only once you add it to an agent's app, and answers that app and nothing else. That app may pass what it learns to its model: [The MCP server](#the-mcp-server) says what an answer holds.
3
+ Agent Lookout runs on your machine and reads a small amount of metadata about your Claude Code and Codex sessions, and about the sessions of any agent that writes a status file for Agent Lookout to read, and which git branch each session's folder has checked out. By default Agent Lookout itself sends nothing anywhere. Email notifications are off unless you set them up. Once you do, it sends a short email when a session has waited, and, if you choose, when one finishes, fails or ends, through the mail server you name, to the address you name, and nothing else. [Email](#email) says what one holds. Webhook posts are off unless you set an address for them. Once you do, it sends the same notices as short JSON posts to that one address, such as a Slack channel's incoming webhook, and nowhere else. [Webhook](#webhook) says what one holds. The Mac app, and only the Mac app, asks GitHub about once a day whether a newer version of it has been released, unless you turn that off in Settings. It sends nothing about your sessions: [Updates (Mac app only)](#updates-mac-app-only) says what it sends. The npm package, `npx agent-lookout` and the repository never check for anything. Pull requests are off unless you start Agent Lookout with `AGENT_LOOKOUT_PULL_REQUESTS=on`. Then it runs your own GitHub CLI, `gh`, which sends GitHub the owner and name of a session's repository and the name of its branch, with `gh`'s own login, which Agent Lookout never reads: [`gh`](#gh) says what it sends and when. Other machines are read only once you name them in `AGENT_LOOKOUT_REMOTES`. Then it runs your own `ssh` to each and asks the Agent Lookout running there for its sessions, and sends nothing of this computer's: [Another machine over SSH](#another-machine-over-ssh) says what is read. It does run Claude Code's own listing command, which may contact Anthropic the way Claude Code normally does. Its MCP server, `agent-lookout mcp`, runs only once you add it to an agent's app, and answers that app and nothing else. That app may pass what it learns to its model: [The MCP server](#the-mcp-server) says what an answer holds.
4
4
 
5
5
  For a Claude Code session that is waiting for you, Agent Lookout reads the last message of its transcript, to show what the session is asking, such as the command it wants to run or the question it put to you. It reads nothing of a transcript while the session is not waiting, and keeps nothing of it once the wait ends. It sends that line off the machine only if you turn it on for email, with `AGENT_LOOKOUT_EMAIL_ASKING=on`, or for the webhook, with `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, and then only in the email or post for that wait. No answer of `agent-lookout mcp` holds it. [Claude Code's transcripts](#claude-codes-transcripts) says exactly what is read, and `AGENT_LOOKOUT_WAITING_TEXT=off` turns it off.
6
6
 
7
7
  For Codex, Agent Lookout opens Codex's session files, which hold the whole conversation. It reads them to find when each turn started and ended, and keeps only that and the few fields listed below. It keeps no prompt, reply, command or output.
8
8
 
9
- Agent Lookout stops a Claude Code session only when you press Stop in its details and confirm, or end the sessions left running after a confirmation that lists each one. It never stops a session on its own, and it never answers one. [Stopping a session](#stopping-a-session) says what it checks first and what it runs.
9
+ Agent Lookout stops a Claude Code session only when you press Stop in its details and confirm, or end the sessions left running after a confirmation that lists each one. It answers a Claude Code permission prompt only when you press Allow or Deny, and only with its Claude Code plugin installed. It never acts on a session on its own. [Stopping a session](#stopping-a-session) and [The Claude Code plugin and permission prompts](#the-claude-code-plugin-and-permission-prompts) say what it checks first and what it runs.
10
10
 
11
11
  ## What it reads and runs
12
12
 
@@ -106,7 +106,7 @@ Nothing is done until you press Stop and then Stop session. The page then sends
106
106
 
107
107
  Then, for a session in a terminal or in VS Code, it sends SIGTERM to that one process, by the system call, as `kill -TERM <pid>` would, and looks every 200 milliseconds, for up to 10 seconds, whether it has ended. If it is still running, the page says so. It never sends SIGKILL to a session, and it signals no other session. For a background job, it runs the `claude` binary it found for `claude agents`, as `claude stop <id>`, with the job's ID that `claude agents` gave it and nothing else, started directly, never through a shell, with stdin closed, a 10 second timeout and the same two variables as `claude agents`. If `claude stop` runs past its 10 seconds, Agent Lookout ends that command, and anything it started, with SIGTERM and then SIGKILL, as it does `claude agents` when that runs past its time: that command is Agent Lookout's own, not the session. It never signals a background job's process, which Claude Code's supervisor would start again. Once a job is stopped, it runs `claude agents --json --all` once more at the next poll, so the job shows as stopped.
108
108
 
109
- The sessions left running are the Claude Code sessions that have been idle for 24 hours or more, by their registry file's status time, whose process still runs and which can be stopped. The Sessions card says how many there are, and lists them when you press Review…. End all… lists each one again, up to 20 at a time, and ending them sends the server each session's ID and the moment its idle began, as the page showed it. The server makes every check above for each one, and also checks that its registry file, and its own latest list of sessions, still say it is idle, since that same moment, and that this was 24 hours or more ago. A session that has done anything since is left running.
109
+ The sessions left running are the Claude Code sessions that have been idle for 24 hours or more, or as long as the [idle rule](docs/GUIDE.md#time-rules) says while it is on, by their registry file's status time, whose process still runs and which can be stopped. The Sessions card says how many there are, and lists them when you press Review…. End all… lists each one again, up to 20 at a time, and ending them sends the server each session's ID and the moment its idle began, as the page showed it. The server makes every check above for each one, and also checks that its registry file, and its own latest list of sessions, still say it is idle, since that same moment, and that this was 24 hours or more ago, or as long ago as the idle rule says. A session that has done anything since is left running.
110
110
 
111
111
  Stopping keeps the conversation: it touches no transcript and no file under `~/.claude`, and a registry file left behind is not deleted. The Events log keeps one event for each session stopped, with its ID, its name, the status it had and that Agent Lookout stopped it, as [The history](#the-history) says. With `AGENT_LOOKOUT_STOP=off`, nothing is offered Stop, and the server answers no request to stop a session.
112
112
 
@@ -190,9 +190,33 @@ With notifications on, and no dashboard page open to show one, Agent Lookout sho
190
190
 
191
191
  Only when you start it with `agent-lookout --open`, Agent Lookout runs `/usr/bin/open` on macOS, or `xdg-open` on Linux, once, to open its own address in your default browser. It gives it one argument, the address it listens on, such as `http://127.0.0.1:4777`, and nothing else. The program is started directly, never through a shell, with nothing connected to its input or output. Agent Lookout waits up to 10 seconds for it to end, to tell whether it worked, and never stops it: `xdg-open` can keep running until the browser it started is closed. Without `--open`, neither is ever run.
192
192
 
193
+ ### The Claude Code plugin and permission prompts
194
+
195
+ Agent Lookout can answer a Claude Code permission prompt only once you install its plugin in Claude Code, which you do yourself, in Claude Code, with `/plugin marketplace add Olanetsoft/agent-lookout` and `/plugin install agent-lookout@agent-lookout`. Claude Code records that in its own settings. Agent Lookout writes nothing under `~/.claude`, and `/plugin uninstall agent-lookout@agent-lookout` takes the plugin away again.
196
+
197
+ The plugin is one hook, a short `sh` script, which Claude Code runs as it is about to ask you for permission, while it shows its own prompt. The script checks for Agent Lookout's socket, `~/.agent-lookout/answer.sock`, or the one `AGENT_LOOKOUT_ANSWER_SOCKET` names, and with none there it exits at once and prints nothing. Otherwise it runs `curl` once, which sends the request over that socket, on this computer and nowhere else, and prints the answer, if there is one. What Claude Code hands the hook, and so what the request holds, is the session's ID, the path of its transcript, its working folder, its scratchpad folder, the ID of the prompt being answered, its permission mode, the tool's name, the tool's input, which is the whole command for a shell command and the whole file for a new file, and the permission rules Claude Code suggests. Agent Lookout reads it as it arrives, keeps the session's ID, the tool's name, what it shows of the input, and whether a subagent asks, and lets the rest go at once. It takes no request larger than 1 MiB. A request is held only once the registry file of a session Agent Lookout lists says it waits for permission, which it must say within 3 seconds, so a request for any other session is let go then, answering nothing, and is never shown.
198
+
199
+ Agent Lookout listens on that socket while it runs, unless you start it with `AGENT_LOOKOUT_ANSWER=off`, or with `AGENT_LOOKOUT_CLAUDE_HOME` set and `AGENT_LOOKOUT_ANSWER_SOCKET` not. It makes the folder with mode 700 if it is not there, and gives its own folder, `~/.agent-lookout`, mode 700 if others could open it. A folder that `AGENT_LOOKOUT_ANSWER_SOCKET` names, which it did not make, it never changes: if others can open it, answering is off, and Settings says why. It gives the socket mode 600, so only your user can connect. A browser cannot reach a socket. It removes the socket when it stops, and one left by a copy that crashed is replaced. A second copy that finds the first answering there leaves it be, and its Settings say so.
200
+
201
+ While the session waits, Agent Lookout holds the request. It reads the session's registry file, `~/.claude/sessions/<pid>.json`, again every half second, as an ordinary file it opens without following a link, and lets the request go, answering nothing, as soon as the file no longer says the session is waiting for permission, or says it is waiting again in a later wait, which is what happens when you answer in the session. It also lets it go when the hook's connection closes, when a newer request of the same session arrives, and after `AGENT_LOOKOUT_ANSWER_WAIT` seconds, 300 unless you set it. The prompt in the session then decides, as it would without the plugin. What it shows of the request, the whole command or the tool and its inputs, is held in memory while the request is held. It goes to the dashboard in the session list, and nowhere else: it is never written to disk and never put in an event, the history, a notification, an email, a webhook post, `agent-lookout status` or an answer of `agent-lookout mcp`.
202
+
203
+ It answers only when you press Allow or Deny on the dashboard. The page sends the session's ID, Agent Lookout's own ID for the request, and the answer. Agent Lookout reads the registry file once more, and answers only if it still says the session is waiting for permission, in the same wait the request was shown in. The answer is one of two fixed texts, allow, or deny with a fixed sentence that says you denied it from Agent Lookout. It never changes the command or the tool's input, and never saves a permission rule, so each Allow allows that one request. Allow is offered only when the whole of what it allows is shown, and not for an edit or a new file, a plan or a question, a request too long to show whole or with more than two blank lines in a row, one with characters that cannot be shown as they are, or a command with right-to-left letters. The Events log keeps one event for each answer, with the session's ID and name, and whether it was allowed or denied, and nothing of what was asked.
204
+
205
+ Settings says whether a request has reached Agent Lookout. To tell, it notes when a Claude Code session waiting for permission sent no request within 5 seconds, which is what a session without the plugin does, and keeps that time and the time of the last request, in memory only.
206
+
207
+ ### Another machine over SSH
208
+
209
+ Only when `AGENT_LOOKOUT_REMOTES` names other machines, Agent Lookout reads the sessions of the Agent Lookout running on each, through an SSH tunnel it opens with your own `ssh`. Unset, it never looks for `ssh`.
210
+
211
+ For each machine it runs `ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ControlMaster=no -o ControlPath=none -L 127.0.0.1:<a free port>:127.0.0.1:<its port> -- <target>`, with the target you named, such as `dev@devbox.local`, after `--`, and with the port Agent Lookout listens on there, 4777 unless you give another. `ssh` is the one on your `PATH`, or in `/usr/bin`, `/opt/homebrew/bin` or `/usr/local/bin`, or the one `AGENT_LOOKOUT_SSH_BIN` names. It is started directly, by its full path, never through a shell, with nothing on its input, and given the environment Agent Lookout was started with, so it uses your `~/.ssh/config` and your ssh agent as it does in a terminal. With BatchMode it asks nothing, so Agent Lookout never sees, types or keeps a password, a passphrase or a key, and never answers a question about a host key. ssh runs no command on the other machine: it forwards one port on this computer's `127.0.0.1` to Agent Lookout's port on that machine's own `127.0.0.1`. With `ControlMaster=no` and `ControlPath=none` it makes a connection of its own every time, even when your ssh config shares connections, so it never joins an ssh you have open and never leaves one running in the background: the forward ends when that ssh ends. Agent Lookout keeps the last lines ssh writes about a failure, in memory, and shows the last of them on the machine's card in Sources, such as `Permission denied (publickey).` When ssh ends, it is started again after a wait, and when Agent Lookout stops, it ends it.
212
+
213
+ Through that port, every 2 seconds, it sends two requests and no others: `GET /api/health`, whose answer is the version of Agent Lookout there, and `GET /api/sessions`, whose answer is that machine's sessions and sources, as [docs/API.md](docs/API.md#get-apisessions) describes. The requests carry nothing of this computer's: no session, no setting, and not the `X-Agent-Lookout-Notifications` header, so they change nothing that machine's Agent Lookout believes about its own pages. Of the answer it keeps, in memory, what this computer shows of its own sessions: each session's name, folder, status, reason, the vendor's wording of a wait, its times, its app, its branch and repository name, its agent's name and whether its process still runs. It drops each session's process ID, Jump, Stop and links, and any permission request that machine holds for it, with what the request would run, as the answer is read, so nothing about a session there can act on this computer. It keeps each source there by its name and its state, and what each of its agents can report. It does not take that machine's time rules, or its word on whether it is in quiet hours: this computer's own decide when a session there is stale, and when a wait there is reminded of or held back here.
214
+
215
+ That includes what a waiting session there is asking, such as `Run: npm test`, when that machine's Agent Lookout sends it, which it does unless `AGENT_LOOKOUT_WAITING_TEXT=off` is set there. With `AGENT_LOOKOUT_WAITING_TEXT=off` set on this computer, it is dropped as it arrives, and a waiting session there shows its reason alone, as one here does. Otherwise it is shown here as a waiting session's is, and in the notifications on this computer, and is kept no longer than the wait, as here. Like a session's on this computer, a session there and its events go into the history kept on this computer, without what it is asking, and into an email or a webhook post as the settings for those say.
216
+
193
217
  ### Settings
194
218
 
195
- It reads twenty-six settings from the environment: `AGENT_LOOKOUT_CLAUDE_HOME`, `AGENT_LOOKOUT_CLAUDE_BIN`, `AGENT_LOOKOUT_CLAUDE_FEED`, `AGENT_LOOKOUT_WAITING_TEXT`, `AGENT_LOOKOUT_CODEX_HOME`, `AGENT_LOOKOUT_STATUS_DIR`, `AGENT_LOOKOUT_HISTORY`, `AGENT_LOOKOUT_HISTORY_DIR`, `AGENT_LOOKOUT_NOTIFICATIONS`, `AGENT_LOOKOUT_TMUX`, `AGENT_LOOKOUT_TERMINAL_JUMP`, `AGENT_LOOKOUT_STOP`, `AGENT_LOOKOUT_PULL_REQUESTS`, `AGENT_LOOKOUT_PORT`, `AGENT_LOOKOUT_HOST`, Codex's own `CODEX_HOME`, the six email settings, `AGENT_LOOKOUT_EMAIL_TO`, `AGENT_LOOKOUT_SMTP_URL`, `AGENT_LOOKOUT_EMAIL_FROM`, `AGENT_LOOKOUT_EMAIL_AFTER`, `AGENT_LOOKOUT_EMAIL_EVENTS` and `AGENT_LOOKOUT_EMAIL_ASKING`, and the four webhook settings, `AGENT_LOOKOUT_WEBHOOK_URL`, `AGENT_LOOKOUT_WEBHOOK_EVENTS`, `AGENT_LOOKOUT_WEBHOOK_AFTER` and `AGENT_LOOKOUT_WEBHOOK_ASKING`. The `agent-lookout` command reads one more, `AGENT_LOOKOUT_URL`, the address of the Agent Lookout to ask, and `agent-lookout status` reads tmux's own `TMUX`, only to tell whether tmux is running it. `AGENT_LOOKOUT_CLAUDE_HOME` replaces `~/.claude` in everything above. [Email](#email) says how the email settings are kept, and [Webhook](#webhook) how the webhook's are. The [guide](docs/GUIDE.md#settings-you-can-change) says what each setting does.
219
+ It reads thirty-two settings from the environment: `AGENT_LOOKOUT_CLAUDE_HOME`, `AGENT_LOOKOUT_CLAUDE_BIN`, `AGENT_LOOKOUT_CLAUDE_FEED`, `AGENT_LOOKOUT_WAITING_TEXT`, `AGENT_LOOKOUT_CODEX_HOME`, `AGENT_LOOKOUT_STATUS_DIR`, `AGENT_LOOKOUT_HISTORY`, `AGENT_LOOKOUT_HISTORY_DIR`, `AGENT_LOOKOUT_SETTINGS_FILE`, `AGENT_LOOKOUT_NOTIFICATIONS`, `AGENT_LOOKOUT_TMUX`, `AGENT_LOOKOUT_TERMINAL_JUMP`, `AGENT_LOOKOUT_STOP`, `AGENT_LOOKOUT_PULL_REQUESTS`, `AGENT_LOOKOUT_ANSWER`, `AGENT_LOOKOUT_ANSWER_SOCKET`, `AGENT_LOOKOUT_ANSWER_WAIT`, `AGENT_LOOKOUT_PORT`, `AGENT_LOOKOUT_HOST`, `AGENT_LOOKOUT_REMOTES`, `AGENT_LOOKOUT_SSH_BIN`, Codex's own `CODEX_HOME`, the six email settings, `AGENT_LOOKOUT_EMAIL_TO`, `AGENT_LOOKOUT_SMTP_URL`, `AGENT_LOOKOUT_EMAIL_FROM`, `AGENT_LOOKOUT_EMAIL_AFTER`, `AGENT_LOOKOUT_EMAIL_EVENTS` and `AGENT_LOOKOUT_EMAIL_ASKING`, and the four webhook settings, `AGENT_LOOKOUT_WEBHOOK_URL`, `AGENT_LOOKOUT_WEBHOOK_EVENTS`, `AGENT_LOOKOUT_WEBHOOK_AFTER` and `AGENT_LOOKOUT_WEBHOOK_ASKING`. The `agent-lookout` command reads one more, `AGENT_LOOKOUT_URL`, the address of the Agent Lookout to ask, and `agent-lookout status` reads tmux's own `TMUX`, only to tell whether tmux is running it. `AGENT_LOOKOUT_CLAUDE_HOME` replaces `~/.claude` in everything above. [Email](#email) says how the email settings are kept, and [Webhook](#webhook) how the webhook's are. The time rules are not in the environment: Agent Lookout keeps them in a file of its own, as [The settings file](#the-settings-file) says. The [guide](docs/GUIDE.md#settings-you-can-change) says what each setting does.
196
220
 
197
221
  ## What it never reads
198
222
 
@@ -202,13 +226,14 @@ It reads twenty-six settings from the environment: `AGENT_LOOKOUT_CLAUDE_HOME`,
202
226
  - Codex's `auth.json`, `config.toml`, `history.jsonl`, its SQLite files (`*.sqlite`), its `log/` folder, `archived_sessions/` and compressed session files (`*.jsonl.zst`), and the Codex desktop app's `external_agent_session_imports.json` and `.codex-global-state.json`.
203
227
  - In a git repository, anything but the `.git` file, `HEAD` and a worktree's `commondir` described under [Git repositories](#git-repositories), and with `AGENT_LOOKOUT_PULL_REQUESTS=on`, the `config` and the remote's `HEAD` described under [`gh`](#gh).
204
228
  - `gh`'s configuration, its login and any token, and your global git configuration.
229
+ - Your `~/.ssh` folder: keys, `known_hosts` and ssh config. For the machines `AGENT_LOOKOUT_REMOTES` names, `ssh` reads them itself, as it does in a terminal.
205
230
  - The files of any other application.
206
231
 
207
- It runs no program but the `claude` binary, for `claude agents` and, only when you stop a background job, `claude stop`, `ps`, `tmux`, on macOS `osascript`, to show a notification and to bring a tab of Terminal or iTerm2 forward, only for `--open`, `open` or `xdg-open`, and only with `AGENT_LOOKOUT_PULL_REQUESTS=on`, `gh`. The Mac app also runs `ditto` and `plutil` to unpack and check a newer version of itself, and, only when you press Install and Restart, a short `sh` script that puts it in place, as [Updates (Mac app only)](#updates-mac-app-only) describes. It runs no git command. It never writes to `~/.claude`, to the Codex folder, to the folder of status files, to any git repository or to any agent tool's files, and it never sends input to a session. It never makes, renames or deletes a status file. It changes two things outside itself, and each only when you ask: which tmux pane, window and session are selected, or which tab of Terminal or iTerm2 is in front, when you press Jump, and, when you press Stop and confirm, whether a Claude Code session's process runs, as [Stopping a session](#stopping-a-session) describes.
232
+ It runs no program but the `claude` binary, for `claude agents` and, only when you stop a background job, `claude stop`, `ps`, `tmux`, on macOS `osascript`, to show a notification and to bring a tab of Terminal or iTerm2 forward, only for `--open`, `open` or `xdg-open`, only with `AGENT_LOOKOUT_PULL_REQUESTS=on`, `gh`, and, only for the machines `AGENT_LOOKOUT_REMOTES` names, `ssh`, as [Another machine over SSH](#another-machine-over-ssh) describes. The Mac app also runs `ditto` and `plutil` to unpack and check a newer version of itself, and, only when you press Install and Restart, a short `sh` script that puts it in place, as [Updates (Mac app only)](#updates-mac-app-only) describes. It runs no git command. The plugin's hook, which Claude Code runs and not Agent Lookout, runs `curl`. It never writes to `~/.claude`, to the Codex folder, to the folder of status files, to any git repository or to any agent tool's files, and it never sends input to a session. It never makes, renames or deletes a status file. It changes three things outside itself, and each only when you ask: which tmux pane, window and session are selected, or which tab of Terminal or iTerm2 is in front, when you press Jump, when you press Stop and confirm, whether a Claude Code session's process runs, as [Stopping a session](#stopping-a-session) describes, and, when you press Allow or Deny, the answer to that one permission prompt, as [The Claude Code plugin and permission prompts](#the-claude-code-plugin-and-permission-prompts) describes. It never sends a session anything else.
208
233
 
209
234
  ## Network
210
235
 
211
- With email and the webhook off, which is the default, the only network traffic Agent Lookout's own code makes is between its own server and the dashboard in your browser, or the `agent-lookout status` and `agent-lookout mcp` commands, on the same machine. With email set up, it also connects to the mail server you named, once for each email, as [Email](#email) describes. With a webhook address set, it also connects to that address, once for each post, as [Webhook](#webhook) describes. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, it runs `gh`, which connects to GitHub, at most once every 2 minutes for each repository and branch your sessions are on, as [`gh`](#gh) describes. One more connection never leaves the machine either: before `npm start` or `agent-lookout` begins listening, it connects once to its own address and port to see whether another program already answers there, and sends nothing over that connection. There is no telemetry, no analytics, no crash reporting and no account. There is no update check either, except in the Mac app, which asks GitHub about once a day whether a newer version of it is out, as [Updates (Mac app only)](#updates-mac-app-only) describes. Fonts and scripts are bundled, so the page loads nothing from the internet.
236
+ With email, the webhook and other machines off, which is the default, the only network traffic Agent Lookout's own code makes is between its own server and the dashboard in your browser, or the `agent-lookout status` and `agent-lookout mcp` commands, on the same machine. With email set up, it also connects to the mail server you named, once for each email, as [Email](#email) describes. With a webhook address set, it also connects to that address, once for each post, as [Webhook](#webhook) describes. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, it runs `gh`, which connects to GitHub, at most once every 2 minutes for each repository and branch your sessions are on, as [`gh`](#gh) describes. With other machines named in `AGENT_LOOKOUT_REMOTES`, your own `ssh` connects to each, and Agent Lookout sends its two requests to Agent Lookout there through that connection, as [Another machine over SSH](#another-machine-over-ssh) describes. Agent Lookout itself connects only to the end of the tunnel on this computer's `127.0.0.1`. One more connection never leaves the machine either: before `npm start` or `agent-lookout` begins listening, it connects once to its own address and port to see whether another program already answers there, and sends nothing over that connection. There is no telemetry, no analytics, no crash reporting and no account. There is no update check either, except in the Mac app, which asks GitHub about once a day whether a newer version of it is out, as [Updates (Mac app only)](#updates-mac-app-only) describes. Fonts and scripts are bundled, so the page loads nothing from the internet.
212
237
 
213
238
  Started with `npx agent-lookout`, npm itself contacts its registry: to download the package the first time, and each time after to ask whether a newer version is out, as it does for any package it runs. That is npm's own traffic, not Agent Lookout's. Installed with `npm install -g agent-lookout` and started as `agent-lookout`, npm is not involved.
214
239
 
@@ -218,11 +243,11 @@ The `claude agents` command is Claude Code's own program, and it may contact Ant
218
243
 
219
244
  The API refuses any request whose `Host` header is not `localhost`, `127.0.0.1` or `[::1]`, any request whose `Origin` header names another site, and any request the browser marks as cross-site. It sends no CORS headers. Together these stop a website you visit from reading your session names and paths through your browser.
220
245
 
221
- Each request the dashboard makes carries one header of Agent Lookout's own, `X-Agent-Lookout-Notifications`, which says which events that page has notifications on for: `off`, `on`, which means a session starting to wait, or `on; events=` followed by the names of the events, such as `on; events=needs-you,finished`. It holds nothing else. It goes to Agent Lookout's own server and nowhere else. [Notifications](#notifications) says what the server does with it. The request the Jump button sends for a session in tmux, Terminal or iTerm2 carries one more, `X-Agent-Lookout-Action: jump`, which names what is asked for, and the request the Clear history button in Settings sends carries `X-Agent-Lookout-Action: clear-history`, with an empty body. The request Stop session sends carries `X-Agent-Lookout-Action: stop` and the session's ID, and the one that ends the sessions left running carries `X-Agent-Lookout-Action: clean-up`, with each session's ID and the moment its idle began.
246
+ Each request the dashboard makes carries one header of Agent Lookout's own, `X-Agent-Lookout-Notifications`, which says which events that page has notifications on for: `off`, `on`, which means a session starting to wait, or `on; events=` followed by the names of the events, such as `on; events=needs-you,finished`. It holds nothing else. It goes to Agent Lookout's own server and nowhere else. [Notifications](#notifications) says what the server does with it. The request the Jump button sends for a session in tmux, Terminal or iTerm2 carries one more, `X-Agent-Lookout-Action: jump`, which names what is asked for, and the request the Clear history button in Settings sends carries `X-Agent-Lookout-Action: clear-history`, with an empty body. The request Stop session sends carries `X-Agent-Lookout-Action: stop` and the session's ID, and the one that ends the sessions left running carries `X-Agent-Lookout-Action: clean-up`, with each session's ID and the moment its idle began. The one Allow and Deny send carries `X-Agent-Lookout-Action: answer`, with the session's ID, Agent Lookout's own ID for the request and the answer.
222
247
 
223
- The local server has no password. While Agent Lookout is running, another program on the same machine can request the same data, and on a shared computer so can another user account. Such a program can also send that header, and so turn the notifications the server shows on or off. It can also ask the server to select a tmux pane, or bring a tab of Terminal or iTerm2 forward, as the Jump button does, to clear the history, as the Clear history button does, and to stop a Claude Code session, or end the sessions left running, as Stop and End all… do, with every check of [Stopping a session](#stopping-a-session). The Mac app opens no port, so there only its own window can do any of this. `AGENT_LOOKOUT_STOP=off` takes stopping away.
248
+ The local server has no password. While Agent Lookout is running, another program on the same machine can request the same data, and on a shared computer so can another user account. Such a program can also send that header, and so turn the notifications the server shows on or off. It can also ask the server to select a tmux pane, or bring a tab of Terminal or iTerm2 forward, as the Jump button does, to clear the history, as the Clear history button does, to stop a Claude Code session, or end the sessions left running, as Stop and End all… do, with every check of [Stopping a session](#stopping-a-session), and to answer a permission prompt Agent Lookout is holding, as Allow and Deny do. A program that can do that can already type into your terminal, but on a shared computer another account could not. The Mac app opens no port, so there only its own window can do any of this. `AGENT_LOOKOUT_STOP=off` takes stopping away, and `AGENT_LOOKOUT_ANSWER=off` takes answering away.
224
249
 
225
- For a session in VS Code, the Jump button opens a `vscode://` address that contains the session ID. Your operating system hands it to VS Code. For a session in tmux, the Jump button sends a request that holds the session's ID to Agent Lookout's own server, which selects the pane as described under [`tmux`](#tmux). For a session in Terminal or iTerm2 it sends the same request, and the server brings the tab forward as described under [Terminal and iTerm2](#terminal-and-iterm2). That request, the one Clear history sends and the ones Stop session and End send are the only ones that change anything, and the server answers them only for a page served from this machine. [SECURITY.md](SECURITY.md) lists its checks.
250
+ For a session in VS Code, the Jump button opens a `vscode://` address that contains the session ID. Your operating system hands it to VS Code. For a session in tmux, the Jump button sends a request that holds the session's ID to Agent Lookout's own server, which selects the pane as described under [`tmux`](#tmux). For a session in Terminal or iTerm2 it sends the same request, and the server brings the tab forward as described under [Terminal and iTerm2](#terminal-and-iterm2). That request, the one Clear history sends, the ones Stop session and End send and the one Allow and Deny send are the only ones that change anything, and the server answers them only for a page served from this machine. [SECURITY.md](SECURITY.md) lists its checks.
226
251
 
227
252
  ## Notifications
228
253
 
@@ -236,9 +261,9 @@ A notification is made in one of two ways. While a dashboard page is open, the p
236
261
 
237
262
  With notifications on, when one of the events switched on happens, the dashboard page makes a notification through the browser's Notifications API, and the browser hands it to the operating system to show. It holds:
238
263
 
239
- - the session's name, as its title
264
+ - the session's name, as its title, followed by "on" and the machine's name for a session on another machine
240
265
  - what happened, as its text: for a session that starts waiting, the reason, "Waiting for permission", "Asked you a question" or "Waiting for you", followed, for a Claude Code session, by what it is asking when its transcript says, as in "Waiting for permission: Run: npm test", and otherwise "Finished", "Failed" or "Ended"
241
- - a tag that is not shown, which the browser uses to keep one notification for each session: `agent-lookout:claude-code:` followed by the session's ID, or by its job ID or process ID when it has no session ID, for Codex `agent-lookout:codex:` followed by the session's ID, and for a status file `agent-lookout:status-files:` followed by the file's name
266
+ - a tag that is not shown, which the browser uses to keep one notification for each session: `agent-lookout:claude-code:` followed by the session's ID, or by its job ID or process ID when it has no session ID, for Codex `agent-lookout:codex:` followed by the session's ID, for a status file `agent-lookout:status-files:` followed by the file's name, and for a session on another machine `agent-lookout:remote:` followed by the machine's name and the session's own tag there
242
267
 
243
268
  It holds none of Claude Code's own wording for the wait, and not the session's folder. What a session is asking can hold a command, which can name any path, a web address, or a file: relative to the session's folder when the file is inside it, and the file's full path when it is outside it, which can name your home folder. No push service, no service worker and no network request is involved. The page that is open in your browser makes the notification, and can do so only while it is open.
244
269
 
@@ -252,11 +277,15 @@ The browser gives its permission to the address, such as `localhost:5173`, not t
252
277
 
253
278
  The local server shows notifications on this machine too. When one of the events switched on happens and no dashboard page is open to show it, the server runs `osascript`, as [described above](#osascript), and macOS shows the notification. It holds:
254
279
 
255
- - the session's name, as its title
280
+ - the session's name, as its title, with the machine's name for a session on another machine
256
281
  - what happened, as its text, in the same wordings, with what a waiting session is asking
257
282
 
258
283
  It holds none of Claude Code's own wording and no tag. As in the page's, what a session is asking can hold a command, a web address, or a file's full path when the file is outside the session's folder. No network request is involved. On any system but macOS the server shows nothing.
259
284
 
285
+ ### Reminders and summaries
286
+
287
+ With the [time rules](docs/GUIDE.md#time-rules) on, the page and the server make two more kinds of notification, in the same two ways and by the same switches. A reminder of a long wait has the session's name as its title, with the machine's name for a session on another machine, and as its text how long it has waited and for what, with what it is asking after it, as in "Has waited 10 minutes for permission: Run: npm test". On the page it takes the place of the wait's own, with its tag. The summary made when quiet hours end has "While quiet" as its title, and as its text the names of the sessions that waited, finished, failed or ended while they held, each with the machine's name for a session on another machine, with how long each waited, as in "checkout-flow waited 25 minutes and billing-webhooks finished". It holds nothing a session was asking. The page's has the tag `agent-lookout:while-quiet`, and stays until you clear it. During quiet hours neither the page nor the server makes any notification.
288
+
260
289
  The server shows them only while notifications are on, and only for the events switched on, and it learns both from the dashboard. Each request a dashboard page makes says, in the `X-Agent-Lookout-Notifications` header, which events are on when that page's choice is on and the browser allows notifications, and `off` otherwise. The server keeps the last thing a page said, in memory, for as long as it runs, and never writes it to disk. Before any page has said anything it is off, unless `AGENT_LOOKOUT_NOTIFICATIONS=on` was set when Agent Lookout started, which turns it on for a session starting to wait. A page that says `off` after that turns it off all the same. The header is listened to only on a request that passes the checks described under [Network](#network), so a page at another address cannot set it.
261
290
 
262
291
  While a page that has notifications on is open and asking every 2 seconds, the page shows each notification and the server does not. The server holds its own back for a few seconds when such a page has just been asking, and drops it once that page has fetched the sessions.
@@ -282,6 +311,8 @@ An email is plain text. Its subject is the session's name followed by the reason
282
311
  - the agent, such as Claude Code or Codex, or the agent a status file names
283
312
  - one line saying Agent Lookout sent it and how to stop these emails
284
313
 
314
+ With the [time rules](docs/GUIDE.md#time-rules) on, two more kinds of email can be sent. A reminder of a long wait is the email of a wait with a subject that says how long it has waited, such as "checkout-flow has waited 10 minutes for permission", and a line on how often reminders come. The summary sent when quiet hours end has the subject "While quiet:" followed by the first few sessions' names and what happened, and in its body, one line for each session that waited, finished, failed or ended while they held, with how long it waited and from when, or when it finished, failed or ended, at most 50, and a line on where quiet hours are set. A summary holds the sessions' names alone, nothing of what they were asking and no folder.
315
+
285
316
  It holds no folder path, no prompt, none of Claude Code's own wording for the wait, no link, no image, nothing that reports back when it is opened and, unless you set `AGENT_LOOKOUT_EMAIL_ASKING=on` (below), nothing of what a waiting session is asking. Its headers are the ones the mail library writes for every email: From, with the name Agent Lookout and the sender's address, To, Subject, Date, Message-ID, and three that say it is plain text in UTF-8. A session's name and its folder's name are cut to 80 characters, and anything in them that would end a line becomes a space, so nothing in a name can add a header or a recipient.
286
317
 
287
318
  #### What a waiting session is asking, in an email
@@ -348,6 +379,43 @@ It holds:
348
379
  - `at`: when it happened, as a time in UTC: when the wait began, or when Agent Lookout saw the session finish, fail or end.
349
380
  - `waitedSeconds`, for a wait only: how long it had waited when it was posted.
350
381
 
382
+ With the [time rules](docs/GUIDE.md#time-rules) on, two more kinds of post can be sent. A reminder of a long wait is the post of a wait, with the line saying how long it has waited, such as "checkout-flow has waited 10 minutes for permission", and one more field, `"reminder": true`. The summary sent when quiet hours end looks like this:
383
+
384
+ ```json
385
+ {
386
+ "text": "While quiet: checkout-flow waited 25 minutes and billing-webhooks finished",
387
+ "event": "quiet-summary",
388
+ "from": "2026-10-05T21:00:00.000Z",
389
+ "to": "2026-10-06T07:00:00.000Z",
390
+ "items": [
391
+ {
392
+ "event": "needs-you",
393
+ "session": {
394
+ "name": "checkout-flow",
395
+ "agent": "Claude Code",
396
+ "folder": "storefront",
397
+ "app": "VS Code"
398
+ },
399
+ "at": "2026-10-05T22:10:00.000Z",
400
+ "waitedSeconds": 1500,
401
+ "times": 1
402
+ },
403
+ {
404
+ "event": "finished",
405
+ "session": {
406
+ "name": "billing-webhooks",
407
+ "agent": "Claude Code",
408
+ "folder": "billing",
409
+ "app": "Terminal"
410
+ },
411
+ "at": "2026-10-06T00:12:00.000Z"
412
+ }
413
+ ]
414
+ }
415
+ ```
416
+
417
+ `from` and `to` are when Agent Lookout saw the quiet hours begin and end, and `items` holds each session that waited, finished, failed or ended while they held, at most 100, with `more` saying how many were left out. A session that waited and then finished, failed or ended is one item, whose `then` says what it did and when, as `{"event": "ended", "at": "..."}`. Each item's `session` holds what a post's always does, and no item holds anything a session was asking.
418
+
351
419
  It holds no folder path, no prompt, none of Claude Code's own wording for the wait, no session ID or process ID, nothing about this computer and, unless you set `AGENT_LOOKOUT_WEBHOOK_ASKING=on` (below), nothing of what a waiting session is asking. Its headers are `Content-Type: application/json`, `Content-Length`, `User-Agent: Agent Lookout/` followed by the version, `Host` and `Connection: close`. It carries no cookie and no other header.
352
420
 
353
421
  A session's name, its folder's name and its agent's are each cut to 80 characters, and anything in them that would end a line becomes a space. In `text`, `<`, `>` and `&` are written as `&lt;`, `&gt;` and `&amp;`, which Slack shows as the characters themselves, and each `@` is followed by a space of no width, so nothing in a name can mention someone, ping a channel or make a link that shows other words than its address. The JSON is written whole by Node's own writer, so nothing in a name can add a field. Slack does show a web address written out in a name as a link to that address.
@@ -423,7 +491,7 @@ For each tool the agent calls, it asks Agent Lookout's own server for the list o
423
491
 
424
492
  It answers the app that started it, over stdout, and nothing and nobody else. An answer is one of three:
425
493
 
426
- - For each session: its ID, its name, its agent, its status, why it waits, the name of its folder and never the rest of the path, its branch or commit, its app, when its status began, and how long its agent has written nothing. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, also the number of its branch's pull request and whether its checks are failing, pending or passing, and never its title. Never what a waiting session is asking, which is read from its transcript.
494
+ - For each session: its ID, its name, its agent, the other machine it runs on, when it runs on one read over SSH, its status, why it waits, the name of its folder and never the rest of the path, its branch or commit, its app, when its status began, and how long its agent has written nothing. With `AGENT_LOOKOUT_PULL_REQUESTS=on`, also the number of its branch's pull request and whether its checks are failing, pending or passing, and never its title. Never what a waiting session is asking, which is read from its transcript.
427
495
  - For each session that needs you: the same, with how long it has waited, and one sentence naming each of them and any agent that could not be read.
428
496
  - For each source: its state, the sentence that says how it is read or what went wrong, which can name the folders Agent Lookout reads, such as `~/.claude/sessions`, and what its agent can report.
429
497
 
@@ -439,9 +507,9 @@ Nothing. Each answer is worked out from one reading of the list and is gone once
439
507
 
440
508
  ## Storage
441
509
 
442
- Agent Lookout keeps one thing about your sessions on disk: the history behind the Events log and the charts, so that they are still there after it restarts. [The history](#the-history) says what it holds, where it is, how long it is kept, how to clear it and how to turn it off. Apart from that, its own code writes no files, except the Mac app's, below.
510
+ Agent Lookout keeps one thing about your sessions on disk: the history behind the Events log and the charts, so that they are still there after it restarts. [The history](#the-history) says what it holds, where it is, how long it is kept, how to clear it and how to turn it off. It also keeps its time rules in a file, which holds nothing about your sessions: [The settings file](#the-settings-file). Apart from those, its own code writes no files, except the Mac app's, below.
443
511
 
444
- The latest session list is held in memory only, and is gone when Agent Lookout stops. What a waiting session is asking is in that list only while it waits, and in no event or point of history, on disk or in memory. With `AGENT_LOOKOUT_EMAIL_ASKING=on` or `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, it is also in the email or post for that wait from the moment it is written until it is handed over, and nowhere else. So are the tmux panes and terminal tabs it last found, the branches and repositories it last read, with pull requests on the pull requests `gh` last gave and where each branch's is looked for, and what the dashboard pages last said about notifications.
512
+ The latest session list is held in memory only, and is gone when Agent Lookout stops. What a waiting session is asking is in that list only while it waits, and in no event or point of history, on disk or in memory. So is a permission request held for the dashboard, which is also kept in memory only while it is held. With `AGENT_LOOKOUT_EMAIL_ASKING=on` or `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, it is also in the email or post for that wait from the moment it is written until it is handed over, and nowhere else. So are the tmux panes and terminal tabs it last found, the branches and repositories it last read, with pull requests on the pull requests `gh` last gave and where each branch's is looked for, and what the dashboard pages last said about notifications.
445
513
 
446
514
  ### The history
447
515
 
@@ -449,7 +517,7 @@ The history is kept in the folder `~/.agent-lookout/history`, beside the folder
449
517
 
450
518
  It writes there what it already holds in memory for the Events log and the charts, and nothing else:
451
519
 
452
- - Each event: its ID, its time, the session's ID and name, whether the session appeared, changed status, ended or was stopped from Agent Lookout, the status it changed from and to, how serious the change was, and for a session that was stopped, that Agent Lookout stopped it. A session's ID is its agent's own ID for it, or the name of its status file.
520
+ - Each event: its ID, its time, the session's ID and name, whether the session appeared, changed status, ended, was stopped from Agent Lookout or had a permission prompt answered from it, the status it changed from and to, how serious the change was, for a session that was stopped, that Agent Lookout stopped it, and for a prompt that was answered, that Agent Lookout answered it and whether it allowed or denied it. A session's ID is its agent's own ID for it, or the name of its status file.
453
521
  - Each history point, one for each poll: its time, and how many sessions were waiting on you, working, idle, and in all.
454
522
  - When Agent Lookout started writing, and when the history was cleared.
455
523
 
@@ -463,6 +531,12 @@ To clear the history, press Clear history on the History card in Settings and co
463
531
 
464
532
  To keep nothing on disk, start Agent Lookout with `AGENT_LOOKOUT_HISTORY=off`. It then makes no folder and writes no file, and the last 1,000 events, the last six hours of history and what the Waits card counts are held in memory only and are gone when it stops.
465
533
 
534
+ ### The settings file
535
+
536
+ Agent Lookout keeps the [time rules](docs/GUIDE.md#time-rules) you set in Settings in `~/.agent-lookout/settings.json`, beside the history, or in the file `AGENT_LOOKOUT_SETTINGS_FILE` names, so they hold with no dashboard open and after a restart. It holds the three rules and nothing else of Agent Lookout's: whether each is on, the minutes before a reminder, the hours before a session is stale, and the times and days of quiet hours with the switch for the summary. It holds nothing about a session.
537
+
538
+ Agent Lookout reads it once, as it starts, and writes it only when you change a rule in Settings, through `POST /api/settings/time-rules`, which only the dashboard's own page can send. It writes the file whole: to a new file of mode 600, so only your user can read or change it, in the same folder, which then takes the old one's place, and makes the folder with mode 700 when it is not there. It neither reads nor writes through a link, at the file's name or the folder's, and reads no file larger than 64 KB. Anything else the file holds, such as a later version's settings, is written back as it was. A file it found but could not read as it started, such as one larger than 64 KB or one it may not read, it never writes over: a change is not saved until the file is mended or removed and Agent Lookout started again. Until you change a rule, nothing is written and no folder is made. To go back to every rule off, delete the file while Agent Lookout is stopped.
539
+
466
540
  With notifications on, each notification holds a session's name, and for a wait what the session is asking, and the operating system keeps it in its notification list, as does the browser for one it made. [Notifications](#notifications) says what it holds and how long it stays. With email set up, each email holds a session's name and its folder's name, and with `AGENT_LOOKOUT_EMAIL_ASKING=on` the email for a wait holds what the session was asking, and the mail server and the mailbox keep it, as [Email](#email) says. With a webhook set up, each post holds the same, with `AGENT_LOOKOUT_WEBHOOK_ASKING=on` for what a session was asking, and the service it went to keeps it, as [Webhook](#webhook) says.
467
541
 
468
542
  The tools that run it write files of their own. None of these holds session data.
@@ -481,6 +555,8 @@ The dashboard saves seven values in your browser's local storage. Your theme cho
481
555
 
482
556
  Session names, folder paths, repository names, branch names, pull request titles and what a waiting session is asking, such as a command or a file, can show what you are working on. Check a screenshot before you share it.
483
557
 
558
+ Resume puts a session's folder path and its ID on the clipboard, and only when you press it. From there, anything that reads your clipboard can see them, such as a clipboard manager, and on a Mac with Handoff on, Universal Clipboard makes them available on your other Apple devices. The page never reads the clipboard.
559
+
484
560
  With notifications on, a session's name, and what a waiting session is asking, also appear in a system notification, outside the dashboard: over other apps, in Notification Centre and, depending on your system's settings, on the lock screen and while you mirror, share or record the screen. One the dashboard page made for a wait stays there until the session stops waiting or you clear it, and one that says Finished, Failed or Ended stays until you clear it. One the server showed stays until you clear it. To keep names off those, open Notifications in System Settings on macOS and change what your browser's notifications may show, which does not cover the ones the server shows, or leave notifications off. In the Mac app, the number of sessions that need you is shown in the menu bar, beside its icon, and the menu it opens lists their names and what each is asking, with the agent, project, branch or app of two that share a name. Both show while you share or record the screen. To keep them off the menu bar, set Show in menu bar to Off in Settings. An email shows the same name, and the folder's, wherever that mailbox is read, including the notifications a phone shows for it, and so does what a waiting session is asking, with `AGENT_LOOKOUT_EMAIL_ASKING=on`. A webhook post shows them to everyone who can read the channel it goes to, and in the notifications their apps show for it, and with `AGENT_LOOKOUT_WEBHOOK_ASKING=on` shows what a waiting session is asking there too.
485
561
 
486
562
  ## Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Agent Lookout shows the AI agent sessions on your Mac or Linux computer in one browser page, and which need you.
4
4
 
5
- It finds Claude Code and Codex sessions with no setup, and the sessions of any other agent that writes a small status file. It never starts or answers a session, and it stops one only when you press Stop and confirm: it never acts on its own. By default Agent Lookout itself sends nothing anywhere. Start it with `npx agent-lookout`, or download the [Mac app](#mac-app).
5
+ It finds Claude Code and Codex sessions with no setup, and the sessions of any other agent that writes a small status file. It never starts a session. It stops one only when you press Stop and confirm, and answers a Claude Code permission prompt only when you press Allow or Deny, with its Claude Code plugin installed: it never acts on its own. By default Agent Lookout itself sends nothing anywhere. Start it with `npx agent-lookout`, or download the [Mac app](#mac-app).
6
6
 
7
7
  [Install](#install) · [Guide](docs/GUIDE.md) · [Privacy](#privacy) · [Roadmap](https://github.com/Olanetsoft/agent-lookout/milestones) · [Website](https://agent-lookout.vercel.app)
8
8
 
@@ -19,7 +19,10 @@ It finds Claude Code and Codex sessions with no setup, and the sessions of any o
19
19
  | [Sessions](docs/GUIDE.md#sessions) | Lists the other sessions by status, by repository or on a board, each with its folder and git branch. Click one to see its details. |
20
20
  | [Jump](docs/GUIDE.md#jump) | Takes you to a Claude Code session. It opens it in VS Code, selects its tmux pane, or on a Mac brings its Terminal or iTerm2 tab to the front. |
21
21
  | [Stop](docs/GUIDE.md#stop-a-session) | Ends a Claude Code session's process when you press Stop in its details and confirm. The conversation is kept. Sessions left running, idle for a day or more, can be ended together after a confirmation. |
22
+ | [Resume](docs/GUIDE.md#resume-a-session) | Copies the command that continues a Claude Code session that has ended, `cd` to its folder and `claude --resume` with its ID, for you to run in a terminal. It runs nothing itself. |
23
+ | [Allow and Deny](docs/GUIDE.md#answer-a-permission-prompt) | Answers a Claude Code permission prompt from the dashboard, with the Agent Lookout plugin installed in Claude Code. It shows the whole command first, and the prompt in the session still works. |
22
24
  | [Notifications](docs/GUIDE.md#notifications) | Tell you when a session starts waiting and, if you choose, when one finishes, fails or ends. Off until you turn them on. On a Mac they keep coming after you close the tab, once a page with them on has been open. |
25
+ | [Time rules](docs/GUIDE.md#time-rules) | Remind you of a long wait, set how long a session is idle before it is stale, and hold everything back during quiet hours, with one summary when they end. Off until you turn them on. |
23
26
  | [Pull requests](docs/GUIDE.md#pull-requests) | Shows a session's pull request on github.com and its checks, through your own `gh`, and marks a branch whose checks are failing. Off until you turn it on. |
24
27
  | [Email](docs/GUIDE.md#email) and [webhook](docs/GUIDE.md#webhook) | Send the same events to one email address, or to one webhook such as a Slack channel's, for when you are away. Off until you set them up. |
25
28
  | [`agent-lookout status`](docs/GUIDE.md#in-the-terminal) | Prints which sessions need you, for a terminal, a tmux status line or a script. |
@@ -190,9 +193,10 @@ AGENT_LOOKOUT_CLAUDE_FEED=off npx agent-lookout
190
193
  | `AGENT_LOOKOUT_URL` | The address `agent-lookout status` and `agent-lookout mcp` ask, such as `http://127.0.0.1:4778`. Set it where those commands run, not where Agent Lookout starts. |
191
194
  | `AGENT_LOOKOUT_NOTIFICATIONS=on` | On a Mac, shows notifications of waits from the moment it starts, with no dashboard tab open. |
192
195
  | `AGENT_LOOKOUT_CLAUDE_FEED=off` | Never runs the `claude` command. Sessions come from Claude Code's session files alone. |
193
- | `AGENT_LOOKOUT_WAITING_TEXT=off` | Never opens a Claude Code transcript. A waiting session shows its reason alone. |
196
+ | `AGENT_LOOKOUT_WAITING_TEXT=off` | Never opens a Claude Code transcript. A waiting session shows its reason alone, here and on another machine read over SSH. |
194
197
  | `AGENT_LOOKOUT_HISTORY=off` | Keeps the Events log and the charts in memory only, so they start empty each time. Nothing is written to `~/.agent-lookout/history`. |
195
198
  | `AGENT_LOOKOUT_STOP=off` | Takes away Stop and the ending of sessions left running. No session is ever stopped from Agent Lookout. |
199
+ | `AGENT_LOOKOUT_ANSWER=off` | Takes away Allow and Deny. No socket is opened for the plugin, and no permission prompt is answered from Agent Lookout. |
196
200
 
197
201
  [The guide](docs/GUIDE.md#settings-you-can-change) lists every setting, those for [email](docs/GUIDE.md#email) and the [webhook](docs/GUIDE.md#webhook) among them. The theme and the page's notifications are in the dashboard's Settings view.
198
202
 
@@ -206,15 +210,17 @@ By default Agent Lookout itself sends nothing anywhere: no telemetry, no analyti
206
210
  - It runs Claude Code's own `claude agents` command, which may contact Anthropic the way Claude Code normally does. `AGENT_LOOKOUT_CLAUDE_FEED=off` stops it running that command.
207
211
  - While a Claude Code session waits for you, it reads the end of its transcript to show what it asks, such as `Run: npm test`. It forgets that line when the wait ends. The line stays on this machine unless you start Agent Lookout with `AGENT_LOOKOUT_EMAIL_ASKING=on` or `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, which put it in the email or post for that wait. It can hold a command, a web address or a file's full path. No answer of `agent-lookout mcp` holds it. `AGENT_LOOKOUT_WAITING_TEXT=off` turns it off.
208
212
  - Codex's session files hold your conversations. Agent Lookout reads them for when each turn started and ended, and for a few details such as the session's folder. It keeps none of your prompts, Codex's replies or the commands Codex ran.
213
+ - With the [Agent Lookout plugin](docs/GUIDE.md#answer-a-permission-prompt) installed in Claude Code, each permission request, the tool and its input, which for a new file is the whole file, goes from Claude Code to Agent Lookout over a socket in `~/.agent-lookout` that only you can open. Agent Lookout keeps what it shows of it in memory until the prompt is answered, and never writes it down or sends it on.
209
214
  - `agent-lookout mcp` does nothing until you add it to an agent's app. That app usually hands its answers, with your session names, folders and branches, to its model, which for most agents runs on the vendor's servers. [The MCP server](PRIVACY.md#the-mcp-server) says what an answer holds.
215
+ - Other machines are read only once you name them in `AGENT_LOOKOUT_REMOTES`. Then it runs your own `ssh` to each, which signs in with your config and agent, and asks the Agent Lookout running there for its sessions, sending nothing of this computer's. While it is connected, other programs and user accounts on this computer can reach Agent Lookout there through its tunnel, as they can reach it here. [Another machine over SSH](PRIVACY.md#another-machine-over-ssh) says what is read.
210
216
  - Jump to a Terminal or iTerm2 tab needs macOS to let the program you start Agent Lookout from control that app. macOS asks once, and what you allow covers anything else you run from that program. Choose Don't Allow and only that Jump stops working. `AGENT_LOOKOUT_TERMINAL_JUMP=off` turns it off.
211
217
 
212
- It listens on a loopback address only, `127.0.0.1`, or `::1` if you set `AGENT_LOOKOUT_HOST`, and refuses any other, so other computers cannot reach it. It turns away requests from websites open in your browser. It has no password. While it runs, other programs and other user accounts on this computer can read it. They can also turn the notifications it shows on or off, do what the Jump button does, clear the history, and stop a Claude Code session as the Stop button does. The Mac app opens no port, so there only its own window can. It writes to no agent's files. It changes two things outside itself, and only when you ask. When you press Jump, it brings a tmux pane, or a Terminal or iTerm2 tab, to the front. When you press Stop and confirm, it ends that Claude Code session's process. `AGENT_LOOKOUT_STOP=off` takes Stop away.
218
+ It listens on a loopback address only, `127.0.0.1`, or `::1` if you set `AGENT_LOOKOUT_HOST`, and refuses any other, so other computers cannot reach it. It turns away requests from websites open in your browser. It has no password. While it runs, other programs and other user accounts on this computer can read it. They can also turn the notifications it shows on or off, change the time rules, do what the Jump button does, clear the history, stop a Claude Code session as the Stop button does, and answer a permission prompt it is holding as Allow and Deny do. The Mac app opens no port, so there only its own window can. It writes to no agent's files. It changes three things outside itself, and only when you ask. When you press Jump, it brings a tmux pane, or a Terminal or iTerm2 tab, to the front. When you press Stop and confirm, it ends that Claude Code session's process. When you press Allow or Deny, it answers that one permission prompt, and rewrites nothing and saves no rule. `AGENT_LOOKOUT_STOP=off` takes Stop away, and `AGENT_LOOKOUT_ANSWER=off` takes Allow and Deny away.
213
219
 
214
220
  ## What it does not do yet
215
221
 
216
- - It cannot resume or answer a session. It stops a Claude Code session only when you press Stop and confirm, and not one in the desktop app. An agent using `agent-lookout mcp` can do none of these.
217
- - It shows only sessions on this computer. Cloud sessions, Codex cloud tasks and chats in a browser tab do not appear.
222
+ - It does not resume a session itself: Resume copies the command that does, for a Claude Code session that has ended. It cannot send a session a message. It stops a Claude Code session only when you press Stop and confirm, and not one in the desktop app. It answers a permission prompt only with the plugin installed, and offers Deny alone for an edit, since it does not show the change, for a plan or a question, and for a request it cannot show whole and as it is. An agent using `agent-lookout mcp` can do none of these.
223
+ - It shows sessions on this computer, and on [another machine](docs/GUIDE.md#another-machine-over-ssh) running Agent Lookout that you reach over SSH. Cloud sessions, Codex cloud tasks and chats in a browser tab do not appear.
218
224
  - Claude Code's session files and Codex's files are not documented by their makers. An update to either can make Agent Lookout show less, or nothing, until it is updated. [docs/adapters/codex.md](docs/adapters/codex.md#what-breaks-when-codex-changes) lists what breaks when Codex changes.
219
225
  - The Mac app is not signed with an Apple Developer ID yet, so the first time it is opened, macOS does not open it until you allow it in Privacy & Security. There is no app for Linux. Support for more agents is planned in the [milestones](https://github.com/Olanetsoft/agent-lookout/milestones).
220
226
 
package/SECURITY.md CHANGED
@@ -12,7 +12,7 @@ The project has one maintainer, so replies are best effort. Fixes land on `main`
12
12
 
13
13
  ## What the app handles
14
14
 
15
- Agent Lookout reads metadata about the Claude Code and Codex sessions on your machine: names, working directories, statuses and, for Claude Code, process IDs. For each session's folder it reads which git branch is checked out, and which repository the folder belongs to, from the repository's `.git` entry, `HEAD` and a worktree's `commondir` alone, and runs no git command. On a Mac it also lists every process's ID, parent, terminal and program name with `ps` when a new Claude Code session appears, to find the Terminal or iTerm2 tab it runs in, and keeps only the app and the session's terminal. It serves that to the dashboard over HTTP on a loopback address. Of Claude Code's transcripts it reads only the end of a waiting session's own, opened without following a link, and keeps one line of it, what the session is asking, until the wait ends. That line goes to the dashboard and to notifications on this machine, never to an email, a webhook post or `agent-lookout mcp`. It does open Codex's session files, which hold whole conversations, and keeps only when turns started and ended and a few fields. It writes to no agent's files. It ends a Claude Code session's process only when the person presses Stop and confirms, by the route described below, and never on its own. It also reads the folder of status files, `~/.agent-lookout/sessions` or the one `AGENT_LOOKOUT_STATUS_DIR` names, where any program can write a small JSON file to show one of its sessions, and it writes nothing there. It keeps the Events log and the counts behind its charts in its own folder, `~/.agent-lookout/history`, which it makes with mode 700, in files of mode 600 that it neither reads nor writes through a link. They hold session IDs and names, statuses and times, and never what a waiting session is asking. `AGENT_LOOKOUT_HISTORY=off` keeps them in memory only. [PRIVACY.md](PRIVACY.md) lists everything it reads, runs and keeps.
15
+ Agent Lookout reads metadata about the Claude Code and Codex sessions on your machine, and, once you name them, on other machines of yours that run Agent Lookout, read over SSH: names, working directories, statuses and, for Claude Code, process IDs. For each session's folder it reads which git branch is checked out, and which repository the folder belongs to, from the repository's `.git` entry, `HEAD` and a worktree's `commondir` alone, and runs no git command. On a Mac it also lists every process's ID, parent, terminal and program name with `ps` when a new Claude Code session appears, to find the Terminal or iTerm2 tab it runs in, and keeps only the app and the session's terminal. It serves that to the dashboard over HTTP on a loopback address. Of Claude Code's transcripts it reads only the end of a waiting session's own, opened without following a link, and keeps one line of it, what the session is asking, until the wait ends. That line goes to the dashboard and to notifications on this machine, never to an email, a webhook post or `agent-lookout mcp`. It does open Codex's session files, which hold whole conversations, and keeps only when turns started and ended and a few fields. It writes to no agent's files. It ends a Claude Code session's process only when the person presses Stop and confirms, and answers a Claude Code permission prompt only when the person presses Allow or Deny, by the routes described below, and never on its own. It also reads the folder of status files, `~/.agent-lookout/sessions` or the one `AGENT_LOOKOUT_STATUS_DIR` names, where any program can write a small JSON file to show one of its sessions, and it writes nothing there. It keeps the Events log and the counts behind its charts in its own folder, `~/.agent-lookout/history`, which it makes with mode 700, in files of mode 600 that it neither reads nor writes through a link. They hold session IDs and names, statuses and times, and never what a waiting session is asking. `AGENT_LOOKOUT_HISTORY=off` keeps them in memory only. Beside them it keeps the time rules set in Settings in `~/.agent-lookout/settings.json`, or the file `AGENT_LOOKOUT_SETTINGS_FILE` names, of mode 600, which holds nothing about a session and which it neither reads nor writes through a link. [PRIVACY.md](PRIVACY.md) lists everything it reads, runs and keeps.
16
16
 
17
17
  With notifications turned on in Settings, the dashboard page hands a session's name and a fixed text, the reason it waits or Finished, Failed or Ended, to the browser's Notifications API, which passes them to the operating system. When no dashboard page is open, the local server shows the notification itself on macOS: it runs `/usr/bin/osascript` with a fixed script, and passes the name and the text to that script as arguments. Each dashboard page tells the server whether its notifications are on, in a header on its requests, and the server shows its own only while the last page to say so said they are on, or `AGENT_LOOKOUT_NOTIFICATIONS=on` was set and no page has said anything. Nothing is sent over the network for any of this. [PRIVACY.md](PRIVACY.md#notifications) has the details.
18
18
 
@@ -20,7 +20,11 @@ Email notifications are off unless they are set up in the environment Agent Look
20
20
 
21
21
  Webhook posts are off unless `AGENT_LOOKOUT_WEBHOOK_URL` is set in the environment Agent Lookout starts with. Then, for each event chosen, the collector sends one short JSON post to that one address, over HTTPS with the certificate checked, or over plain HTTP only to `127.0.0.1` or `localhost`. It holds the session's name, what happened, when, the name of its project folder, its app and its agent, and, for a wait with `AGENT_LOOKOUT_WEBHOOK_ASKING=on`, what the session is asking. It follows no redirect and tries nothing twice. The address is itself a secret: whoever has a Slack incoming webhook's address can post to its channel. So it lives in the environment of the Agent Lookout process and nowhere else: it is never written to a file, sent to the dashboard or printed. `GET /api/webhook` tells the dashboard only whether the webhook is on, the host, the events, the delay, whether a wait's post says what the session is asking, and how the last post went. [PRIVACY.md](PRIVACY.md#webhook) has the details.
22
22
 
23
- Four routes of the local API change something: `POST /api/jump`, which the Jump button of a session in tmux, Terminal or iTerm2 sends, `POST /api/history/clear`, which the Clear history button in Settings sends, and `POST /api/sessions/stop` and `POST /api/sessions/clean-up`, which Stop session and the ending of the sessions left running send. Every other route only reads. The two that stop sessions are described after the jump route, below. `POST /api/history/clear` deletes the history files in Agent Lookout's own folder and empties the Events log and the history in memory. Its body is exactly `{}`, nothing in it names a file, and it makes the same checks as the jump route, below, with `X-Agent-Lookout-Action: clear-history`. The rest of this part is about `POST /api/jump`. For a session in tmux it can change three things, all inside tmux: which pane is selected in its window, which window is selected in its tmux session, and which tmux session each attached terminal is showing. For a session in a tab of Terminal or iTerm2, on macOS, it can bring that tab forward: select it in its window, bring the window back from the Dock if it was minimised and in front of the app's others, and bring the app to the front. It sends no keys, runs nothing inside a pane or a tab, starts no tmux server and starts no app.
23
+ Other machines are read only when `AGENT_LOOKOUT_REMOTES` names them, each as a name and a target. A name is letters, digits and dashes, and a target is a host alias, a host name or `user@host` made of letters, digits, dots, dashes and underscores, starting with a letter, a digit or an underscore, so no space, leading dash, option such as `-oProxyCommand=`, or shell character gets in. One entry that is not, and no machine is read. For each, the collector runs the person's own `ssh`, found on `PATH`, in a fixed place or by `AGENT_LOOKOUT_SSH_BIN`, by its full path, never through a shell, with nothing on its input, as `ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ControlMaster=no -o ControlPath=none -L 127.0.0.1:<free port>:127.0.0.1:<port there> -- <target>`. The target comes after `--`, as one argument. ssh runs no command on the other machine and asks for nothing, so Agent Lookout never handles a key or a password. `ControlMaster=no` and `ControlPath=none`, given on the command line, come before the person's own ssh config, so each ssh is a connection of its own: it never joins an ssh the person has open, whose forward would outlive Agent Lookout, and never leaves one in the background through `ControlPersist`. When Agent Lookout stops, it sends each ssh SIGTERM, and SIGKILL to one that has not ended two seconds later. Through the tunnel the collector sends `GET /api/health` and `GET /api/sessions` to the Agent Lookout there and nothing else. The requests arrive on that machine's own loopback, so its `Host` and `Origin` checks pass as they do for its own page, and they carry no `Origin` and no notifications header. The page on this computer never talks to the other machine, only to its own server. The answer is read as untrusted: every field is checked and cleaned again, sessions there are given ids of their own, `remote:<name>:` and the id there, and their process ID, Jump, Stop, held permission request and links are dropped, so nothing in an answer can make this computer select a pane, bring a tab forward, open a link, stop a process or answer a permission prompt. [PRIVACY.md](PRIVACY.md#another-machine-over-ssh) says what is kept.
24
+
25
+ While a tunnel is up, its end on this computer's `127.0.0.1` is open to every program and every user account on this computer, not only to Agent Lookout, and it leads to the other machine's Agent Lookout and its whole API, not only the two routes the collector asks for. Through it, a program here can read that machine's sessions and send the routes that change something there: `POST /api/jump`, `POST /api/history/clear`, `POST /api/sessions/stop`, `POST /api/sessions/clean-up`, `POST /api/permission/answer` and `POST /api/settings/time-rules`. Their `Origin` and action-header checks stop a website, but not a program that is not a browser, which can set those headers. Before a machine is named, only someone on that machine could do this. There is also a moment, before ssh has signed in and listens on the port it was given, when that port is free: another program here could listen on it first and answer as that machine's Agent Lookout, and its sessions would be shown as that machine's, with no Jump, no Stop and no Allow or Deny. So name a machine only from a computer whose other users and programs you trust as you trust those on that machine.
26
+
27
+ Six routes of the local API change something: `POST /api/jump`, which the Jump button of a session in tmux, Terminal or iTerm2 sends, `POST /api/history/clear`, which the Clear history button in Settings sends, `POST /api/sessions/stop` and `POST /api/sessions/clean-up`, which Stop session and the ending of the sessions left running send, `POST /api/permission/answer`, which Allow and Deny send, and `POST /api/settings/time-rules`, which the Time rules card in Settings sends. Every other route only reads. The two that stop sessions, and the one that answers, are described after the jump route, below. `POST /api/history/clear` deletes the history files in Agent Lookout's own folder and empties the Events log and the history in memory. Its body is exactly `{}`, nothing in it names a file, and it makes the same checks as the jump route, below, with `X-Agent-Lookout-Action: clear-history`. `POST /api/settings/time-rules` puts the time rules in force and writes them to the settings file. It makes the same checks as the jump route, with `X-Agent-Lookout-Action: time-rules`, and takes a body of 2,048 bytes or less that is exactly the three rules, each with exactly its own fields and every value in range, and nothing else. Nothing in it names a file: the file is the one Agent Lookout was started with, written whole to a new file of mode 600 that then takes its place, never through a link. The rules decide only when Agent Lookout tells you of something, and which idle sessions count as stale, and so which are offered under Left running: ending one still takes a press of End and every check below. The rest of this part is about `POST /api/jump`. For a session in tmux it can change three things, all inside tmux: which pane is selected in its window, which window is selected in its tmux session, and which tmux session each attached terminal is showing. For a session in a tab of Terminal or iTerm2, on macOS, it can bring that tab forward: select it in its window, bring the window back from the Dock if it was minimised and in front of the app's others, and bring the app to the front. It sends no keys, runs nothing inside a pane or a tab, starts no tmux server and starts no app.
24
28
 
25
29
  A request to it names a session by its ID and nothing else. The server looks that session up in its own list and takes the pane it found for that session's process when it last asked tmux. It checks that the pane's ID is a `%` followed by digits, and gives it to a fixed list of tmux commands: `select-window`, `select-pane`, `display-message -p`, `list-clients` and `switch-client`. Nothing from the request is passed to tmux, and tmux is started directly, never through a shell. So the route can never be made to run another program, another tmux command or a command with other arguments. The most a request can do is choose which of the panes already found for a listed session is selected.
26
30
 
@@ -36,18 +40,22 @@ On top of the checks every route makes, the route answers only a request that:
36
40
 
37
41
  It makes one jump a second, and one at a time. `AGENT_LOOKOUT_TMUX=off` stops Agent Lookout running tmux, and the route then finds no pane for any session. `AGENT_LOOKOUT_TERMINAL_JUMP=off` stops it looking for tabs, and the route then brings no tab forward. [PRIVACY.md](PRIVACY.md#tmux) lists each command, and [Terminal and iTerm2](PRIVACY.md#terminal-and-iterm2) the script.
38
42
 
39
- `POST /api/sessions/stop` ends one Claude Code session's process, and `POST /api/sessions/clean-up` ends up to 20 sessions left running, idle for a day or more. Each passes the checks above, with `X-Agent-Lookout-Action: stop` or `clean-up`, and a body of 1,024 bytes or less that is exactly `{"sessionId": "..."}`, or of 8 KiB or less that is exactly `{"sessions": [{"sessionId": "...", "statusSince": ...}]}`. A request names sessions, and for a clean-up the moment each one's idle began, and nothing else. The server looks each session up in its own list and acts on what the Claude Code adapter found for it: its process ID, the start time its registry file recorded and, for a background job, the job's ID, none of which is sent to the page or read from a request. Before it acts, it makes every check again at that moment, with nothing remembered:
43
+ `POST /api/sessions/stop` ends one Claude Code session's process, and `POST /api/sessions/clean-up` ends up to 20 sessions left running, idle for a day or more, or as long as the idle rule says while it is on. Each passes the checks above, with `X-Agent-Lookout-Action: stop` or `clean-up`, and a body of 1,024 bytes or less that is exactly `{"sessionId": "..."}`, or of 8 KiB or less that is exactly `{"sessions": [{"sessionId": "...", "statusSince": ...}]}`. A request names sessions, and for a clean-up the moment each one's idle began, and nothing else. The server looks each session up in its own list and acts on what the Claude Code adapter found for it: its process ID, the start time its registry file recorded and, for a background job, the job's ID, none of which is sent to the page or read from a request. Before it acts, it makes every check again at that moment, with nothing remembered:
40
44
 
41
45
  - It reads the session's registry file again, an ordinary file it opens without following a link. The file must still name that session and that process, be of kind `interactive` or `bg`, the kind it had, and record the same start time. A file that names no kind, or another, is refused.
42
46
  - It runs `ps` for that process again, and acts only when `ps` gives the same start time the file recorded, a second apart at most, since `ps` gives it in whole seconds. A start time `ps` does not give, or that cannot be compared, is refused as not confirmed. So a pid that has been handed to another program is not signalled unless that program started within a second of the session, and the few milliseconds before the signal, below, are a known gap.
43
47
  - It refuses process 1, its own process, the process it was started from and every process above that, which it finds by following its own process up through its parents in `ps -A -o pid=,ppid=` at that moment, a process of another user's, and a session in Claude Code's desktop app.
44
- - For a clean-up, the file, and the server's own latest list of sessions, which can come from `claude agents`, must also still say the session is idle, since the moment the page sent, and that moment must be 24 hours or more ago. A session that has done anything since is left running.
48
+ - For a clean-up, the file, and the server's own latest list of sessions, which can come from `claude agents`, must also still say the session is idle, since the moment the page sent, and that moment must be 24 hours or more ago, or as long ago as the idle rule says. A session that has done anything since is left running.
45
49
 
46
50
  Then it sends SIGTERM to that one process, never SIGKILL, and waits up to 10 seconds for it to end, or, for a background job, runs the `claude` binary it already runs for `claude agents`, directly, never through a shell, with stdin closed and a 10 second timeout, as `claude stop` and the job's ID, which must be letters, digits, dashes and underscores, starting with a letter or a digit. One stop or clean-up is made at a time, a second apart. The time between the `ps` check and the signal is a few milliseconds, and for a pid to be given to another program in that time, the process would have to end and the system would have to give out every other pid first. macOS has no way to close that gap entirely. `AGENT_LOOKOUT_STOP=off` removes both routes. [Stopping a session](PRIVACY.md#stopping-a-session) has the rest.
47
51
 
52
+ `POST /api/permission/answer` answers one Claude Code permission prompt that Agent Lookout holds. It reaches a session only through the Agent Lookout plugin, which the person installs in Claude Code themselves. The plugin's one hook, a short `sh` script, sends each permission request with `curl` to a Unix socket, `~/.agent-lookout/answer.sock` unless `AGENT_LOOKOUT_ANSWER_SOCKET` names another, and prints the answer it gets back, when it is one of the two Agent Lookout gives. The socket is in a folder of mode 700 that must belong to the user, and has mode 600. Agent Lookout tightens its own folder, `~/.agent-lookout`, to 700, but never changes a folder `AGENT_LOOKOUT_ANSWER_SOCKET` names that it did not make: when others can open one, answering is off. So only that user's programs can connect, and no web page can reach a socket at all. It takes only `POST /hooks/permission-request` with `X-Agent-Lookout-Hook: permission-request`, a JSON body of 1 MiB or less and no `Origin`, and answers anything else with an empty 200, which the hook prints nothing for. A request is shown, and can be answered, only once the registry file of a session it lists says that session waits for permission, within 3 seconds of the request, and otherwise it is let go with nothing. It refuses a socket path longer than a socket can have, rather than let it be cut short to another path, and never removes anything at that path but a socket nobody answers on. One copy listens at a time.
53
+
54
+ The route passes the checks above, with `X-Agent-Lookout-Action: answer`, and a body of 1,024 bytes or less that is exactly `{"sessionId": "...", "requestId": "...", "decision": "allow" | "deny"}`. The request ID is 128 random bits Agent Lookout made for the request it holds, so a page showing an older request cannot answer a newer one. It answers only a request it holds for that session under that ID, Allow only when the whole of what Allow allows was shown, which is never the case for an edit, a new file, a plan or a question, and only after it has read the session's registry file again and found it still says the session waits for permission, in the same wait, by its `statusUpdatedAt` and `waitingFor`, that the request was shown in. What it writes is one of two fixed texts: allow, or deny with a fixed sentence. It never sends `updatedInput` or `updatedPermissions`, so it cannot change what a tool does or save a rule, and Claude Code still applies its own deny rules after an allow. A request is let go, answered with nothing, once the session is no longer waiting for permission or is waiting in a later wait, the hook's connection closes, or `AGENT_LOOKOUT_ANSWER_WAIT` has passed, and the prompt in the session decides as it would without the plugin. `AGENT_LOOKOUT_ANSWER=off` opens no socket and removes the route.
55
+
48
56
  The Mac app answers four more routes, under `/api/app/` on its own address, which no port reaches and which `npm start` and the npm package do not have: `GET /api/app/update`, and three POSTs that check GitHub for a newer version, install the one that is ready, and turn the daily check on or off. Each POST passes the checks above, with an `X-Agent-Lookout-Action` of its own, and a body of 256 bytes or less that is exactly what it needs. The app's only requests off the machine are the update check's and its download's, over HTTPS, with the certificate checked, to GitHub and the two hosts GitHub redirects a release's downloads to, and to no other address, a redirect's included. A download must have the size and SHA-512 the release's `latest-mac.yml` gives, and the bundle in it must be this app, by its bundle identifier, at the version offered, with no link where the bundle, its `Contents` or its `Info.plist` should be. Only Install and Restart starts the helper that replaces the app: `/bin/sh` with a fixed script, and the app's process ID and three paths as its arguments, two of them in the app's own temporary folder. Nothing from the network or from a request reaches its arguments or any command. The app is not signed with a Developer ID yet, so a download is vouched for by the release it comes from and the hash in that release, and is as safe as the project's GitHub releases are.
49
57
 
50
- `agent-lookout mcp` is a Model Context Protocol server for an agent that keeps track of your other agents. The agent's app starts it and speaks to it over stdin and stdout, so it opens no port. For each tool call it reads `GET /api/sessions` from the local server, at a loopback address only, with no `Origin` and no notifications header, exactly as `agent-lookout status` does, and answers the app with what it read. Its three tools only read: none of them reaches `POST /api/jump`, `POST /api/history/clear`, `POST /api/sessions/stop` or `POST /api/sessions/clean-up`, changes a notification or changes anything else. A session's name, agent, folder and branch are text that other programs wrote, and the client is a model that takes instructions from text. So each tool's description and each answer say that this text is data, never instructions, the one-sentence summary puts each name in quotation marks, and escape sequences and control characters are taken out of every text, as `agent-lookout status` does. So are characters that show nothing, such as zero-width spaces and Unicode's tag characters, in which words can be hidden from a person but not from a model.
58
+ `agent-lookout mcp` is a Model Context Protocol server for an agent that keeps track of your other agents. The agent's app starts it and speaks to it over stdin and stdout, so it opens no port. For each tool call it reads `GET /api/sessions` from the local server, at a loopback address only, with no `Origin` and no notifications header, exactly as `agent-lookout status` does, and answers the app with what it read. Its three tools only read: none of them reaches `POST /api/jump`, `POST /api/history/clear`, `POST /api/sessions/stop`, `POST /api/sessions/clean-up`, `POST /api/permission/answer` or `POST /api/settings/time-rules`, changes a notification or changes anything else. A session's name, agent, folder and branch are text that other programs wrote, and the client is a model that takes instructions from text. So each tool's description and each answer say that this text is data, never instructions, the one-sentence summary puts each name in quotation marks, and escape sequences and control characters are taken out of every text, as `agent-lookout status` does. So are characters that show nothing, such as zero-width spaces and Unicode's tag characters, in which words can be hidden from a person but not from a model.
51
59
 
52
60
  The local API has no token, and the MCP server adds none, though one was suggested for it. The server opens no port of its own, and the API it reads is the same loopback API that the dashboard and `agent-lookout status` read without one. Websites and other computers are already kept away from that API by the `Host` and `Origin` checks and by its listening on loopback alone. Against websites and other computers a token would add nothing to those checks, and programs running as you could read a token as easily as the API. It could keep out other accounts on a shared computer, the known limit listed below, which includes another account asking the server to stop your Claude Code sessions, and is left for later. An API that can be reached some other way, such as from another computer, would need one too, and Agent Lookout has none. [docs/API.md](docs/API.md) describes every route and its checks.
53
61
 
@@ -57,7 +65,11 @@ Session names and folder paths can be sensitive. The main risk is that something
57
65
 
58
66
  - A website, or another machine on the network, reading the local API. That includes getting past the `Host` and `Origin` checks and DNS rebinding.
59
67
  - `npm start` listening on any address other than loopback.
60
- - Any network request from Agent Lookout itself to anything but its own local server, apart from the mail server named in `AGENT_LOOKOUT_SMTP_URL` once email is set up and the address in `AGENT_LOOKOUT_WEBHOOK_URL` once a webhook is set up.
68
+ - Any network request from Agent Lookout itself to anything but its own local server, apart from the mail server named in `AGENT_LOOKOUT_SMTP_URL` once email is set up, the address in `AGENT_LOOKOUT_WEBHOOK_URL` once a webhook is set up, and the end on `127.0.0.1` of an SSH tunnel to a machine `AGENT_LOOKOUT_REMOTES` names.
69
+ - `ssh` run while `AGENT_LOOKOUT_REMOTES` is unset or cannot be read, through a shell, with any arguments but the fixed ones and a target that passed the check, or with a target that ssh can read as an option. Also a forward that listens on anything but `127.0.0.1` here, or reaches anything but `127.0.0.1` there.
70
+ - A request through the tunnel other than `GET /api/health` and `GET /api/sessions`, or one that carries anything of this computer's, such as its sessions or its notifications header.
71
+ - Anything in another machine's answer that runs as script or markup in the dashboard, that becomes a Jump, a Stop, an Allow or Deny, a link or a command on this computer, or that passes for one of this computer's own sessions.
72
+ - An ssh process Agent Lookout started that keeps running after Agent Lookout has stopped.
61
73
  - An email sent, or a connection to a mail server opened, while `AGENT_LOOKOUT_EMAIL_TO` or `AGENT_LOOKOUT_SMTP_URL` is unset, or while one of the email settings cannot be read.
62
74
  - The mail server's password, its user name or its address reaching the dashboard, the API, a log, the console or a file.
63
75
  - An email that holds more than PRIVACY.md lists, or that goes to any address but the one in `AGENT_LOOKOUT_EMAIL_TO`.
@@ -84,26 +96,32 @@ Session names and folder paths can be sensitive. The main risk is that something
84
96
  - In the Mac app, a request, a release or anything from the network that makes it run anything but `/usr/bin/ditto`, `/usr/bin/plutil` and its fixed update helper, passes them arguments it did not make itself, installs a download whose size or SHA-512 is not the one the release gives or whose bundle is not Agent Lookout at the version offered, installs anything before Install and Restart is pressed, or makes a request to any host but GitHub's three for a release.
85
97
  - A website, or a page served from anywhere but this machine, making the app select a tmux pane or bring a tab of Terminal or iTerm2 forward.
86
98
  - A request to `POST /api/jump` that makes tmux do anything but select a pane found for a listed session, its window and its tmux session: running a command, sending keys, or reaching any other pane.
99
+ - A request to `POST /api/permission/answer`, or anything sent to the answer socket, that answers a permission prompt Agent Lookout does not hold for that session under that request ID, that allows one whose whole request was not shown or that offers Deny alone, that answers one after the session's registry file stopped saying it waits, that writes anything but the two fixed answers, or that rewrites a tool's input or saves a permission rule. Also a website, or a page served from anywhere but this machine, making the app answer a prompt, a request's text reaching a file, an event, the history, an email, a webhook post or `agent-lookout mcp`, and the socket or its folder being open to another user.
87
100
  - A request to `POST /api/sessions/stop` or `POST /api/sessions/clean-up` that signals any process but a listed Claude Code session's own, one whose registry file and start time were not confirmed at that moment, or one in the desktop app, that sends any signal but SIGTERM, that runs `claude` with any arguments but `stop` and a job's ID found for a listed session, or that ends a session that was not idle since the moment the page sent. Also a website, or a page served from anywhere but this machine, making the app stop a session.
88
101
  - A session's name, a tmux session's name, or anything else from a session or from tmux, that `tmux` reads as a command, a target or an option.
89
102
  - A request to `POST /api/jump` that makes `osascript` run any script but the fixed one for Terminal or iTerm2, or with any argument but a terminal found for a listed session, or that makes Terminal or iTerm2 do anything but bring a tab forward: running a command, sending keys or text, or reading what a tab shows.
90
103
  - A session name, or anything else from a session, that `osascript` reads as AppleScript or as one of its options instead of showing it as text.
91
104
  - A Jump link that opens anything other than the intended `vscode://` address.
92
105
  - A notification, or the browser's question about allowing them, appearing when you have not turned notifications on, in Settings or with `AGENT_LOOKOUT_NOTIFICATIONS=on`.
93
- - A notification that holds anything but the session's name and the reason, or Finished, Failed or Ended, or that makes the browser fetch anything.
106
+ - A notification that holds anything but the session's name and the reason, or Finished, Failed or Ended, or for a reminder how long the session has waited and for what, or for the summary of quiet hours the names of the sessions and what happened to them, or that makes the browser fetch anything.
107
+ - A request to `POST /api/settings/time-rules` that writes anything but the three rules to the settings file, writes to any other file, writes through a link, or changes anything else in Agent Lookout. A settings file that is read through a link, or that makes Agent Lookout read more than 64 KB, send anything anywhere, or run anything. Also a website, or a page served from anywhere but this machine, changing the time rules.
108
+ - Quiet hours that let a notification, an email or a post through while they hold, or a summary that holds more than PRIVACY.md lists.
94
109
  - A page at another address reading or steering the dashboard's notifications, or turning the server's own on or off.
95
- - A tool of `agent-lookout mcp` that acts: that reaches `POST /api/jump` or a route that stops a session, sends the notifications header, or changes anything in Agent Lookout, in another tool or on this computer.
110
+ - A tool of `agent-lookout mcp` that acts: that reaches `POST /api/jump`, a route that stops a session or the route that answers one, sends the notifications header, or changes anything in Agent Lookout, in another tool or on this computer.
96
111
  - `agent-lookout mcp` reading anything but `GET /api/sessions` from Agent Lookout at a loopback address, asking an address that is not loopback, opening a port, or making any other connection.
97
112
  - Text from a session reaching the app that started `agent-lookout mcp` without being marked as data: a tool description or an answer that does not say it is untrusted text, a name outside its quotation marks in the summary, or a name, agent, folder, branch or source detail with an escape sequence, control character or invisible character such as a Unicode tag character left in it.
98
113
  - Anything `agent-lookout mcp` writes to stdout that is not the protocol's own message, or an answer that holds more than PRIVACY.md lists, such as a session's whole folder path.
99
114
 
100
115
  ## Out of scope
101
116
 
102
- - Other programs on the same machine reading the local API, sending it the header that turns the server's notifications on or off, asking it to select a tmux pane or bring a tab forward, or asking it to stop a Claude Code session, with every check it makes for the Stop button. The API has no authentication, so any local process can request it, and on a shared computer that includes other user accounts. This is a known limit of this version. A program running as you could end your processes itself, but on a shared computer another account can also ask this server to stop your sessions, which it could not do itself. On such a computer, use the Mac app, which opens no port, so only its own window can ask, or set `AGENT_LOOKOUT_STOP=off`, which takes stopping away.
117
+ - Other programs on the same machine reading the local API, sending it the header that turns the server's notifications on or off, changing the time rules, asking it to select a tmux pane or bring a tab forward, asking it to stop a Claude Code session, with every check it makes for the Stop button, or asking it to answer a permission prompt it holds, with every check it makes for Allow and Deny. The API has no authentication, so any local process can request it, and on a shared computer that includes other user accounts. This is a known limit of this version. A program running as you could end your processes itself, but on a shared computer another account can also ask this server to stop your sessions, or allow a permission prompt it holds, which it could not do itself. On such a computer, use the Mac app, which opens no port, so only its own window can ask, or set `AGENT_LOOKOUT_STOP=off`, which takes stopping away, and `AGENT_LOOKOUT_ANSWER=off`, which takes answering away.
103
118
  - A program on this machine that names itself after Terminal or iTerm2 in the list of processes, and so gives a session under it a Jump button. Pressing it asks the real app for a tab on that session's terminal, which it does not have.
104
119
  - A notification the server showed staying in Notification Centre after its session has moved on. The server cannot take one down, and PRIVACY.md says so.
105
120
  - The dev server started with Vite's `--host` flag, which makes it listen on the network. `npm start` refuses to listen on anything but loopback.
106
121
  - Attacks that need control of your user account first. Someone with that control can read `~/.claude` and `~/.codex` directly.
122
+ - What the other machine's Agent Lookout reports about its own sessions. Someone who controls that machine, or its Agent Lookout, can make it list sessions that do not exist, and they are shown here as that machine's, marked with its name. They cannot act on this computer.
123
+ - What `ssh` does with the person's own ssh config, such as a `ProxyCommand` or a `LocalCommand` set there for the host named. Agent Lookout gives ssh its fixed options and the target, and the rest is the person's config, as in a terminal.
124
+ - Another program or user on this computer reaching the other machine's Agent Lookout through a tunnel's port while it is up, or listening on that port before ssh does, as described above. Keeping the port to this user alone would need a forward to a socket file, which this version does not make.
107
125
  - A program on this machine writing a status file to show a session that does not exist, or to change one that does. Any program that can write in the folder can do that: it is how the format works. The folder sits in your home folder, so that means programs you run.
108
126
  - More than 200 status files: only the 200 written most recently are read.
109
127
  - A hard link in the folder of status files to a file elsewhere. It is an ordinary file in the folder and is read as one, and making it takes the same access as writing a status file.