agent-lookout 0.2.4 → 0.2.6

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,62 @@
2
2
 
3
3
  Changes that a user of Agent Lookout would notice, newest first.
4
4
 
5
+ ## 0.2.6 - 2026-10-07
6
+
7
+ Set rules that answer Claude Code's permission prompts for you, run Agent Lookout on Windows, and see a prompt you answered leave Needs you at once.
8
+
9
+ ### Added
10
+
11
+ - Permission rules, a card in Settings under Permission prompts. Decide once how a kind of Claude Code permission prompt is answered: always allow, always ask or always deny, by tool and, for Bash, by command, exactly or as a prefix written as Claude Code writes one, such as `npm test:*`. Add, edit, move up and down, and remove rules there; Agent Lookout keeps them in `~/.agent-lookout/settings.json`. Deny goes first, then ask, then allow, as in Claude Code's own rules. A deny or an allow rule answers a matching request at once through the plugin, with every check Allow and Deny make. An allow rule answers only what you could allow by hand, and for Bash only one plain command: a line with `;`, `&&`, `|`, `$( )`, a variable, quotes or a redirection in it waits for you, and no allow rule can begin with a program that runs another, such as `sudo`, `env` or `bash`, or with one that sends requests or runs the code it is given, such as `curl`, `ssh`, `node` or `python`, through which Claude could reach Agent Lookout itself and add a rule. A rule for a package manager or a build tool names its subcommand, such as `npm test:*`, never `npm:*`. Nor does an allow rule answer a subcommand on its list that fetches or runs code, such as `npm exec` or `docker run`, an option through which a program runs another, such as `git -c`, or a command that names Agent Lookout's own folder, its settings file or socket, or a folder above them, and it answers no command of a session whose folder is your home folder or above. A deny or an ask rule finds its words in any command of the line, with options between them, so `git push:*` also holds back `git -C . push`, and can name every tool of an MCP server, such as `mcp__docs`. Each answer is in the Events log with the rule, never the command, and the card lists the last 100. Unless a read of Claude Code's file in the instant before the plugin's request arrives saw the wait begin, a prompt a rule answers sends no notification, email, webhook post or reminder, does not light the lamp or come into Needs you, its count, the tab's title, the Mac app's Dock badge and menu bar, or what `agent-lookout status` and `agent-lookout mcp` count as needing you, and Waits does not count it: for the second or two before Claude Code says what it does next, the session is listed with the working sessions as Answered. A computer that reads this one over SSH takes the wait as answered too. A deny or an allow rule in Claude Code's own settings still decides first. There is no rule until you add one, and rules answer Claude Code sessions with the plugin only. [Permission rules](docs/GUIDE.md#permission-rules) has the details.
12
+ - `GET /api/settings` gives the permission rules and the requests they answered since Agent Lookout started, and `POST /api/settings/permission-rules` makes one change to them. No page on another site can send it, though another program on this computer can, as [Permission rules](docs/GUIDE.md#permission-rules) warns. The Mac app has no such route, but it takes up at its next poll a change another copy saved in the settings file. An `answered` event a rule gave has `tool` and `rule`.
13
+
14
+ #### On Windows
15
+
16
+ - Agent Lookout runs on Windows, started the same way, with `npx agent-lookout`, and reading the same folders in your user folder: `.claude\sessions`, `.codex` and `.agent-lookout\sessions`. It has been checked in CI on Windows by running the tests, less those of what Windows does not have, such as POSIX signals and Unix sockets, and by starting it with `npm start` and reading back its sessions, and not yet by a person on a Windows desktop. The guide's [On Windows](docs/GUIDE.md#on-windows) part says what works there.
17
+ - The `claude` command is looked for as `claude.exe` on `PATH`, in `%USERPROFILE%\.local\bin` and in npm's folder, `%APPDATA%\npm`. For the `claude.cmd` that npm makes, Agent Lookout runs the `claude.exe` that it runs, and it never runs a `.cmd` or a `.bat`, which need a shell.
18
+ - Stop, ending the sessions left running, Allow and Deny, the permission rules, Resume, Jump to a tmux pane or a tab of Terminal or iTerm2, and notifications with no dashboard tab open are not offered on Windows. What each agent can report, in Sources, says why for Stop, Answer and Jump, and the guide's On Windows part says why for each.
19
+ - `--open` opens the page in your default browser on Windows too.
20
+
21
+ ### Changed
22
+
23
+ - After you press Allow or Deny, the session leaves Needs you at once, and with it the lamp, the Needs you count, the tab's title and the Mac app's Dock badge and menu bar. For the second or two before Claude Code says what it does next, it is listed with the working sessions as Answered, `agent-lookout status` and `agent-lookout mcp` count it as working, and `GET /api/sessions` marks it `answered: true`. A computer that reads this one over SSH takes the wait as answered too.
24
+
25
+ ### Fixed
26
+
27
+ - A background job you stopped could still show as working, with Stop, after its details said it was stopped, until Claude Code had ended its process and the sessions were read again. Agent Lookout now waits up to 10 seconds for that process to end before it answers, as it does after SIGTERM.
28
+ - A Claude Code session whose registry file was read while it was being written dropped out of the list for a poll, which read as the session ending and starting again. What the file held at the read before now stands for it, once.
29
+ - The Python example in [Your own agents](docs/GUIDE.md#your-own-agents) stopped at once on Windows, which has no `SIGHUP`. It runs there now.
30
+ - A long wait reminder could still go out just after you pressed Allow or Deny, when its minutes were up before Claude Code's file said the session had moved on. A wait is now over from the moment it is answered, on every channel.
31
+ - With `NODE_TLS_REJECT_UNAUTHORIZED=0` in the environment, an email, and the password in `AGENT_LOOKOUT_SMTP_URL`, went to a mail server whose certificate was not checked. The certificate is now checked whatever that variable says, as the webhook's always was.
32
+ - With two copies running at once, such as the Mac app and `npx agent-lookout`, each kept the rules it read as it started, and a change saved in one could be undone by the next change in the other. Each now reads the settings file again before a change, makes the change to what the file holds, and picks up the other's change at its next poll. The Time rules card saves all three rules at once, so a time rule changed in one copy in the second or two before the other's change has reached it still puts that change back.
33
+ - With the answer socket not opened, as beside another copy of Agent Lookout that answers already, `POST /api/permission/answer` got 404 for every answer. It now gets 405, as with answering off.
34
+
35
+ ## 0.2.5 - 2026-10-07
36
+
37
+ 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.
38
+
39
+ ### Added
40
+
41
+ - 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.
42
+ - `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.
43
+ - 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.
44
+ - [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.
45
+ - 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.
46
+ - 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.
47
+ - 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".
48
+ - 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.
49
+ - 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.
50
+ - `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`.
51
+
52
+ ### Changed
53
+
54
+ - 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.
55
+
56
+ ### Fixed
57
+
58
+ - 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.
59
+ - 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.
60
+
5
61
  ## 0.2.4 - 2026-10-06
6
62
 
7
63
  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,24 +1,24 @@
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
- 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.
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. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH. That computer shows it, and its own email and webhook settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. `AGENT_LOOKOUT_WAITING_TEXT=off` on this computer keeps it from there too. 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, or when a [permission rule](#permission-rules) you added in Settings decides it, 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
 
13
13
  ### Claude Code's session registry
14
14
 
15
- Every 2 seconds Agent Lookout lists the folder `~/.claude/sessions/` and reads each file in it whose name ends in `.json`. Claude Code keeps one small file there for each running session, named `<pid>.json`. Agent Lookout reads only ordinary files of 256 KB or less, and on macOS and Linux it does not follow a link. From each file it keeps these fields and drops the rest: `pid`, `sessionId`, `cwd`, `startedAt`, `kind`, `entrypoint`, `name`, `status`, `waitingFor`, `state`, `statusUpdatedAt` and `procStart`. `entrypoint` says whether the session runs in a terminal, in VS Code or in the desktop app, `statusUpdatedAt` says when its status last changed, and `procStart` says when its process started. It makes no use of when these files were last written.
15
+ Every 2 seconds Agent Lookout lists the folder `~/.claude/sessions/` and reads each file in it whose name ends in `.json`. Claude Code keeps one small file there for each running session, named `<pid>.json`. Agent Lookout reads only ordinary files of 256 KB or less, and it does not follow a link. Windows has no way to open a file without following a link at its name, so there Agent Lookout looks at the name first, opens only an ordinary file, and reads nothing unless the file it opened is the one it looked at. Each file this page says is read without following a link is opened that way on Windows. From each file it keeps these fields and drops the rest: `pid`, `sessionId`, `cwd`, `startedAt`, `kind`, `entrypoint`, `name`, `status`, `waitingFor`, `state`, `statusUpdatedAt` and `procStart`. `entrypoint` says whether the session runs in a terminal, in VS Code or in the desktop app, `statusUpdatedAt` says when its status last changed, and `procStart` says when its process started. It makes no use of when these files were last written.
16
16
 
17
17
  ### Claude Code's transcripts
18
18
 
19
19
  Claude Code keeps each session's conversation in a transcript, `~/.claude/projects/<folder>/<session ID>.jsonl`, where the folder is named after the session's folder. Agent Lookout reads one only while its session is waiting for you, as the registry or the `claude` command says, and reads nothing of any transcript otherwise.
20
20
 
21
- For a waiting session it looks for the file in the folder named after the session's folder, and, when it is not there, in each folder in `~/.claude/projects/`, by name, which lists those folders. It uses the session ID only once it has checked that it is an ID, and opens only an ordinary file: on macOS and Linux it does not follow the transcript file when it is a link, and it never reads a named pipe or a device. The folders above the file are followed when they are links: a folder in `~/.claude/projects/` that is a link, or `projects` itself. It reads at most the last 256 KB of the file, by position, and reads it again only when the file's size or modified time has changed. While a wait goes on it does not look for a file again, except every 10 seconds when none was found.
21
+ For a waiting session it looks for the file in the folder named after the session's folder, and, when it is not there, in each folder in `~/.claude/projects/`, by name, which lists those folders. It uses the session ID only once it has checked that it is an ID, and opens only an ordinary file: it does not follow the transcript file when it is a link, and it never reads a named pipe or a device. The folders above the file are followed when they are links: a folder in `~/.claude/projects/` that is a link, or `projects` itself. It reads at most the last 256 KB of the file, by position, and reads it again only when the file's size or modified time has changed. While a wait goes on it does not look for a file again, except every 10 seconds when none was found.
22
22
 
23
23
  From those bytes it takes the last tool the session asked to use that has no answer yet, leaving out the lines of the session's subagents, and keeps one line of plain text of at most 200 characters about it, cleaned of control characters:
24
24
 
@@ -31,7 +31,7 @@ From those bytes it takes the last tool the session asked to use that has no ans
31
31
 
32
32
  Everything else in those bytes, which can include your prompts, the agent's replies, the code it wrote and the output of tools, is dropped as soon as the line is made. When nothing in the end of the file fits, there is no line, and nothing is guessed.
33
33
 
34
- That line is shown in the Needs you panel and in the session's details, is put in the notification of that wait that the dashboard page or the server shows on this machine, in the Mac app is in the menu that its icon in the menu bar opens, and is in the session list the API serves on this computer. It is kept in memory beside the place of the file and its size and modified time, only while the session waits, and is forgotten on the first poll after the wait ends. It is not written to disk, not kept in the event log or the history, and not put in an answer of `agent-lookout mcp`. It is put in an email or a webhook post only when you turn that on, as [Email](#email) and [Webhook](#webhook) say. Set `AGENT_LOOKOUT_WAITING_TEXT=off` and no transcript is opened at all, so there is no line to send either.
34
+ That line is shown in the Needs you panel and in the session's details, is put in the notification of that wait that the dashboard page or the server shows on this machine, in the Mac app is in the menu that its icon in the menu bar opens, and is in the session list the API serves on this computer. It is kept in memory beside the place of the file and its size and modified time, only while the session waits, and is forgotten on the first poll after the wait ends. It is not written to disk, not kept in the event log or the history, and not put in an answer of `agent-lookout mcp`. It is put in an email or a webhook post only when you turn that on, as [Email](#email) and [Webhook](#webhook) say. It also goes to another computer that names this one in `AGENT_LOOKOUT_REMOTES` and reads its sessions over SSH, which shows it, and whose own email and webhook settings decide whether it is sent on, as [Another machine over SSH](#another-machine-over-ssh) says. Set `AGENT_LOOKOUT_WAITING_TEXT=off` and no transcript is opened at all, so there is no line to send either.
35
35
 
36
36
  ### `claude agents --json --all`
37
37
 
@@ -41,11 +41,11 @@ From each entry it keeps these fields and drops the rest: `pid`, `cwd`, `kind`,
41
41
 
42
42
  The command is started directly, with no shell, with stdin closed and a 5 second timeout. It is not run at all when `AGENT_LOOKOUT_CLAUDE_FEED` is `off`, or when `AGENT_LOOKOUT_CLAUDE_HOME` is set and `AGENT_LOOKOUT_CLAUDE_BIN` is not.
43
43
 
44
- To find the `claude` binary, Agent Lookout reads the `PATH` environment variable and checks whether a program named `claude` exists in each `PATH` directory, then at `~/.local/bin/claude`, `/opt/homebrew/bin/claude`, `/usr/local/bin/claude`, `~/.npm-global/bin/claude` and `/usr/bin/claude`. When `AGENT_LOOKOUT_CLAUDE_BIN` is set, it checks that path and nothing else.
44
+ To find the `claude` binary, Agent Lookout reads the `PATH` environment variable and checks whether a program named `claude` exists in each `PATH` directory, then at `~/.local/bin/claude`, `/opt/homebrew/bin/claude`, `/usr/local/bin/claude`, `~/.npm-global/bin/claude` and `/usr/bin/claude`. On Windows it checks each `PATH` directory for `claude.com` and `claude.exe`, and then for `node_modules\@anthropic-ai\claude-code\bin\claude.exe` under it, where npm keeps the program that its `claude.cmd` runs, then `%USERPROFILE%\.local\bin\claude.exe` and that npm path under `%APPDATA%\npm`. It never runs a `.cmd` or a `.bat`, which only a shell can run. When `AGENT_LOOKOUT_CLAUDE_BIN` is set, it checks that path and nothing else.
45
45
 
46
46
  ### `ps`
47
47
 
48
- A registry file can outlive a crashed session, and the system can later give its process ID to another program. To catch that, Agent Lookout runs `ps -o pid=,lstart= -p <pids>`, which prints when each of those processes started, and compares the answer with the `procStart` that the registry file recorded. A file whose process started at another time is not shown as a session. One run covers several processes, and each process is asked about at most once every 30 seconds. `ps` is run directly from `/usr/bin` or `/bin`, never through a shell, and never on Windows.
48
+ A registry file can outlive a crashed session, and the system can later give its process ID to another program. To catch that, Agent Lookout runs `ps -o pid=,lstart= -p <pids>`, which prints when each of those processes started, and compares the answer with the `procStart` that the registry file recorded. A file whose process started at another time is not shown as a session. One run covers several processes, and each process is asked about at most once every 30 seconds. `ps` is run directly from `/usr/bin` or `/bin`, never through a shell, and never on Windows. On Windows, which has no `ps` and where Claude Code records no start time Agent Lookout can compare, nothing is compared: a file whose process ID has been given to another program shows as a session until that program ends.
49
49
 
50
50
  ### `tmux`
51
51
 
@@ -104,11 +104,11 @@ Nothing is done until you press Stop and then Stop session. The page then sends
104
104
  - It runs `ps -o pid=,lstart= -p <pid>` again, for that one process, and goes on only when `ps` gives the same start time the file recorded, to the second. When `ps` gives none, or one it cannot compare, nothing is done.
105
105
  - It never stops the first process, its own process, the process it was started from or any process above that, such as the Claude Code session whose terminal started it. To find those, it runs `ps -A -o pid=,ppid=`, which prints the ID of every process and of its parent, and nothing else, follows its own process up through its parents, and drops the list. It stops no process of another user's.
106
106
 
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.
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 `claude stop` has answered, it looks the same way, for up to 10 seconds, whether the job's process has ended, since the supervisor can end it a moment later, and then 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
- 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.
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. On Windows it is the same whatever the setting, since there is no `ps` to confirm a process's start time and no POSIX signal to send.
112
112
 
113
113
  ### Codex's files
114
114
 
@@ -133,7 +133,7 @@ A session file or `session_index.jsonl` that is a link, a named pipe or a device
133
133
 
134
134
  Any agent can write one small JSON file for each of its sessions into a folder for Agent Lookout to read, as the [guide](docs/GUIDE.md#your-own-agents) describes. The folder is `~/.agent-lookout/sessions`, or the one `AGENT_LOOKOUT_STATUS_DIR` names. Agent Lookout never makes it.
135
135
 
136
- Every 2 seconds Agent Lookout lists that folder and reads each file directly in it whose name ends in `.json` and does not start with a dot: at most 200 files. When there are more, it looks up when each was last written, without opening it, and reads the 200 written most recently. It reads only ordinary files of 16 KB or less, and on macOS and Linux it does not follow a symbolic link in the folder. The folder itself is followed when it is a link. It does not look in folders inside it. When the folder is not there, it lists nothing else.
136
+ Every 2 seconds Agent Lookout lists that folder and reads each file directly in it whose name ends in `.json` and does not start with a dot: at most 200 files. When there are more, it looks up when each was last written, without opening it, and reads the 200 written most recently. It reads only ordinary files of 16 KB or less, and it does not follow a symbolic link in the folder. The folder itself is followed when it is a link. It does not look in folders inside it. When the folder is not there, it lists nothing else.
137
137
 
138
138
  From each file it keeps these fields and drops the rest: `agent`, `name`, `cwd`, `status`, `reason`, `since`, `pid` and `app`. `agent` is the agent's name, `cwd` the folder the session works in, `since` when its status began, `pid` the ID of its process and `app` the app the session runs in. It also keeps each file's name, which tells one session from another, and the time the file was last written, taken from the file it opened, which says how long a finished session stays and how long a working session has been quiet. The Sources card names the first file it skipped, so you can find it.
139
139
 
@@ -149,7 +149,7 @@ It looks for an entry named `.git` in the session's folder, then in each folder
149
149
  - When `.git` is a file, as in a worktree or a submodule, it reads that file, which names the repository's git folder in one line, `gitdir: <path>`, then reads `HEAD` in the folder it names, and `commondir` beside it when there is one. A worktree's git folder has a `commondir`, one line naming the git folder of the repository the worktree was made from. A submodule's has none.
150
150
  - When `.git` is anything else, such as a symbolic link, it reads nothing, and the session has no branch.
151
151
 
152
- It reads only ordinary files of 4 KB or less, and on macOS and Linux it does not follow a `HEAD` or a `commondir` that is a symbolic link. `HEAD` says either the name of the branch that is checked out, which Agent Lookout keeps, cleaned of control characters and of the characters that change the direction of text, and cut to 200 characters, or the ID of a commit when no branch is, of which it keeps the first seven characters. It keeps nothing else from these files. For each session's folder it reads them at most once every 10 seconds, and it forgets a folder when no session is in it.
152
+ It reads only ordinary files of 4 KB or less, and it does not follow a `HEAD` or a `commondir` that is a symbolic link. `HEAD` says either the name of the branch that is checked out, which Agent Lookout keeps, cleaned of control characters and of the characters that change the direction of text, and cut to 200 characters, or the ID of a commit when no branch is, of which it keeps the first seven characters. It keeps nothing else from these files. For each session's folder it reads them at most once every 10 seconds, and it forgets a folder when no session is in it.
153
153
 
154
154
  With the branch it keeps which repository the folder belongs to, so the dashboard can group the worktrees of one repository together. A repository is known by its own git folder, which all its worktrees share: a `.git` folder is its own, a worktree's `commondir` names it, and any other `.git` file, such as a submodule's, names it in its one line. Agent Lookout reads nothing more for this. It keeps two things about the repository: its name, cleaned and cut as a branch is, and an id to tell repositories apart, the first 16 characters of the SHA-256 hash of the git folder's path. The name is that of the folder that holds `.git`, such as `storefront`, and for a worktree the one its repository's git folder gives: `storefront` for `storefront/.git`, `storefront/.bare` or `storefront.git`. Both are in the session list the API serves on this computer, beside the branch, and nowhere else: no email, post, notification or answer of `agent-lookout mcp` holds them. The path itself is not sent. The id is never shown, and holds no part of the path, though someone who guessed a path could check the guess against it.
155
155
 
@@ -182,17 +182,47 @@ The pull request is in the session list the API serves on this computer, and so
182
182
 
183
183
  ### `osascript`
184
184
 
185
- With notifications on, and no dashboard page open to show one, Agent Lookout shows a notification itself when a session starts waiting for you, or, if you chose those too, finishes, fails or ends. On macOS it does that by running `/usr/bin/osascript`, the program macOS provides for running AppleScript. It gives it a script that never changes, which shows a notification, and two arguments for that script: the session's name and the reason, with what the session is asking when that is known, or what happened. The name is handed over as text to be shown. It is never made part of the script, so nothing in a name can run as AppleScript or be read as an option.
185
+ With notifications on, and no dashboard page open to show one, Agent Lookout shows a notification itself when a session starts waiting for you, or, if you chose those too, finishes, fails or ends. On macOS it does that by running `/usr/bin/osascript`, the program macOS provides for running AppleScript, except in the Mac app, which shows them as its own, as [From the server](#from-the-server) says. It gives it a script that never changes, which shows a notification, and two arguments for that script: the session's name and the reason, with what the session is asking when that is known, or what happened. The name is handed over as text to be shown. It is never made part of the script, so nothing in a name can run as AppleScript or be read as an option.
186
186
 
187
187
  `osascript` is started directly, by that full path, never through a shell, with stdin closed and a 5 second timeout. It is not run on any other system, and never while notifications are off. [Terminal and iTerm2](#terminal-and-iterm2) says when it is also run for Jump. While it runs, for a fraction of a second, the name is one of that program's arguments, which other programs on this machine can read from the list of running processes. [Notifications](#notifications) says when notifications are on and what one holds.
188
188
 
189
- ### `open` and `xdg-open`
189
+ ### `open`, `xdg-open` and `rundll32.exe`
190
190
 
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.
191
+ Only when you start it with `agent-lookout --open`, Agent Lookout runs `/usr/bin/open` on macOS, `xdg-open` on Linux, or on Windows `rundll32.exe url.dll,FileProtocolHandler` from `System32` in the Windows folder, 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`, none of them is ever run.
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. On Windows Agent Lookout opens no socket, so the hook always finds none. 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 when you press Allow or Deny on the dashboard, or when a [permission rule](#permission-rules) you added decides. 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. Claude Code's registry file can say the session waits for a second or two after the answer, so Agent Lookout keeps the session's ID, when that wait began and when it answered, in memory only, until the file says the session has moved on or for 10 seconds at most, and takes the wait as over from the answer.
204
+
205
+ #### Permission rules
206
+
207
+ The [permission rules](docs/GUIDE.md#permission-rules) are rules you add in Settings, none until you do. A request Agent Lookout holds is put to them once the session's registry file says it waits for permission. A deny or an allow rule that decides it answers it at once, through the same path as a press, with the same checks and the same two fixed answers: it reads the registry file once more, answers only in the same wait, allows only what Allow would be offered for, and for Bash only one plain command. An ask rule, or none, leaves it held for you. The rules read only what Agent Lookout already holds of a request, the tool's name, the command and the names of the other inputs, in memory, and write none of it anywhere.
208
+
209
+ For each request a rule answers, Agent Lookout keeps the session's ID and name, the tool's name, whether it was allowed or denied, the rule as you wrote it, its tool and for Bash its command, and the time: as an event in the Events log, which the history keeps on disk with the other events, and in a list of the last 100 kept in memory for Settings, gone when Agent Lookout stops. Neither holds the command the session asked to run, or anything else of the request. The rules themselves are kept in [the settings file](#the-settings-file). With `AGENT_LOOKOUT_ANSWER=off` no rule answers anything.
210
+
211
+ 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.
212
+
213
+ ### Another machine over SSH
214
+
215
+ 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`.
216
+
217
+ 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.
218
+
219
+ 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.
220
+
221
+ 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.
192
222
 
193
223
  ### Settings
194
224
 
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.
225
+ 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 and the permission 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
226
 
197
227
  ## What it never reads
198
228
 
@@ -202,13 +232,14 @@ It reads twenty-six settings from the environment: `AGENT_LOOKOUT_CLAUDE_HOME`,
202
232
  - 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
233
  - 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
234
  - `gh`'s configuration, its login and any token, and your global git configuration.
235
+ - 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
236
  - The files of any other application.
206
237
 
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.
238
+ 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`, `xdg-open` or `rundll32.exe`, 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, or a [permission rule](#permission-rules) you added decides it, 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
239
 
209
240
  ## Network
210
241
 
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.
242
+ 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
243
 
213
244
  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
245
 
@@ -218,11 +249,11 @@ The `claude agents` command is Claude Code's own program, and it may contact Ant
218
249
 
219
250
  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
251
 
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.
252
+ 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. The one the Time rules card sends carries `X-Agent-Lookout-Action: time-rules`, with the three rules, and the one the Permission rules card sends carries `X-Agent-Lookout-Action: permission-rules`, with one change to the rules. In the Mac app, the Updates card also sends `X-Agent-Lookout-Action: check-for-updates` and `install-update`, each with the body `{}`, and `update-setting`, with `{"automatic": true}` or `false`. The Menu bar card sends `menu-bar-setting`, with `{"show": true}` or `false`.
222
253
 
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.
254
+ 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), to answer a permission prompt Agent Lookout is holding, as Allow and Deny do, and to change the time rules or the permission rules, as Settings does, which includes adding an allow rule that then answers prompts without a press. 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 ask any of this. It shares the settings file, though. A program running as you can write `~/.agent-lookout/settings.json`, or start `npx agent-lookout` beside the app and ask that copy to change a rule, and the Mac app puts the time rules and permission rules the file then holds in force at its next poll, an allow rule included. `AGENT_LOOKOUT_STOP=off` takes stopping away, and `AGENT_LOOKOUT_ANSWER=off` takes answering away.
224
255
 
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.
256
+ 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, the one Allow and Deny send and the ones the Time rules and Permission rules cards send are the seven that change anything, with the Mac app's four for its updates and its menu bar. The server refuses them from any website, though not from another program on this machine, as the paragraph above says, and the Mac app answers its four only for its own window. [SECURITY.md](SECURITY.md) lists its checks.
226
257
 
227
258
  ## Notifications
228
259
 
@@ -236,9 +267,9 @@ A notification is made in one of two ways. While a dashboard page is open, the p
236
267
 
237
268
  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
269
 
239
- - the session's name, as its title
270
+ - the session's name, as its title, followed by "on" and the machine's name for a session on another machine
240
271
  - 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
272
+ - 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
273
 
243
274
  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
275
 
@@ -252,10 +283,14 @@ The browser gives its permission to the address, such as `localhost:5173`, not t
252
283
 
253
284
  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
285
 
255
- - the session's name, as its title
286
+ - the session's name, as its title, with the machine's name for a session on another machine
256
287
  - what happened, as its text, in the same wordings, with what a waiting session is asking
257
288
 
258
- 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.
289
+ 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. In the Mac app, the server shows them as the app's own notifications instead, and runs no `osascript` for them. macOS files them under Agent Lookout, and a click on one brings the app's window forward.
290
+
291
+ ### Reminders and summaries
292
+
293
+ 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.
259
294
 
260
295
  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
296
 
@@ -263,8 +298,8 @@ While a page that has notifications on is open and asking every 2 seconds, the p
263
298
 
264
299
  A notification the server shows differs from the browser's:
265
300
 
266
- - macOS shows it as coming from Script Editor, which is how it labels whatever `osascript` shows. The browser's permission and its notification settings do not apply to it. On macOS 26.5, where this was checked in the system's log, macOS delivered it without first asking whether Script Editor may show notifications.
267
- - It holds nothing that could open the session or the dashboard. What a click on it does has not been checked.
301
+ - Outside the Mac app, macOS shows it as coming from Script Editor, which is how it labels whatever `osascript` shows. In the Mac app, macOS shows it as coming from Agent Lookout. The browser's permission and its notification settings do not apply to it. On macOS 26.5, where this was checked in the system's log, macOS delivered it without first asking whether Script Editor may show notifications.
302
+ - Outside the Mac app, it holds nothing that could open the session or the dashboard, and what a click on it does has not been checked. In the Mac app a click brings the window forward.
268
303
  - The server cannot take it down. It stays in Notification Centre after the session stops waiting, and after Agent Lookout stops, until you clear it.
269
304
 
270
305
  ## Email
@@ -282,6 +317,8 @@ An email is plain text. Its subject is the session's name followed by the reason
282
317
  - the agent, such as Claude Code or Codex, or the agent a status file names
283
318
  - one line saying Agent Lookout sent it and how to stop these emails
284
319
 
320
+ 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.
321
+
285
322
  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
323
 
287
324
  #### What a waiting session is asking, in an email
@@ -299,11 +336,11 @@ It is never in the subject, and never in an email for a session that finished, f
299
336
 
300
337
  Each email goes to the one address in `AGENT_LOOKOUT_EMAIL_TO`, and to no other. It comes from the address in `AGENT_LOOKOUT_EMAIL_FROM`, or from the same address when that is not set. It goes through the mail server in `AGENT_LOOKOUT_SMTP_URL`, which then delivers it the way it delivers any email.
301
338
 
302
- For each email Agent Lookout looks up the server's name, opens a connection to it, signs in with the user name and password from `AGENT_LOOKOUT_SMTP_URL`, hands the email over and closes the connection. It greets the server as `[127.0.0.1]`, not by this computer's name. The server sees the network address the connection comes from, as any server you connect to does. The connection is encrypted with TLS: from the first byte when the address begins `smtps://`, and when it begins `smtp://`, the server must offer to switch to TLS before anything is sent, or nothing is sent. The server's certificate is always checked. Only to a mail server on this machine, at `127.0.0.1` or `localhost`, does `smtp://` go without TLS, since nothing leaves the machine on the way to it.
339
+ For each email Agent Lookout looks up the server's name, opens a connection to it, signs in with the user name and password from `AGENT_LOOKOUT_SMTP_URL`, hands the email over and closes the connection. It greets the server as `[127.0.0.1]`, not by this computer's name. The server sees the network address the connection comes from, as any server you connect to does. The connection is encrypted with TLS: from the first byte when the address begins `smtps://`, and when it begins `smtp://`, the server must offer to switch to TLS before anything is sent, or nothing is sent. The server's certificate is always checked, even when `NODE_TLS_REJECT_UNAUTHORIZED=0` is set. Only to a mail server on this machine, at `127.0.0.1` or `localhost`, does `smtp://` go without TLS, since nothing leaves the machine on the way to it.
303
340
 
304
341
  ### When
305
342
 
306
- `AGENT_LOOKOUT_EMAIL_EVENTS` names the events that are emailed, from `needs-you`, `finished`, `failed` and `ended`, and is `needs-you` alone unless you set it. An email is sent for a wait that begins after Agent Lookout started, once it has lasted the delay, which is one minute unless `AGENT_LOOKOUT_EMAIL_AFTER` says otherwise, if the session is still waiting then. A wait answered before then sends nothing, and so does a session that was already waiting when Agent Lookout started. Each wait sends one email at most. With the others set, an email is sent as soon as a session is seen to finish, fail or end, and none for a session that had already finished or failed when Agent Lookout started. An email that could not be sent is not tried again. At most 20 are sent in any hour, whatever they are for. Past that, none goes until the hour has passed. Claude Code sessions and sessions from a status file can be seen waiting; a Codex session never can, so it never sends one for a wait.
343
+ `AGENT_LOOKOUT_EMAIL_EVENTS` names the events that are emailed, from `needs-you`, `finished`, `failed` and `ended`, and is `needs-you` alone unless you set it. An email is sent for a wait that begins after Agent Lookout started, once it has lasted the delay, which is one minute unless `AGENT_LOOKOUT_EMAIL_AFTER` says otherwise, if the session is still waiting then. A wait answered before then sends nothing, and so does a session that was already waiting when Agent Lookout started. Each wait sends one email at most, apart from reminders. With the long wait reminder on, a wait is also reminded of once it has waited the rule's minutes, unless its email went only after that. That includes a wait already open when Agent Lookout started, if it had not waited that long yet. Each change to the minutes while it waits can bring one more, once it has waited the new number. With the others set, an email is sent as soon as a session is seen to finish, fail or end, and none for a session that had already finished or failed when Agent Lookout started. An email that could not be sent is not tried again. At most 20 are sent in any hour, whatever they are for. Past that, none goes until the hour has passed. Claude Code sessions and sessions from a status file can be seen waiting; a Codex session never can, so it never sends one for a wait.
307
344
 
308
345
  The button and the switches for notifications in Settings do not turn emails on or off, or choose what is emailed. To stop them, start Agent Lookout again without `AGENT_LOOKOUT_EMAIL_TO`.
309
346
 
@@ -348,6 +385,43 @@ It holds:
348
385
  - `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
386
  - `waitedSeconds`, for a wait only: how long it had waited when it was posted.
350
387
 
388
+ 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:
389
+
390
+ ```json
391
+ {
392
+ "text": "While quiet: checkout-flow waited 25 minutes and billing-webhooks finished",
393
+ "event": "quiet-summary",
394
+ "from": "2026-10-05T21:00:00.000Z",
395
+ "to": "2026-10-06T07:00:00.000Z",
396
+ "items": [
397
+ {
398
+ "event": "needs-you",
399
+ "session": {
400
+ "name": "checkout-flow",
401
+ "agent": "Claude Code",
402
+ "folder": "storefront",
403
+ "app": "VS Code"
404
+ },
405
+ "at": "2026-10-05T22:10:00.000Z",
406
+ "waitedSeconds": 1500,
407
+ "times": 1
408
+ },
409
+ {
410
+ "event": "finished",
411
+ "session": {
412
+ "name": "billing-webhooks",
413
+ "agent": "Claude Code",
414
+ "folder": "billing",
415
+ "app": "Terminal"
416
+ },
417
+ "at": "2026-10-06T00:12:00.000Z"
418
+ }
419
+ ]
420
+ }
421
+ ```
422
+
423
+ `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.
424
+
351
425
  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
426
 
353
427
  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.
@@ -369,7 +443,7 @@ Each post goes to the one address in `AGENT_LOOKOUT_WEBHOOK_URL`, and to no othe
369
443
 
370
444
  ### When
371
445
 
372
- `AGENT_LOOKOUT_WEBHOOK_EVENTS` names the events that are posted, from `needs-you`, `finished`, `failed` and `ended`, and is `needs-you` alone unless you set it. A post goes for a wait that begins after Agent Lookout started, once it has lasted the delay, which is one minute unless `AGENT_LOOKOUT_WEBHOOK_AFTER` says otherwise, if the session is still waiting then. A wait answered before then sends nothing, and so does a session that was already waiting when Agent Lookout started. Each wait sends one post at most. With the others set, a post goes as soon as a session is seen to finish, fail or end, and none for a session that had already finished or failed when Agent Lookout started. A post that could not be sent is not tried again. At most 20 posts are tried in any hour, whatever they are for, counted apart from emails. Past that, none goes until the hour has passed. These are the rules emails follow, and the two share one copy of them.
446
+ `AGENT_LOOKOUT_WEBHOOK_EVENTS` names the events that are posted, from `needs-you`, `finished`, `failed` and `ended`, and is `needs-you` alone unless you set it. A post goes for a wait that begins after Agent Lookout started, once it has lasted the delay, which is one minute unless `AGENT_LOOKOUT_WEBHOOK_AFTER` says otherwise, if the session is still waiting then. A wait answered before then sends nothing, and so does a session that was already waiting when Agent Lookout started. Each wait sends one post at most, apart from reminders. With the long wait reminder on, a wait is also reminded of once it has waited the rule's minutes, unless its post went only after that. That includes a wait already open when Agent Lookout started, if it had not waited that long yet. Each change to the minutes while it waits can bring one more, once it has waited the new number. With the others set, a post goes as soon as a session is seen to finish, fail or end, and none for a session that had already finished or failed when Agent Lookout started. A post that could not be sent is not tried again. At most 20 posts are tried in any hour, whatever they are for, counted apart from emails. Past that, none goes until the hour has passed. These are the rules emails follow, and the two share one copy of them.
373
447
 
374
448
  The button and the switches for notifications in Settings do not turn posts on or off, or choose what is posted. To stop them, start Agent Lookout again without `AGENT_LOOKOUT_WEBHOOK_URL`.
375
449
 
@@ -423,15 +497,15 @@ For each tool the agent calls, it asks Agent Lookout's own server for the list o
423
497
 
424
498
  It answers the app that started it, over stdout, and nothing and nobody else. An answer is one of three:
425
499
 
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.
500
+ - 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
501
  - 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
- - 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.
502
+ - 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, for another machine, the SSH target you named, such as `dev@devbox.local`, and the last line ssh printed when it failed, and what its agent can report.
429
503
 
430
504
  When Agent Lookout cannot be reached, the answer is one sentence that says so, with the address it tried.
431
505
 
432
506
  ### What happens to an answer
433
507
 
434
- What the app does with an answer is up to the app. An agent's app normally hands each answer to its model, and for most agents the model runs on the vendor's servers, so the names of your sessions, their folders and their branches, and with `AGENT_LOOKOUT_PULL_REQUESTS=on` the numbers of their pull requests and whether their checks pass, can leave this computer that way, under that app's own settings and terms. Add the server only to an app you would show your session names to, and remove it from the app to stop it: for Claude Code, `claude mcp remove agent-lookout`.
508
+ What the app does with an answer is up to the app. An agent's app normally hands each answer to its model, and for most agents the model runs on the vendor's servers, so the names of your sessions, their folders and their branches, the SSH targets of the other machines you named, and with `AGENT_LOOKOUT_PULL_REQUESTS=on` the numbers of their pull requests and whether their checks pass, can leave this computer that way, under that app's own settings and terms. Add the server only to an app you would show your session names to, and remove it from the app to stop it: for Claude Code, `claude mcp remove agent-lookout`.
435
509
 
436
510
  ### What is kept
437
511
 
@@ -439,9 +513,9 @@ Nothing. Each answer is worked out from one reading of the list and is gone once
439
513
 
440
514
  ## Storage
441
515
 
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.
516
+ 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 and permission 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
517
 
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.
518
+ 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
519
 
446
520
  ### The history
447
521
 
@@ -449,7 +523,7 @@ The history is kept in the folder `~/.agent-lookout/history`, beside the folder
449
523
 
450
524
  It writes there what it already holds in memory for the Events log and the charts, and nothing else:
451
525
 
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.
526
+ - 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, and, when a permission rule answered it, the tool's name and the rule as you wrote it, never the command the session asked to run. A session's ID is its agent's own ID for it, or the name of its status file.
453
527
  - Each history point, one for each poll: its time, and how many sessions were waiting on you, working, idle, and in all.
454
528
  - When Agent Lookout started writing, and when the history was cleared.
455
529
 
@@ -461,7 +535,13 @@ When more than one copy runs at once, such as the Mac app and `npx agent-lookout
461
535
 
462
536
  To clear the history, press Clear history on the History card in Settings and confirm. Agent Lookout deletes every history file this version wrote in the folder, and empties the Events log and the charts, which start again from that moment. The History card says where the files are, how much they hold and how far back they go. Deleting the folder while Agent Lookout is stopped clears it too.
463
537
 
464
- 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.
538
+ To keep nothing on disk, start Agent Lookout with `AGENT_LOOKOUT_HISTORY=off`. It then makes no history folder and writes no history 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. While answering is on, it still makes the folder of its socket, `~/.agent-lookout` unless `AGENT_LOOKOUT_ANSWER_SOCKET` names another, with mode 700, and opens the socket there. The socket holds nothing and is removed when Agent Lookout stops, and the folder stays. Start it with `AGENT_LOOKOUT_ANSWER=off` as well to make no folder at all. A change to a rule in Settings still writes [the settings file](#the-settings-file).
539
+
540
+ ### The settings file
541
+
542
+ Agent Lookout keeps the [time rules](docs/GUIDE.md#time-rules) and the [permission rules](docs/GUIDE.md#permission-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 those rules and nothing else of Agent Lookout's: whether each time rule 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, and each permission rule in its order, with an ID Agent Lookout gave it, its decision, its tool and, for Bash, the command you wrote. It holds nothing about a session. A command you write in a rule is in the file as you wrote it, so leave out of a rule anything you would not keep in a file.
543
+
544
+ Agent Lookout reads it as it starts and again just before each change, and at each poll, every 2 seconds, it looks at the file's size and times to read it again when another copy of Agent Lookout, such as the Mac app beside `npx agent-lookout`, has written it. It writes it only when you change a rule in Settings, through `POST /api/settings/time-rules` or `POST /api/settings/permission-rules`, which the dashboard's page sends, and which another program on this computer can send too, as [Network](#network) says. 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 finds but cannot read, 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. Permission rules it cannot read whole, such as a later version's, are no rule at all: none of them answers anything, Settings says so, and they stay in the file as they were until you change a permission rule, which writes the list again with the rules Settings shows. Until you change a rule, nothing is written and no folder is made. To go back to every time rule off and no permission rule, delete the file while Agent Lookout is stopped.
465
545
 
466
546
  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
547
 
@@ -481,7 +561,9 @@ The dashboard saves seven values in your browser's local storage. Your theme cho
481
561
 
482
562
  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
563
 
484
- 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.
564
+ 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.
565
+
566
+ 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 (in the Mac app, both kinds come from Agent Lookout, so change Agent Lookout's own entry there), 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
567
 
486
568
  ## Changes
487
569